diff options
3 files changed, 153 insertions, 61 deletions
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java new file mode 100644 index 000000000000..c7b60da2fc51 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java @@ -0,0 +1,106 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.UserInfo; +import android.os.Handler; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +/** + * Provides accesses to per-user additional {@link android.view.inputmethod.InputMethodSubtype} + * persistent storages. + */ +final class AdditionalSubtypeMapRepository { + @GuardedBy("ImfLock.class") + @NonNull + private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>(); + + /** + * Not intended to be instantiated. + */ + private AdditionalSubtypeMapRepository() { + } + + @NonNull + @GuardedBy("ImfLock.class") + static AdditionalSubtypeMap get(@UserIdInt int userId) { + final AdditionalSubtypeMap map = sPerUserMap.get(userId); + if (map != null) { + return map; + } + final AdditionalSubtypeMap newMap = AdditionalSubtypeUtils.load(userId); + sPerUserMap.put(userId, newMap); + return newMap; + } + + @GuardedBy("ImfLock.class") + static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map, + @NonNull InputMethodMap inputMethodMap) { + final AdditionalSubtypeMap previous = sPerUserMap.get(userId); + if (previous == map) { + return; + } + sPerUserMap.put(userId, map); + // TODO: Offload this to a background thread. + // TODO: Skip if the previous data is exactly the same as new one. + AdditionalSubtypeUtils.save(map, inputMethodMap, userId); + } + + static void initialize(@NonNull Handler handler) { + final UserManagerInternal userManagerInternal = + LocalServices.getService(UserManagerInternal.class); + handler.post(() -> { + userManagerInternal.addUserLifecycleListener( + new UserManagerInternal.UserLifecycleListener() { + @Override + public void onUserCreated(UserInfo user, @Nullable Object token) { + final int userId = user.id; + handler.post(() -> { + synchronized (ImfLock.class) { + if (!sPerUserMap.contains(userId)) { + sPerUserMap.put(userId, + AdditionalSubtypeUtils.load(userId)); + } + } + }); + } + + @Override + public void onUserRemoved(UserInfo user) { + final int userId = user.id; + handler.post(() -> { + synchronized (ImfLock.class) { + sPerUserMap.remove(userId); + } + }); + } + }); + synchronized (ImfLock.class) { + for (int userId : userManagerInternal.getUserIds()) { + sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId)); + } + } + }); + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 6688f536c8c7..12a95165f0ff 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -288,9 +288,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ImePlatformCompatUtils mImePlatformCompatUtils; final InputMethodDeviceConfigs mInputMethodDeviceConfigs; - @GuardedBy("ImfLock.class") - @NonNull - private AdditionalSubtypeMap mAdditionalSubtypeMap = AdditionalSubtypeMap.EMPTY_MAP; private final UserManagerInternal mUserManagerInternal; private final InputMethodMenuController mMenuController; @NonNull private final InputMethodBindingController mBindingController; @@ -1310,13 +1307,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final int userId = getChangingUserId(); synchronized (ImfLock.class) { final boolean isCurrentUser = (userId == mSettings.getUserId()); - final AdditionalSubtypeMap additionalSubtypeMap; + final AdditionalSubtypeMap additionalSubtypeMap = + AdditionalSubtypeMapRepository.get(userId); final InputMethodSettings settings; if (isCurrentUser) { - additionalSubtypeMap = mAdditionalSubtypeMap; settings = mSettings; } else { - additionalSubtypeMap = AdditionalSubtypeUtils.load(userId); settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO); } @@ -1331,11 +1327,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final AdditionalSubtypeMap newMap = additionalSubtypeMap.cloneWithRemoveOrSelf(changedImes); if (newMap != additionalSubtypeMap) { - if (isCurrentUser) { - mAdditionalSubtypeMap = newMap; - } - AdditionalSubtypeUtils.save( - newMap, settings.getMethodMap(), settings.getUserId()); + AdditionalSubtypeMapRepository.putAndSave(userId, newMap, + settings.getMethodMap()); } if (!changedImes.isEmpty()) { mChangedPackages.add(packageName); @@ -1382,13 +1375,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { final int userId = getChangingUserId(); final boolean isCurrentUser = (userId == mSettings.getUserId()); - AdditionalSubtypeMap additionalSubtypeMap; + AdditionalSubtypeMap additionalSubtypeMap = + AdditionalSubtypeMapRepository.get(userId); final InputMethodSettings settings; if (isCurrentUser) { - additionalSubtypeMap = mAdditionalSubtypeMap; settings = mSettings; } else { - additionalSubtypeMap = AdditionalSubtypeUtils.load(userId); settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO); } @@ -1419,11 +1411,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + imi.getComponent()); additionalSubtypeMap = additionalSubtypeMap.cloneWithRemoveOrSelf(imi.getId()); - AdditionalSubtypeUtils.save(additionalSubtypeMap, - settings.getMethodMap(), userId); - if (isCurrentUser) { - mAdditionalSubtypeMap = additionalSubtypeMap; - } + AdditionalSubtypeMapRepository.putAndSave(userId, + additionalSubtypeMap, settings.getMethodMap()); } } @@ -1658,12 +1647,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mShowOngoingImeSwitcherForPhones = false; + AdditionalSubtypeMapRepository.initialize(mHandler); + final int userId = mActivityManagerInternal.getCurrentUserId(); // mSettings should be created before buildInputMethodListLocked mSettings = InputMethodSettings.createEmptyMap(userId); - mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(context, mSettings.getMethodMap(), userId); @@ -1808,8 +1798,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mSettingsObserver.registerContentObserverLocked(newUserId); mSettings = InputMethodSettings.createEmptyMap(newUserId); - // Additional subtypes should be reset when the user is changed - mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(newUserId); final String defaultImiId = mSettings.getSelectedInputMethod(); if (DEBUG) { @@ -2017,7 +2005,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList. //TODO(b/210039666): use cache. - final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodSettings settings = queryMethodMapForUserLocked(userId); final InputMethodInfo imi = settings.getMethodMap().get( settings.getSelectedInputMethod()); return imi != null && imi.supportsStylusHandwriting() @@ -2045,7 +2033,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && directBootAwareness == DirectBootAwareness.AUTO) { settings = mSettings; } else { - final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId); + final AdditionalSubtypeMap additionalSubtypeMap = + AdditionalSubtypeMapRepository.get(userId); settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, directBootAwareness); } @@ -2066,7 +2055,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub methodList = mSettings.getEnabledInputMethodList(); settings = mSettings; } else { - settings = queryMethodMapForUser(userId); + settings = queryMethodMapForUserLocked(userId); methodList = settings.getEnabledInputMethodList(); } // filter caller's access to input methods @@ -2141,7 +2130,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return mSettings.getEnabledInputMethodSubtypeList( imi, allowsImplicitlyEnabledSubtypes); } - final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodSettings settings = queryMethodMapForUserLocked(userId); final InputMethodInfo imi = settings.getMethodMap().get(imiId); if (imi == null) { return Collections.emptyList(); @@ -4307,7 +4296,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return mSettings.getLastInputMethodSubtype(); } - final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodSettings settings = queryMethodMapForUserLocked(userId); return settings.getLastInputMethodSubtype(); } } @@ -4338,33 +4327,25 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return; } - if (mSettings.getUserId() == userId) { - final var newAdditionalSubtypeMap = mSettings.getNewAdditionalSubtypeMap( - imiId, toBeAdded, mAdditionalSubtypeMap, mPackageManagerInternal, - callingUid); - if (mAdditionalSubtypeMap == newAdditionalSubtypeMap) { - return; - } - AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, mSettings.getMethodMap(), - mSettings.getUserId()); - mAdditionalSubtypeMap = newAdditionalSubtypeMap; - final long ident = Binder.clearCallingIdentity(); - try { - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); - } finally { - Binder.restoreCallingIdentity(ident); - } - return; - } - - final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId); - final InputMethodSettings settings = queryInputMethodServicesInternal(mContext, userId, - additionalSubtypeMap, DirectBootAwareness.AUTO); + final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); + final boolean isCurrentUser = (mSettings.getUserId() == userId); + final InputMethodSettings settings = isCurrentUser + ? mSettings + : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, + DirectBootAwareness.AUTO); final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap( imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid); if (additionalSubtypeMap != newAdditionalSubtypeMap) { - AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, settings.getMethodMap(), - settings.getUserId()); + AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, + settings.getMethodMap()); + if (isCurrentUser) { + final long ident = Binder.clearCallingIdentity(); + try { + buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } } } @@ -4391,7 +4372,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { final boolean currentUser = (mSettings.getUserId() == userId); final InputMethodSettings settings = currentUser - ? mSettings : queryMethodMapForUser(userId); + ? mSettings : queryMethodMapForUserLocked(userId); if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) { return; } @@ -5293,7 +5274,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mMyPackageMonitor.clearKnownImePackageNamesLocked(); mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(), - mAdditionalSubtypeMap, DirectBootAwareness.AUTO); + AdditionalSubtypeMapRepository.get(mSettings.getUserId()), + DirectBootAwareness.AUTO); // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is @@ -5565,7 +5547,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return getCurrentInputMethodSubtypeLocked(); } - final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodSettings settings = queryMethodMapForUserLocked(userId); return settings.getCurrentInputMethodSubtypeForNonCurrentUsers(); } } @@ -5632,15 +5614,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (userId == mSettings.getUserId()) { settings = mSettings; } else { - final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId); + final AdditionalSubtypeMap additionalSubtypeMap = + AdditionalSubtypeMapRepository.get(userId); settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO); } return settings.getMethodMap().get(settings.getSelectedInputMethod()); } - private InputMethodSettings queryMethodMapForUser(@UserIdInt int userId) { - final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId); + @GuardedBy("ImfLock.class") + private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) { + final AdditionalSubtypeMap additionalSubtypeMap = + AdditionalSubtypeMapRepository.get(userId); return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO); } @@ -5656,7 +5641,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); return true; } - final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodSettings settings = queryMethodMapForUserLocked(userId); if (!settings.getMethodMap().containsKey(imeId) || !settings.getEnabledInputMethodList().contains( settings.getMethodMap().get(imeId))) { @@ -5796,7 +5781,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodEnabledLocked(imeId, enabled); return true; } - final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodSettings settings = queryMethodMapForUserLocked(userId); if (!settings.getMethodMap().containsKey(imeId)) { return false; // IME is not found. } @@ -6550,7 +6535,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled); } } else { - final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodSettings settings = queryMethodMapForUserLocked(userId); if (enabled) { if (!settings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; @@ -6685,7 +6670,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub nextEnabledImes = mSettings.getEnabledInputMethodList(); } else { final AdditionalSubtypeMap additionalSubtypeMap = - AdditionalSubtypeUtils.load(userId); + AdditionalSubtypeMapRepository.get(userId); final InputMethodSettings settings = queryInputMethodServicesInternal( mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index f4d95afaacf4..b9c5b36f9775 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -201,6 +201,7 @@ public class InputMethodManagerServiceTestBase { when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true); when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean())) .thenReturn(new int[] {0}); + when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0}); when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true); when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt())) .thenReturn(Binder.getCallingUid()); |