diff options
6 files changed, 164 insertions, 252 deletions
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 5ab493b25859..9837ab16a310 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 @@ final class InputMethodBindingController { 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 000000000000..b835d058f00f --- /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 e1803dc7caf6..e342c78edad0 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.NonNull; 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.StartInputReason; 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.Objects; 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }); } - // 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final var userData = getUserData(userId); userData.mSwitchingController.resetCircularListLocked(mContext, settings); userData.mHardwareKeyboardShortcutController.update(settings); - sendOnNavButtonFlagsChangedLocked(); + sendOnNavButtonFlagsChangedLocked(userData); } @GuardedBy("ImfLock.class") @@ -5005,7 +4954,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @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 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 656c87deac6b..06f73f34e427 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 @@ final class InputMethodMenuController { attrs.setTitle("Select input method"); w.setAttributes(attrs); mService.updateSystemUiLocked(userId); - mService.sendOnNavButtonFlagsChangedLocked(); + mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId)); mSwitchingDialog.show(); } @@ -242,7 +242,7 @@ final class InputMethodMenuController { // 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 33e7a7621340..000000000000 --- 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 98d7548d3dd2..7c68d547ecd3 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.annotations.GuardedBy; 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 @@ final class UserDataRepository { 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, |