Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8d0fb4ee authored by Benjamin Franz's avatar Benjamin Franz Committed by Android (Google) Code Review
Browse files

Merge "Add unit tests to ActivityStarterInterceptor"

parents be09d8d3 563707b3
Loading
Loading
Loading
Loading
+42 −13
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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;
@@ -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;
@@ -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;
@@ -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() {
@@ -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;
        }
@@ -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
@@ -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) {
+15 −11
Original line number Diff line number Diff line
@@ -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<>();
@@ -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;
@@ -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,
+2 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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;
+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));
    }
}