From 4b86a040b4b03334673f59855c87d57c5a8f56f6 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Mon, 10 Jan 2022 23:07:40 +0800 Subject: [PATCH] Ensure that participated activities are not omitted by ancestor ... when preparing transition targets. Previously, the first step of the calculation finds the top participant if possible, and once there is one, only the level above task will be added. Because it assumes that the parent of participant can represent it. But there are more than one participants belong to an ancestor, the descendants may have individual changes, so they should be still in the transition targets. Otherwise the animation may have nothing to do. Now all valid participants are added into the target lists, and then combine the targets from bottom to up based on depth. So it can be done in 1 pass (originally it requires 3 passes when launching a new task from home, 4 passes when starting recents). The population of ChangeInfo#mParent is moved after tryPromo so it can skip the targets which were removed, and the parent info can be updated in a single place. Bug: 213867980 Test: TransitionTests#testOpenActivityInTheSameTaskWithDisplayChange Test: adb shell setprop persist.debug.shell_transit 1; reboot Start a landscape activity from a portrait activity in the same task. The animation should not jump cut. Change-Id: I23a3326611dcb5664b204748280ae15b2377f318 --- data/etc/services.core.protolog.json | 72 ++-- .../com/android/server/wm/Transition.java | 408 +++++++++--------- .../android/server/wm/WindowContainer.java | 5 + .../android/server/wm/TransitionTests.java | 91 ++-- 4 files changed, 284 insertions(+), 292 deletions(-) diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 8f73b9a12da2f..15672332a5220 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -355,6 +355,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayRotation.java" }, + "-1728919185": { + "message": " unrelated invisible sibling %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1715268616": { "message": "Last window, removing starting window %s", "level": "VERBOSE", @@ -451,12 +457,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "-1587921395": { - "message": " Top targets: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Transition.java" - }, "-1585311008": { "message": "Bring to front target: %s from %s", "level": "DEBUG", @@ -709,12 +709,6 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, - "-1375751630": { - "message": " --- Start combine pass ---", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Transition.java" - }, "-1364754753": { "message": "Task vanished taskId=%d", "level": "VERBOSE", @@ -1177,12 +1171,6 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, - "-855366859": { - "message": " merging children in from %s: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Transition.java" - }, "-853404763": { "message": "\twallpaper=%s", "level": "DEBUG", @@ -1243,6 +1231,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-779095785": { + "message": " sibling is a participant with mode %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-775004869": { "message": "Not a match: %s", "level": "DEBUG", @@ -1555,12 +1549,6 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, - "-446752714": { - "message": " SKIP: sibling contains top target %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Transition.java" - }, "-445944810": { "message": "finish(%b): mCanceled=%b", "level": "DEBUG", @@ -1729,12 +1717,6 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, - "-302335479": { - "message": " remove from topTargets %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Transition.java" - }, "-292790591": { "message": "Attempted to set IME policy to a display that does not exist: %d", "level": "WARN", @@ -2077,6 +2059,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "112145970": { + "message": " SKIP: its sibling was rejected", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "114070759": { "message": "New wallpaper target: %s prevTarget: %s caller=%s", "level": "VERBOSE", @@ -2413,6 +2401,12 @@ "group": "WM_SHOW_SURFACE_ALLOC", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "405146734": { + "message": " Final targets: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "416924848": { "message": "InsetsSource Control %s for target %s", "level": "DEBUG", @@ -2437,12 +2431,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "430260320": { - "message": " sibling is a top target with mode %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Transition.java" - }, "431715812": { "message": "Launch on display check: allow launch any on display", "level": "DEBUG", @@ -2761,12 +2749,6 @@ "group": "WM_SHOW_SURFACE_ALLOC", "at": "com\/android\/server\/wm\/WindowStateAnimator.java" }, - "751854538": { - "message": "DisplayArea keep clear rects changed name =%s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_ORGANIZER", - "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" - }, "765395228": { "message": "onAnimationFinished(): controller=%s reorderMode=%d", "level": "DEBUG", @@ -3097,12 +3079,6 @@ "group": "WM_DEBUG_WALLPAPER", "at": "com\/android\/server\/wm\/WallpaperController.java" }, - "1186730970": { - "message": " no common mode yet, so set it", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Transition.java" - }, "1191587912": { "message": "Moved rootTask=%s behind rootTask=%s", "level": "DEBUG", diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 9b6827e3c66ac..ded58f48b207d 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -37,7 +37,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHA import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; -import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -72,6 +71,7 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseArray; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; @@ -156,7 +156,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ArraySet mParticipants = new ArraySet<>(); /** The final animation targets derived from participants after promotion. */ - private ArraySet mTargets = null; + private ArrayList mTargets; /** The main display running this transition. */ private DisplayContent mTargetDisplay; @@ -385,7 +385,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // usually only size 1 final ArraySet displays = new ArraySet<>(); for (int i = mTargets.size() - 1; i >= 0; --i) { - final WindowContainer target = mTargets.valueAt(i); + final WindowContainer target = mTargets.get(i); if (target.getParent() != null) { final SurfaceControl targetLeash = getLeashSurface(target); final SurfaceControl origParent = getOrigParentSurface(target); @@ -818,7 +818,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Search for the home task. If it is supposed to be visible, then the navbar is not at // the bottom of the screen, so we need to animate it. for (int i = 0; i < mTargets.size(); ++i) { - final Task task = mTargets.valueAt(i).asTask(); + final Task task = mTargets.get(i).asTask(); if (task == null || !task.isHomeOrRecentsRootTask()) continue; animate = task.isVisibleRequested(); break; @@ -901,23 +901,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private static boolean reportIfNotTop(WindowContainer wc) { // Organized tasks need to be reported anyways because Core won't show() their surfaces // and we can't rely on onTaskAppeared because it isn't in sync. - // Also report wallpaper so it can be handled properly during display change/rotation. // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN. - return wc.isOrganized() || isWallpaper(wc); - } - - /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */ - private static int getChildDepth(WindowContainer child, WindowContainer ancestor) { - WindowContainer parent = child; - int depth = 0; - while (parent != null) { - if (parent == ancestor) { - return depth; - } - parent = parent.getParent(); - ++depth; - } - return -1; + return wc.isOrganized(); } private static boolean isWallpaper(WindowContainer wc) { @@ -947,61 +932,48 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe * * @return {@code true} if transition in target can be promoted to its parent. */ - private static boolean canPromote(WindowContainer target, ArraySet topTargets, + private static boolean canPromote(WindowContainer target, Targets targets, ArrayMap changes) { - final WindowContainer parent = target.getParent(); - final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null; - if (parent == null || !parent.canCreateRemoteAnimationTarget() - || parentChanges == null || !parentChanges.hasChanged(parent)) { + final WindowContainer parent = target.getParent(); + final ChangeInfo parentChange = changes.get(parent); + if (!parent.canCreateRemoteAnimationTarget() + || parentChange == null || !parentChange.hasChanged(parent)) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s", - parent == null ? "no parent" : ("parent can't be target " + parent)); + "parent can't be target " + parent); return false; } if (isWallpaper(target)) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: is wallpaper"); return false; } - @TransitionInfo.TransitionMode int mode = TRANSIT_NONE; - // Go through all siblings of this target to see if any of them would prevent - // the target from promoting. - siblingLoop: + + final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target); for (int i = parent.getChildCount() - 1; i >= 0; --i) { - final WindowContainer sibling = parent.getChildAt(i); + final WindowContainer sibling = parent.getChildAt(i); + if (target == sibling) continue; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s", sibling); - // Check if any topTargets are the sibling or within it - for (int j = topTargets.size() - 1; j >= 0; --j) { - final int depth = getChildDepth(topTargets.valueAt(j), sibling); - if (depth < 0) continue; - if (depth == 0) { - final int siblingMode = changes.get(sibling).getTransitMode(sibling); + final ChangeInfo siblingChange = changes.get(sibling); + if (siblingChange == null || !targets.wasParticipated(sibling)) { + if (sibling.isVisibleRequested()) { + // Sibling is visible but not animating, so no promote. ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " sibling is a top target with mode %s", - TransitionInfo.modeToString(siblingMode)); - if (mode == TRANSIT_NONE) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " no common mode yet, so set it"); - mode = siblingMode; - } else if (mode != siblingMode) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " SKIP: common mode mismatch. was %s", - TransitionInfo.modeToString(mode)); - return false; - } - continue siblingLoop; - } else { - // Sibling subtree may not be promotable. - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " SKIP: sibling contains top target %s", - topTargets.valueAt(j)); + " SKIP: sibling is visible but not part of transition"); return false; } - } - // No other animations are playing in this sibling - if (sibling.isVisibleRequested()) { - // Sibling is visible but not animating, so no promote. ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " SKIP: sibling is visible but not part of transition"); + " unrelated invisible sibling %s", sibling); + continue; + } + + final int siblingMode = siblingChange.getTransitMode(sibling); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " sibling is a participant with mode %s", + TransitionInfo.modeToString(siblingMode)); + if (mode != siblingMode) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: common mode mismatch. was %s", + TransitionInfo.modeToString(mode)); return false; } } @@ -1011,58 +983,40 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** * Go through topTargets and try to promote (see {@link #canPromote}) one of them. * - * @param topTargets set of just the top-most targets in the hierarchy of participants. * @param targets all targets that will be sent to the player. - * @return {@code true} if something was promoted. */ - private static boolean tryPromote(ArraySet topTargets, - ArraySet targets, ArrayMap changes) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---"); - // Go through each target until we find one that can be promoted. - for (WindowContainer targ : topTargets) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ); - if (!canPromote(targ, topTargets, changes)) { + private static void tryPromote(Targets targets, ArrayMap changes) { + WindowContainer lastNonPromotableParent = null; + // Go through from the deepest target. + for (int i = targets.mArray.size() - 1; i >= 0; --i) { + final WindowContainer target = targets.mArray.valueAt(i); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target); + final WindowContainer parent = target.getParent(); + if (parent == lastNonPromotableParent) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " SKIP: its sibling was rejected"); continue; } - // No obstructions found to promotion, so promote - final WindowContainer parent = targ.getParent(); - final ChangeInfo parentInfo = changes.get(parent); - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " CAN PROMOTE: promoting to parent %s", parent); - targets.add(parent); - - // Go through all children of newly-promoted container and remove them from the - // top-targets. - for (int i = parent.getChildCount() - 1; i >= 0; --i) { - final WindowContainer child = parent.getChildAt(i); - int idx = targets.indexOf(child); - if (idx >= 0) { - final ChangeInfo childInfo = changes.get(child); - if (reportIfNotTop(child)) { - childInfo.mParent = parent; - parentInfo.addChild(child); - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " keep as target %s", child); - } else { - if (childInfo.mChildren != null) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " merging children in from %s: %s", child, - childInfo.mChildren); - parentInfo.addChildren(childInfo.mChildren); - } - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " remove from targets %s", child); - targets.removeAt(idx); - } - } - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - " remove from topTargets %s", child); - topTargets.remove(child); + if (!canPromote(target, targets, changes)) { + lastNonPromotableParent = parent; + continue; + } + if (reportIfNotTop(target)) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " keep as target %s", target); + } else { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " remove from targets %s", target); + targets.remove(i, target); + } + if (targets.mArray.indexOfValue(parent) < 0) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + " CAN PROMOTE: promoting to parent %s", parent); + // The parent has lower depth, so it will be checked in the later iteration. + i++; + targets.add(parent); } - topTargets.add(parent); - return true; } - return false; } /** @@ -1071,22 +1025,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe */ @VisibleForTesting @NonNull - static ArraySet calculateTargets(ArraySet participants, + static ArrayList calculateTargets(ArraySet participants, ArrayMap changes) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start calculating TransitionInfo based on participants: %s", participants); - final ArraySet topTargets = new ArraySet<>(); - // The final animation targets which cannot promote to higher level anymore. - final ArraySet targets = new ArraySet<>(); - - final ArrayList tmpList = new ArrayList<>(); - - // Build initial set of top-level participants by removing any participants that are no-ops - // or children of other participants or are otherwise invalid; however, keep around a list - // of participants that should always be reported even if they aren't top. - for (WindowContainer wc : participants) { - // Don't include detached windows. + // Add all valid participants to the target container. + final Targets targets = new Targets(); + for (int i = participants.size() - 1; i >= 0; --i) { + final WindowContainer wc = participants.valueAt(i); if (!wc.isAttached()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Rejecting as detached: %s", wc); @@ -1101,70 +1048,62 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe " Rejecting as no-op: %s", wc); continue; } - - // Search through ancestors to find the top-most participant (if one exists) - WindowContainer topParent = null; - tmpList.clear(); - if (reportIfNotTop(wc)) { - tmpList.add(wc); - } - // Wallpaper must be the top (regardless of how nested it is in DisplayAreas). - boolean skipIntermediateReports = isWallpaper(wc); - for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { - if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) { - // Again, we're skipping no-ops - break; - } - if (participants.contains(p)) { - topParent = p; - break; - } else if (isWallpaper(p)) { - skipIntermediateReports = true; - } else if (reportIfNotTop(p) && !skipIntermediateReports) { - tmpList.add(p); - } - } - if (topParent != null) { - // There was an ancestor participant, so don't add wc to targets unless always- - // report. Similarly, add any always-report parents along the way. - for (int i = 0; i < tmpList.size(); ++i) { - targets.add(tmpList.get(i)); - final ChangeInfo info = changes.get(tmpList.get(i)); - info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent; - } - continue; - } - // No ancestors in participant-list, so wc is a top target. targets.add(wc); - topTargets.add(wc); + targets.mValidParticipants.add(wc); } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", + targets.mArray); + // Combine the targets from bottom to top if possible. + tryPromote(targets, changes); + // Establish the relationship between the targets and their top changes. + populateParentChanges(targets, changes); - // Populate children lists - for (int i = targets.size() - 1; i >= 0; --i) { - if (changes.get(targets.valueAt(i)).mParent != null) { - changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i)); - } - } - - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s", targets); - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets); - - // Combine targets by repeatedly going through the topTargets to see if they can be - // promoted until there aren't any promotions possible. - while (tryPromote(topTargets, targets, changes)) { - // Empty on purpose - } - return targets; + final ArrayList targetList = targets.getListSortedByZ(); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList); + return targetList; } - /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */ - private static void addMembersInOrder(WindowContainer root, ArraySet members, - ArrayList out) { - for (int i = root.getChildCount() - 1; i >= 0; --i) { - final WindowContainer child = root.getChildAt(i); - addMembersInOrder(child, members, out); - if (members.contains(child)) { - out.add(child); + /** Populates parent to the change info and collects intermediate targets. */ + private static void populateParentChanges(Targets targets, + ArrayMap changes) { + final ArrayList> intermediates = new ArrayList<>(); + for (int i = targets.mValidParticipants.size() - 1; i >= 0; --i) { + WindowContainer wc = targets.mValidParticipants.get(i); + // Go up if the participant has been represented by its parent. + while (targets.mArray.indexOfValue(wc) < 0 && wc.getParent() != null) { + wc = wc.getParent(); + } + // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas). + final boolean skipIntermediateReports = isWallpaper(wc); + intermediates.clear(); + // Collect the intermediate parents between target and top changed parent. + for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) { + final ChangeInfo parentChange = changes.get(p); + if (parentChange == null || !parentChange.hasChanged(p)) break; + if (parentChange.mParent != null && !skipIntermediateReports) { + changes.get(wc).mParent = p; + // The chain above the parent was processed. + break; + } + if (targets.mValidParticipants.contains(p)) { + if (skipIntermediateReports) { + changes.get(wc).mParent = p; + } else { + intermediates.add(p); + } + // The parent reaches a participant. + break; + } else if (reportIfNotTop(p) && !skipIntermediateReports) { + intermediates.add(p); + } + } + if (intermediates.isEmpty()) continue; + // Add any always-report parents along the way. + changes.get(wc).mParent = intermediates.get(0); + for (int j = 0; j < intermediates.size() - 1; j++) { + final WindowContainer intermediate = intermediates.get(j); + changes.get(intermediate).mParent = intermediates.get(j + 1); + targets.add(intermediate); } } } @@ -1201,32 +1140,36 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** * Construct a TransitionInfo object from a set of targets and changes. Also populates the * root surface. + * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom. */ @VisibleForTesting @NonNull static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags, - ArraySet targets, ArrayMap changes) { + ArrayList sortedTargets, + ArrayMap changes) { final TransitionInfo out = new TransitionInfo(type, flags); - final ArraySet appTargets = new ArraySet<>(); - final ArraySet wallpapers = new ArraySet<>(); - for (int i = targets.size() - 1; i >= 0; --i) { - (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i)); + WindowContainer topApp = null; + for (int i = 0; i < sortedTargets.size(); i++) { + final WindowContainer wc = sortedTargets.get(i); + if (!isWallpaper(wc)) { + topApp = wc; + break; + } } - - // Find the top-most shared ancestor of app targets - if (appTargets.isEmpty()) { + if (topApp == null) { out.setRootLeash(new SurfaceControl(), 0, 0); return out; } - WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent(); + // Find the top-most shared ancestor of app targets. + WindowContainer ancestor = topApp.getParent(); // Go up ancestor parent chain until all targets are descendants. ancestorLoop: while (ancestor != null) { - for (int i = appTargets.size() - 1; i >= 0; --i) { - final WindowContainer wc = appTargets.valueAt(i); - if (!wc.isDescendantOf(ancestor)) { + for (int i = sortedTargets.size() - 1; i >= 0; --i) { + final WindowContainer wc = sortedTargets.get(i); + if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) { ancestor = ancestor.getParent(); continue ancestorLoop; } @@ -1234,11 +1177,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe break; } - // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself - // is animating: then we want to include wallpapers at the right position. - ArrayList sortedTargets = new ArrayList<>(); - addMembersInOrder(ancestor, targets, sortedTargets); - // make leash based on highest (z-order) direct child of ancestor with a participant. WindowContainer leashReference = sortedTargets.get(0); while (leashReference.getParent() != ancestor) { @@ -1252,14 +1190,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe t.close(); out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top); - // add the wallpapers at the bottom - for (int i = wallpapers.size() - 1; i >= 0; --i) { - final WindowContainer wc = wallpapers.valueAt(i); - // If the displayarea itself is animating, then the wallpaper was already added. - if (wc.isDescendantOf(ancestor)) break; - sortedTargets.add(wc); - } - // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order. final int count = sortedTargets.size(); for (int i = 0; i < count; ++i) { @@ -1403,7 +1333,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe static class ChangeInfo { // Usually "post" change state. WindowContainer mParent; - ArraySet mChildren; // State tracking boolean mExistenceChanged = false; @@ -1498,19 +1427,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } return flags; } - - void addChild(@NonNull WindowContainer wc) { - if (mChildren == null) { - mChildren = new ArraySet<>(); - } - mChildren.add(wc); - } - void addChildren(@NonNull ArraySet wcs) { - if (mChildren == null) { - mChildren = new ArraySet<>(); - } - mChildren.addAll(wcs); - } } /** @@ -1603,4 +1519,64 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return b.toString(); } } + + /** + * The container to represent the depth relation for calculating transition targets. The window + * container with larger depth is put at larger index. For the same depth, higher z-order has + * larger index. + */ + private static class Targets { + /** All targets. Its keys (depth) are sorted in ascending order naturally. */ + final SparseArray> mArray = new SparseArray<>(); + /** The initial participants which have changes. */ + final ArrayList> mValidParticipants = new ArrayList<>(); + /** The targets which were represented by their parent. */ + private ArrayList> mRemovedTargets; + private int mDepthFactor; + + void add(WindowContainer target) { + // The number of slots per depth is larger than the total number of window container, + // so the depth score (key) won't have collision. + if (mDepthFactor == 0) { + mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1; + } + int score = target.getPrefixOrderIndex(); + WindowContainer wc = target; + while (wc != null) { + final WindowContainer parent = wc.getParent(); + if (parent != null) { + score += mDepthFactor; + } + wc = parent; + } + mArray.put(score, target); + } + + void remove(int index, WindowContainer removingTarget) { + mArray.removeAt(index); + if (mRemovedTargets == null) { + mRemovedTargets = new ArrayList<>(); + } + mRemovedTargets.add(removingTarget); + } + + boolean wasParticipated(WindowContainer wc) { + return mArray.indexOfValue(wc) >= 0 + || (mRemovedTargets != null && mRemovedTargets.contains(wc)); + } + + /** Returns the target list sorted by z-order in ascending order (index 0 is top). */ + ArrayList getListSortedByZ() { + final SparseArray> arrayByZ = new SparseArray<>(mArray.size()); + for (int i = mArray.size() - 1; i >= 0; --i) { + final int zOrder = mArray.keyAt(i) % mDepthFactor; + arrayByZ.put(zOrder, mArray.valueAt(i)); + } + final ArrayList sortedTargets = new ArrayList<>(arrayByZ.size()); + for (int i = arrayByZ.size() - 1; i >= 0; --i) { + sortedTargets.add(arrayByZ.valueAt(i)); + } + return sortedTargets; + } + } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 1205dee595b90..11d19839d4016 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -661,6 +661,11 @@ class WindowContainer extends ConfigurationContainer< } } + /** Returns the total number of descendants, including self. */ + int getTreeWeight() { + return mTreeWeight; + } + /** * @return The index of this element in the hierarchy tree in prefix order. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 9bb4a4f3b8b27..fd523f0585c78 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -50,6 +50,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.view.SurfaceControl; import android.view.TransactionCommittedListener; +import android.window.IDisplayAreaOrganizer; import android.window.ITaskOrganizer; import android.window.ITransitionPlayer; import android.window.TransitionInfo; @@ -60,6 +61,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.util.ArrayList; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -106,7 +109,7 @@ public class TransitionTests extends WindowTestsBase { // Check basic both tasks participating participants.add(oldTask); participants.add(newTask); - ArraySet targets = Transition.calculateTargets(participants, changes); + ArrayList targets = Transition.calculateTargets(participants, changes); TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); assertEquals(2, info.getChanges().size()); assertEquals(transit, info.getType()); @@ -171,7 +174,7 @@ public class TransitionTests extends WindowTestsBase { participants.add(oldTask); participants.add(opening); participants.add(opening2); - ArraySet targets = Transition.calculateTargets(participants, changes); + ArrayList targets = Transition.calculateTargets(participants, changes); TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); assertEquals(2, info.getChanges().size()); assertEquals(transit, info.getType()); @@ -217,15 +220,14 @@ public class TransitionTests extends WindowTestsBase { // Check promotion to DisplayArea participants.add(showing); participants.add(showing2); - ArraySet targets = Transition.calculateTargets(participants, changes); + ArrayList targets = Transition.calculateTargets(participants, changes); TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); assertEquals(1, info.getChanges().size()); assertEquals(transit, info.getType()); assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); - ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); // Check that organized tasks get reported even if not top - showTask.mTaskOrganizer = mockOrg; + makeTaskOrganized(showTask); targets = Transition.calculateTargets(participants, changes); info = Transition.calculateTransitionInfo(transit, flags, targets, changes); assertEquals(2, info.getChanges().size()); @@ -255,7 +257,7 @@ public class TransitionTests extends WindowTestsBase { opening.mVisibleRequested = true; closing.mVisibleRequested = false; - ArraySet targets = Transition.calculateTargets( + ArrayList targets = Transition.calculateTargets( transition.mParticipants, transition.mChanges); TransitionInfo info = Transition.calculateTransitionInfo( 0, 0, targets, transition.mChanges); @@ -292,7 +294,7 @@ public class TransitionTests extends WindowTestsBase { tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0; } - ArraySet targets = Transition.calculateTargets( + ArrayList targets = Transition.calculateTargets( transition.mParticipants, transition.mChanges); TransitionInfo info = Transition.calculateTransitionInfo( 0, 0, targets, transition.mChanges); @@ -341,7 +343,7 @@ public class TransitionTests extends WindowTestsBase { tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0; } - ArraySet targets = Transition.calculateTargets( + ArrayList targets = Transition.calculateTargets( transition.mParticipants, transition.mChanges); TransitionInfo info = Transition.calculateTransitionInfo( 0, 0, targets, transition.mChanges); @@ -363,13 +365,7 @@ public class TransitionTests extends WindowTestsBase { mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); // Make DA organized so we can check that they don't get included. WindowContainer parent = wallpaperWindowToken.getParent(); - while (parent != null && parent != mDisplayContent) { - if (parent.asDisplayArea() != null) { - parent.asDisplayArea().setOrganizer( - mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */); - } - parent = parent.getParent(); - } + makeDisplayAreaOrganized(parent, mDisplayContent); final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken, "wallpaperWindow"); wallpaperWindowToken.setVisibleRequested(false); @@ -381,7 +377,7 @@ public class TransitionTests extends WindowTestsBase { mDisplayContent.getWindowConfiguration().setRotation( (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4); - ArraySet targets = Transition.calculateTargets( + ArrayList targets = Transition.calculateTargets( transition.mParticipants, transition.mChanges); TransitionInfo info = Transition.calculateTransitionInfo( 0, 0, targets, transition.mChanges); @@ -393,12 +389,38 @@ public class TransitionTests extends WindowTestsBase { info.getChanges().get(0).getParent()); } + @Test + public void testOpenActivityInTheSameTaskWithDisplayChange() { + final ActivityRecord closing = createActivityRecord(mDisplayContent); + closing.mVisibleRequested = true; + final Task task = closing.getTask(); + makeTaskOrganized(task); + final ActivityRecord opening = createActivityRecord(task); + opening.mVisibleRequested = false; + makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent); + final WindowContainer[] wcs = { closing, opening, task, mDisplayContent }; + final Transition transition = createTestTransition(TRANSIT_OPEN); + for (WindowContainer wc : wcs) { + transition.collect(wc); + } + closing.mVisibleRequested = false; + opening.mVisibleRequested = true; + final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1; + for (WindowContainer wc : wcs) { + wc.getWindowConfiguration().setRotation(newRotation); + } + + final ArrayList targets = Transition.calculateTargets( + transition.mParticipants, transition.mChanges); + // Especially the activities must be in the targets. + assertTrue(targets.containsAll(Arrays.asList(wcs))); + } + @Test public void testIndependent() { final Transition transition = createTestTransition(TRANSIT_OPEN); ArrayMap changes = transition.mChanges; ArraySet participants = transition.mParticipants; - ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); final Task openTask = createTask(mDisplayContent); final Task openInOpenTask = createTaskInRootTask(openTask, 0); @@ -410,10 +432,8 @@ public class TransitionTests extends WindowTestsBase { final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask); final ActivityRecord openInChange = createActivityRecord(openInChangeTask); // set organizer for everything so that they all get added to transition info - for (Task t : new Task[]{ - openTask, openInOpenTask, changeTask, changeInChangeTask, openInChangeTask}) { - t.mTaskOrganizer = mockOrg; - } + makeTaskOrganized(openTask, openInOpenTask, changeTask, changeInChangeTask, + openInChangeTask); // Start states. changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); @@ -440,7 +460,8 @@ public class TransitionTests extends WindowTestsBase { participants.add(openInChange); // Explicitly add changeTask (to test independence with parents) participants.add(changeTask); - ArraySet targets = Transition.calculateTargets(participants, changes); + final ArrayList targets = + Transition.calculateTargets(participants, changes); TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); // Root changes should always be considered independent assertTrue(isIndependent( @@ -581,17 +602,15 @@ public class TransitionTests extends WindowTestsBase { final TransitionController controller = new TransitionController(mAtm, snapshotController); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* appThread */); - ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); // Start out with task2 visible and set up a transition that closes task2 and opens task1 final Task task1 = createTask(mDisplayContent); - task1.mTaskOrganizer = mockOrg; final ActivityRecord activity1 = createActivityRecord(task1); activity1.mVisibleRequested = false; activity1.setVisible(false); final Task task2 = createTask(mDisplayContent); - task2.mTaskOrganizer = mockOrg; + makeTaskOrganized(task1, task2); final ActivityRecord activity2 = createActivityRecord(task1); activity2.mVisibleRequested = true; activity2.setVisible(true); @@ -647,17 +666,15 @@ public class TransitionTests extends WindowTestsBase { final TransitionController controller = new TransitionController(mAtm, snapshotController); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* appThread */); - ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); // Start out with task2 visible and set up a transition that closes task2 and opens task1 final Task task1 = createTask(mDisplayContent); - task1.mTaskOrganizer = mockOrg; final ActivityRecord activity1 = createActivityRecord(task1); activity1.mVisibleRequested = false; activity1.setVisible(false); final Task task2 = createTask(mDisplayContent); - task2.mTaskOrganizer = mockOrg; + makeTaskOrganized(task1, task2); final ActivityRecord activity2 = createActivityRecord(task2); activity2.mVisibleRequested = true; activity2.setVisible(true); @@ -706,6 +723,24 @@ public class TransitionTests extends WindowTestsBase { verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false)); } + private static void makeTaskOrganized(Task... tasks) { + final ITaskOrganizer organizer = mock(ITaskOrganizer.class); + for (Task t : tasks) { + t.mTaskOrganizer = organizer; + } + } + + private static void makeDisplayAreaOrganized(WindowContainer from, + WindowContainer end) { + final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class); + while (from != null && from != end) { + if (from.asDisplayArea() != null) { + from.asDisplayArea().mOrganizer = organizer; + } + from = from.getParent(); + } + } + /** Fill the change map with all the parents of top. Change maps are usually fully populated */ private static void fillChangeMap(ArrayMap changes, WindowContainer top) {