summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Issei Suzuki <issei@google.com> 2019-10-18 17:39:15 +0200
committer Issei Suzuki <issei@google.com> 2019-12-20 11:05:33 +0100
commit0ae90d0cca755260b1c4ca532b50fcbc7ba34ef0 (patch)
treee51ccf1ddb1389eac0fcb6184b66c959669ab1c2
parent41b9203793e83223ae770237706159c3e2bbf1d3 (diff)
Promote app transition target its ancestor if possible.
Previously we only animated ActivityRecord. When hierarchical animation is enabled, we find the top most entity from the window hierarchy (i.e. ActivityRecord < Task < ActivityStack) which we can animate without user visible changes, and set app transition animation on it. The hierarchical animation is disabled by default. To enable it, system property "persist.wm.hierarchical_animations" must be set. Test: atest AppTransitionControllerTest Bug: 131661052 Change-Id: I4709fd178de09d289d72cf1833b056b4e34fc92b
-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 30604768e6d0..d450db7cb47f 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1729,6 +1729,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 6bfa1aea4931..e8d6bf977f09 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 */));
+ }
}