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 872ebe8fa176e..1cf509786d890 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -16,13 +16,18 @@ package com.android.server.am; +import android.app.IUserSwitchObserver; import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.UserInfo; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; +import android.os.IRemoteCallback; +import android.os.Looper; +import android.os.Message; import android.os.RemoteException; import android.os.UserManagerInternal; import android.test.AndroidTestCase; @@ -35,16 +40,30 @@ import org.mockito.Mockito; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.android.server.am.ActivityManagerService.CONTINUE_USER_SWITCH_MSG; +import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG; +import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG; +import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG; +import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG; +import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; public class UserControllerTest extends AndroidTestCase { + private static final int TEST_USER_ID = 10; private static String TAG = UserControllerTest.class.getSimpleName(); private UserController mUserController; private TestInjector mInjector; @@ -54,7 +73,7 @@ public class UserControllerTest extends AndroidTestCase { super.setUp(); mInjector = new TestInjector(getContext()); mUserController = new UserController(mInjector); - setUpUser(10, 0); + setUpUser(TEST_USER_ID, 0); } @Override @@ -65,13 +84,100 @@ public class UserControllerTest extends AndroidTestCase { } public void testStartUser() throws RemoteException { - mUserController.startUser(10, true); - + mUserController.startUser(TEST_USER_ID, true); Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt()); Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); List expectedActions = Arrays.asList(Intent.ACTION_USER_STARTED, Intent.ACTION_USER_SWITCHED, Intent.ACTION_USER_STARTING); assertEquals(expectedActions, getActions(mInjector.sentIntents)); + Set expectedCodes = new HashSet<>( + Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, + SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG)); + Set actualCodes = mInjector.handler.getMessageCodes(); + assertEquals("Unexpected message sent", expectedCodes, actualCodes); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + assertNotNull(userState); + assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier()); + assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state); + assertEquals("Unexpected old user id", 0, reportMsg.arg1); + assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2); + } + + public void testDispatchUserSwitch() throws RemoteException { + // Prepare mock observer and register it + IUserSwitchObserver observer = mock(IUserSwitchObserver.class); + when(observer.asBinder()).thenReturn(new Binder()); + doAnswer(invocation -> { + IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; + callback.sendResult(null); + return null; + }).when(observer).onUserSwitching(anyInt(), any()); + mUserController.registerUserSwitchObserver(observer, "mock"); + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + int oldUserId = reportMsg.arg1; + int newUserId = reportMsg.arg2; + // Call dispatchUserSwitch and verify that observer was called only once + mInjector.handler.clearAllRecordedMessages(); + mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); + Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); + Set expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG); + Set actualCodes = mInjector.handler.getMessageCodes(); + assertEquals("Unexpected message sent", expectedCodes, actualCodes); + Message conMsg = mInjector.handler.getMessageForCode(CONTINUE_USER_SWITCH_MSG); + assertNotNull(conMsg); + userState = (UserState) conMsg.obj; + assertNotNull(userState); + assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier()); + assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state); + assertEquals("Unexpected old user id", 0, conMsg.arg1); + assertEquals("Unexpected new user id", TEST_USER_ID, conMsg.arg2); + } + + public void testDispatchUserSwitchBadReceiver() throws RemoteException { + // Prepare mock observer which doesn't notify the callback and register it + IUserSwitchObserver observer = mock(IUserSwitchObserver.class); + when(observer.asBinder()).thenReturn(new Binder()); + mUserController.registerUserSwitchObserver(observer, "mock"); + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + int oldUserId = reportMsg.arg1; + int newUserId = reportMsg.arg2; + // Call dispatchUserSwitch and verify that observer was called only once + mInjector.handler.clearAllRecordedMessages(); + mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); + Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); + // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout) + Set actualCodes = mInjector.handler.getMessageCodes(); + assertTrue("No messages should be sent", actualCodes.isEmpty()); + } + + public void testContinueUserSwitch() throws RemoteException { + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + int oldUserId = reportMsg.arg1; + int newUserId = reportMsg.arg2; + mInjector.handler.clearAllRecordedMessages(); + // Verify that continueUserSwitch worked as expected + mUserController.continueUserSwitch(userState, oldUserId, newUserId); + Mockito.verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); + Set expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG); + Set actualCodes = mInjector.handler.getMessageCodes(); + assertEquals("Unexpected message sent", expectedCodes, actualCodes); + Message msg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG); + assertNotNull(msg); + assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1); } private void setUpUser(int userId, int flags) { @@ -89,7 +195,7 @@ public class UserControllerTest extends AndroidTestCase { private static class TestInjector extends UserController.Injector { final Object lock = new Object(); - Handler handler; + TestHandler handler; HandlerThread handlerThread; UserManagerService userManagerMock; UserManagerInternal userManagerInternalMock; @@ -102,7 +208,7 @@ public class UserControllerTest extends AndroidTestCase { mCtx = ctx; handlerThread = new HandlerThread(TAG); handlerThread.start(); - handler = new Handler(handlerThread.getLooper()); + handler = new TestHandler(handlerThread.getLooper()); userManagerMock = mock(UserManagerService.class); userManagerInternalMock = mock(UserManagerInternal.class); windowManagerMock = mock(WindowManagerService.class); @@ -176,4 +282,41 @@ public class UserControllerTest extends AndroidTestCase { Log.i(TAG, "startHomeActivityLocked " + userId); } } + + private static class TestHandler extends Handler { + private final List mMessages = new ArrayList<>(); + + TestHandler(Looper looper) { + super(looper); + } + + Set getMessageCodes() { + Set result = new LinkedHashSet<>(); + for (Message msg : mMessages) { + result.add(msg.what); + } + return result; + } + + Message getMessageForCode(int what) { + for (Message msg : mMessages) { + if (msg.what == what) { + return msg; + } + } + return null; + } + + void clearAllRecordedMessages() { + mMessages.clear(); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + Message copy = new Message(); + copy.copyFrom(msg); + mMessages.add(copy); + return super.sendMessageAtTime(msg, uptimeMillis); + } + } } \ No newline at end of file