summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/inputmethodservice/NavigationBarController.java83
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java8
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java45
-rw-r--r--services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java159
4 files changed, 235 insertions, 60 deletions
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index a247db2f3310..0f9075b498ae 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -16,7 +16,6 @@
package android.inputmethodservice;
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import android.animation.ValueAnimator;
@@ -24,18 +23,12 @@ import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.inputmethodservice.navigationbar.NavigationBarFrame;
import android.inputmethodservice.navigationbar.NavigationBarView;
-import android.os.PatternMatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -145,9 +138,6 @@ final class NavigationBarController {
@Nullable
Insets mLastInsets;
- @Nullable
- private BroadcastReceiver mSystemOverlayChangedReceiver;
-
private boolean mShouldShowImeSwitcherWhenImeIsShown;
@Appearance
@@ -360,14 +350,6 @@ final class NavigationBarController {
});
}
- private boolean imeDrawsImeNavBar() {
- final Resources resources = mService.getResources();
- if (resources == null) {
- return false;
- }
- return resources.getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar);
- }
-
@Override
public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
final Window window = softInputWindow.getWindow();
@@ -380,27 +362,6 @@ final class NavigationBarController {
if (mDestroyed) {
return;
}
- mImeDrawsImeNavBar = imeDrawsImeNavBar();
- if (mSystemOverlayChangedReceiver == null) {
- final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
- intentFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
- mSystemOverlayChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mDestroyed) {
- return;
- }
- mImeDrawsImeNavBar = imeDrawsImeNavBar();
- if (mImeDrawsImeNavBar) {
- installNavigationBarFrameIfNecessary();
- } else {
- uninstallNavigationBarFrameIfNecessary();
- }
- }
- };
- mService.registerReceiver(mSystemOverlayChangedReceiver, intentFilter);
- }
installNavigationBarFrameIfNecessary();
}
@@ -413,10 +374,6 @@ final class NavigationBarController {
mTintAnimator.cancel();
mTintAnimator = null;
}
- if (mSystemOverlayChangedReceiver != null) {
- mService.unregisterReceiver(mSystemOverlayChangedReceiver);
- mSystemOverlayChangedReceiver = null;
- }
mDestroyed = true;
}
@@ -454,26 +411,38 @@ final class NavigationBarController {
return;
}
+ final boolean imeDrawsImeNavBar =
+ (navButtonFlags & InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR) != 0;
final boolean shouldShowImeSwitcherWhenImeIsShown =
(navButtonFlags & InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN)
!= 0;
- if (mShouldShowImeSwitcherWhenImeIsShown == shouldShowImeSwitcherWhenImeIsShown) {
- return;
- }
+
+ mImeDrawsImeNavBar = imeDrawsImeNavBar;
+ final boolean prevShouldShowImeSwitcherWhenImeIsShown =
+ mShouldShowImeSwitcherWhenImeIsShown;
mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
- if (mNavigationBarFrame == null) {
- return;
- }
- final NavigationBarView navigationBarView =
- mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
- if (navigationBarView == null) {
- return;
+ if (imeDrawsImeNavBar) {
+ installNavigationBarFrameIfNecessary();
+ if (mNavigationBarFrame == null) {
+ return;
+ }
+ if (mShouldShowImeSwitcherWhenImeIsShown
+ == prevShouldShowImeSwitcherWhenImeIsShown) {
+ return;
+ }
+ final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
+ NavigationBarView.class::isInstance);
+ if (navigationBarView == null) {
+ return;
+ }
+ final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+ | (shouldShowImeSwitcherWhenImeIsShown
+ ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0);
+ navigationBarView.setNavigationIconHints(hints);
+ } else {
+ uninstallNavigationBarFrameIfNecessary();
}
- final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
- | (shouldShowImeSwitcherWhenImeIsShown
- ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0);
- navigationBarView.setNavigationIconHints(hints);
}
@Override
diff --git a/core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java b/core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java
index 1b37c6cbe5ae..728a42907cc4 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java
@@ -33,11 +33,17 @@ import java.lang.annotation.Retention;
*/
@Retention(SOURCE)
@IntDef(flag = true, value = {
+ InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR,
InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN,
})
public @interface InputMethodNavButtonFlags {
/**
+ * When set, the IME process needs to render and handle the navigation bar buttons such as the
+ * back button and the IME switcher button.
+ */
+ int IME_DRAWS_IME_NAV_BAR = 1;
+ /**
* When set, the IME switcher icon needs to be shown on the navigation bar.
*/
- int SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN = 1;
+ int SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN = 2;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 584193654bfa..1f9d08063714 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -336,6 +336,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
private final HandwritingModeController mHwController;
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
+
static class SessionState {
final ClientState client;
final IInputMethodInvoker method;
@@ -1724,11 +1728,43 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@GuardedBy("ImfLock.class")
+ private void recreateImeDrawsImeNavBarResIfNecessary(@UserIdInt int targetUserId) {
+ // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the
+ // profile parent user.
+ // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups.
+ final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId);
+ if (mImeDrawsImeNavBarRes != null
+ && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) {
+ mImeDrawsImeNavBarRes.close();
+ mImeDrawsImeNavBarRes = null;
+ }
+ if (mImeDrawsImeNavBarRes == null) {
+ final Context userContext;
+ if (mContext.getUserId() == profileParentUserId) {
+ userContext = mContext;
+ } else {
+ userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId),
+ 0 /* flags */);
+ }
+ mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext,
+ com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> {
+ synchronized (ImfLock.class) {
+ if (resource == mImeDrawsImeNavBarRes) {
+ sendOnNavButtonFlagsChangedLocked();
+ }
+ }
+ });
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
private void switchUserOnHandlerLocked(@UserIdInt int newUserId,
IInputMethodClient clientToBeReset) {
if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
+ " currentUserId=" + mSettings.getCurrentUserId());
+ recreateImeDrawsImeNavBarResIfNecessary(newUserId);
+
// ContentObserver should be registered again when the user is changed
mSettingsObserver.registerContentObserverLocked(newUserId);
@@ -1833,6 +1869,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
});
}
+ recreateImeDrawsImeNavBarResIfNecessary(currentUserId);
+
mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
mSettingsObserver.registerContentObserverLocked(currentUserId);
@@ -2941,10 +2979,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
@InputMethodNavButtonFlags
private int getInputMethodNavButtonFlagsLocked() {
+ final boolean canImeDrawsImeNavBar =
+ mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get();
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
- return shouldShowImeSwitcherWhenImeIsShown
- ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0;
+ return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
+ | (shouldShowImeSwitcherWhenImeIsShown
+ ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0);
}
@GuardedBy("ImfLock.class")
diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
new file mode 100644
index 000000000000..33e7a7621340
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.annotation.AnyThread;
+import android.annotation.BoolRes;
+import android.annotation.NonNull;
+import android.annotation.UserHandleAware;
+import android.annotation.UserIdInt;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.util.Slog;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is
+ * aware of per-user Runtime Resource Overlay (RRO).
+ */
+final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable {
+ private static final String TAG = "OverlayableSystemBooleanResourceWrapper";
+
+ private static final String SYSTEM_PACKAGE_NAME = "android";
+
+ @UserIdInt
+ private final int mUserId;
+ @NonNull
+ private final AtomicBoolean mValueRef;
+ @NonNull
+ private final AtomicReference<Runnable> mCleanerRef;
+
+ /**
+ * Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID
+ * with a value change callback for the user associated with the {@link Context}.
+ *
+ * @param userContext The {@link Context} to be used to access the resource. This needs to be
+ * associated with the right user because the Runtime Resource Overlay (RRO)
+ * is per-user configuration.
+ * @param boolResId The resource ID to be queried.
+ * @param handler {@link Handler} to be used to dispatch {@code callback}.
+ * @param callback The callback to be notified when the specified value might be updated.
+ * The callback needs to take care of spurious wakeup. The value returned from
+ * {@link #get()} may look to be exactly the same as the previously read value
+ * e.g. when the value is changed from {@code false} to {@code true} to
+ * {@code false} in a very short period of time, because {@link #get()} always
+ * does volatile-read.
+ * @return New {@link OverlayableSystemBooleanResourceWrapper}.
+ */
+ @NonNull
+ @UserHandleAware
+ static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext,
+ @BoolRes int boolResId, @NonNull Handler handler,
+ @NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) {
+
+ // Note that we cannot fully trust this initial value due to the dead time between obtaining
+ // the value here and setting up a broadcast receiver for change callback below.
+ // We will refresh the value again later after setting up the change callback anyway.
+ final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId));
+
+ final AtomicReference<Runnable> cleanerRef = new AtomicReference<>();
+
+ final OverlayableSystemBooleanResourceWrapper object =
+ new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef,
+ cleanerRef);
+
+ final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+ final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean newValue = evaluate(userContext, boolResId);
+ if (newValue != valueRef.getAndSet(newValue)) {
+ callback.accept(object);
+ }
+ }
+ };
+ userContext.registerReceiver(broadcastReceiver, intentFilter,
+ null /* broadcastPermission */, handler,
+ Context.RECEIVER_NOT_EXPORTED);
+ cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver));
+
+ // Make sure that the initial observable value is obtained after the change callback is set.
+ valueRef.set(evaluate(userContext, boolResId));
+ return object;
+ }
+
+ private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId,
+ @NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) {
+ mUserId = userId;
+ mValueRef = valueRef;
+ mCleanerRef = cleanerRef;
+ }
+
+ /**
+ * @return The boolean resource value.
+ */
+ @AnyThread
+ boolean get() {
+ return mValueRef.get();
+ }
+
+ /**
+ * @return The user ID associated with this resource reader.
+ */
+ @AnyThread
+ @UserIdInt
+ int getUserId() {
+ return mUserId;
+ }
+
+ @AnyThread
+ private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) {
+ try {
+ return context.getPackageManager()
+ .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
+ .getBoolean(boolResId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e);
+ return false;
+ }
+ }
+
+ /**
+ * Cleans up the callback.
+ */
+ @AnyThread
+ @Override
+ public void close() {
+ final Runnable cleaner = mCleanerRef.getAndSet(null);
+ if (cleaner != null) {
+ cleaner.run();
+ }
+ }
+}