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

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

Move #mSwitchingController to UserData

With this CL

  InputMethodManagerService#mSwitchingController

to UserData so that each user can keep maintaining its own instance.

The observable behavior should remain to be the same for single user
mode. For experimental concurrent multi-user mode, the controller is
now properly initialized in

  IMMS#experimentalInitializeVisibleBackgroundUserLocked().

Bug: 305849394
Test: atest FrameworksInputMethodSystemServerTests
Flag: android.view.inputmethod.concurrent_input_methods
Change-Id: I47c641623aebc6885fa005f4ff688c3786c64e8d
parent d9227e90
Loading
Loading
Loading
Loading
+23 −36
Original line number Original line Diff line number Diff line
@@ -377,11 +377,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
    @SharedByAllUsersField
    @SharedByAllUsersField
    private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
    private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();


    // TODO: Instantiate mSwitchingController for each user.
    @NonNull
    @MultiUserUnawareField
    private InputMethodSubtypeSwitchingController mSwitchingController;

    @Nullable
    @Nullable
    private StatusBarManagerInternal mStatusBarManagerInternal;
    private StatusBarManagerInternal mStatusBarManagerInternal;
    @SharedByAllUsersField
    @SharedByAllUsersField
@@ -1287,7 +1282,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
            @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
                    bindingControllerFactory = userId -> new InputMethodBindingController(userId,
                    bindingControllerFactory = userId -> new InputMethodBindingController(userId,
                    InputMethodManagerService.this);
                    InputMethodManagerService.this);
            mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
            mUserDataRepository = new UserDataRepository(mContext, mHandler, mUserManagerInternal,
                    bindingControllerForTesting != null ? bindingControllerForTesting
                    bindingControllerForTesting != null ? bindingControllerForTesting
                            : bindingControllerFactory);
                            : bindingControllerFactory);
            for (int id : mUserManagerInternal.getUserIds()) {
            for (int id : mUserManagerInternal.getUserIds()) {
@@ -1295,9 +1290,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            }
            }


            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
            final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);

            final var userData = getUserData(mCurrentUserId);
            mSwitchingController = new InputMethodSubtypeSwitchingController(context, settings);
            userData.mSwitchingController.resetCircularListLocked(settings);
            getUserData(mCurrentUserId).mHardwareKeyboardShortcutController.update(settings);
            userData.mHardwareKeyboardShortcutController.update(settings);
            mMenuController = new InputMethodMenuController(this);
            mMenuController = new InputMethodMenuController(this);
            mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
            mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
            mVisibilityApplier = new DefaultImeVisibilityApplier(this);
            mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -2928,7 +2923,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
     *         {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
     *         {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
     *     </li>
     *     </li>
     *     <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li>
     *     <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li>
     *     <li>{@link #mSwitchingController} is ignored.</li>
     *     <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li>
     *     <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li>
     *     <li>and so on.</li>
     *     <li>and so on.</li>
     * </ul>
     * </ul>
@@ -2963,6 +2957,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
        }
        }


        final var userData = getUserData(userId);
        final var userData = getUserData(userId);
        userData.mSwitchingController.resetCircularListLocked(settings);
        userData.mHardwareKeyboardShortcutController.update(settings);
        userData.mHardwareKeyboardShortcutController.update(settings);
    }
    }


@@ -3039,13 +3034,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
            resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
        }
        }


        // TODO: Instantiate mSwitchingController for each user.
        final var userData = getUserData(userId);
        if (userId == mSwitchingController.getUserId()) {
        userData.mSwitchingController.resetCircularListLocked(settings);
            mSwitchingController.resetCircularListLocked(settings);
        userData.mHardwareKeyboardShortcutController.update(settings);
        } else {
            mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings);
        }
        getUserData(userId).mHardwareKeyboardShortcutController.update(settings);
        sendOnNavButtonFlagsChangedLocked();
        sendOnNavButtonFlagsChangedLocked();
    }
    }


@@ -4188,8 +4179,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
        final int userId = mCurrentUserId;
        final int userId = mCurrentUserId;
        final var bindingController = getInputMethodBindingController(userId);
        final var bindingController = getInputMethodBindingController(userId);
        final var currentImi = bindingController.getSelectedMethod();
        final var currentImi = bindingController.getSelectedMethod();
        final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
        final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
                onlyCurrentIme, currentImi, bindingController.getCurrentSubtype());
                .getNextInputMethodLocked(onlyCurrentIme, currentImi,
                        bindingController.getCurrentSubtype());
        if (nextSubtype == null) {
        if (nextSubtype == null) {
            return false;
            return false;
        }
        }
@@ -4207,8 +4199,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            final int userId = mCurrentUserId;
            final int userId = mCurrentUserId;
            final var bindingController = getInputMethodBindingController(userId);
            final var bindingController = getInputMethodBindingController(userId);
            final var currentImi = bindingController.getSelectedMethod();
            final var currentImi = bindingController.getSelectedMethod();
            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
            final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
                    false /* onlyCurrentIme */, currentImi, bindingController.getCurrentSubtype());
                    .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
                            bindingController.getCurrentSubtype());
            return nextSubtype != null;
            return nextSubtype != null;
        }
        }
    }
    }
@@ -4641,13 +4634,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
                return;
                return;
            }
            }
            final int userId = mCurrentUserId;
            final int userId = mCurrentUserId;
            if (userId != mSwitchingController.getUserId()) {
            final var bindingController = getInputMethodBindingController(userId);
                return;
            final InputMethodInfo imi = bindingController.getSelectedMethod();
            }
            final var imi = getInputMethodBindingController(userId).getSelectedMethod();
            if (imi != null) {
            if (imi != null) {
                mSwitchingController.onUserActionLocked(imi,
                getUserData(userId).mSwitchingController.onUserActionLocked(imi,
                        getInputMethodBindingController(userId).getCurrentSubtype());
                        bindingController.getCurrentSubtype());
            }
            }
        }
        }
    }
    }
@@ -5308,13 +5299,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.


        updateDefaultVoiceImeIfNeededLocked();
        updateDefaultVoiceImeIfNeededLocked();


        // TODO: Instantiate mSwitchingController for each user.
        final var userData = getUserData(userId);
        if (userId == mSwitchingController.getUserId()) {
        userData.mSwitchingController.resetCircularListLocked(settings);
            mSwitchingController.resetCircularListLocked(settings);
        userData.mHardwareKeyboardShortcutController.update(settings);
        } else {
            mSwitchingController = new InputMethodSubtypeSwitchingController(mContext, settings);
        }
        getUserData(userId).mHardwareKeyboardShortcutController.update(settings);


        sendOnNavButtonFlagsChangedLocked();
        sendOnNavButtonFlagsChangedLocked();


@@ -6115,6 +6102,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
                        p.println("      hasMainConnection="
                        p.println("      hasMainConnection="
                                + u.mBindingController.hasMainConnection());
                                + u.mBindingController.hasMainConnection());
                        p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
                        p.println("      isVisibleBound=" + u.mBindingController.isVisibleBound());
                        p.println("      mSwitchingController:");
                        u.mSwitchingController.dump(p, "        ");
                    };
                    };
            mUserDataRepository.forAllUserData(userDataDump);
            mUserDataRepository.forAllUserData(userDataDump);


@@ -6135,8 +6124,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            p.println("  mSettingsObserver=" + mSettingsObserver);
            p.println("  mSettingsObserver=" + mSettingsObserver);
            p.println("  mStylusIds=" + (mStylusIds != null
            p.println("  mStylusIds=" + (mStylusIds != null
                    ? Arrays.toString(mStylusIds.toArray()) : ""));
                    ? Arrays.toString(mStylusIds.toArray()) : ""));
            p.println("  mSwitchingController:");
            mSwitchingController.dump(p, "    ");


            p.println("  mStartInputHistory:");
            p.println("  mStartInputHistory:");
            mStartInputHistory.dump(pw, "    ");
            mStartInputHistory.dump(pw, "    ");
+17 −4
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.inputmethod;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.Handler;
import android.util.SparseArray;
import android.util.SparseArray;
@@ -35,12 +36,15 @@ final class UserDataRepository {


    private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
    private final IntFunction<InputMethodBindingController> mBindingControllerFactory;


    @NonNull
    private final Context mContext;

    @GuardedBy("ImfLock.class")
    @GuardedBy("ImfLock.class")
    @NonNull
    @NonNull
    UserData getOrCreate(@UserIdInt int userId) {
    UserData getOrCreate(@UserIdInt int userId) {
        UserData userData = mUserData.get(userId);
        UserData userData = mUserData.get(userId);
        if (userData == null) {
        if (userData == null) {
            userData = new UserData(userId, mBindingControllerFactory.apply(userId));
            userData = new UserData(userId, mBindingControllerFactory.apply(userId), mContext);
            mUserData.put(userId, userData);
            mUserData.put(userId, userData);
        }
        }
        return userData;
        return userData;
@@ -53,8 +57,11 @@ final class UserDataRepository {
        }
        }
    }
    }


    UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
    UserDataRepository(
            @NonNull Context context,
            @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
            @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
            @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
        mContext = context;
        mBindingControllerFactory = bindingControllerFactory;
        mBindingControllerFactory = bindingControllerFactory;
        userManagerInternal.addUserLifecycleListener(
        userManagerInternal.addUserLifecycleListener(
                new UserManagerInternal.UserLifecycleListener() {
                new UserManagerInternal.UserLifecycleListener() {
@@ -88,6 +95,9 @@ final class UserDataRepository {
        @NonNull
        @NonNull
        final InputMethodBindingController mBindingController;
        final InputMethodBindingController mBindingController;


        @NonNull
        final InputMethodSubtypeSwitchingController mSwitchingController;

        @NonNull
        @NonNull
        final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
        final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;


@@ -95,11 +105,14 @@ final class UserDataRepository {
         * Intended to be instantiated only from this file.
         * Intended to be instantiated only from this file.
         */
         */
        private UserData(@UserIdInt int userId,
        private UserData(@UserIdInt int userId,
                @NonNull InputMethodBindingController bindingController) {
                @NonNull InputMethodBindingController bindingController, @NonNull Context context) {
            mUserId = userId;
            mUserId = userId;
            mBindingController = bindingController;
            mBindingController = bindingController;
            final var emptySettings = InputMethodSettings.createEmptyMap(userId);
            mSwitchingController = new InputMethodSubtypeSwitchingController(context,
                    emptySettings);
            mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
            mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
                    InputMethodSettings.createEmptyMap(userId));
                    emptySettings);
        }
        }


        @Override
        @Override
+1 −0
Original line number Original line Diff line number Diff line
@@ -181,6 +181,7 @@ public class InputMethodManagerServiceTestBase {
        // InputMethodManagerService.
        // InputMethodManagerService.
        doNothing().when(mContext).enforceCallingPermission(anyString(), anyString());
        doNothing().when(mContext).enforceCallingPermission(anyString(), anyString());
        doNothing().when(mContext).sendBroadcastAsUser(any(), any());
        doNothing().when(mContext).sendBroadcastAsUser(any(), any());
        doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
        doReturn(null).when(mContext).registerReceiver(any(), any());
        doReturn(null).when(mContext).registerReceiver(any(), any());
        doReturn(null)
        doReturn(null)
                .when(mContext)
                .when(mContext)
+17 −5
Original line number Original line Diff line number Diff line
@@ -18,16 +18,22 @@ package com.android.server.inputmethod;


import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertThat;


import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;


import android.content.Context;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo;
import android.os.ConditionVariable;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
import android.platform.test.ravenwood.RavenwoodRule;
import android.platform.test.ravenwood.RavenwoodRule;


import androidx.test.platform.app.InstrumentationRegistry;

import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal;


import org.junit.After;
import org.junit.After;
@@ -61,10 +67,15 @@ public final class UserDataRepositoryTest {


    private IntFunction<InputMethodBindingController> mBindingControllerFactory;
    private IntFunction<InputMethodBindingController> mBindingControllerFactory;


    private Context mMockedContext;

    @Before
    @Before
    public void setUp() {
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
        SecureSettingsWrapper.startTestMode();
        SecureSettingsWrapper.startTestMode();
        mMockedContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
        doReturn(mMockedContext).when(mMockedContext).createContextAsUser(any(), anyInt());

        mHandler = new Handler(Looper.getMainLooper());
        mHandler = new Handler(Looper.getMainLooper());
        mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
        mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {


@@ -85,8 +96,8 @@ public final class UserDataRepositoryTest {
        // Create UserDataRepository and capture the user lifecycle listener
        // Create UserDataRepository and capture the user lifecycle listener
        final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
        final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
        final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
        final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
        final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
        final var repository = new UserDataRepository(mMockedContext, mHandler,
                bindingControllerFactorySpy);
                mMockUserManagerInternal, bindingControllerFactorySpy);


        verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
        verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
        final var listener = captor.getValue();
        final var listener = captor.getValue();
@@ -112,7 +123,8 @@ public final class UserDataRepositoryTest {
    public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
    public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
        // Create UserDataRepository and capture the user lifecycle listener
        // Create UserDataRepository and capture the user lifecycle listener
        final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
        final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
        final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
        final var repository = new UserDataRepository(mMockedContext, mHandler,
                mMockUserManagerInternal,
                userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
                userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));


        verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
        verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
@@ -134,8 +146,8 @@ public final class UserDataRepositoryTest {


    @Test
    @Test
    public void testGetOrCreate() {
    public void testGetOrCreate() {
        final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
        final var repository = new UserDataRepository(mMockedContext, mHandler,
                mBindingControllerFactory);
                mMockUserManagerInternal, mBindingControllerFactory);


        synchronized (ImfLock.class) {
        synchronized (ImfLock.class) {
            final var userData = repository.getOrCreate(ANY_USER_ID);
            final var userData = repository.getOrCreate(ANY_USER_ID);