From e1249ddf4d64a39d6f6117bd2247d57d6c2d6c4f Mon Sep 17 00:00:00 2001 From: Yasin Kilicdere Date: Wed, 17 May 2023 16:26:13 +0100 Subject: [PATCH] Wait for keyguard to be shown before completing the user switch. If the target user has a pin/password we lock the device and show the keyguard, but not confirm that it is shown. With this CL we make sure the keyguard is shown, before completing the user switch and dismiss the user swithing dialog only then, which protects the target user's privacy. Otherwise, it's possible for target user's apps or wallpaper or etc to be exposed in case the keyguard is shown late. Bug: 281786028 Test: Apply ag/23285192 Test: atest FrameworksServicesTests:UserControllerTest Test: atest FrameworksServicesTests:UserControllerTest#testStallUserSwitchUntilTheKeyguardIsShown (this test MUST_SLEEP) Change-Id: Ide6ea04b9c40113ffde86508c7694c5279c1d0fc --- .../com/android/server/am/UserController.java | 48 +++++++++++++++++- .../wm/ActivityTaskManagerInternal.java | 8 +++ .../server/wm/ActivityTaskManagerService.java | 9 +++- .../android/server/am/UserControllerTest.java | 50 +++++++++++++++++++ 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 12bb5d23e4cf3..76a994ec63e90 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -151,6 +151,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -1707,7 +1709,8 @@ class UserController implements Handler.Callback { mInjector.getWindowManager().setSwitchingUser(true); // Only lock if the user has a secure keyguard PIN/Pattern/Pwd if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { - mInjector.getWindowManager().lockNow(null); + // Make sure the device is locked before moving on with the user switch + mInjector.lockDeviceNowAndWaitForKeyguardShown(); } } @@ -3444,6 +3447,11 @@ class UserController implements Handler.Callback { WindowManagerService getWindowManager() { return mService.mWindowManager; } + + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mService.mAtmInternal; + } + void activityManagerOnUserStopped(@UserIdInt int userId) { LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId); } @@ -3667,5 +3675,43 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } + + void lockDeviceNowAndWaitForKeyguardShown() { + if (getWindowManager().isKeyguardLocked()) { + return; + } + + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("lockDeviceNowAndWaitForKeyguardShown"); + + final CountDownLatch latch = new CountDownLatch(1); + ActivityTaskManagerInternal.ScreenObserver screenObserver = + new ActivityTaskManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { + + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + if (isShowing) { + latch.countDown(); + } + } + }; + + getActivityTaskManagerInternal().registerScreenObserver(screenObserver); + getWindowManager().lockDeviceNow(); + try { + if (!latch.await(20, TimeUnit.SECONDS)) { + throw new RuntimeException("Keyguard is not shown in 20 seconds"); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); + t.traceEnd(); + } + } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index e07c654262785..32f1f42aacb94 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -262,8 +262,16 @@ public abstract class ActivityTaskManagerInternal { */ public abstract void setVr2dDisplayId(int vr2dDisplayId); + /** + * Registers a {@link ScreenObserver}. + */ public abstract void registerScreenObserver(ScreenObserver observer); + /** + * Unregisters the given {@link ScreenObserver}. + */ + public abstract void unregisterScreenObserver(ScreenObserver observer); + /** * Returns is the caller has the same uid as the Recents component */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 750ed986f5673..e70e0754f06e1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -296,6 +296,7 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -652,7 +653,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ float mMinPercentageMultiWindowSupportWidth; - final List mScreenObservers = new ArrayList<>(); + final List mScreenObservers = + Collections.synchronizedList(new ArrayList<>()); // VR Vr2d Display Id. int mVr2dDisplayId = INVALID_DISPLAY; @@ -5866,6 +5868,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mScreenObservers.add(observer); } + @Override + public void unregisterScreenObserver(ScreenObserver observer) { + mScreenObservers.remove(observer); + } + @Override public boolean isCallerRecents(int callingUid) { return ActivityTaskManagerService.this.isCallerRecents(callingUid); diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 317fd58e0c761..dccacb4d301ac 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -58,6 +58,7 @@ import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -103,11 +104,13 @@ import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserJourneyLogger; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.Arrays; @@ -187,6 +190,7 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); + doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. @@ -951,6 +955,45 @@ public class UserControllerTest { .systemServiceManagerOnUserCompletedEvent(eq(user2), eq(event2a)); } + @Test + public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception { + // enable user switch ui, because keyguard is only shown then + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + + // mock the device to be secure in order to expect the keyguard to be shown + when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); + + // call real lockDeviceNowAndWaitForKeyguardShown method for this test + doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); + + // call startUser on a thread because we're expecting it to be blocked + Thread threadStartUser = new Thread(()-> { + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + }); + threadStartUser.start(); + + // make sure the switch is stalled... + Thread.sleep(2000); + // by checking REPORT_USER_SWITCH_MSG is not sent yet + assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is still alive + assertTrue(threadStartUser.isAlive()); + + // mock send the keyguard shown event + ArgumentCaptor captor = ArgumentCaptor.forClass( + ActivityTaskManagerInternal.ScreenObserver.class); + verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); + captor.getValue().onKeyguardStateChanged(true); + + // verify the switch now moves on... + Thread.sleep(1000); + // by checking REPORT_USER_SWITCH_MSG is sent + assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); + // and the thread is finished + assertFalse(threadStartUser.isAlive()); + } + private void setUpAndStartUserInBackground(int userId) throws Exception { setUpUser(userId, 0); mUserController.startUser(userId, USER_START_MODE_BACKGROUND); @@ -1092,6 +1135,7 @@ public class UserControllerTest { private final IStorageManager mStorageManagerMock; private final UserManagerInternal mUserManagerInternalMock; private final WindowManagerService mWindowManagerMock; + private final ActivityTaskManagerInternal mActivityTaskManagerInternal; private final KeyguardManager mKeyguardManagerMock; private final LockPatternUtils mLockPatternUtilsMock; @@ -1111,6 +1155,7 @@ public class UserControllerTest { mUserManagerMock = mock(UserManagerService.class); mUserManagerInternalMock = mock(UserManagerInternal.class); mWindowManagerMock = mock(WindowManagerService.class); + mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); mStorageManagerMock = mock(IStorageManager.class); mKeyguardManagerMock = mock(KeyguardManager.class); when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); @@ -1171,6 +1216,11 @@ public class UserControllerTest { return mWindowManagerMock; } + @Override + ActivityTaskManagerInternal getActivityTaskManagerInternal() { + return mActivityTaskManagerInternal; + } + @Override KeyguardManager getKeyguardManager() { return mKeyguardManagerMock;