Loading services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +56 −66 Original line number Diff line number Diff line Loading @@ -717,13 +717,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return; } for (int userId : mUserManagerInternal.getUserIds()) { final InputMethodSettings settings = queryInputMethodServicesInternal( mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); // Does InputMethodInfo really have data dependency on system locale? // TODO(b/356679261): Check if we really need to update RawInputMethodInfo here. { final var userData = getUserData(userId); final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final var rawMethodMap = queryRawInputMethodServiceMap(mContext, userId); userData.mRawInputMethodMap.set(rawMethodMap); final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, DirectBootAwareness.AUTO, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); final var settings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, settings); } postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId); // If the locale is changed, needs to reset the default ime resetDefaultImeLocked(mContext, userId); Loading Loading @@ -797,17 +803,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void onFinishPackageChangesInternal() { final int userId = getChangingUserId(); final var userData = getUserData(userId); // Instantiating InputMethodInfo requires disk I/O. // Do them before acquiring the lock to minimize the chances of ANR (b/340221861). final var newMethodMapWithoutAdditionalSubtypes = queryInputMethodServicesInternal(mContext, userId, AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO) .getMethodMap(); userData.mRawInputMethodMap.set(queryRawInputMethodServiceMap(mContext, userId)); synchronized (ImfLock.class) { final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); InputMethodInfo curIm = null; Loading Loading @@ -837,6 +839,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Clear additional subtypes as a batch operation. final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final AdditionalSubtypeMap newAdditionalSubtypeMap = additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes); final boolean additionalSubtypeChanged = Loading @@ -846,8 +849,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. settings.getMethodMap()); } final var newMethodMap = newMethodMapWithoutAdditionalSubtypes .applyAdditionalSubtypes(newAdditionalSubtypeMap); final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap( newAdditionalSubtypeMap, DirectBootAwareness.AUTO, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); if (InputMethodMap.areSame(settings.getMethodMap(), newMethodMap)) { // No update in the actual IME map. Loading Loading @@ -1065,9 +1070,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int userId = user.getUserIdentifier(); SecureSettingsWrapper.onUserUnlocking(userId); mService.mIoHandler.post(() -> { final var settings = queryInputMethodServicesInternal(mService.mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, settings); final var userData = mService.getUserData(userId); final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, true); final var newSettings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, newSettings); synchronized (ImfLock.class) { if (!mService.mSystemReady) { return; Loading Loading @@ -1105,19 +1112,22 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. for (int userId : userIds) { Slog.d(TAG, "Start initialization for user=" + userId); final var userData = mService.getUserData(userId); AdditionalSubtypeMapRepository.initializeIfNecessary(userId); final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final var settings = InputMethodManagerService.queryInputMethodServicesInternal( context, userId, additionalSubtypeMap, DirectBootAwareness.AUTO).getMethodMap(); InputMethodSettingsRepository.put(userId, InputMethodSettings.create(settings, userId)); final var rawMethodMap = queryRawInputMethodServiceMap(context, userId); userData.mRawInputMethodMap.set(rawMethodMap); final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, DirectBootAwareness.AUTO, userManagerInternal.isUserUnlockingOrUnlocked(userId)); final var settings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, settings); final int profileParentId = userManagerInternal.getProfileParentId(userId); final boolean value = InputMethodDrawsNavBarResourceMonitor.evaluate(context, profileParentId); final var userData = mService.getUserData(userId); userData.mImeDrawsNavBar.set(value); userData.mBackgroundLoadLatch.countDown(); Loading @@ -1132,12 +1142,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Called on ActivityManager thread. SecureSettingsWrapper.onUserStopped(userId); mService.mIoHandler.post(() -> { final var userData = mService.getUserData(userId); final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final var settings = InputMethodManagerService.queryInputMethodServicesInternal( mService.mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO).getMethodMap(); final var rawMethodMap = userData.mRawInputMethodMap.get(); final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, DirectBootAwareness.AUTO, false /* userUnlocked */); InputMethodSettingsRepository.put(userId, InputMethodSettings.create(settings, userId)); InputMethodSettings.create(methodMap, userId)); }); } } Loading Loading @@ -1635,15 +1646,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid) { final InputMethodSettings settings; if (directBootAwareness == DirectBootAwareness.AUTO) { settings = InputMethodSettingsRepository.get(userId); } else { final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, directBootAwareness); } final var userData = getUserData(userId); final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( AdditionalSubtypeMapRepository.get(userId), directBootAwareness, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); final var settings = InputMethodSettings.create(methodMap, userId); // Create a copy. final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList()); // filter caller's access to input methods Loading Loading @@ -4342,6 +4349,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + subtype.getLocale() + ", " + subtype.getMode()); } } final var userData = getUserData(userId); synchronized (ImfLock.class) { if (!mSystemReady) { return; Loading @@ -4357,9 +4365,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. settings.getMethodMap()); final long ident = Binder.clearCallingIdentity(); try { final InputMethodSettings newSettings = queryInputMethodServicesInternal( mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); final var newSettings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, newSettings); if (isCurrentUser) { postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, Loading Loading @@ -5298,31 +5307,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @NonNull static InputMethodSettings queryInputMethodServicesInternal(Context context, @UserIdInt int userId, @NonNull AdditionalSubtypeMap additionalSubtypeMap, @DirectBootAwareness int directBootAwareness) { static RawInputMethodMap queryRawInputMethodServiceMap(Context context, @UserIdInt int userId) { final Context userAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); final int directBootAwarenessFlags; switch (directBootAwareness) { case DirectBootAwareness.ANY: directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; break; case DirectBootAwareness.AUTO: directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO; break; default: directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO; Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness + ". Falling back to DirectBootAwareness.AUTO"); break; } final int flags = PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS | directBootAwarenessFlags; | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; // Beware that package visibility filtering will be enforced based on the effective calling // identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid() Loading @@ -5338,14 +5331,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final List<String> enabledInputMethodList = InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId); final InputMethodMap methodMap = filterInputMethodServices( additionalSubtypeMap, enabledInputMethodList, userAwareContext, services); return InputMethodSettings.create(methodMap, userId); return filterInputMethodServices(enabledInputMethodList, userAwareContext, services); } @NonNull static InputMethodMap filterInputMethodServices( @NonNull AdditionalSubtypeMap additionalSubtypeMap, static RawInputMethodMap filterInputMethodServices( List<String> enabledInputMethodList, Context userAwareContext, List<ResolveInfo> services) { final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>(); Loading @@ -5366,7 +5356,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. try { final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri, additionalSubtypeMap.get(imeId)); Collections.emptyList()); if (imi.isVrOnly()) { continue; // Skip VR-only IME, which isn't supported for now. } Loading @@ -5389,7 +5379,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Slog.wtf(TAG, "Unable to load input method " + imeId, e); } } return InputMethodMap.of(methodMap); return RawInputMethodMap.of(methodMap); } @GuardedBy("ImfLock.class") Loading services/core/java/com/android/server/inputmethod/RawInputMethodMap.java 0 → 100644 +105 −0 Original line number Diff line number Diff line /* * 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.AnyThread; import android.annotation.NonNull; import android.util.ArrayMap; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import com.android.internal.inputmethod.DirectBootAwareness; import java.util.List; /** * This is quite similar to {@link InputMethodMap} with two major differences. * * <ul> * <li>Additional {@link android.view.inputmethod.InputMethodSubtype} is not included.</li> * <li>Always include direct-boot unaware {@link android.inputmethodservice.InputMethodService}. * </li> * </ul> * * <p>As seen in {@link #toInputMethodMap(AdditionalSubtypeMap, int, boolean)}, you can consider * this is a prototype data where you can always derive {@link InputMethodMap} with * {@link AdditionalSubtypeMap} and a boolean information whether * {@link com.android.server.pm.UserManagerInternal#isUserUnlockingOrUnlocked(int)} returns * {@code true} or not.</p> */ final class RawInputMethodMap { static final String TAG = "RawInputMethodMap"; private static final ArrayMap<String, InputMethodInfo> EMPTY_MAP = new ArrayMap<>(); private final ArrayMap<String, InputMethodInfo> mMap; static RawInputMethodMap emptyMap() { return new RawInputMethodMap(EMPTY_MAP); } static RawInputMethodMap of(@NonNull ArrayMap<String, InputMethodInfo> map) { return new RawInputMethodMap(map); } private RawInputMethodMap(@NonNull ArrayMap<String, InputMethodInfo> map) { mMap = map.isEmpty() ? EMPTY_MAP : new ArrayMap<>(map); } @AnyThread @NonNull List<InputMethodInfo> values() { return List.copyOf(mMap.values()); } @NonNull InputMethodMap toInputMethodMap(@NonNull AdditionalSubtypeMap additionalSubtypeMap, @DirectBootAwareness int directBootAwareness, boolean userUnlocked) { final int size = mMap.size(); final var newMap = new ArrayMap<String, InputMethodInfo>(size); final boolean requireDirectBootAwareFlag; switch (directBootAwareness) { case DirectBootAwareness.ANY -> requireDirectBootAwareFlag = false; case DirectBootAwareness.AUTO -> requireDirectBootAwareFlag = !userUnlocked; default -> { requireDirectBootAwareFlag = !userUnlocked; Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness + ". Falling back to DirectBootAwareness.AUTO"); } } boolean updated = false; for (int i = 0; i < size; ++i) { final var imeId = mMap.keyAt(i); final var imi = mMap.valueAt(i); if (requireDirectBootAwareFlag && !imi.getServiceInfo().directBootAware) { updated = true; continue; } final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId); if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) { newMap.put(imi.getId(), imi); } else { updated = true; newMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes)); } } // If newMap is semantically the same as mMap, we can reuse mMap and discard newMap. return InputMethodMap.of(updated ? newMap : mMap); } } services/core/java/com/android/server/inputmethod/UserData.java +12 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.internal.inputmethod.IRemoteInputConnection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** Placeholder for all IMMS user specific fields */ final class UserData { Loading @@ -43,6 +44,17 @@ final class UserData { @NonNull final CountDownLatch mBackgroundLoadLatch = new CountDownLatch(1); /** * Contains non-null {@link RawInputMethodMap}, which represents the latest collections of * {@link android.view.inputmethod.InputMethodInfo} for both direct-boot aware and unaware IMEs * before taking {@link AdditionalSubtypeMap} into account. * * <p>See {@link RawInputMethodMap} for details on when to use this.</p> */ @NonNull final AtomicReference<RawInputMethodMap> mRawInputMethodMap = new AtomicReference<>(RawInputMethodMap.emptyMap()); @NonNull final InputMethodBindingController mBindingController; Loading services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -122,8 +122,8 @@ public class InputMethodManagerServiceRestrictImeAmountTest extends private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList, List<String> enabledComponents) { final InputMethodMap methodMap = InputMethodManagerService.filterInputMethodServices( AdditionalSubtypeMap.EMPTY_MAP, enabledComponents, mContext, resolveInfoList); final var methodMap = InputMethodManagerService.filterInputMethodServices( enabledComponents, mContext, resolveInfoList); return methodMap.values(); } Loading services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +9 −3 Original line number Diff line number Diff line Loading @@ -269,15 +269,21 @@ public class InputMethodManagerServiceTestBase { LocalServices.removeServiceForTest(InputMethodManagerInternal.class); lifecycle.onStart(); final var userData = mInputMethodManagerService.getUserData(mUserId); // Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml. // TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it. AdditionalSubtypeMapRepository.initializeIfNecessary(mUserId); final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext, mUserId, AdditionalSubtypeMapRepository.get(mUserId), DirectBootAwareness.AUTO); final var rawMethodMap = InputMethodManagerService.queryRawInputMethodServiceMap(mContext, mUserId); userData.mRawInputMethodMap.set(rawMethodMap); final var settings = InputMethodSettings.create(rawMethodMap.toInputMethodMap( AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO, true /* userUnlocked */), mUserId); InputMethodSettingsRepository.put(mUserId, settings); // Emulate that the user initialization is done. mInputMethodManagerService.getUserData(mUserId).mBackgroundLoadLatch.countDown(); userData.mBackgroundLoadLatch.countDown(); // After this boot phase, services can broadcast Intents. lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); Loading Loading
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +56 −66 Original line number Diff line number Diff line Loading @@ -717,13 +717,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return; } for (int userId : mUserManagerInternal.getUserIds()) { final InputMethodSettings settings = queryInputMethodServicesInternal( mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); // Does InputMethodInfo really have data dependency on system locale? // TODO(b/356679261): Check if we really need to update RawInputMethodInfo here. { final var userData = getUserData(userId); final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final var rawMethodMap = queryRawInputMethodServiceMap(mContext, userId); userData.mRawInputMethodMap.set(rawMethodMap); final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, DirectBootAwareness.AUTO, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); final var settings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, settings); } postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId); // If the locale is changed, needs to reset the default ime resetDefaultImeLocked(mContext, userId); Loading Loading @@ -797,17 +803,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void onFinishPackageChangesInternal() { final int userId = getChangingUserId(); final var userData = getUserData(userId); // Instantiating InputMethodInfo requires disk I/O. // Do them before acquiring the lock to minimize the chances of ANR (b/340221861). final var newMethodMapWithoutAdditionalSubtypes = queryInputMethodServicesInternal(mContext, userId, AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO) .getMethodMap(); userData.mRawInputMethodMap.set(queryRawInputMethodServiceMap(mContext, userId)); synchronized (ImfLock.class) { final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); InputMethodInfo curIm = null; Loading Loading @@ -837,6 +839,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Clear additional subtypes as a batch operation. final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final AdditionalSubtypeMap newAdditionalSubtypeMap = additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes); final boolean additionalSubtypeChanged = Loading @@ -846,8 +849,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. settings.getMethodMap()); } final var newMethodMap = newMethodMapWithoutAdditionalSubtypes .applyAdditionalSubtypes(newAdditionalSubtypeMap); final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap( newAdditionalSubtypeMap, DirectBootAwareness.AUTO, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); if (InputMethodMap.areSame(settings.getMethodMap(), newMethodMap)) { // No update in the actual IME map. Loading Loading @@ -1065,9 +1070,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int userId = user.getUserIdentifier(); SecureSettingsWrapper.onUserUnlocking(userId); mService.mIoHandler.post(() -> { final var settings = queryInputMethodServicesInternal(mService.mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, settings); final var userData = mService.getUserData(userId); final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, true); final var newSettings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, newSettings); synchronized (ImfLock.class) { if (!mService.mSystemReady) { return; Loading Loading @@ -1105,19 +1112,22 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. for (int userId : userIds) { Slog.d(TAG, "Start initialization for user=" + userId); final var userData = mService.getUserData(userId); AdditionalSubtypeMapRepository.initializeIfNecessary(userId); final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final var settings = InputMethodManagerService.queryInputMethodServicesInternal( context, userId, additionalSubtypeMap, DirectBootAwareness.AUTO).getMethodMap(); InputMethodSettingsRepository.put(userId, InputMethodSettings.create(settings, userId)); final var rawMethodMap = queryRawInputMethodServiceMap(context, userId); userData.mRawInputMethodMap.set(rawMethodMap); final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, DirectBootAwareness.AUTO, userManagerInternal.isUserUnlockingOrUnlocked(userId)); final var settings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, settings); final int profileParentId = userManagerInternal.getProfileParentId(userId); final boolean value = InputMethodDrawsNavBarResourceMonitor.evaluate(context, profileParentId); final var userData = mService.getUserData(userId); userData.mImeDrawsNavBar.set(value); userData.mBackgroundLoadLatch.countDown(); Loading @@ -1132,12 +1142,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Called on ActivityManager thread. SecureSettingsWrapper.onUserStopped(userId); mService.mIoHandler.post(() -> { final var userData = mService.getUserData(userId); final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final var settings = InputMethodManagerService.queryInputMethodServicesInternal( mService.mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO).getMethodMap(); final var rawMethodMap = userData.mRawInputMethodMap.get(); final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, DirectBootAwareness.AUTO, false /* userUnlocked */); InputMethodSettingsRepository.put(userId, InputMethodSettings.create(settings, userId)); InputMethodSettings.create(methodMap, userId)); }); } } Loading Loading @@ -1635,15 +1646,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid) { final InputMethodSettings settings; if (directBootAwareness == DirectBootAwareness.AUTO) { settings = InputMethodSettingsRepository.get(userId); } else { final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, directBootAwareness); } final var userData = getUserData(userId); final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( AdditionalSubtypeMapRepository.get(userId), directBootAwareness, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); final var settings = InputMethodSettings.create(methodMap, userId); // Create a copy. final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList()); // filter caller's access to input methods Loading Loading @@ -4342,6 +4349,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + subtype.getLocale() + ", " + subtype.getMode()); } } final var userData = getUserData(userId); synchronized (ImfLock.class) { if (!mSystemReady) { return; Loading @@ -4357,9 +4365,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. settings.getMethodMap()); final long ident = Binder.clearCallingIdentity(); try { final InputMethodSettings newSettings = queryInputMethodServicesInternal( mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, mUserManagerInternal.isUserUnlockingOrUnlocked(userId)); final var newSettings = InputMethodSettings.create(methodMap, userId); InputMethodSettingsRepository.put(userId, newSettings); if (isCurrentUser) { postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, Loading Loading @@ -5298,31 +5307,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @NonNull static InputMethodSettings queryInputMethodServicesInternal(Context context, @UserIdInt int userId, @NonNull AdditionalSubtypeMap additionalSubtypeMap, @DirectBootAwareness int directBootAwareness) { static RawInputMethodMap queryRawInputMethodServiceMap(Context context, @UserIdInt int userId) { final Context userAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); final int directBootAwarenessFlags; switch (directBootAwareness) { case DirectBootAwareness.ANY: directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; break; case DirectBootAwareness.AUTO: directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO; break; default: directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO; Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness + ". Falling back to DirectBootAwareness.AUTO"); break; } final int flags = PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS | directBootAwarenessFlags; | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; // Beware that package visibility filtering will be enforced based on the effective calling // identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid() Loading @@ -5338,14 +5331,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final List<String> enabledInputMethodList = InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId); final InputMethodMap methodMap = filterInputMethodServices( additionalSubtypeMap, enabledInputMethodList, userAwareContext, services); return InputMethodSettings.create(methodMap, userId); return filterInputMethodServices(enabledInputMethodList, userAwareContext, services); } @NonNull static InputMethodMap filterInputMethodServices( @NonNull AdditionalSubtypeMap additionalSubtypeMap, static RawInputMethodMap filterInputMethodServices( List<String> enabledInputMethodList, Context userAwareContext, List<ResolveInfo> services) { final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>(); Loading @@ -5366,7 +5356,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. try { final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri, additionalSubtypeMap.get(imeId)); Collections.emptyList()); if (imi.isVrOnly()) { continue; // Skip VR-only IME, which isn't supported for now. } Loading @@ -5389,7 +5379,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Slog.wtf(TAG, "Unable to load input method " + imeId, e); } } return InputMethodMap.of(methodMap); return RawInputMethodMap.of(methodMap); } @GuardedBy("ImfLock.class") Loading
services/core/java/com/android/server/inputmethod/RawInputMethodMap.java 0 → 100644 +105 −0 Original line number Diff line number Diff line /* * 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.AnyThread; import android.annotation.NonNull; import android.util.ArrayMap; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import com.android.internal.inputmethod.DirectBootAwareness; import java.util.List; /** * This is quite similar to {@link InputMethodMap} with two major differences. * * <ul> * <li>Additional {@link android.view.inputmethod.InputMethodSubtype} is not included.</li> * <li>Always include direct-boot unaware {@link android.inputmethodservice.InputMethodService}. * </li> * </ul> * * <p>As seen in {@link #toInputMethodMap(AdditionalSubtypeMap, int, boolean)}, you can consider * this is a prototype data where you can always derive {@link InputMethodMap} with * {@link AdditionalSubtypeMap} and a boolean information whether * {@link com.android.server.pm.UserManagerInternal#isUserUnlockingOrUnlocked(int)} returns * {@code true} or not.</p> */ final class RawInputMethodMap { static final String TAG = "RawInputMethodMap"; private static final ArrayMap<String, InputMethodInfo> EMPTY_MAP = new ArrayMap<>(); private final ArrayMap<String, InputMethodInfo> mMap; static RawInputMethodMap emptyMap() { return new RawInputMethodMap(EMPTY_MAP); } static RawInputMethodMap of(@NonNull ArrayMap<String, InputMethodInfo> map) { return new RawInputMethodMap(map); } private RawInputMethodMap(@NonNull ArrayMap<String, InputMethodInfo> map) { mMap = map.isEmpty() ? EMPTY_MAP : new ArrayMap<>(map); } @AnyThread @NonNull List<InputMethodInfo> values() { return List.copyOf(mMap.values()); } @NonNull InputMethodMap toInputMethodMap(@NonNull AdditionalSubtypeMap additionalSubtypeMap, @DirectBootAwareness int directBootAwareness, boolean userUnlocked) { final int size = mMap.size(); final var newMap = new ArrayMap<String, InputMethodInfo>(size); final boolean requireDirectBootAwareFlag; switch (directBootAwareness) { case DirectBootAwareness.ANY -> requireDirectBootAwareFlag = false; case DirectBootAwareness.AUTO -> requireDirectBootAwareFlag = !userUnlocked; default -> { requireDirectBootAwareFlag = !userUnlocked; Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness + ". Falling back to DirectBootAwareness.AUTO"); } } boolean updated = false; for (int i = 0; i < size; ++i) { final var imeId = mMap.keyAt(i); final var imi = mMap.valueAt(i); if (requireDirectBootAwareFlag && !imi.getServiceInfo().directBootAware) { updated = true; continue; } final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId); if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) { newMap.put(imi.getId(), imi); } else { updated = true; newMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes)); } } // If newMap is semantically the same as mMap, we can reuse mMap and discard newMap. return InputMethodMap.of(updated ? newMap : mMap); } }
services/core/java/com/android/server/inputmethod/UserData.java +12 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.internal.inputmethod.IRemoteInputConnection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** Placeholder for all IMMS user specific fields */ final class UserData { Loading @@ -43,6 +44,17 @@ final class UserData { @NonNull final CountDownLatch mBackgroundLoadLatch = new CountDownLatch(1); /** * Contains non-null {@link RawInputMethodMap}, which represents the latest collections of * {@link android.view.inputmethod.InputMethodInfo} for both direct-boot aware and unaware IMEs * before taking {@link AdditionalSubtypeMap} into account. * * <p>See {@link RawInputMethodMap} for details on when to use this.</p> */ @NonNull final AtomicReference<RawInputMethodMap> mRawInputMethodMap = new AtomicReference<>(RawInputMethodMap.emptyMap()); @NonNull final InputMethodBindingController mBindingController; Loading
services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -122,8 +122,8 @@ public class InputMethodManagerServiceRestrictImeAmountTest extends private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList, List<String> enabledComponents) { final InputMethodMap methodMap = InputMethodManagerService.filterInputMethodServices( AdditionalSubtypeMap.EMPTY_MAP, enabledComponents, mContext, resolveInfoList); final var methodMap = InputMethodManagerService.filterInputMethodServices( enabledComponents, mContext, resolveInfoList); return methodMap.values(); } Loading
services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +9 −3 Original line number Diff line number Diff line Loading @@ -269,15 +269,21 @@ public class InputMethodManagerServiceTestBase { LocalServices.removeServiceForTest(InputMethodManagerInternal.class); lifecycle.onStart(); final var userData = mInputMethodManagerService.getUserData(mUserId); // Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml. // TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it. AdditionalSubtypeMapRepository.initializeIfNecessary(mUserId); final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext, mUserId, AdditionalSubtypeMapRepository.get(mUserId), DirectBootAwareness.AUTO); final var rawMethodMap = InputMethodManagerService.queryRawInputMethodServiceMap(mContext, mUserId); userData.mRawInputMethodMap.set(rawMethodMap); final var settings = InputMethodSettings.create(rawMethodMap.toInputMethodMap( AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO, true /* userUnlocked */), mUserId); InputMethodSettingsRepository.put(mUserId, settings); // Emulate that the user initialization is done. mInputMethodManagerService.getUserData(mUserId).mBackgroundLoadLatch.countDown(); userData.mBackgroundLoadLatch.countDown(); // After this boot phase, services can broadcast Intents. lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); Loading