Loading services/core/java/com/android/server/am/ActivityStartInterceptor.java +42 −13 Original line number Diff line number Diff line Loading @@ -29,10 +29,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; Loading @@ -43,18 +43,26 @@ import android.os.Binder; import android.os.UserHandle; import android.os.UserManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; /** * A class that contains activity intercepting logic for {@link ActivityStarter#startActivityLocked} * It's initialized * It's initialized via setStates and interception occurs via the intercept method. * * Note that this class is instantiated when {@link ActivityManagerService} gets created so there * is no guarantee that other system services are already present. */ class ActivityStartInterceptor { private final ActivityManagerService mService; private UserManager mUserManager; private final ActivityStackSupervisor mSupervisor; private final Context mServiceContext; private final UserController mUserController; // UserManager cannot be final as it's not ready when this class is instantiated during boot private UserManager mUserManager; /* * Per-intent states loaded from ActivityStarter than shouldn't be changed by any Loading @@ -69,7 +77,8 @@ class ActivityStartInterceptor { /* * Per-intent states that were load from ActivityStarter and are subject to modifications * by the interception routines. After calling {@link #intercept} the caller should assign * these values back to {@link ActivityStarter#startActivityLocked}'s local variables. * these values back to {@link ActivityStarter#startActivityLocked}'s local variables if * {@link #intercept} returns true. */ Intent mIntent; int mCallingPid; Loading @@ -81,10 +90,22 @@ class ActivityStartInterceptor { ActivityOptions mActivityOptions; ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor) { this(service, supervisor, service.mContext, service.mUserController); } @VisibleForTesting ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor, Context context, UserController userController) { mService = service; mSupervisor = supervisor; mServiceContext = context; mUserController = userController; } /** * Effectively initialize the class before intercepting the start intent. The values set in this * method should not be changed during intercept. */ void setStates(int userId, int realCallingPid, int realCallingUid, int startFlags, String callingPackage) { mRealCallingPid = realCallingPid; Loading @@ -94,9 +115,16 @@ class ActivityStartInterceptor { mCallingPackage = callingPackage; } void intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, /** * Intercept the launch intent based on various signals. If an interception happened the * internal variables get assigned and need to be read explicitly by the caller. * * @return true if an interception occurred */ boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, TaskRecord inTask, int callingPid, int callingUid, ActivityOptions activityOptions) { mUserManager = UserManager.get(mService.mContext); mUserManager = UserManager.get(mServiceContext); mIntent = intent; mCallingPid = callingPid; mCallingUid = callingUid; Loading @@ -105,17 +133,18 @@ class ActivityStartInterceptor { mResolvedType = resolvedType; mInTask = inTask; mActivityOptions = activityOptions; if (interceptSuspendPackageIfNeed()) { // Skip the rest of interceptions as the package is suspended by device admin so // no user action can undo this. return; return true; } if (interceptQuietProfileIfNeeded()) { // If work profile is turned off, skip the work challenge since the profile can only // be unlocked when profile's user is running. return; return true; } interceptWorkProfileChallengeIfNeeded(); return interceptWorkProfileChallengeIfNeeded(); } private boolean interceptQuietProfileIfNeeded() { Loading Loading @@ -146,8 +175,8 @@ class ActivityStartInterceptor { (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) { return false; } DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService( DevicePolicyManagerInternal.class); DevicePolicyManagerInternal devicePolicyManager = LocalServices .getService(DevicePolicyManagerInternal.class); if (devicePolicyManager == null) { return false; } Loading Loading @@ -207,7 +236,7 @@ class ActivityStartInterceptor { */ private Intent interceptWithConfirmCredentialsIfNeeded(Intent intent, String resolvedType, ActivityInfo aInfo, String callingPackage, int userId) { if (!mService.mUserController.shouldConfirmCredentials(userId)) { if (!mUserController.shouldConfirmCredentials(userId)) { return null; } // TODO(b/28935539): should allow certain activities to bypass work challenge Loading @@ -216,7 +245,7 @@ class ActivityStartInterceptor { Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, new String[]{ resolvedType }, FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE, null); final KeyguardManager km = (KeyguardManager) mService.mContext final KeyguardManager km = (KeyguardManager) mServiceContext .getSystemService(KEYGUARD_SERVICE); final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId); if (newIntent == null) { Loading services/core/java/com/android/server/am/ActivityStarter.java +15 −11 Original line number Diff line number Diff line Loading @@ -144,7 +144,7 @@ class ActivityStarter { private final ActivityManagerService mService; private final ActivityStackSupervisor mSupervisor; private ActivityStartInterceptor mInterceptor; private final ActivityStartInterceptor mInterceptor; private WindowManagerService mWindowManager; final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); Loading Loading @@ -446,8 +446,10 @@ class ActivityStarter { } mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage); mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid, callingUid, options); if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid, callingUid, options)) { // activity start was intercepted, e.g. because the target user is currently in quiet // mode (turn off work) or the target application is suspended intent = mInterceptor.mIntent; rInfo = mInterceptor.mRInfo; aInfo = mInterceptor.mAInfo; Loading @@ -456,6 +458,8 @@ class ActivityStarter { callingPid = mInterceptor.mCallingPid; callingUid = mInterceptor.mCallingUid; options = mInterceptor.mActivityOptions; } if (abort) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Loading services/core/java/com/android/server/am/UserController.java +2 −4 Original line number Diff line number Diff line Loading @@ -22,9 +22,7 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; import static android.app.ActivityManager.USER_OP_SUCCESS; import static android.content.Context.KEYGUARD_SERVICE; import static android.os.Process.SYSTEM_UID; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; Loading Loading @@ -109,7 +107,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Helper class for {@link ActivityManagerService} responsible for multi-user functionality. */ final class UserController { class UserController { private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM; // Maximum number of users we allow to be running at a time. Loading Loading @@ -1602,7 +1600,7 @@ final class UserController { * Returns whether the given user requires credential entry at this time. This is used to * intercept activity launches for work apps when the Work Challenge is present. */ boolean shouldConfirmCredentials(int userId) { protected boolean shouldConfirmCredentials(int userId) { synchronized (mLock) { if (mStartedUsers.get(userId) == null) { return false; Loading services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java 0 → 100644 +163 −0 Original line number Diff line number Diff line /* * Copyright 2017, 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 static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; import android.support.test.filters.SmallTest; import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** * Unit tests for {@link ActivityStartInterceptorTest}. * * Build/Install/Run: * bit FrameworksServicesTests:com.android.server.am.ActivityStartInterceptorTest */ @SmallTest public class ActivityStartInterceptorTest { private static final int TEST_USER_ID = 1; private static final int TEST_REAL_CALLING_UID = 2; private static final int TEST_REAL_CALLING_PID = 3; private static final String TEST_CALLING_PACKAGE = "com.test.caller"; private static final int TEST_START_FLAGS = 4; private static final Intent ADMIN_SUPPORT_INTENT = new Intent("com.test.ADMIN_SUPPORT"); private static final Intent CONFIRM_CREDENTIALS_INTENT = new Intent("com.test.CONFIRM_CREDENTIALS"); private static final UserInfo PARENT_USER_INFO = new UserInfo(0 /* userId */, "parent", 0 /* flags */); private static final String TEST_PACKAGE_NAME = "com.test.package"; @Mock private Context mContext; @Mock private ActivityManagerService mService; @Mock private ActivityStackSupervisor mSupervisor; @Mock private DevicePolicyManagerInternal mDevicePolicyManager; @Mock private UserManager mUserManager; @Mock private UserController mUserController; @Mock private KeyguardManager mKeyguardManager; private ActivityStartInterceptor mInterceptor; private ActivityInfo mAInfo = new ActivityInfo(); @Before public void setUp() { // This property is used to allow mocking of package private classes with mockito System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext, mUserController); mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID, TEST_START_FLAGS, TEST_CALLING_PACKAGE); // Mock DevicePolicyManagerInternal LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManager); when(mDevicePolicyManager .createShowAdminSupportIntent(TEST_USER_ID, true)) .thenReturn(ADMIN_SUPPORT_INTENT); // Mock UserManager when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mUserManager.getProfileParent(TEST_USER_ID)).thenReturn(PARENT_USER_INFO); // Mock KeyguardManager when(mContext.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(mKeyguardManager); when(mKeyguardManager.createConfirmDeviceCredentialIntent( nullable(CharSequence.class), nullable(CharSequence.class), eq(TEST_USER_ID))). thenReturn(CONFIRM_CREDENTIALS_INTENT); // Initialise activity info mAInfo.packageName = TEST_PACKAGE_NAME; mAInfo.applicationInfo = new ApplicationInfo(); } @Test public void testSuspendedPackage() { // GIVEN the package we're about to launch is currently suspended mAInfo.applicationInfo.flags = FLAG_SUSPENDED; // THEN calling intercept returns true assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); // THEN the returned intent is the admin support intent assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent); } @Test public void testInterceptQuietProfile() { // GIVEN that the user the activity is starting as is currently in quiet mode when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); // THEN calling intercept returns true assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); // THEN the returned intent is the quiet mode intent assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID) .filterEquals(mInterceptor.mIntent)); } @Test public void testWorkChallenge() { // GIVEN that the user the activity is starting as is currently locked when(mUserController.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true); // THEN calling intercept returns true mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null); // THEN the returned intent is the quiet mode intent assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent)); } @Test public void testNoInterception() { // GIVEN that none of the interception conditions are met // THEN calling intercept returns false assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); } } Loading
services/core/java/com/android/server/am/ActivityStartInterceptor.java +42 −13 Original line number Diff line number Diff line Loading @@ -29,10 +29,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; Loading @@ -43,18 +43,26 @@ import android.os.Binder; import android.os.UserHandle; import android.os.UserManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; /** * A class that contains activity intercepting logic for {@link ActivityStarter#startActivityLocked} * It's initialized * It's initialized via setStates and interception occurs via the intercept method. * * Note that this class is instantiated when {@link ActivityManagerService} gets created so there * is no guarantee that other system services are already present. */ class ActivityStartInterceptor { private final ActivityManagerService mService; private UserManager mUserManager; private final ActivityStackSupervisor mSupervisor; private final Context mServiceContext; private final UserController mUserController; // UserManager cannot be final as it's not ready when this class is instantiated during boot private UserManager mUserManager; /* * Per-intent states loaded from ActivityStarter than shouldn't be changed by any Loading @@ -69,7 +77,8 @@ class ActivityStartInterceptor { /* * Per-intent states that were load from ActivityStarter and are subject to modifications * by the interception routines. After calling {@link #intercept} the caller should assign * these values back to {@link ActivityStarter#startActivityLocked}'s local variables. * these values back to {@link ActivityStarter#startActivityLocked}'s local variables if * {@link #intercept} returns true. */ Intent mIntent; int mCallingPid; Loading @@ -81,10 +90,22 @@ class ActivityStartInterceptor { ActivityOptions mActivityOptions; ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor) { this(service, supervisor, service.mContext, service.mUserController); } @VisibleForTesting ActivityStartInterceptor(ActivityManagerService service, ActivityStackSupervisor supervisor, Context context, UserController userController) { mService = service; mSupervisor = supervisor; mServiceContext = context; mUserController = userController; } /** * Effectively initialize the class before intercepting the start intent. The values set in this * method should not be changed during intercept. */ void setStates(int userId, int realCallingPid, int realCallingUid, int startFlags, String callingPackage) { mRealCallingPid = realCallingPid; Loading @@ -94,9 +115,16 @@ class ActivityStartInterceptor { mCallingPackage = callingPackage; } void intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, /** * Intercept the launch intent based on various signals. If an interception happened the * internal variables get assigned and need to be read explicitly by the caller. * * @return true if an interception occurred */ boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, TaskRecord inTask, int callingPid, int callingUid, ActivityOptions activityOptions) { mUserManager = UserManager.get(mService.mContext); mUserManager = UserManager.get(mServiceContext); mIntent = intent; mCallingPid = callingPid; mCallingUid = callingUid; Loading @@ -105,17 +133,18 @@ class ActivityStartInterceptor { mResolvedType = resolvedType; mInTask = inTask; mActivityOptions = activityOptions; if (interceptSuspendPackageIfNeed()) { // Skip the rest of interceptions as the package is suspended by device admin so // no user action can undo this. return; return true; } if (interceptQuietProfileIfNeeded()) { // If work profile is turned off, skip the work challenge since the profile can only // be unlocked when profile's user is running. return; return true; } interceptWorkProfileChallengeIfNeeded(); return interceptWorkProfileChallengeIfNeeded(); } private boolean interceptQuietProfileIfNeeded() { Loading Loading @@ -146,8 +175,8 @@ class ActivityStartInterceptor { (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) { return false; } DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService( DevicePolicyManagerInternal.class); DevicePolicyManagerInternal devicePolicyManager = LocalServices .getService(DevicePolicyManagerInternal.class); if (devicePolicyManager == null) { return false; } Loading Loading @@ -207,7 +236,7 @@ class ActivityStartInterceptor { */ private Intent interceptWithConfirmCredentialsIfNeeded(Intent intent, String resolvedType, ActivityInfo aInfo, String callingPackage, int userId) { if (!mService.mUserController.shouldConfirmCredentials(userId)) { if (!mUserController.shouldConfirmCredentials(userId)) { return null; } // TODO(b/28935539): should allow certain activities to bypass work challenge Loading @@ -216,7 +245,7 @@ class ActivityStartInterceptor { Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, new String[]{ resolvedType }, FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE, null); final KeyguardManager km = (KeyguardManager) mService.mContext final KeyguardManager km = (KeyguardManager) mServiceContext .getSystemService(KEYGUARD_SERVICE); final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId); if (newIntent == null) { Loading
services/core/java/com/android/server/am/ActivityStarter.java +15 −11 Original line number Diff line number Diff line Loading @@ -144,7 +144,7 @@ class ActivityStarter { private final ActivityManagerService mService; private final ActivityStackSupervisor mSupervisor; private ActivityStartInterceptor mInterceptor; private final ActivityStartInterceptor mInterceptor; private WindowManagerService mWindowManager; final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); Loading Loading @@ -446,8 +446,10 @@ class ActivityStarter { } mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage); mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid, callingUid, options); if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid, callingUid, options)) { // activity start was intercepted, e.g. because the target user is currently in quiet // mode (turn off work) or the target application is suspended intent = mInterceptor.mIntent; rInfo = mInterceptor.mRInfo; aInfo = mInterceptor.mAInfo; Loading @@ -456,6 +458,8 @@ class ActivityStarter { callingPid = mInterceptor.mCallingPid; callingUid = mInterceptor.mCallingUid; options = mInterceptor.mActivityOptions; } if (abort) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Loading
services/core/java/com/android/server/am/UserController.java +2 −4 Original line number Diff line number Diff line Loading @@ -22,9 +22,7 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; import static android.app.ActivityManager.USER_OP_SUCCESS; import static android.content.Context.KEYGUARD_SERVICE; import static android.os.Process.SYSTEM_UID; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; Loading Loading @@ -109,7 +107,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Helper class for {@link ActivityManagerService} responsible for multi-user functionality. */ final class UserController { class UserController { private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM; // Maximum number of users we allow to be running at a time. Loading Loading @@ -1602,7 +1600,7 @@ final class UserController { * Returns whether the given user requires credential entry at this time. This is used to * intercept activity launches for work apps when the Work Challenge is present. */ boolean shouldConfirmCredentials(int userId) { protected boolean shouldConfirmCredentials(int userId) { synchronized (mLock) { if (mStartedUsers.get(userId) == null) { return false; Loading
services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java 0 → 100644 +163 −0 Original line number Diff line number Diff line /* * Copyright 2017, 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 static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.when; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; import android.support.test.filters.SmallTest; import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** * Unit tests for {@link ActivityStartInterceptorTest}. * * Build/Install/Run: * bit FrameworksServicesTests:com.android.server.am.ActivityStartInterceptorTest */ @SmallTest public class ActivityStartInterceptorTest { private static final int TEST_USER_ID = 1; private static final int TEST_REAL_CALLING_UID = 2; private static final int TEST_REAL_CALLING_PID = 3; private static final String TEST_CALLING_PACKAGE = "com.test.caller"; private static final int TEST_START_FLAGS = 4; private static final Intent ADMIN_SUPPORT_INTENT = new Intent("com.test.ADMIN_SUPPORT"); private static final Intent CONFIRM_CREDENTIALS_INTENT = new Intent("com.test.CONFIRM_CREDENTIALS"); private static final UserInfo PARENT_USER_INFO = new UserInfo(0 /* userId */, "parent", 0 /* flags */); private static final String TEST_PACKAGE_NAME = "com.test.package"; @Mock private Context mContext; @Mock private ActivityManagerService mService; @Mock private ActivityStackSupervisor mSupervisor; @Mock private DevicePolicyManagerInternal mDevicePolicyManager; @Mock private UserManager mUserManager; @Mock private UserController mUserController; @Mock private KeyguardManager mKeyguardManager; private ActivityStartInterceptor mInterceptor; private ActivityInfo mAInfo = new ActivityInfo(); @Before public void setUp() { // This property is used to allow mocking of package private classes with mockito System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext, mUserController); mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID, TEST_START_FLAGS, TEST_CALLING_PACKAGE); // Mock DevicePolicyManagerInternal LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManager); when(mDevicePolicyManager .createShowAdminSupportIntent(TEST_USER_ID, true)) .thenReturn(ADMIN_SUPPORT_INTENT); // Mock UserManager when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mUserManager.getProfileParent(TEST_USER_ID)).thenReturn(PARENT_USER_INFO); // Mock KeyguardManager when(mContext.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(mKeyguardManager); when(mKeyguardManager.createConfirmDeviceCredentialIntent( nullable(CharSequence.class), nullable(CharSequence.class), eq(TEST_USER_ID))). thenReturn(CONFIRM_CREDENTIALS_INTENT); // Initialise activity info mAInfo.packageName = TEST_PACKAGE_NAME; mAInfo.applicationInfo = new ApplicationInfo(); } @Test public void testSuspendedPackage() { // GIVEN the package we're about to launch is currently suspended mAInfo.applicationInfo.flags = FLAG_SUSPENDED; // THEN calling intercept returns true assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); // THEN the returned intent is the admin support intent assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent); } @Test public void testInterceptQuietProfile() { // GIVEN that the user the activity is starting as is currently in quiet mode when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); // THEN calling intercept returns true assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); // THEN the returned intent is the quiet mode intent assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID) .filterEquals(mInterceptor.mIntent)); } @Test public void testWorkChallenge() { // GIVEN that the user the activity is starting as is currently locked when(mUserController.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true); // THEN calling intercept returns true mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null); // THEN the returned intent is the quiet mode intent assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent)); } @Test public void testNoInterception() { // GIVEN that none of the interception conditions are met // THEN calling intercept returns false assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); } }