Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f93769b1 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

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
      ff7b453c
 [2]: Id0cfa44cce5de515dc5d28254e1d41bdfc01e201
      177e4aaf

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
parent 5f9f1ca5
Loading
Loading
Loading
Loading
+26 −57
Original line number Diff line number Diff line
@@ -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,19 +411,28 @@ 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 (imeDrawsImeNavBar) {
                installNavigationBarFrameIfNecessary();
                if (mNavigationBarFrame == null) {
                    return;
                }
            final NavigationBarView navigationBarView =
                    mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
                if (mShouldShowImeSwitcherWhenImeIsShown
                        == prevShouldShowImeSwitcherWhenImeIsShown) {
                    return;
                }
                final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
                        NavigationBarView.class::isInstance);
                if (navigationBarView == null) {
                    return;
                }
@@ -474,6 +440,9 @@ final class NavigationBarController {
                        | (shouldShowImeSwitcherWhenImeIsShown
                                ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0);
                navigationBarView.setNavigationIconHints(hints);
            } else {
                uninstallNavigationBarFrameIfNecessary();
            }
        }

        @Override
+7 −1
Original line number Diff line number Diff line
@@ -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;
}
+43 −2
Original line number Diff line number Diff line
@@ -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;
@@ -1723,12 +1727,44 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
    }

    @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")
+159 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    }
}