Include activity reparent in transition

When an activity is reparented from a Task to a TaskFragment of
different bounds, freeze the surface and include it in app transition.

Bug: 196173550
Test: test with demo app
Test: AppTransitionTests#testActivityRecordReparentToTaskFragment
Change-Id: I73edc2c4091f0e4156ecf963ce4d1fcbd00333c8
This commit is contained in:
Chris Li
2021-08-20 15:41:17 -07:00
parent 285761876b
commit 494ce0751d
5 changed files with 100 additions and 12 deletions

View File

@@ -1389,6 +1389,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return parent != null ? parent.asTaskFragment() : null;
}
/** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */
private boolean shouldStartChangeTransition(
@Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) {
if (mWmService.mDisableTransitionAnimation
|| mDisplayContent == null || newParent == null || oldParent == null
|| getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) {
return false;
}
// Transition change for the activity moving into a TaskFragment of different bounds.
return newParent.isOrganizedTaskFragment()
&& !newParent.getBounds().equals(oldParent.getBounds());
}
@Override
void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) {
final TaskFragment oldParent = (TaskFragment) rawOldParent;
@@ -1397,6 +1411,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final Task newTask = newParent != null ? newParent.getTask() : null;
this.task = newTask;
if (shouldStartChangeTransition(newParent, oldParent)) {
initializeChangeTransition(getBounds());
}
super.onParentChanged(newParent, oldParent);
if (isPersistable()) {
@@ -2607,6 +2625,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return parent != null ? parent.getOrganizedTaskFragment() : null;
}
@Override
boolean isEmbedded() {
final TaskFragment parent = getTaskFragment();
return parent != null && parent.isEmbedded();

View File

@@ -237,6 +237,8 @@ public class AppTransitionController {
// Check if there is any override
if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
// Unfreeze the windows that were previously frozen for TaskFragment animation.
unfreezeEmbeddedChangingWindows();
overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
}
@@ -341,6 +343,9 @@ public class AppTransitionController {
switch (changingType) {
case TYPE_TASK:
return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
case TYPE_ACTIVITY:
// ActivityRecord is put in a change transition only when it is reparented
// to an organized TaskFragment. See ActivityRecord#shouldStartChangeTransition.
case TYPE_TASK_FRAGMENT:
return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
default:
@@ -511,6 +516,16 @@ public class AppTransitionController {
: null;
}
private void unfreezeEmbeddedChangingWindows() {
final ArraySet<WindowContainer> changingContainers = mDisplayContent.mChangingContainers;
for (int i = changingContainers.size() - 1; i >= 0; i--) {
final WindowContainer wc = changingContainers.valueAt(i);
if (wc.isEmbedded()) {
wc.mSurfaceFreezer.unfreeze(wc.getSyncTransaction());
}
}
}
/**
* Overrides the pending transition with the remote animation defined by the
* {@link ITaskFragmentOrganizer} if all windows in the transition are children of

View File

@@ -33,7 +33,6 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -465,7 +464,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return this;
}
/** Returns {@code true} if this is a container for embedded activities or tasks. */
@Override
boolean isEmbedded() {
if (mIsEmbedded) {
return true;
@@ -2073,15 +2072,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return !startBounds.equals(getBounds());
}
/**
* Initializes a change transition. See {@link SurfaceFreezer} for more information.
*/
void initializeChangeTransition(Rect startBounds) {
mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
mDisplayContent.mChangingContainers.add(this);
mSurfaceFreezer.freeze(getSyncTransaction(), startBounds);
}
@Override
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
@@ -2163,6 +2153,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return mTaskFragmentOrganizer != null;
}
/** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */
final boolean isOrganizedTaskFragment() {
return mTaskFragmentOrganizer != null;
}
/** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
void clearLastPausedActivity() {
forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);

View File

@@ -30,6 +30,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -2578,13 +2579,34 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mSurfaceFreezer.unfreeze(getPendingTransaction());
}
/**
* Initializes a change transition. See {@link SurfaceFreezer} for more information.
*
* For now, this will only be called for the following cases:
* 1. {@link Task} is changing windowing mode between fullscreen and freeform.
* 2. {@link TaskFragment} is organized and is changing window bounds.
* 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}.
*
* This shouldn't be called on other {@link WindowContainer} unless there is a valid use case.
*/
void initializeChangeTransition(Rect startBounds) {
mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
mDisplayContent.mChangingContainers.add(this);
mSurfaceFreezer.freeze(getSyncTransaction(), startBounds);
}
ArraySet<WindowContainer> getAnimationSources() {
return mSurfaceAnimationSources;
}
@Override
public SurfaceControl getFreezeSnapshotTarget() {
return null;
// Only allow freezing if this window is in a TRANSIT_CHANGE
if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)
|| !mDisplayContent.mChangingContainers.contains(this)) {
return null;
}
return getSurfaceControl();
}
@Override
@@ -3189,6 +3211,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return false;
}
/** @return {@code true} if this is a container for embedded activities or tasks. */
boolean isEmbedded() {
return false;
}
/**
* @return {@code true} if this container's surface should be shown when it is created.
*/

View File

@@ -43,6 +43,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -54,6 +55,8 @@ import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
@@ -391,6 +394,35 @@ public class AppTransitionTests extends WindowTestsBase {
mDc.mAppTransition.getAnimationStyleResId(attrs));
}
@Test
public void testActivityRecordReparentToTaskFragment() {
final ActivityRecord activity = createActivityRecord(mDc);
activity.setVisibility(true);
final Task task = activity.getTask();
// Add a TaskFragment of half of the Task size.
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final ITaskFragmentOrganizer iOrganizer =
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setOrganizer(organizer)
.build();
final Rect taskBounds = new Rect();
task.getBounds(taskBounds);
taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
assertTrue(mDc.mChangingContainers.isEmpty());
assertFalse(mDc.mAppTransition.isTransitionSet());
// Schedule app transition when reparent activity to a TaskFragment of different size.
activity.reparent(taskFragment, POSITION_TOP);
assertTrue(mDc.mChangingContainers.contains(activity));
assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
}
private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
boolean mCancelled = false;
@Override