Merge "Adding unit tests for ActivityOptions" into main
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user