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

Commit 93e7f79f authored by Bryce Lee's avatar Bryce Lee
Browse files

Add tests to exercise ActivityStarter precondition failures.

This changelist adds tests which simulate various scenarios
encountered during the initial stages of starting an activity. They
ensure that the expected errors are returned under specified
conditions.

Bug: 64750076
Test: bit FrameworksServicesTests:com.android.server.am.ActivityStarterTests

Change-Id: I93fef3dce020fe03a8b2001b75a875980506a0dd
parent 2a3cc464
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -2784,7 +2784,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
        mTaskChangeNotificationController =
                new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
        mActivityStarter = new ActivityStarter(this);
        mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
        mRecentTasks = createRecentTasks();
        mStackSupervisor.setRecentTasks(mRecentTasks);
        mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -5352,8 +5352,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        return -1;
    }
    final ProcessRecord getRecordForAppLocked(
            IApplicationThread thread) {
    ProcessRecord getRecordForAppLocked(IApplicationThread thread) {
        if (thread == null) {
            return null;
        }
+13 −5
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
@@ -133,6 +134,7 @@ class ActivityStarter {
    private static final int INVALID_LAUNCH_MODE = -1;

    private final ActivityManagerService mService;
    private final IPackageManager mPackageManager;
    private final ActivityStackSupervisor mSupervisor;
    private final ActivityStartInterceptor mInterceptor;

@@ -232,8 +234,9 @@ class ActivityStarter {
        mIntentDelivered = false;
    }

    ActivityStarter(ActivityManagerService service) {
    ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
        mService = service;
        mPackageManager = packageManager;
        mSupervisor = mService.mStackSupervisor;
        mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
    }
@@ -264,8 +267,12 @@ class ActivityStarter {
            outActivity[0] = mLastStartActivityRecord[0];
        }

        return getExternalResult(mLastStartActivityResult);
    }

    public static int getExternalResult(int result) {
        // Aborted results are treated as successes externally, but we must track them internally.
        return mLastStartActivityResult != START_ABORTED ? mLastStartActivityResult : START_SUCCESS;
        return result != START_ABORTED ? result : START_SUCCESS;
    }

    /** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */
@@ -295,7 +302,8 @@ class ActivityStarter {
            }
        }

        final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
        final int userId = aInfo != null && aInfo.applicationInfo != null
                ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;

        if (err == ActivityManager.START_SUCCESS) {
            Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
@@ -371,7 +379,7 @@ class ActivityStarter {
                    && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
                try {
                    intent.addCategory(Intent.CATEGORY_VOICE);
                    if (!AppGlobals.getPackageManager().activitySupportsIntent(
                    if (!mPackageManager.activitySupportsIntent(
                            intent.getComponent(), intent, resolvedType)) {
                        Slog.w(TAG,
                                "Activity being started in current voice task does not support voice: "
@@ -389,7 +397,7 @@ class ActivityStarter {
            // If the caller is starting a new voice session, just make sure the target
            // is actually allowing it to run this way.
            try {
                if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
                if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
                        intent, resolvedType)) {
                    Slog.w(TAG,
                            "Activity being started in new voice task does not support: "
+188 −1
Original line number Diff line number Diff line
@@ -16,13 +16,28 @@

package com.android.server.am;

import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_SWITCHES_CANCELED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;

import android.app.ActivityOptions;
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.service.voice.IVoiceInteractionSession;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

@@ -36,10 +51,20 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyObject;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;

import static android.app.ActivityManager.START_PERMISSION_DENIED;
import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED;

import com.android.internal.os.BatteryStatsImpl;

/**
 * Tests for the {@link ActivityStack} class.
 *
@@ -52,12 +77,26 @@ import static org.mockito.Mockito.times;
public class ActivityStarterTests extends ActivityTestsBase {
    private ActivityManagerService mService;
    private ActivityStarter mStarter;
    private IPackageManager mPackageManager;

    private static final int PRECONDITION_NO_CALLER_APP = 1;
    private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1;
    private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2;
    private static final int PRECONDITION_SOURCE_PRESENT = 1 << 3;
    private static final int PRECONDITION_REQUEST_CODE = 1 << 4;
    private static final int PRECONDITION_SOURCE_VOICE_SESSION = 1 << 5;
    private static final int PRECONDITION_NO_VOICE_SESSION_SUPPORT = 1 << 6;
    private static final int PRECONDITION_DIFFERENT_UID = 1 << 7;
    private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8;
    private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9;
    private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        mService = createActivityManagerService();
        mStarter = new ActivityStarter(mService);
        mPackageManager = mock(IPackageManager.class);
        mStarter = new ActivityStarter(mService, mPackageManager);
    }

    @Test
@@ -92,4 +131,152 @@ public class ActivityStarterTests extends ActivityTestsBase {
            assertEquals(task2.mBounds, null);
        }
    }

    @Test
    public void testStartActivityPreconditions() throws Exception {
        verifyStartActivityPreconditions(PRECONDITION_NO_CALLER_APP, START_PERMISSION_DENIED);
        verifyStartActivityPreconditions(PRECONDITION_NO_INTENT_COMPONENT,
                START_INTENT_NOT_RESOLVED);
        verifyStartActivityPreconditions(PRECONDITION_NO_ACTIVITY_INFO, START_CLASS_NOT_FOUND);
        verifyStartActivityPreconditions(PRECONDITION_SOURCE_PRESENT | PRECONDITION_REQUEST_CODE,
                Intent.FLAG_ACTIVITY_FORWARD_RESULT, START_FORWARD_AND_REQUEST_CONFLICT);
        verifyStartActivityPreconditions(
                PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT
                        | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID,
                START_NOT_VOICE_COMPATIBLE);
        verifyStartActivityPreconditions(
                PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT
                        | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID
                        | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION,
                START_NOT_VOICE_COMPATIBLE);
        verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED);
        verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING,
                START_SWITCHES_CANCELED);
    }

    private static boolean containsConditions(int preconditions, int mask) {
        return (preconditions & mask) == mask;
    }

    private void verifyStartActivityPreconditions(int preconditions, int expectedResult) {
        verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult);
    }

    /**
     * Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller
     * provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP})
     * and the launch flags specified in the intent. The method constructs a call to
     * {@link ActivityStarter#startActivityLocked} based on these preconditions and ensures the
     * result matches the expected. It is important to note that the method also checks side effects
     * of the start, such as ensuring {@link ActivityOptions#abort()} is called in the relevant
     * scenarios.
     * @param preconditions A bitmask representing the preconditions for the launch
     * @param launchFlags The launch flags to be provided by the launch {@link Intent}.
     * @param expectedResult The expected result from the launch.
     */
    private void verifyStartActivityPreconditions(int preconditions, int launchFlags,
            int expectedResult) {
        final ActivityManagerService service = createActivityManagerService();
        final IPackageManager packageManager = mock(IPackageManager.class);
        final ActivityStarter starter = new ActivityStarter(service, packageManager);

        final IApplicationThread caller = mock(IApplicationThread.class);

        // If no caller app, return {@code null} {@link ProcessRecord}.
        final ProcessRecord record = containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
                ? null : new ProcessRecord(mock(BatteryStatsImpl.class),
                mock(ApplicationInfo.class), null, 0);

        doReturn(record).when(service).getRecordForAppLocked(anyObject());

        final Intent intent = new Intent();
        intent.setFlags(launchFlags);

        final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO)
                ?  null : new ActivityInfo();

        if (aInfo != null) {
            aInfo.applicationInfo = new ApplicationInfo();
            aInfo.applicationInfo.packageName = ActivityBuilder.DEFAULT_PACKAGE;
        }

        IVoiceInteractionSession voiceSession =
                containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
                ? mock(IVoiceInteractionSession.class) : null;

        // Create source token
        final ActivityBuilder builder = new ActivityBuilder(service).setTask(
                new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build());

        // Offset uid by one from {@link ActivityInfo} to simulate different uids.
        if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) {
            builder.setUid(aInfo.applicationInfo.uid + 1);
        }

        final ActivityRecord source = builder.build();

        if (!containsConditions(preconditions, PRECONDITION_NO_INTENT_COMPONENT)) {
            intent.setComponent(source.realActivity);
        }

        if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) {
            doReturn(false).when(service).checkAppSwitchAllowedLocked(anyInt(), anyInt(), anyInt(),
                    anyInt(), any());
        }

        if (containsConditions(preconditions,PRECONDITION_CANNOT_START_ANY_ACTIVITY)) {
            doReturn(false).when(service.mStackSupervisor).checkStartAnyActivityPermission(
                    any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), anyBoolean(),
                    any(), any(), any(), any());
        }

        try {
            if (containsConditions(preconditions,
                    PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION)) {
                doAnswer((inv) -> {
                    throw new RemoteException();
                }).when(packageManager).activitySupportsIntent(eq(source.realActivity), eq(intent),
                        any());
            } else {
                doReturn(!containsConditions(preconditions, PRECONDITION_NO_VOICE_SESSION_SUPPORT))
                        .when(packageManager).activitySupportsIntent(eq(source.realActivity),
                        eq(intent), any());
            }
        } catch (RemoteException e) {
        }

        final IBinder resultTo = containsConditions(preconditions, PRECONDITION_SOURCE_PRESENT)
                || containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
                ? source.appToken : null;

        final int requestCode = containsConditions(preconditions, PRECONDITION_REQUEST_CODE)
                ? 1 : 0;

        final int result = starter.startActivityLocked(caller, intent,
                null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
                null /*voiceSession*/, null /*voiceInteractor*/, resultTo,
                null /*resultWho*/, requestCode, 0 /*callingPid*/, 0 /*callingUid*/,
                null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
                0 /*startFlags*/, null /*options*/, false /*ignoreTargetSecurity*/,
                false /*componentSpecified*/, null /*outActivity*/,
                null /*inTask*/, "testLaunchActivityPermissionDenied");

        // In some cases the expected result internally is different than the published result. We
        // must use ActivityStarter#getExternalResult to translate.
        assertEquals(ActivityStarter.getExternalResult(expectedResult), result);

        // Ensure that {@link ActivityOptions} are aborted with unsuccessful result.
        if (expectedResult != START_SUCCESS) {
            final ActivityOptions options = spy(ActivityOptions.makeBasic());
            final int optionResult = starter.startActivityLocked(caller, intent,
                    null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
                    null /*voiceSession*/, null /*voiceInteractor*/, resultTo,
                    null /*resultWho*/, requestCode, 0 /*callingPid*/, 0 /*callingUid*/,
                    null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
                    0 /*startFlags*/, options /*options*/, false /*ignoreTargetSecurity*/,
                    false /*componentSpecified*/, null /*outActivity*/,
                    null /*inTask*/, "testLaunchActivityPermissionDenied");
            verify(options, times(1)).abort();
        }
    }
}
+13 −4
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.HandlerThread;
import android.os.Looper;
import android.service.voice.IVoiceInteractionSession;
import android.support.test.InstrumentationRegistry;
import com.android.server.AttributeCache;
import com.android.server.wm.AppWindowContainerController;
@@ -81,7 +82,10 @@ public class ActivityTestsBase {
    }

    protected ActivityManagerService createActivityManagerService() {
        return setupActivityManagerService(new TestActivityManagerService(mContext));
        final ActivityManagerService service =
                setupActivityManagerService(new TestActivityManagerService(mContext));
        AttributeCache.init(mContext);
        return service;
    }

    protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
@@ -98,7 +102,7 @@ public class ActivityTestsBase {
        private static int sCurrentActivityId = 0;

        // Default package name
        private static final String DEFAULT_PACKAGE = "com.foo";
        static final String DEFAULT_PACKAGE = "com.foo";

        // Default base activity name
        private static final String DEFAULT_BASE_ACTIVITY_NAME = ".BarActivity";
@@ -159,7 +163,6 @@ public class ActivityTestsBase {
            aInfo.applicationInfo = new ApplicationInfo();
            aInfo.applicationInfo.packageName = mComponent.getPackageName();
            aInfo.applicationInfo.uid = mUid;
            AttributeCache.init(mService.mContext);
            final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
                    0 /* launchedFromPid */, 0, null, intent, null,
                    aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
@@ -185,6 +188,7 @@ public class ActivityTestsBase {
        private String mPackage;
        private int mFlags = 0;
        private int mTaskId = 0;
        private IVoiceInteractionSession mVoiceSession;

        private ActivityStack mStack;

@@ -202,6 +206,11 @@ public class ActivityTestsBase {
            return this;
        }

        TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
            mVoiceSession = session;
            return this;
        }

        TaskBuilder setFlags(int flags) {
            mFlags = flags;
            return this;
@@ -232,7 +241,7 @@ public class ActivityTestsBase {
            intent.setFlags(mFlags);

            final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
                    intent /*intent*/, null /*_taskDescription*/);
                    intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
            mSupervisor.setFocusStackUnchecked("test", mStack);
            mStack.addTask(task, true, "creating test task");
            task.setStack(mStack);