Loading services/core/java/com/android/server/wm/SafeActivityOptions.java +27 −20 Original line number Diff line number Diff line Loading @@ -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)) { Loading Loading @@ -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; Loading services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +126 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +134 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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); } } Loading
services/core/java/com/android/server/wm/SafeActivityOptions.java +27 −20 Original line number Diff line number Diff line Loading @@ -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)) { Loading Loading @@ -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; Loading
services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +126 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading
services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +134 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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); } }