diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 0ade0934f2a6..152d729da3b6 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -384,6 +384,7 @@ message ActivityRecordProto { optional float min_aspect_ratio = 33; optional bool provides_max_bounds = 34; optional bool enable_recents_screenshot = 35; + optional int32 last_drop_input_mode = 36; } /* represents WindowToken */ diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 10459ca69d61..e1b32b5f22c5 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -577,6 +577,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityStarter.java" }, + "-1488852351": { + "message": "Task=%d contains embedded TaskFragment in untrusted mode. Disabled all input during TaskFragment remote animation.", + "level": "DEBUG", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/AppTransitionController.java" + }, "-1483435730": { "message": "InsetsSource setWin %s for type %s", "level": "DEBUG", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 4bef126ee2c8..7dc1c6f99bae 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -155,6 +155,7 @@ import static com.android.server.wm.ActivityRecordProto.IN_SIZE_COMPAT_MODE; import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING; import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START; import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN; +import static com.android.server.wm.ActivityRecordProto.LAST_DROP_INPUT_MODE; import static com.android.server.wm.ActivityRecordProto.LAST_SURFACE_SHOWING; import static com.android.server.wm.ActivityRecordProto.MIN_ASPECT_RATIO; import static com.android.server.wm.ActivityRecordProto.NAME; @@ -786,6 +787,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** The last set {@link DropInputMode} for this activity surface. */ @DropInputMode private int mLastDropInputMode = DropInputMode.NONE; + /** Whether the input to this activity will be dropped during the current playing animation. */ + private boolean mIsInputDroppedForAnimation; /** * If it is non-null, it requires all activities who have the same starting data to be drawn @@ -1567,6 +1570,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** Sets if all input will be dropped as a protection during the client-driven animation. */ + void setDropInputForAnimation(boolean isInputDroppedForAnimation) { + if (mIsInputDroppedForAnimation == isInputDroppedForAnimation) { + return; + } + mIsInputDroppedForAnimation = isInputDroppedForAnimation; + updateUntrustedEmbeddingInputProtection(); + } + /** * Sets to drop input when obscured to activity if it is embedded in untrusted mode. * @@ -1576,11 +1588,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * all untrusted activities. */ private void updateUntrustedEmbeddingInputProtection() { - final SurfaceControl sc = getSurfaceControl(); - if (sc == null) { + if (getSurfaceControl() == null) { return; } - if (isEmbeddedInUntrustedMode()) { + if (mIsInputDroppedForAnimation) { + // Disable all input during the animation. + setDropInputMode(DropInputMode.ALL); + } else if (isEmbeddedInUntrustedMode()) { // Set drop input to OBSCURED when untrusted embedded. setDropInputMode(DropInputMode.OBSCURED); } else { @@ -1591,7 +1605,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @VisibleForTesting void setDropInputMode(@DropInputMode int mode) { - if (mLastDropInputMode != mode && getSurfaceControl() != null) { + if (mLastDropInputMode != mode) { mLastDropInputMode = mode; mWmService.mTransactionFactory.get() .setDropInputMode(getSurfaceControl(), mode) @@ -9301,6 +9315,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // permission to access the device configs. proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds()); proto.write(ENABLE_RECENTS_SCREENSHOT, mEnableRecentsScreenshot); + proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode); } @Override diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 457ea131a51f..a31d8603e2f2 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -98,6 +98,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.LinkedList; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -541,12 +542,13 @@ public class AppTransitionController { } /** - * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all app windows - * in the current transition. - * @return {@code null} if there is no such organizer, or if there are more than one. + * Finds the common parent {@link Task} that is parent of all embedded app windows in the + * current transition. + * @return {@code null} if app windows in the transition are not children of the same Task, or + * if none of the app windows is embedded. */ @Nullable - private ITaskFragmentOrganizer findTaskFragmentOrganizerForAllWindows() { + private Task findParentTaskForAllEmbeddedWindows() { mTempTransitionWindows.clear(); mTempTransitionWindows.addAll(mDisplayContent.mClosingApps); mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps); @@ -600,13 +602,22 @@ public class AppTransitionController { leafTask = task; } mTempTransitionWindows.clear(); - if (leafTask == null) { + return leafTask; + } + + /** + * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all embedded + * {@link TaskFragment} belong to the given {@link Task}. + * @return {@code null} if there is no such organizer, or if there are more than one. + */ + @Nullable + private ITaskFragmentOrganizer findTaskFragmentOrganizer(@Nullable Task task) { + if (task == null) { return null; } - // We don't support remote animation for Task with multiple TaskFragmentOrganizers. final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1]; - final boolean hasMultipleOrganizers = leafTask.forAllLeafTaskFragments(taskFragment -> { + final boolean hasMultipleOrganizers = task.forAllLeafTaskFragments(taskFragment -> { final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer(); if (tfOrganizer == null) { return false; @@ -638,7 +649,8 @@ public class AppTransitionController { return false; } - final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizerForAllWindows(); + final Task task = findParentTaskForAllEmbeddedWindows(); + final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task); final RemoteAnimationDefinition definition = organizer != null ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController .getRemoteAnimationDefinition(organizer) @@ -653,6 +665,24 @@ public class AppTransitionController { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Override with TaskFragment remote animation for transit=%s", AppTransition.appTransitionOldToString(transit)); + + final boolean hasUntrustedEmbedding = task.forAllLeafTasks( + taskFragment -> !taskFragment.isAllowedToBeEmbeddedInTrustedMode()); + final RemoteAnimationController remoteAnimationController = + mDisplayContent.mAppTransition.getRemoteAnimationController(); + if (hasUntrustedEmbedding && remoteAnimationController != null) { + // We are going to use client-driven animation, but the Task is in untrusted embedded + // mode. We need to disable all input on activity windows during the animation to + // ensure it is safe. This is needed for all activity windows in the animation Task. + remoteAnimationController.setOnRemoteAnimationReady(() -> { + final Consumer updateActivities = + activity -> activity.setDropInputForAnimation(true); + task.forAllActivities(updateActivities); + }); + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "Task=%d contains embedded TaskFragment in" + + " untrusted mode. Disabled all input during TaskFragment remote animation.", + task.mTaskId); + } return true; } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index eeac230489f9..2aefd40b07ba 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -23,6 +23,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; @@ -49,6 +50,7 @@ import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; +import java.util.function.Consumer; /** * Helper class to run app animations in a remote process. @@ -72,6 +74,8 @@ class RemoteAnimationController implements DeathRecipient { private FinishedCallback mFinishedCallback; private boolean mCanceled; private boolean mLinkedToDeathOfRunner; + @Nullable + private Runnable mOnRemoteAnimationReady; RemoteAnimationController(WindowManagerService service, DisplayContent displayContent, RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) { @@ -101,6 +105,11 @@ class RemoteAnimationController implements DeathRecipient { return adapters; } + /** Sets callback to run before starting remote animation. */ + void setOnRemoteAnimationReady(@Nullable Runnable onRemoteAnimationReady) { + mOnRemoteAnimationReady = onRemoteAnimationReady; + } + /** * Called when the transition is ready to be started, and all leashes have been set up. */ @@ -133,6 +142,11 @@ class RemoteAnimationController implements DeathRecipient { return; } + if (mOnRemoteAnimationReady != null) { + mOnRemoteAnimationReady.run(); + mOnRemoteAnimationReady = null; + } + // Create the remote wallpaper animation targets (if any) final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations(); @@ -292,6 +306,10 @@ class RemoteAnimationController implements DeathRecipient { mService.closeSurfaceTransaction("RemoteAnimationController#finished"); } } + // Reset input for all activities when the remote animation is finished. + final Consumer updateActivities = + activity -> activity.setDropInputForAnimation(false); + mDisplayContent.forAllActivities(updateActivities); setRunningRemoteAnimation(false); ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation"); } @@ -302,6 +320,7 @@ class RemoteAnimationController implements DeathRecipient { } catch (RemoteException e) { Slog.e(TAG, "Failed to notify cancel", e); } + mOnRemoteAnimationReady = null; } private void releaseFinishedCallback() { diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index bdec49e2ec40..0a92ffced48d 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -304,18 +304,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr synchronized (mGlobalLock) { final TaskFragmentOrganizerState organizerState = mTaskFragmentOrganizerState.get(organizer.asBinder()); - if (organizerState == null) { - return null; - } - for (TaskFragment tf : organizerState.mOrganizedTaskFragments) { - if (!tf.isAllowedToBeEmbeddedInTrustedMode()) { - // Disable client-driven animations for organizer if at least one of the - // embedded task fragments is not embedding in trusted mode. - // TODO(b/197364677): replace with a stub or Shell-driven one instead of skip? - return null; - } - } - return organizerState.mRemoteAnimationDefinition; + return organizerState != null ? organizerState.mRemoteAnimationDefinition : null; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 72521fd06245..94f900efb845 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -33,21 +33,26 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.annotation.Nullable; +import android.gui.DropInputMode; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -739,20 +744,36 @@ public class AppTransitionControllerTest extends WindowTestsBase { } static class TestRemoteAnimationRunner implements IRemoteAnimationRunner { + private IRemoteAnimationFinishedCallback mFinishedCallback; + @Override public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + mFinishedCallback = finishedCallback; } @Override public void onAnimationCancelled() throws RemoteException { + mFinishedCallback = null; } @Override public IBinder asBinder() { return new Binder(); } + + boolean isAnimationStarted() { + return mFinishedCallback != null; + } + + void finishAnimation() { + try { + mFinishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + fail(); + } + } } @Test @@ -841,144 +862,139 @@ public class AppTransitionControllerTest extends WindowTestsBase { @Test public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - new TestRemoteAnimationRunner(), 10, 1); - setupTaskFragmentRemoteAnimation(organizer, adapter); + final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); + setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); // Create a TaskFragment with embedded activity. final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( createTask(mDisplayContent), organizer); final ActivityRecord activity = taskFragment.getTopMostActivity(); - activity.allDrawn = true; + prepareActivityForAppTransition(activity); spyOn(mDisplayContent.mAppTransition); - // Prepare a transition. + // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // Should be overridden. - verify(mDisplayContent.mAppTransition) - .overridePendingAppTransitionRemote(adapter, false /* sync */); + // Animation run by the remote handler. + assertTrue(remoteAnimationRunner.isAnimationStarted()); } @Test public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - new TestRemoteAnimationRunner(), 10, 1); - setupTaskFragmentRemoteAnimation(organizer, adapter); + final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); + setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); final Task task = createTask(mDisplayContent); // Closing non-embedded activity. final ActivityRecord closingActivity = createActivityRecord(task); - closingActivity.allDrawn = true; + prepareActivityForAppTransition(closingActivity); // Opening TaskFragment with embedded activity. final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord openingActivity = taskFragment.getTopMostActivity(); - openingActivity.allDrawn = true; + prepareActivityForAppTransition(openingActivity); task.effectiveUid = openingActivity.getUid(); spyOn(mDisplayContent.mAppTransition); - // Prepare a transition. + // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // Should be overridden. - verify(mDisplayContent.mAppTransition) - .overridePendingAppTransitionRemote(adapter, false /* sync */); + // Animation run by the remote handler. + assertTrue(remoteAnimationRunner.isAnimationStarted()); } @Test public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - new TestRemoteAnimationRunner(), 10, 1); - setupTaskFragmentRemoteAnimation(organizer, adapter); + final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); + setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); final Task task = createTask(mDisplayContent); // Closing TaskFragment with embedded activity. final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord closingActivity = taskFragment1.getTopMostActivity(); - closingActivity.allDrawn = true; + prepareActivityForAppTransition(closingActivity); closingActivity.info.applicationInfo.uid = 12345; // Opening TaskFragment with embedded activity with different UID. final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord openingActivity = taskFragment2.getTopMostActivity(); + prepareActivityForAppTransition(openingActivity); openingActivity.info.applicationInfo.uid = 54321; - openingActivity.allDrawn = true; spyOn(mDisplayContent.mAppTransition); - // Prepare a transition. + // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // Should be overridden. - verify(mDisplayContent.mAppTransition) - .overridePendingAppTransitionRemote(adapter, false /* sync */); + // Animation run by the remote handler. + assertTrue(remoteAnimationRunner.isAnimationStarted()); } @Test public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - new TestRemoteAnimationRunner(), 10, 1); - setupTaskFragmentRemoteAnimation(organizer, adapter); + final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); + setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); // Closing activity in Task1. final ActivityRecord closingActivity = createActivityRecord(mDisplayContent); - closingActivity.allDrawn = true; + prepareActivityForAppTransition(closingActivity); // Opening TaskFragment with embedded activity in Task2. final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( createTask(mDisplayContent), organizer); final ActivityRecord openingActivity = taskFragment.getTopMostActivity(); - openingActivity.allDrawn = true; + prepareActivityForAppTransition(openingActivity); spyOn(mDisplayContent.mAppTransition); - // Prepare a transition for TaskFragment. + // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // Should not be overridden. - verify(mDisplayContent.mAppTransition, never()) - .overridePendingAppTransitionRemote(adapter, false /* sync */); + // Animation not run by the remote handler. + assertFalse(remoteAnimationRunner.isAnimationStarted()); } @Test public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - new TestRemoteAnimationRunner(), 10, 1); - setupTaskFragmentRemoteAnimation(organizer, adapter); + final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); + setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); final Task task = createTask(mDisplayContent); // Closing TaskFragment with embedded activity. final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord closingActivity = taskFragment.getTopMostActivity(); - closingActivity.allDrawn = true; + prepareActivityForAppTransition(closingActivity); closingActivity.info.applicationInfo.uid = 12345; task.effectiveUid = closingActivity.getUid(); // Opening non-embedded activity with different UID. final ActivityRecord openingActivity = createActivityRecord(task); + prepareActivityForAppTransition(openingActivity); openingActivity.info.applicationInfo.uid = 54321; - openingActivity.allDrawn = true; spyOn(mDisplayContent.mAppTransition); - // Prepare a transition. + // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // Should not be overridden - verify(mDisplayContent.mAppTransition, never()) - .overridePendingAppTransitionRemote(adapter, false /* sync */); + // Animation should not run by the remote handler when there are non-embedded activities of + // different UID. + assertFalse(remoteAnimationRunner.isAnimationStarted()); } @Test public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); - final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( - new TestRemoteAnimationRunner(), 10, 1); - setupTaskFragmentRemoteAnimation(organizer, adapter); + final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); + setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); // Create a TaskFragment with embedded activity. final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( createTask(mDisplayContent), organizer); final ActivityRecord activity = taskFragment.getTopMostActivity(); - activity.allDrawn = true; + prepareActivityForAppTransition(activity); // Set wallpaper as visible. final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); @@ -986,12 +1002,66 @@ public class AppTransitionControllerTest extends WindowTestsBase { doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); spyOn(mDisplayContent.mAppTransition); - // Prepare a transition. + // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // Should not be overridden when there is wallpaper in the transition. - verify(mDisplayContent.mAppTransition, never()) - .overridePendingAppTransitionRemote(adapter, false /* sync */); + // Animation should not run by the remote handler when there is wallpaper in the transition. + assertFalse(remoteAnimationRunner.isAnimationStarted()); + } + + @Test + public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner(); + setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner); + + // Create a TaskFragment with embedded activities, one is trusted embedded, and the other + // one is untrusted embedded. + final Task task = createTask(mDisplayContent); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(2) + .setOrganizer(organizer) + .build(); + final ActivityRecord activity0 = taskFragment.getChildAt(0).asActivityRecord(); + final ActivityRecord activity1 = taskFragment.getChildAt(1).asActivityRecord(); + // Also create a non-embedded activity in the Task. + final ActivityRecord activity2 = new ActivityBuilder(mAtm).build(); + task.addChild(activity2, POSITION_BOTTOM); + prepareActivityForAppTransition(activity0); + prepareActivityForAppTransition(activity1); + prepareActivityForAppTransition(activity2); + doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0); + doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1); + spyOn(mDisplayContent.mAppTransition); + + // Prepare and start transition. + prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + + // The animation will be animated remotely by client and all activities are input disabled + // for untrusted animation. + assertTrue(remoteAnimationRunner.isAnimationStarted()); + verify(activity0).setDropInputForAnimation(true); + verify(activity1).setDropInputForAnimation(true); + verify(activity2).setDropInputForAnimation(true); + verify(activity0).setDropInputMode(DropInputMode.ALL); + verify(activity1).setDropInputMode(DropInputMode.ALL); + verify(activity2).setDropInputMode(DropInputMode.ALL); + + // Reset input after animation is finished. + clearInvocations(activity0); + clearInvocations(activity1); + clearInvocations(activity2); + remoteAnimationRunner.finishAnimation(); + + verify(activity0).setDropInputForAnimation(false); + verify(activity1).setDropInputForAnimation(false); + verify(activity2).setDropInputForAnimation(false); + verify(activity0).setDropInputMode(DropInputMode.OBSCURED); + verify(activity1).setDropInputMode(DropInputMode.NONE); + verify(activity2).setDropInputMode(DropInputMode.NONE); } @Test @@ -1004,7 +1074,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { .setParentTask(task) .setOrganizer(organizer) .build(); - changeTaskFragment.getTopMostActivity().allDrawn = true; + prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity()); spyOn(mDisplayContent.mAppTransition); spyOn(emptyTaskFragment); @@ -1034,7 +1104,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { .setParentTask(task) .setOrganizer(organizer) .build(); - changeTaskFragment.getTopMostActivity().allDrawn = true; + prepareActivityForAppTransition(changeTaskFragment.getTopMostActivity()); // To make sure that having a detached activity won't cause any issue. final ActivityRecord detachedActivity = createActivityRecord(task); detachedActivity.removeImmediately(); @@ -1060,7 +1130,9 @@ public class AppTransitionControllerTest extends WindowTestsBase { /** Registers remote animation for the organizer. */ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, - RemoteAnimationAdapter adapter) { + TestRemoteAnimationRunner remoteAnimationRunner) { + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + remoteAnimationRunner, 10, 1); final ITaskFragmentOrganizer iOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); final RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); @@ -1087,4 +1159,14 @@ public class AppTransitionControllerTest extends WindowTestsBase { } mDisplayContent.mAppTransitionController.handleAppTransitionReady(); } + + private static void prepareActivityForAppTransition(ActivityRecord activity) { + // Transition will wait until all participated activities to be drawn. + activity.allDrawn = true; + // Skip manipulate the SurfaceControl. + doNothing().when(activity).setDropInputMode(anyInt()); + // Make sure activity can create remote animation target. + doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget( + any()); + } } \ No newline at end of file