diff options
3 files changed, 34 insertions, 78 deletions
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 731c1ea28ade..3c0d8e94616b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -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; @@ -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 @@ -1006,6 +1011,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. SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier()); @@ -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); diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java index 292f9f8e3501..98d7548d3dd2 100644 --- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java +++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java @@ -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; @@ -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; @@ -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(() -> { - mUserDataLock.writeLock().lock(); - try { - mUserData.remove(userId); - } finally { - mUserDataLock.writeLock().unlock(); - } - }); - } - }); + } + + @AnyThread + void remove(@UserIdInt int userId) { + mUserDataLock.writeLock().lock(); + try { + mUserData.remove(userId); + } finally { + mUserDataLock.writeLock().unlock(); + } } /** Placeholder for all IMMS user specific fields */ diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java index e07a05566f1d..81fb1a092887 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java @@ -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; @@ -52,13 +41,8 @@ public final class UserDataRepositoryTest { .setProvideMainThread(true).build(); @Mock - private UserManagerInternal mMockUserManagerInternal; - - @Mock private InputMethodManagerService mMockInputMethodManagerService; - private Handler mHandler; - private IntFunction<InputMethodBindingController> mBindingControllerFactory; @Before @@ -66,7 +50,6 @@ public final class UserDataRepositoryTest { MockitoAnnotations.initMocks(this); SecureSettingsWrapper.startTestMode(); - mHandler = new Handler(Looper.getMainLooper()); mBindingControllerFactory = new IntFunction<InputMethodBindingController>() { @Override @@ -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(); @@ -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); @@ -151,9 +104,4 @@ public final class UserDataRepositoryTest { return collected; } - private void waitForIdle() { - final var done = new ConditionVariable(); - mHandler.post(done::open); - done.block(); - } } |