Loading services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +19 −2 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.hardware.input.InputManager; import android.inputmethodservice.InputMethodService; Loading Loading @@ -923,11 +924,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * {@link SystemService} used to publish and manage the lifecycle of * {@link InputMethodManagerService}. */ public static final class Lifecycle extends SystemService { public static final class Lifecycle extends SystemService implements UserManagerInternal.UserLifecycleListener { private final InputMethodManagerService mService; public Lifecycle(Context context) { this(context, createServiceForProduction(context)); // For production code, hook up user lifecycle mService.mUserManagerInternal.addUserLifecycleListener(this); } @VisibleForTesting Loading Loading @@ -1005,6 +1010,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } @Override public void onUserCreated(UserInfo user, @Nullable Object token) { // Called directly from UserManagerService. Do not block the calling thread. } @Override public void onUserRemoved(UserInfo user) { // Called directly from UserManagerService. Do not block the calling thread. final int userId = user.id; mService.mUserDataRepository.remove(userId); } @Override public void onUserUnlocking(@NonNull TargetUser user) { // Called on ActivityManager thread. Loading Loading @@ -1114,7 +1131,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController> bindingControllerFactory = userId -> new InputMethodBindingController(userId, InputMethodManagerService.this); mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal, mUserDataRepository = new UserDataRepository( bindingControllerForTesting != null ? bindingControllerForTesting : bindingControllerFactory); Loading services/core/java/com/android/server/inputmethod/UserDataRepository.java +10 −19 Original line number Diff line number Diff line Loading @@ -20,8 +20,6 @@ import android.annotation.AnyThread; 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 android.view.inputmethod.EditorInfo; import android.view.inputmethod.ImeTracker; Loading @@ -30,7 +28,6 @@ import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.server.pm.UserManagerInternal; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; Loading Loading @@ -76,24 +73,18 @@ final class UserDataRepository { } UserDataRepository( @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal, @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) { mBindingControllerFactory = bindingControllerFactory; userManagerInternal.addUserLifecycleListener( new UserManagerInternal.UserLifecycleListener() { @Override public void onUserRemoved(UserInfo user) { final int userId = user.id; handler.post(() -> { } @AnyThread void remove(@UserIdInt int userId) { mUserDataLock.writeLock().lock(); try { mUserData.remove(userId); } finally { mUserDataLock.writeLock().unlock(); } }); } }); } /** Placeholder for all IMMS user specific fields */ Loading services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java +5 −57 Original line number Diff line number Diff line Loading @@ -18,23 +18,12 @@ package com.android.server.inputmethod; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.pm.UserInfo; import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; import android.platform.test.ravenwood.RavenwoodRule; import com.android.server.pm.UserManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; Loading @@ -51,14 +40,9 @@ public final class UserDataRepositoryTest { public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() .setProvideMainThread(true).build(); @Mock private UserManagerInternal mMockUserManagerInternal; @Mock private InputMethodManagerService mMockInputMethodManagerService; private Handler mHandler; private IntFunction<InputMethodBindingController> mBindingControllerFactory; @Before Loading @@ -66,7 +50,6 @@ public final class UserDataRepositoryTest { MockitoAnnotations.initMocks(this); SecureSettingsWrapper.startTestMode(); mHandler = new Handler(Looper.getMainLooper()); mBindingControllerFactory = new IntFunction<InputMethodBindingController>() { @Override Loading @@ -81,49 +64,20 @@ public final class UserDataRepositoryTest { SecureSettingsWrapper.endTestMode(); } @Test public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() { // Create UserDataRepository and capture the user lifecycle listener final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class); final var bindingControllerFactorySpy = spy(mBindingControllerFactory); final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, bindingControllerFactorySpy); verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture()); final var listener = captor.getValue(); // Assert that UserDataRepository is empty and then call onUserCreated assertThat(collectUserData(repository)).isEmpty(); final var userInfo = new UserInfo(); userInfo.id = ANY_USER_ID; listener.onUserCreated(userInfo, /* unused token */ new Object()); waitForIdle(); // Assert UserDataRepository remains to be empty. assertThat(collectUserData(repository)).isEmpty(); } // TODO(b/352615651): Move this to end-to-end test. @Test public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() { // Create UserDataRepository and capture the user lifecycle listener final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class); final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, // Create UserDataRepository final var repository = new UserDataRepository( userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService)); verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture()); final var listener = captor.getValue(); // Add one UserData ... final var userData = repository.getOrCreate(ANY_USER_ID); assertThat(userData.mUserId).isEqualTo(ANY_USER_ID); // ... and then call onUserRemoved assertThat(collectUserData(repository)).hasSize(1); final var userInfo = new UserInfo(); userInfo.id = ANY_USER_ID; listener.onUserRemoved(userInfo); waitForIdle(); repository.remove(ANY_USER_ID); // Assert UserDataRepository is now empty assertThat(collectUserData(repository)).isEmpty(); Loading @@ -131,8 +85,7 @@ public final class UserDataRepositoryTest { @Test public void testGetOrCreate() { final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, mBindingControllerFactory); final var repository = new UserDataRepository(mBindingControllerFactory); final var userData = repository.getOrCreate(ANY_USER_ID); assertThat(userData.mUserId).isEqualTo(ANY_USER_ID); Loading @@ -151,9 +104,4 @@ public final class UserDataRepositoryTest { return collected; } private void waitForIdle() { final var done = new ConditionVariable(); mHandler.post(done::open); done.block(); } } Loading
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +19 −2 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.hardware.input.InputManager; import android.inputmethodservice.InputMethodService; Loading Loading @@ -923,11 +924,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * {@link SystemService} used to publish and manage the lifecycle of * {@link InputMethodManagerService}. */ public static final class Lifecycle extends SystemService { public static final class Lifecycle extends SystemService implements UserManagerInternal.UserLifecycleListener { private final InputMethodManagerService mService; public Lifecycle(Context context) { this(context, createServiceForProduction(context)); // For production code, hook up user lifecycle mService.mUserManagerInternal.addUserLifecycleListener(this); } @VisibleForTesting Loading Loading @@ -1005,6 +1010,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } @Override public void onUserCreated(UserInfo user, @Nullable Object token) { // Called directly from UserManagerService. Do not block the calling thread. } @Override public void onUserRemoved(UserInfo user) { // Called directly from UserManagerService. Do not block the calling thread. final int userId = user.id; mService.mUserDataRepository.remove(userId); } @Override public void onUserUnlocking(@NonNull TargetUser user) { // Called on ActivityManager thread. Loading Loading @@ -1114,7 +1131,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController> bindingControllerFactory = userId -> new InputMethodBindingController(userId, InputMethodManagerService.this); mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal, mUserDataRepository = new UserDataRepository( bindingControllerForTesting != null ? bindingControllerForTesting : bindingControllerFactory); Loading
services/core/java/com/android/server/inputmethod/UserDataRepository.java +10 −19 Original line number Diff line number Diff line Loading @@ -20,8 +20,6 @@ import android.annotation.AnyThread; 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 android.view.inputmethod.EditorInfo; import android.view.inputmethod.ImeTracker; Loading @@ -30,7 +28,6 @@ import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.server.pm.UserManagerInternal; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; Loading Loading @@ -76,24 +73,18 @@ final class UserDataRepository { } UserDataRepository( @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal, @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) { mBindingControllerFactory = bindingControllerFactory; userManagerInternal.addUserLifecycleListener( new UserManagerInternal.UserLifecycleListener() { @Override public void onUserRemoved(UserInfo user) { final int userId = user.id; handler.post(() -> { } @AnyThread void remove(@UserIdInt int userId) { mUserDataLock.writeLock().lock(); try { mUserData.remove(userId); } finally { mUserDataLock.writeLock().unlock(); } }); } }); } /** Placeholder for all IMMS user specific fields */ Loading
services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java +5 −57 Original line number Diff line number Diff line Loading @@ -18,23 +18,12 @@ package com.android.server.inputmethod; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.pm.UserInfo; import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; import android.platform.test.ravenwood.RavenwoodRule; import com.android.server.pm.UserManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; Loading @@ -51,14 +40,9 @@ public final class UserDataRepositoryTest { public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() .setProvideMainThread(true).build(); @Mock private UserManagerInternal mMockUserManagerInternal; @Mock private InputMethodManagerService mMockInputMethodManagerService; private Handler mHandler; private IntFunction<InputMethodBindingController> mBindingControllerFactory; @Before Loading @@ -66,7 +50,6 @@ public final class UserDataRepositoryTest { MockitoAnnotations.initMocks(this); SecureSettingsWrapper.startTestMode(); mHandler = new Handler(Looper.getMainLooper()); mBindingControllerFactory = new IntFunction<InputMethodBindingController>() { @Override Loading @@ -81,49 +64,20 @@ public final class UserDataRepositoryTest { SecureSettingsWrapper.endTestMode(); } @Test public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() { // Create UserDataRepository and capture the user lifecycle listener final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class); final var bindingControllerFactorySpy = spy(mBindingControllerFactory); final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, bindingControllerFactorySpy); verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture()); final var listener = captor.getValue(); // Assert that UserDataRepository is empty and then call onUserCreated assertThat(collectUserData(repository)).isEmpty(); final var userInfo = new UserInfo(); userInfo.id = ANY_USER_ID; listener.onUserCreated(userInfo, /* unused token */ new Object()); waitForIdle(); // Assert UserDataRepository remains to be empty. assertThat(collectUserData(repository)).isEmpty(); } // TODO(b/352615651): Move this to end-to-end test. @Test public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() { // Create UserDataRepository and capture the user lifecycle listener final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class); final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, // Create UserDataRepository final var repository = new UserDataRepository( userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService)); verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture()); final var listener = captor.getValue(); // Add one UserData ... final var userData = repository.getOrCreate(ANY_USER_ID); assertThat(userData.mUserId).isEqualTo(ANY_USER_ID); // ... and then call onUserRemoved assertThat(collectUserData(repository)).hasSize(1); final var userInfo = new UserInfo(); userInfo.id = ANY_USER_ID; listener.onUserRemoved(userInfo); waitForIdle(); repository.remove(ANY_USER_ID); // Assert UserDataRepository is now empty assertThat(collectUserData(repository)).isEmpty(); Loading @@ -131,8 +85,7 @@ public final class UserDataRepositoryTest { @Test public void testGetOrCreate() { final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, mBindingControllerFactory); final var repository = new UserDataRepository(mBindingControllerFactory); final var userData = repository.getOrCreate(ANY_USER_ID); assertThat(userData.mUserId).isEqualTo(ANY_USER_ID); Loading @@ -151,9 +104,4 @@ public final class UserDataRepositoryTest { return collected; } private void waitForIdle() { final var done = new ConditionVariable(); mHandler.post(done::open); done.block(); } }