Disable all input on ActivityRecord during untrusted animation

For cross-process embedding, there can be activity of other app embedded
in untrusted mode. When it happens, we need to disable all input on the
Task if we are going to play client-driven animation to make sure the
host client can't abuse the animation leash.

Bug: 197364677
Test: atest WmTests:AppTransitionControllerTest
Change-Id: I3e299c0a43ac823b7df6af9d02c7168bd65d3271
This commit is contained in:
Chris Li 2022-03-20 15:24:32 +08:00
parent 6cc0fa4d00
commit a53b05ca8c
7 changed files with 221 additions and 79 deletions

View File

@ -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 */

View File

@ -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",

View File

@ -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

View File

@ -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<ActivityRecord> 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;
}

View File

@ -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<ActivityRecord> 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() {

View File

@ -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;
}
}

View File

@ -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());
}
}