Merge "Make IMMS#mImeDrawsImeNavBarRes multi-user aware" into main
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 5ab493b..9837ab1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,7 +408,8 @@
                         InputMethodManager
                                 .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
                     }
-                    mService.initializeImeLocked(mCurMethod, mCurToken, mUserId);
+                    mService.initializeImeLocked(mCurMethod, mCurToken,
+                            InputMethodBindingController.this);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked(mUserId);
                     mAutofillController.performOnCreateInlineSuggestionsRequest();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
new file mode 100644
index 0000000..b835d05
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+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.os.UserHandle;
+import android.util.Slog;
+
+final class InputMethodDrawsNavBarResourceMonitor {
+    private static final String TAG = "InputMethodDrawsNavBarResourceMonitor";
+
+    private static final String SYSTEM_PACKAGE_NAME = "android";
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodDrawsNavBarResourceMonitor() {
+    }
+
+    @WorkerThread
+    static boolean evaluate(@NonNull Context context, @UserIdInt int userId) {
+        final Context userAwareContext;
+        if (context.getUserId() == userId) {
+            userAwareContext = context;
+        } else {
+            userAwareContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+        }
+        try {
+            return userAwareContext.getPackageManager()
+                    .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
+                    .getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed",
+                    e);
+            return false;
+        }
+    }
+
+    @FunctionalInterface
+    interface OnUpdateCallback {
+        void onUpdate(@UserIdInt int userId);
+    }
+
+    @SuppressLint("MissingPermission")
+    @AnyThread
+    static void registerCallback(@NonNull Context context, @NonNull Handler ioHandler,
+            @NonNull OnUpdateCallback callback) {
+        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 int userId = getSendingUserId();
+                callback.onUpdate(userId);
+            }
+        };
+        context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, intentFilter,
+                null /* broadcastPermission */, ioHandler, Context.RECEIVER_NOT_EXPORTED);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e1803dc..e342c78 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,6 +67,7 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -169,7 +170,6 @@
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.AccessibilityManagerInternal;
@@ -202,7 +202,6 @@
 import java.util.OptionalInt;
 import java.util.WeakHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.IntFunction;
@@ -399,15 +398,6 @@
     @SharedByAllUsersField
     private IntArray mStylusIds;
 
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    @MultiUserUnawareField
-    private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
-    @GuardedBy("ImfLock.class")
-    @Nullable
-    @MultiUserUnawareField
-    Future<?> mImeDrawsImeNavBarResLazyInitFuture;
-
     private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
         /**
          * {@inheritDoc}
@@ -484,13 +474,13 @@
     @SharedByAllUsersField
     boolean mSystemReady;
 
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     @NonNull
     UserDataRepository.UserData getUserData(@UserIdInt int userId) {
         return mUserDataRepository.getOrCreate(userId);
     }
 
-    @GuardedBy("ImfLock.class")
+    @AnyThread
     @NonNull
     InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
         return getUserData(userId).mBindingController;
@@ -934,9 +924,13 @@
             // For production code, hook up user lifecycle
             mService.mUserManagerInternal.addUserLifecycleListener(this);
 
+            // Hook up resource change first before initializeUsersAsync() starts reading the
+            // seemingly initial data so that we can eliminate the race condition.
+            InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
+                    mService::onUpdateResourceOverlay);
+
             // Also schedule user init tasks onto an I/O thread.
-            initializeUsersAsync(context, mService.mIoHandler,
-                    mService.mUserManagerInternal.getUserIds());
+            initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
         }
 
         @VisibleForTesting
@@ -1019,7 +1013,7 @@
         @Override
         public void onUserCreated(UserInfo user, @Nullable Object token) {
             // Called directly from UserManagerService. Do not block the calling thread.
-            initializeUsersAsync(mService.mContext, mService.mIoHandler, new int[user.id]);
+            initializeUsersAsync(new int[user.id]);
         }
 
         @Override
@@ -1057,9 +1051,12 @@
         }
 
         @AnyThread
-        private static void initializeUsersAsync(
-                @NonNull Context context, @NonNull Handler ioHandler, @UserIdInt int[] userIds) {
-            ioHandler.post(() -> {
+        private void initializeUsersAsync(@UserIdInt int[] userIds) {
+            mService.mIoHandler.post(() -> {
+                final var service = mService;
+                final var context = service.mContext;
+                final var userManagerInternal = service.mUserManagerInternal;
+
                 // We first create InputMethodMap for each user without loading AdditionalSubtypes.
                 final int numUsers = userIds.length;
                 final InputMethodMap[] rawMethodMaps = new InputMethodMap[numUsers];
@@ -1068,6 +1065,12 @@
                     rawMethodMaps[i] = InputMethodManagerService.queryInputMethodServicesInternal(
                             context, userId, AdditionalSubtypeMap.EMPTY_MAP,
                             DirectBootAwareness.AUTO).getMethodMap();
+                    final int profileParentId = userManagerInternal.getProfileParentId(userId);
+                    final boolean value =
+                            InputMethodDrawsNavBarResourceMonitor.evaluate(context,
+                                    profileParentId);
+                    final var userData = mService.getUserData(userId);
+                    userData.mImeDrawsNavBar.set(value);
                 }
 
                 // Then create full InputMethodMap for each user. Note that
@@ -1242,36 +1245,6 @@
         setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
     }
 
-    @GuardedBy("ImfLock.class")
-    private void maybeInitImeNavbarConfigLocked(@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();
-                            }
-                        }
-                    });
-        }
-    }
-
     @NonNull
     private static PackageManager getPackageManagerForUser(@NonNull Context context,
             @UserIdInt int userId) {
@@ -1304,8 +1277,6 @@
 
         // Hereafter we start initializing things for "newUserId".
 
-        maybeInitImeNavbarConfigLocked(newUserId);
-
         final var newUserData = getUserData(newUserId);
 
         // TODO(b/342027196): Double check if we need to always reset upon user switching.
@@ -1384,23 +1355,6 @@
                     });
                 }
 
-                // TODO(b/32343335): The entire systemRunning() method needs to be revisited.
-                mImeDrawsImeNavBarResLazyInitFuture = SystemServerInitThreadPool.submit(() -> {
-                    // Note that the synchronization block below guarantees that the task
-                    // can never be completed before the returned Future<?> object is assigned to
-                    // the "mImeDrawsImeNavBarResLazyInitFuture" field.
-                    synchronized (ImfLock.class) {
-                        mImeDrawsImeNavBarResLazyInitFuture = null;
-                        if (currentUserId != mCurrentUserId) {
-                            // This means that the current user is already switched to other user
-                            // before the background task is executed. In this scenario the relevant
-                            // field should already be initialized.
-                            return;
-                        }
-                        maybeInitImeNavbarConfigLocked(currentUserId);
-                    }
-                }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
-
                 mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
                 SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
                         new String[] {
@@ -1922,7 +1876,8 @@
                     userData.mCurClient.mUid, true /* direct */);
         }
 
-        @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+        @InputMethodNavButtonFlags final int navButtonFlags =
+                getInputMethodNavButtonFlagsLocked(userData);
         final SessionState session = userData.mCurClient.mCurSession;
         setEnabledSessionLocked(session, userData);
         session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
@@ -2314,15 +2269,15 @@
 
     @GuardedBy("ImfLock.class")
     void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
-            @UserIdInt int userId) {
+            @NonNull InputMethodBindingController bindingController) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
-                    + getInputMethodBindingController(userId).getCurTokenDisplayId());
+                    + bindingController.getCurTokenDisplayId());
         }
+        final int userId = bindingController.getUserId();
         inputMethod.initializeInternal(token,
                 new InputMethodPrivilegedOperationsImpl(this, token, userId),
-                // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware
-                getInputMethodNavButtonFlagsLocked());
+                getInputMethodNavButtonFlagsLocked(getUserData(userId)));
     }
 
     @AnyThread
@@ -2621,23 +2576,17 @@
 
     @GuardedBy("ImfLock.class")
     @InputMethodNavButtonFlags
-    private int getInputMethodNavButtonFlagsLocked() {
-        // TODO(b/345519864): Make mImeDrawsImeNavBarRes multi-user aware.
-        final int userId = mCurrentUserId;
-        final var bindingController = getInputMethodBindingController(userId);
-        if (mImeDrawsImeNavBarResLazyInitFuture != null) {
-            // TODO(b/225366708): Avoid Future.get(), which is internally used here.
-            ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
-                    "Waiting for the lazy init of mImeDrawsImeNavBarRes");
-        }
+    private int getInputMethodNavButtonFlagsLocked(
+            @NonNull UserDataRepository.UserData userData) {
+        final int userId = userData.mUserId;
+        final var bindingController = userData.mBindingController;
         // Whether the current display has a navigation bar. When this is false (e.g. emulator),
         // the IME should not draw the IME navigation bar.
         final int tokenDisplayId = bindingController.getCurTokenDisplayId();
         final boolean hasNavigationBar = mWindowManagerInternal
                 .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
                         ? tokenDisplayId : DEFAULT_DISPLAY);
-        final boolean canImeDrawsImeNavBar =
-                mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
+        final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar;
         final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
                 InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
         return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2981,7 +2930,7 @@
         final var userData = getUserData(userId);
         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
-        sendOnNavButtonFlagsChangedLocked();
+        sendOnNavButtonFlagsChangedLocked(userData);
     }
 
     @GuardedBy("ImfLock.class")
@@ -5005,7 +4954,7 @@
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
                 mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
                 synchronized (ImfLock.class) {
-                    sendOnNavButtonFlagsChangedLocked();
+                    sendOnNavButtonFlagsChangedToAllImesLocked();
                 }
                 return true;
             case MSG_SYSTEM_UNLOCK_USER: {
@@ -5339,7 +5288,7 @@
         userData.mSwitchingController.resetCircularListLocked(mContext, settings);
         userData.mHardwareKeyboardShortcutController.update(settings);
 
-        sendOnNavButtonFlagsChangedLocked();
+        sendOnNavButtonFlagsChangedLocked(userData);
 
         // Notify InputMethodListListeners of the new installed InputMethods.
         final List<InputMethodInfo> inputMethodList = settings.getMethodList();
@@ -5348,14 +5297,38 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void sendOnNavButtonFlagsChangedLocked() {
-        final var bindingController = getInputMethodBindingController(mCurrentUserId);
+    void sendOnNavButtonFlagsChangedToAllImesLocked() {
+        for (int userId : mUserManagerInternal.getUserIds()) {
+            sendOnNavButtonFlagsChangedLocked(getUserData(userId));
+        }
+    }
+
+    @GuardedBy("ImfLock.class")
+    void sendOnNavButtonFlagsChangedLocked(@NonNull UserDataRepository.UserData userData) {
+        final var bindingController = userData.mBindingController;
         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod == null) {
             // No need to send the data if the IME is not yet bound.
             return;
         }
-        curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked());
+        curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData));
+    }
+
+    @WorkerThread
+    private void onUpdateResourceOverlay(@UserIdInt int userId) {
+        final int profileParentId = mUserManagerInternal.getProfileParentId(userId);
+        final boolean value =
+                InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
+        final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
+        final ArrayList<UserDataRepository.UserData> updatedUsers = new ArrayList<>();
+        for (int profileUserId : profileUserIds) {
+            final var userData = getUserData(profileUserId);
+            userData.mImeDrawsNavBar.set(value);
+            updatedUsers.add(userData);
+        }
+        synchronized (ImfLock.class) {
+            updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked);
+        }
     }
 
     @GuardedBy("ImfLock.class")
@@ -6123,6 +6096,7 @@
                         u.mImeBindingState.dump("        ", p);
                         p.println("      enabledSession=" + u.mEnabledSession);
                         p.println("      inFullscreenMode=" + u.mInFullscreenMode);
+                        p.println("      imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
                         p.println("      switchingController:");
                         u.mSwitchingController.dump(p, "        ");
                         p.println("      mLastEnabledInputMethodsStr="
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 656c87d..06f73f3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -202,7 +202,7 @@
         attrs.setTitle("Select input method");
         w.setAttributes(attrs);
         mService.updateSystemUiLocked(userId);
-        mService.sendOnNavButtonFlagsChangedLocked();
+        mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId));
         mSwitchingDialog.show();
     }
 
@@ -242,7 +242,7 @@
             // TODO(b/305849394): Make InputMethodMenuController multi-user aware
             final int userId = mService.getCurrentImeUserIdLocked();
             mService.updateSystemUiLocked(userId);
-            mService.sendOnNavButtonFlagsChangedLocked();
+            mService.sendOnNavButtonFlagsChangedToAllImesLocked();
             mDialogBuilder = null;
             mIms = null;
         }
diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
deleted file mode 100644
index 33e7a76..0000000
--- a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 98d7548..7c68d54 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -29,6 +29,7 @@
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Consumer;
 import java.util.function.IntFunction;
@@ -181,6 +182,12 @@
         String mLastEnabledInputMethodsStr = "";
 
         /**
+         * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+         */
+        @NonNull
+        final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+        /**
          * Intended to be instantiated only from this file.
          */
         private UserData(@UserIdInt int userId,