diff options
| author | 2022-03-07 11:13:58 -0800 | |
|---|---|---|
| committer | 2022-03-07 11:13:58 -0800 | |
| commit | f93769b1ae390d4dc38bccb01627f5f7189cd075 (patch) | |
| tree | 9792941fdea7bbb533b74172e39d84748eea85f2 | |
| parent | 5f9f1ca560d8b9e0dd8811f80f991f126ea80518 (diff) | |
Use parentUserId when reading config_imeDrawsImeNavBar
This CL rewrites my previous CLs [1][2], which were written with an
incorrect assumption that config_imeDrawsImeNavBar was overlaid for
the entire profile group.
While SysUI's navigation mode is dynamically configurable with Runtime
Resource Overlay (RRO), it turns out that we currently configure RRO
only for the profile parent user. This means that processes run under
other profile users continue seeing the base resource value regardless
of how RRO is configured for the profile parent user. This is the
root cause of Bug 219604375.
To work around this limitation, this CL uses InputMethodManagerService
to monitor the value of config_imeDrawsImeNavBar for the profile
parent user then to propagate it to the IME process. Luckily we have
already been doing a similar thing for the IME switcher visibility.
What this CL does is 1) adding a new flag to InputMethodNavButtonFlags
then 2) just using the flag sent from IMMS instead of directly reading
config_imeDrawsImeNavBar
NavigationBarController.
Alternative solutions considered:
* Set RRO for profile users
One of straightforward ways to address this problem is letting the
Setting app apply the same RRO for other profile users. However,
this could be tricky when 1) the user changes navigation mode then
2) sets up a new profile, because the Settings app is not an
always-running process. While we might be able to rely on
com.android.settings.SettingsInitialize#onReceive()
to do so, the profile user's state could be left in a broken state
if that method was somehow interrupted. To minimize the risk, we
decided to not take this approach for T.
* Make OverlayManager be aware of profile groups
Given how RRO is used in SysUI, it's make more sense if
OverlayManager natively supports resource overlay for the entire
profile group. However, introducing such a new concept is too late
for Android T. We have filed Bug 221443458 to see if we can do this
in a future version of Android.
[1]: I3e7e1f83554444131e2765dc159617bb9e2337c7
ff7b453ca8e23b4ef75ba2c3f5becaf511cb07d3
[2]: Id0cfa44cce5de515dc5d28254e1d41bdfc01e201
177e4aafdb33ec0e4b9172a16a07a34dc6420e36
Fix: 219820813
Test: Manually verified as follows
1. Build aosp_coral-userdebug then flash it.
2. adb root
3. adb shell setprop persist.sys.ime.can_render_gestural_nav_buttons true
4. adb reboot
5. adb install -r TestDPC-normalv8001.apk
6. adb shell am start -n com.afwsamples.testdpc/.SetupManagementLaunchActivity
7. Set up work-profile
8. make -j EditTextVariations
9. adb install -r \
$ANDROID_TARGET_OUT_TESTCASES/EditTextVariations/arm64/EditTextVariations.apk
10. adb shell am start --user 0 -n \
com.android.inputmethod.tools.edittextvariations/.EditTextVariations
11. adb shell dumpsys input_method | grep mNavigation
-> "mNavigationBarController={mImeDrawsImeNavBar=false, ..."
12. Enable gesture navigation
13. adb shell dumpsys input_method | grep mNavigation
-> "mNavigationBarController={mImeDrawsImeNavBar=true, ..."
14. adb shell am start --user 10 -n \
com.android.inputmethod.tools.edittextvariations/.EditTextVariations
15. adb shell dumpsys input_method | grep mNavigation
-> "mNavigationBarController={mImeDrawsImeNavBar=true, ..."
Change-Id: Id3d6a71d8ba1bfa49131350b68aa8d3424eca381
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(); + } + } +} |