diff options
| author | 2024-03-07 11:18:06 -0800 | |
|---|---|---|
| committer | 2024-03-07 11:18:06 -0800 | |
| commit | 8fc358e7c6c9e55ff1158302cc75c80eff1f565c (patch) | |
| tree | e5ea3d6abf6b082680ae70af4dcaa891f1c89a6d | |
| parent | c37d81c43ac7124ae765f6a4471fee53237dc337 (diff) | |
Replace IMMS#mAdditionalSubtypeMap with AdditionalSubtypeMapRepository
With this commit
InputMethodManagerService#mAdditionalSubtypeMap
will be superseded by a newly introduced
AdditionalSubtypeMapRepository
so that all users' additional subtypes can be tracked equally rather
than treating current user's one specially. A notable improvement is
that this change allows us to save unnecessary file loading of
additional subtype persistent files, which may help us mitigate the
potential regression discussed in Bug 327861441.
There must be no observable behavior change in this commit.
Bug: 309837937
Test: atest CtsInputMethodTestCases:InputMethodSubtypeEndToEndTest
Test: atest CtsInputMethodInstallTestCases:AdditionalSubtypeLifecycleTest
Test: atest FrameworksInputMethodSystemServerTests
Change-Id: Ic003648cfc9950f190a6b3fec7fd8fb363bfe081
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 2205986fe9c9..912b8147d3ce 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()); |