/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.am; import android.app.ActivityManager; 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; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import com.android.server.pm.UserManagerService; import com.android.server.wm.WindowManagerService; 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_LOCKED_BOOT_COMPLETE_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.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; 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.doNothing; 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 final int NONEXIST_USER_ID = 2; private static String TAG = UserControllerTest.class.getSimpleName(); private UserController mUserController; private TestInjector mInjector; private static final List START_FOREGROUND_USER_ACTIONS = Arrays.asList( Intent.ACTION_USER_STARTED, Intent.ACTION_USER_SWITCHED, Intent.ACTION_USER_STARTING); private static final List START_BACKGROUND_USER_ACTIONS = Arrays.asList( Intent.ACTION_USER_STARTED, Intent.ACTION_LOCKED_BOOT_COMPLETED, Intent.ACTION_USER_STARTING); private static final Set START_FOREGROUND_USER_MESSAGE_CODES = new HashSet<>(Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG)); private static final Set START_BACKGROUND_USER_MESSAGE_CODES = new HashSet<>(Arrays.asList(SYSTEM_USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG)); @Override public void setUp() throws Exception { super.setUp(); System.setProperty("dexmaker.share_classloader", "true"); mInjector = new TestInjector(getContext()); mUserController = new UserController(mInjector); setUpUser(TEST_USER_ID, 0); } @Override protected void tearDown() throws Exception { super.tearDown(); mInjector.handlerThread.quit(); } @SmallTest public void testStartUser_foreground() throws RemoteException { mUserController.startUser(TEST_USER_ID, true /* foreground */); Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt()); Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true); Mockito.verify(mInjector.getActivityStackSupervisor()).setLockTaskModeLocked( nullable(TaskRecord.class), eq(ActivityManager.LOCK_TASK_MODE_NONE), anyString(), anyBoolean()); startForegroundUserAssertions(); } @SmallTest public void testStartUser_background() throws RemoteException { mUserController.startUser(TEST_USER_ID, false /* foreground */); Mockito.verify( mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); Mockito.verify(mInjector.getActivityStackSupervisor(), never()).setLockTaskModeLocked( nullable(TaskRecord.class), eq(ActivityManager.LOCK_TASK_MODE_NONE), anyString(), anyBoolean()); startBackgroundUserAssertions(); } @SmallTest public void testStartUserUIDisabled() throws RemoteException { mUserController.mUserSwitchUiEnabled = false; mUserController.startUser(TEST_USER_ID, true /* foreground */); Mockito.verify(mInjector.getWindowManager(), never()) .startFreezingScreen(anyInt(), anyInt()); Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); startForegroundUserAssertions(); } private void startUserAssertions( List expectedActions, Set expectedMessageCodes) throws RemoteException { assertEquals(expectedActions, getActions(mInjector.sentIntents)); Set actualCodes = mInjector.handler.getMessageCodes(); assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes); } private void startBackgroundUserAssertions() throws RemoteException { startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES); } private void startForegroundUserAssertions() throws RemoteException { startUserAssertions(START_FOREGROUND_USER_ACTIONS, START_FOREGROUND_USER_MESSAGE_CODES); 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); } @SmallTest public void testFailedStartUserInForeground() throws RemoteException { mUserController.mUserSwitchUiEnabled = false; mUserController.startUserInForeground(NONEXIST_USER_ID); Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(false); } @SmallTest 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); } @SmallTest 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()); } @SmallTest 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(); continueUserSwitchAssertions(); } @SmallTest public void testContinueUserSwitchUIDisabled() throws RemoteException { mUserController.mUserSwitchUiEnabled = false; // 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(), never()).stopFreezingScreen(); continueUserSwitchAssertions(); } private void continueUserSwitchAssertions() throws RemoteException { 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); } @SmallTest public void testDispatchUserSwitchComplete() throws RemoteException { // Prepare mock observer 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); int newUserId = reportMsg.arg2; mInjector.handler.clearAllRecordedMessages(); // Mockito can't reset only interactions, so just verify that this hasn't been // called with 'false' until after dispatchUserSwitchComplete. Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(false); // Call dispatchUserSwitchComplete mUserController.dispatchUserSwitchComplete(newUserId); Mockito.verify(observer, times(1)).onUserSwitchComplete(anyInt()); Mockito.verify(observer).onUserSwitchComplete(TEST_USER_ID); Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(false); } private void setUpUser(int userId, int flags) { UserInfo userInfo = new UserInfo(userId, "User" + userId, flags); when(mInjector.userManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); } private static List getActions(List intents) { List result = new ArrayList<>(); for (Intent intent : intents) { result.add(intent.getAction()); } return result; } private static class TestInjector extends UserController.Injector { final Object lock = new Object(); TestHandler handler; HandlerThread handlerThread; UserManagerService userManagerMock; UserManagerInternal userManagerInternalMock; WindowManagerService windowManagerMock; ActivityStackSupervisor activityStackSupervisor; private Context mCtx; List sentIntents = new ArrayList<>(); TestInjector(Context ctx) { super(null); mCtx = ctx; handlerThread = new HandlerThread(TAG); handlerThread.start(); handler = new TestHandler(handlerThread.getLooper()); userManagerMock = mock(UserManagerService.class); userManagerInternalMock = mock(UserManagerInternal.class); windowManagerMock = mock(WindowManagerService.class); activityStackSupervisor = mock(ActivityStackSupervisor.class); } @Override protected Object getLock() { return lock; } @Override protected Handler getHandler() { return handler; } @Override protected UserManagerService getUserManager() { return userManagerMock; } @Override UserManagerInternal getUserManagerInternal() { return userManagerInternalMock; } @Override protected Context getContext() { return mCtx; } @Override int checkCallingPermission(String permission) { Log.i(TAG, "checkCallingPermission " + permission); return PERMISSION_GRANTED; } @Override WindowManagerService getWindowManager() { return windowManagerMock; } @Override void updateUserConfigurationLocked() { Log.i(TAG, "updateUserConfigurationLocked"); } @Override protected int broadcastIntentLocked(Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) { Log.i(TAG, "broadcastIntentLocked " + intent); sentIntents.add(intent); return 0; } @Override void startHomeActivityLocked(int userId, String reason) { Log.i(TAG, "startHomeActivityLocked " + userId); } @Override ActivityStackSupervisor getActivityStackSupervisor() { return activityStackSupervisor; } } 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); } } }