Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8fc358e7 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

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
parent c37d81c4
Loading
Loading
Loading
Loading
+106 −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.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));
                }
            }
        });
    }
}
+46 −61
Original line number Diff line number Diff line
@@ -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 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) {
                AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
                        settings.getMethodMap());
                if (isCurrentUser) {
                    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 newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                    imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
            if (additionalSubtypeMap != newAdditionalSubtypeMap) {
                AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, settings.getMethodMap(),
                        settings.getUserId());
            }
        }
    }
@@ -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);

+1 −0
Original line number Diff line number Diff line
@@ -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());