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

Commit 633497ed authored by Louis Chang's avatar Louis Chang Committed by Android (Google) Code Review
Browse files

Merge "Adding unit tests for ActivityOptions" into main

parents 2d957bb0 26cceb87
Loading
Loading
Loading
Loading
+27 −20
Original line number Diff line number Diff line
@@ -293,26 +293,7 @@ public class SafeActivityOptions {
            throw new SecurityException(msg);
        }
        // Check if the caller is allowed to launch on the specified display area.
        final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
        TaskDisplayArea taskDisplayArea = daToken != null
                ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;

        // If we do not have a task display area token, check if the launch task display area
        // feature id is specified.
        if (taskDisplayArea == null) {
            final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
            if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
                final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
                        ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
                final DisplayContent dc = supervisor.mRootWindowContainer
                        .getDisplayContent(launchDisplayId);
                if (dc != null) {
                    taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
                            tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
                }
            }
        }

        final TaskDisplayArea taskDisplayArea = getLaunchTaskDisplayArea(options, supervisor);
        if (aInfo != null && taskDisplayArea != null
                && !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid,
                taskDisplayArea, aInfo)) {
@@ -428,6 +409,32 @@ public class SafeActivityOptions {
        }
    }

    @VisibleForTesting
    TaskDisplayArea getLaunchTaskDisplayArea(ActivityOptions options,
            ActivityTaskSupervisor supervisor) {
        final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
        TaskDisplayArea taskDisplayArea = daToken != null
                ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
        if (taskDisplayArea != null) {
            return taskDisplayArea;
        }

        // If we do not have a task display area token, check if the launch task display area
        // feature id is specified.
        final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
        if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
            final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
                    ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
            final DisplayContent dc = supervisor.mRootWindowContainer
                    .getDisplayContent(launchDisplayId);
            if (dc != null) {
                taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
                        tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
            }
        }
        return taskDisplayArea;
    }

    private boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
        if (atmService.mActiveVoiceInteractionServiceComponent == null) {
            return false;
+126 −0
Original line number Diff line number Diff line
@@ -22,11 +22,17 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;

import android.app.Activity;
import android.app.ActivityManager.RunningTaskInfo;
@@ -41,6 +47,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import android.util.Rational;
import android.view.SurfaceControl;
import android.window.TaskOrganizer;
@@ -48,7 +55,10 @@ import android.window.TaskOrganizer;
import androidx.test.filters.MediumTest;

import org.junit.Test;
import org.mockito.ArgumentCaptor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@@ -166,6 +176,122 @@ public class ActivityOptionsTest {
        }
    }

    /**
     * Tests if any unknown key is being used in the ActivityOptions bundle. If so, please review
     * if the newly added bundle should be protected with permissions to avoid malicious attacks.
     *
     * @see SafeActivityOptionsTest#test_getOptions
     */
    @Test
    public void testActivityOptionsFromBundle() {
        // Spy on a bundle that is generated from a basic ActivityOptions.
        final ActivityOptions options = ActivityOptions.makeBasic();
        Bundle bundle = options.toBundle();
        spyOn(bundle);

        // Create a new ActivityOptions from the bundle
        new ActivityOptions(bundle);

        // Verify the keys that are being used.
        final ArgumentCaptor<String> stringCaptor =  ArgumentCaptor.forClass(String.class);
        verify(bundle, atLeastOnce()).getString(stringCaptor.capture());
        verify(bundle, atLeastOnce()).getBoolean(stringCaptor.capture());
        verify(bundle, atLeastOnce()).getParcelable(stringCaptor.capture(), any());
        verify(bundle, atLeastOnce()).getInt(stringCaptor.capture(), anyInt());
        verify(bundle, atLeastOnce()).getBinder(stringCaptor.capture());
        verify(bundle, atLeastOnce()).getBundle(stringCaptor.capture());
        final List<String> keys = stringCaptor.getAllValues();
        final List<String> unknownKeys = new ArrayList<>();
        for (String key : keys) {
            switch (key) {
                case ActivityOptions.KEY_PACKAGE_NAME:
                case ActivityOptions.KEY_LAUNCH_BOUNDS:
                case ActivityOptions.KEY_ANIM_TYPE:
                case ActivityOptions.KEY_ANIM_ENTER_RES_ID:
                case ActivityOptions.KEY_ANIM_EXIT_RES_ID:
                case ActivityOptions.KEY_ANIM_IN_PLACE_RES_ID:
                case ActivityOptions.KEY_ANIM_BACKGROUND_COLOR:
                case ActivityOptions.KEY_ANIM_THUMBNAIL:
                case ActivityOptions.KEY_ANIM_START_X:
                case ActivityOptions.KEY_ANIM_START_Y:
                case ActivityOptions.KEY_ANIM_WIDTH:
                case ActivityOptions.KEY_ANIM_HEIGHT:
                case ActivityOptions.KEY_ANIM_START_LISTENER:
                case ActivityOptions.KEY_SPLASH_SCREEN_THEME:
                case ActivityOptions.KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE:
                case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
                case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
                case ActivityOptions.KEY_TRANSIENT_LAUNCH:
                case "android:activity.animationFinishedListener":
                    // KEY_ANIMATION_FINISHED_LISTENER
                case "android:activity.animSpecs": // KEY_ANIM_SPECS
                case "android:activity.lockTaskMode": // KEY_LOCK_TASK_MODE
                case "android:activity.shareIdentity": // KEY_SHARE_IDENTITY
                case "android.activity.launchDisplayId": // KEY_LAUNCH_DISPLAY_ID
                case "android.activity.callerDisplayId": // KEY_CALLER_DISPLAY_ID
                case "android.activity.launchTaskDisplayAreaToken":
                    // KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN
                case "android.activity.launchTaskDisplayAreaFeatureId":
                    // KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID
                case "android.activity.windowingMode": // KEY_LAUNCH_WINDOWING_MODE
                case "android.activity.activityType": // KEY_LAUNCH_ACTIVITY_TYPE
                case "android.activity.launchTaskId": // KEY_LAUNCH_TASK_ID
                case "android.activity.disableStarting": // KEY_DISABLE_STARTING_WINDOW
                case "android.activity.pendingIntentLaunchFlags":
                    // KEY_PENDING_INTENT_LAUNCH_FLAGS
                case "android.activity.alwaysOnTop": // KEY_TASK_ALWAYS_ON_TOP
                case "android.activity.taskOverlay": // KEY_TASK_OVERLAY
                case "android.activity.taskOverlayCanResume": // KEY_TASK_OVERLAY_CAN_RESUME
                case "android.activity.avoidMoveToFront": // KEY_AVOID_MOVE_TO_FRONT
                case "android.activity.freezeRecentTasksReordering":
                    // KEY_FREEZE_RECENT_TASKS_REORDERING
                case "android:activity.disallowEnterPictureInPictureWhileLaunching":
                    // KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING
                case "android:activity.applyActivityFlagsForBubbles":
                    // KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES
                case "android:activity.applyMultipleTaskFlagForShortcut":
                    // KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT
                case "android:activity.applyNoUserActionFlagForShortcut":
                    // KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT
                case "android:activity.transitionCompleteListener":
                    // KEY_TRANSITION_COMPLETE_LISTENER
                case "android:activity.transitionIsReturning": // KEY_TRANSITION_IS_RETURNING
                case "android:activity.sharedElementNames": // KEY_TRANSITION_SHARED_ELEMENTS
                case "android:activity.resultData": // KEY_RESULT_DATA
                case "android:activity.resultCode": // KEY_RESULT_CODE
                case "android:activity.exitCoordinatorIndex": // KEY_EXIT_COORDINATOR_INDEX
                case "android.activity.sourceInfo": // KEY_SOURCE_INFO
                case "android:activity.usageTimeReport": // KEY_USAGE_TIME_REPORT
                case "android:activity.rotationAnimationHint": // KEY_ROTATION_ANIMATION_HINT
                case "android:instantapps.installerbundle": // KEY_INSTANT_APP_VERIFICATION_BUNDLE
                case "android:activity.specsFuture": // KEY_SPECS_FUTURE
                case "android:activity.remoteAnimationAdapter": // KEY_REMOTE_ANIMATION_ADAPTER
                case "android:activity.remoteTransition": // KEY_REMOTE_TRANSITION
                case "android:activity.overrideTaskTransition": // KEY_OVERRIDE_TASK_TRANSITION
                case "android.activity.removeWithTaskOrganizer": // KEY_REMOVE_WITH_TASK_ORGANIZER
                case "android.activity.launchTypeBubble": // KEY_LAUNCHED_FROM_BUBBLE
                case "android.activity.splashScreenStyle": // KEY_SPLASH_SCREEN_STYLE
                case "android.activity.launchIntoPipParams": // KEY_LAUNCH_INTO_PIP_PARAMS
                case "android.activity.dismissKeyguard": // KEY_DISMISS_KEYGUARD
                case "android.activity.pendingIntentCreatorBackgroundActivityStartMode":
                    // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
                case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
                    // Existing keys
                    break;
                default:
                    unknownKeys.add(key);
                    break;
            }
        }

        // Report if any unknown key exists.
        for (String key : unknownKeys) {
            Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
                    + "Please review if the given bundle should be protected with permissions.");
        }
        assertTrue(unknownKeys.isEmpty());
    }

    public static class TrampolineActivity extends Activity {
        static int sTaskId;

+134 −0
Original line number Diff line number Diff line
@@ -16,17 +16,36 @@

package com.android.server.wm;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.view.Display.DEFAULT_DISPLAY;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;

import android.app.ActivityOptions;
import android.content.pm.ActivityInfo;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.view.RemoteAnimationAdapter;
import android.window.RemoteTransition;
import android.window.WindowContainerToken;

import androidx.test.filters.MediumTest;

import org.junit.Test;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;

/**
 * Build/Install/Run:
@@ -73,4 +92,119 @@ public class SafeActivityOptionsTest {

        assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
    }

    @Test
    public void test_getOptions() {
        // Mock everything necessary
        MockitoSession mockingSession = mockitoSession()
                .mockStatic(ActivityTaskManagerService.class)
                .strictness(Strictness.LENIENT)
                .startMocking();
        doReturn(PERMISSION_DENIED).when(() -> ActivityTaskManagerService.checkPermission(
                any(), anyInt(), anyInt()));

        final LockTaskController lockTaskController = mock(LockTaskController.class);
        doReturn(false).when(lockTaskController).isPackageAllowlisted(anyInt(), any());

        final ActivityTaskManagerService atm = mock(ActivityTaskManagerService.class);
        doReturn(lockTaskController).when(atm).getLockTaskController();

        final ActivityTaskSupervisor taskSupervisor =
                new ActivityTaskSupervisor(atm, mock(Looper.class));
        spyOn(taskSupervisor);
        doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnDisplay(anyInt(),
                anyInt(), anyInt(), any());
        doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnTaskDisplayArea(anyInt(),
                anyInt(), any(), any());

        taskSupervisor.mRecentTasks = mock(RecentTasks.class);
        doReturn(false).when(taskSupervisor.mRecentTasks).isCallerRecents(anyInt());

        // Ensure exceptions are thrown when lack of permissions.
        ActivityOptions activityOptions = ActivityOptions.makeBasic();
        try {
            activityOptions.setLaunchTaskId(100);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setDisableStartingWindow(true);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setTransientLaunch();
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setDismissKeyguard();
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setLaunchActivityType(ACTIVITY_TYPE_STANDARD);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setLaunchedFromBubble(true);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setLockTaskEnabled(true);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeCustomTaskAnimation(
                    getInstrumentation().getContext(), 0, 0, null, null, null);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            RemoteAnimationAdapter remoteAnimationAdapter = mock(RemoteAnimationAdapter.class);
            RemoteTransition remoteTransition = mock(RemoteTransition.class);
            activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter,
                    remoteTransition);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setRemoteAnimationAdapter(remoteAnimationAdapter);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeRemoteTransition(remoteTransition);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            activityOptions = ActivityOptions.makeBasic();
            activityOptions.setRemoteTransition(remoteTransition);
            verifySecureExceptionThrown(activityOptions, taskSupervisor);

            verifySecureExceptionThrown(activityOptions, taskSupervisor,
                    mock(TaskDisplayArea.class));
        } finally {
            mockingSession.finishMocking();
        }
    }

    private void verifySecureExceptionThrown(ActivityOptions activityOptions,
            ActivityTaskSupervisor taskSupervisor) {
        verifySecureExceptionThrown(activityOptions, taskSupervisor, null /* mockTda */);
    }

    private void verifySecureExceptionThrown(ActivityOptions activityOptions,
            ActivityTaskSupervisor taskSupervisor, TaskDisplayArea mockTda) {
        SafeActivityOptions safeActivityOptions = new SafeActivityOptions(activityOptions);
        if (mockTda != null) {
            spyOn(safeActivityOptions);
            doReturn(mockTda).when(safeActivityOptions).getLaunchTaskDisplayArea(any(), any());
        }

        boolean isExceptionThrow = false;
        final ActivityInfo aInfo = mock(ActivityInfo.class);
        try {
            safeActivityOptions.getOptions(null, aInfo, null, taskSupervisor);
        } catch (SecurityException ex) {
            isExceptionThrow = true;
        }
        assertTrue(isExceptionThrow);
    }
}