summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIssei Suzuki <issei@google.com>2019-12-20 12:44:09 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2019-12-20 12:44:09 +0000
commit8cac70a65e4f270a253c010ab86a288cc747db1f (patch)
treef4f540b03efd44d85b1d4e6061041d2c2c4efdf4
parent944c611bd7c9c00541cf1fc7fe731b5ec41747be (diff)
parent0ae90d0cca755260b1c4ca532b50fcbc7ba34ef0 (diff)
Merge "Promote app transition target its ancestor if possible."
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java15
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java161
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java356
5 files changed, 515 insertions, 32 deletions
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9917cee00eaf..1e98e3afa97c 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1741,6 +1741,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "1460759282": {
+ "message": "getAnimationTarget in=%s, out=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"1469292670": {
"message": "Changing focus from %s to %s displayId=%d Callers=%s",
"level": "VERBOSE",
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index c57ac11723b4..b154da412593 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -228,11 +228,10 @@ final class AccessibilityController {
}
}
- public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
- final int displayId = windowState.getDisplayId();
+ public void onAppWindowTransitionLocked(int displayId, int transition) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+ displayMagnifier.onAppWindowTransitionLocked(displayId, transition);
}
// Not relevant for the window observer.
}
@@ -446,11 +445,11 @@ final class AccessibilityController {
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
}
- public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+ public void onAppWindowTransitionLocked(int displayId, int transition) {
if (DEBUG_WINDOW_TRANSITIONS) {
Slog.i(LOG_TAG, "Window transition: "
+ AppTransition.appTransitionToString(transition)
- + " displayId: " + windowState.getDisplayId());
+ + " displayId: " + displayId);
}
final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
if (magnifying) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index bfc626828fa0..91149968e45b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4159,7 +4159,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* transition.
*
* <p class="note><strong>Note:</strong> If the visibility of this {@link ActivityRecord} is
- * already set to {@link #visible}, we don't need to update the visibility. So {@code false} is
+ * already set to {@link #mVisible}, we don't need to update the visibility. So {@code false} is
* returned.</p>
*
* @param visible {@code true} if this {@link ActivityRecord} should become visible,
@@ -4169,16 +4169,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
boolean shouldApplyAnimation(boolean visible) {
// Allow for state update and animation to be applied if:
- // * token is transitioning visibility state
- // * or the token was marked as hidden and is exiting before we had a chance to play the
+ // * activity is transitioning visibility state
+ // * or the activity was marked as hidden and is exiting before we had a chance to play the
// transition animation
- // * or this is an opening app and windows are being replaced
- // * or the token is the opening app and visible while opening task behind existing one.
- final DisplayContent displayContent = getDisplayContent();
+ // * or this is an opening app and windows are being replaced (e.g. freeform window to
+ // normal window).
return isVisible() != visible || (!isVisible() && mIsExiting)
- || (visible && forAllWindows(WindowState::waitingForReplacement, true))
- || (visible && displayContent.mOpeningApps.contains(this)
- && displayContent.mAppTransition.getAppTransition() == TRANSIT_TASK_OPEN_BEHIND);
+ || (visible && forAllWindows(WindowState::waitingForReplacement, true));
}
/**
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 6ea0650df37a..6e09b94a909a 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -48,6 +48,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_S
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -69,6 +70,8 @@ import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.protolog.common.ProtoLog;
+import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.function.Predicate;
@@ -180,11 +183,7 @@ public class AppTransitionController {
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
try {
- // TODO: Apply an app transition animation on TaskStack instead of ActivityRecord when
- // appropriate.
- applyAnimations(mDisplayContent.mClosingApps, transit, false /* visible */,
- animLp, voiceInteraction);
- applyAnimations(mDisplayContent.mOpeningApps, transit, true /* visible */,
+ applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
animLp, voiceInteraction);
handleClosingApps();
handleOpeningApps();
@@ -350,9 +349,9 @@ public class AppTransitionController {
}
/**
- * Apply an app transition animation on a set of {@link ActivityRecord}
+ * Apply animation to the set of window containers.
*
- * @param apps The list of apps to which an app transition animation applies.
+ * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
* @param transit The current transition type.
* @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
* invisible.
@@ -360,24 +359,150 @@ public class AppTransitionController {
* @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
* interaction session driving task.
*/
- private void applyAnimations(ArraySet<ActivityRecord> apps, @TransitionType int transit,
+ private void applyAnimations(ArraySet<WindowContainer> wcs, @TransitionType int transit,
boolean visible, LayoutParams animLp, boolean voiceInteraction) {
- final int appsCount = apps.size();
+ final int appsCount = wcs.size();
for (int i = 0; i < appsCount; i++) {
+ final WindowContainer wc = wcs.valueAt(i);
+ wc.applyAnimation(animLp, transit, visible, voiceInteraction);
+ }
+ }
+
+ /**
+ * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
+ * animation targets to higher level in the window hierarchy if possible.
+ *
+ * @param visible {@code true} to get animation targets for opening apps, {@code false} to get
+ * animation targets for closing apps.
+ * @return {@link WindowContainer}s to be animated.
+ */
+ @VisibleForTesting
+ static ArraySet<WindowContainer> getAnimationTargets(
+ ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
+ boolean visible) {
+
+ // The candidates of animation targets, which might be able to promote to higher level.
+ final LinkedList<WindowContainer> candidates = new LinkedList<>();
+ final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps;
+ for (int i = 0; i < apps.size(); ++i) {
final ActivityRecord app = apps.valueAt(i);
- if (transit != WindowManager.TRANSIT_UNSET && app.shouldApplyAnimation(visible)) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Changing app %s visible=%b performLayout=%b",
+ if (app.shouldApplyAnimation(visible)) {
+ candidates.add(app);
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Changing app %s visible=%b performLayout=%b",
app, app.isVisible(), false);
- if (!app.mUseTransferredAnimation) {
- app.applyAnimation(animLp, transit, visible, voiceInteraction);
+ }
+ }
+
+ if (!WindowManagerService.sHierarchicalAnimations) {
+ return new ArraySet<>(candidates);
+ }
+
+ final ArraySet<ActivityRecord> otherApps = visible ? closingApps : openingApps;
+ // Ancestors of closing apps while finding animation targets for opening apps, or ancestors
+ // of opening apps while finding animation targets for closing apps.
+ final ArraySet<WindowContainer> otherAncestors = new ArraySet<>();
+ for (int i = 0; i < otherApps.size(); ++i) {
+ for (WindowContainer wc = otherApps.valueAt(i); wc != null; wc = wc.getParent()) {
+ otherAncestors.add(wc);
+ }
+ }
+
+ // The final animation targets which cannot promote to higher level anymore.
+ final ArraySet<WindowContainer> targets = new ArraySet<>();
+ final ArrayList<WindowContainer> siblings = new ArrayList<>();
+ while (!candidates.isEmpty()) {
+ final WindowContainer current = candidates.removeFirst();
+ final WindowContainer parent = current.getParent();
+ boolean canPromote = true;
+
+ if (parent == null) {
+ canPromote = false;
+ } else {
+ // In case a descendant of the parent belongs to the other group, we cannot promote
+ // the animation target from "current" to the parent.
+ //
+ // Example: Imagine we're checking if we can animate a Task instead of a set of
+ // ActivityRecords. In case an activity starts a new activity within a same Task,
+ // an ActivityRecord of an existing activity belongs to the opening apps, at the
+ // same time, the other ActivityRecord of a new activity belongs to the closing
+ // apps. In this case, we cannot promote the animation target to Task level, but
+ // need to animate each individual activity.
+ //
+ // [Task] +- [ActivityRecord1] (in opening apps)
+ // +- [ActivityRecord2] (in closing apps)
+ if (otherAncestors.contains(parent)) {
+ canPromote = false;
}
- final WindowState window = app.findMainWindow();
- final AccessibilityController accessibilityController =
- app.mWmService.mAccessibilityController;
- if (window != null && accessibilityController != null) {
- accessibilityController.onAppWindowTransitionLocked(window, transit);
+
+ // Find all siblings of the current WindowContainer in "candidates", move them into
+ // a separate list "siblings", and checks if an animation target can be promoted
+ // to its parent.
+ //
+ // We can promote an animation target to its parent if and only if all visible
+ // siblings will be animating.
+ //
+ // Example: Imagine that a Task contains two visible activity record, but only one
+ // of them is included in the opening apps and the other belongs to neither opening
+ // or closing apps. This happens when an activity launches another translucent
+ // activity in the same Task. In this case, we cannot animate Task, but have to
+ // animate each activity, otherwise an activity behind the translucent activity also
+ // animates.
+ //
+ // [Task] +- [ActivityRecord1] (visible, in opening apps)
+ // +- [ActivityRecord2] (visible, not in opening apps)
+ siblings.clear();
+ for (int j = 0; j < parent.getChildCount(); ++j) {
+ final WindowContainer sibling = parent.getChildAt(j);
+ if (sibling == current || candidates.remove(sibling)) {
+ siblings.add(sibling);
+ } else if (sibling.isVisible()) {
+ canPromote = false;
+ }
}
}
+
+ if (canPromote) {
+ candidates.add(parent);
+ } else {
+ targets.addAll(siblings);
+ }
+ }
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s",
+ apps, targets);
+ return targets;
+ }
+
+ /**
+ * Apply an app transition animation based on a set of {@link ActivityRecord}
+ *
+ * @param openingApps The list of opening apps to which an app transition animation applies.
+ * @param closingApps The list of closing apps to which an app transition animation applies.
+ * @param transit The current transition type.
+ * @param animLp Layout parameters in which an app transition animation runs.
+ * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
+ * interaction session driving task.
+ */
+ private void applyAnimations(ArraySet<ActivityRecord> openingApps,
+ ArraySet<ActivityRecord> closingApps, @TransitionType int transit,
+ LayoutParams animLp, boolean voiceInteraction) {
+ if (transit == WindowManager.TRANSIT_UNSET
+ || (openingApps.isEmpty() && closingApps.isEmpty())) {
+ return;
+ }
+
+ final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
+ openingApps, closingApps, true /* visible */);
+ final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
+ openingApps, closingApps, false /* visible */);
+ applyAnimations(openingWcs, transit, true /* visible */, animLp, voiceInteraction);
+ applyAnimations(closingWcs, transit, false /* visible */, animLp, voiceInteraction);
+
+ final AccessibilityController accessibilityController =
+ mDisplayContent.mWmService.mAccessibilityController;
+ if (accessibilityController != null) {
+ accessibilityController.onAppWindowTransitionLocked(
+ mDisplayContent.getDisplayId(), transit);
}
}
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 d415f25baab9..1311889d5605 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
@@ -29,6 +30,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.view.WindowManager;
import androidx.test.filters.FlakyTest;
@@ -116,4 +118,358 @@ public class AppTransitionControllerTest extends WindowTestsBase {
assertTrue(mAppTransitionController.isTransitWithinTask(TRANSIT_ACTIVITY_OPEN, task));
assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_TASK_OPEN, task));
}
+
+ @Test
+ public void testGetAnimationTargets_noHierarchicalAnimations() {
+ WindowManagerService.sHierarchicalAnimations = false;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, invisible)
+ // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, visible)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Don't promote when the flag is disabled.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_visibilityAlreadyUpdated() {
+ // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible)
+ // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = false;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // No animation, since visibility of the opening and closing apps are already updated
+ // outside of AppTransition framework.
+ WindowManagerService.sHierarchicalAnimations = false;
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+
+ WindowManagerService.sHierarchicalAnimations = true;
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_exitingBeforeTransition() {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(stack);
+ activity.setVisible(false);
+ activity.mIsExiting = true;
+
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity);
+
+ // Animate closing apps even if it's not visible when it is exiting before we had a chance
+ // to play the transition animation.
+ WindowManagerService.sHierarchicalAnimations = false;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity}),
+ AppTransitionController.getAnimationTargets(
+ new ArraySet<>(), closing, false /* visible */));
+
+ WindowManagerService.sHierarchicalAnimations = true;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack}),
+ AppTransitionController.getAnimationTargets(
+ new ArraySet<>(), closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_windowsAreBeingReplaced() {
+ // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible)
+ // +- [AppWindow1] (being-replaced)
+ // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
+ // +- [AppWindow2] (being-replaced)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+ TYPE_BASE_APPLICATION);
+ attrs.setTitle("AppWindow1");
+ final WindowTestUtils.TestWindowState appWindow1 = createWindowState(attrs, activity1);
+ appWindow1.mWillReplaceWindow = true;
+ activity1.addWindow(appWindow1);
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = false;
+ attrs.setTitle("AppWindow2");
+ final WindowTestUtils.TestWindowState appWindow2 = createWindowState(attrs, activity2);
+ appWindow2.mWillReplaceWindow = true;
+ activity2.addWindow(appWindow2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Animate opening apps even if it's already visible in case its windows are being replaced.
+ // Don't animate closing apps if it's already invisible even though its windows are being
+ // replaced.
+ WindowManagerService.sHierarchicalAnimations = false;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+
+ WindowManagerService.sHierarchicalAnimations = true;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_openingClosingInDifferentTask() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible)
+ // | +- [ActivityRecord2] (invisible)
+ // |
+ // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible)
+ // +- [ActivityRecord4] (invisible)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = false;
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+ final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ activity4.setVisible(false);
+ activity4.mVisibleRequested = false;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity3);
+
+ // Promote animation targets to TaskStack level. Invisible ActivityRecords don't affect
+ // promotion decision.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_openingClosingInSameTask() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] - [TaskStack] - [Task] -+- [ActivityRecord1] (opening, invisible)
+ // +- [ActivityRecord2] (closing, visible)
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Don't promote an animation target to Task level, since the same task contains both
+ // opening and closing app.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_animateOnlyTranslucentApp() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible)
+ // | +- [ActivityRecord2] (visible)
+ // |
+ // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible)
+ // +- [ActivityRecord4] (visible)
+
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ activity1.setOccludesParent(false);
+
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+ final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ activity3.setOccludesParent(false);
+ final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity3);
+
+ // Don't promote an animation target to Task level, since opening (closing) app is
+ // translucent and is displayed over other non-animating app.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity3}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_animateTranslucentAndOpaqueApps() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible)
+ // | +- [ActivityRecord2] (opening, invisible)
+ // |
+ // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible)
+ // +- [ActivityRecord4] (closing, visible)
+
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ activity1.setOccludesParent(false);
+
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = true;
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+ final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ activity3.setOccludesParent(false);
+ final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ opening.add(activity2);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity3);
+ closing.add(activity4);
+
+ // Promote animation targets to TaskStack level even though opening (closing) app is
+ // translucent as long as all visible siblings animate at the same time.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_stackContainsMultipleTasks() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] - [TaskStack] -+- [Task1] - [ActivityRecord1] (opening, invisible)
+ // +- [Task2] - [ActivityRecord2] (closing, visible)
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ final Task task2 = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Promote animation targets up to Task level, not beyond.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{task1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{task2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
}