Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +68 −10 Original line number Diff line number Diff line Loading @@ -176,27 +176,85 @@ object PipUtils { return (if (`val` >= 0f) ceil(`val`) else floor(`val`)).toInt() } /** * Calculates the transform to apply on a UNTRANSFORMED (config-at-end) Activity surface in * order for it's hint-rect to occupy the same task-relative position/dimensions as it would * have at the end of the transition (post-configuration). * * This is intended to be used in tandem with [calcStartTransform] below applied to the parent * task. Applying both transforms simultaneously should result in the appearance of nothing * having happened yet. * * Only the task should be animated (into it's identity state) and then WMCore will reset the * activity transform in sync with its new configuration upon finish. * * Usage example: * calcEndTransform(pipActivity, pipTask, scale, pos); * t.setScale(pipActivity.getLeash(), scale.x, scale.y); * t.setPosition(pipActivity.getLeash(), pos.x, pos.y); * * @see calcStartTransform */ @JvmStatic fun calcEndTransform(pipActivity: TransitionInfo.Change, pipTask: TransitionInfo.Change, outScale: PointF, outPos: PointF) { val actStartBounds = pipActivity.startAbsBounds val actEndBounds = pipActivity.endAbsBounds val taskEndBounds = pipTask.endAbsBounds var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint if (hintRect == null) { hintRect = Rect(actStartBounds) hintRect.offsetTo(0, 0) } // FA = final activity bounds (absolute) // FT = final task bounds (absolute) // SA = start activity bounds (absolute) // H = source hint (relative to start activity bounds) // We want to transform the activity so that when the task is at FT, H overlaps with FA // This scales the activity such that the hint rect has the same dimensions // as the final activity bounds. val hintToEndScaleX = (actEndBounds.width().toFloat()) / (hintRect.width().toFloat()) val hintToEndScaleY = (actEndBounds.height().toFloat()) / (hintRect.height().toFloat()) // top-left needs to be (FA.tl - FT.tl) - H.tl * hintToEnd . H is relative to the // activity; so, for example, if shrinking H to FA (hintToEnd < 1), then the tl of the // shrunk SA is closer to H than expected, so we need to reduce how much we offset SA // to get H.tl to match. val startActPosInTaskEndX = (actEndBounds.left - taskEndBounds.left) - hintRect.left * hintToEndScaleX val startActPosInTaskEndY = (actEndBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY outScale.set(hintToEndScaleX, hintToEndScaleY) outPos.set(startActPosInTaskEndX, startActPosInTaskEndY) } /** * Calculates the transform and crop to apply on a Task surface in order for the config-at-end * activity inside it (original-size activity transformed to match it's hint rect to the final * Task bounds) to occupy the same world-space position/dimensions as it had before the * transition. * * Intended to be used in tandem with [calcEndTransform]. * * Usage example: * calcStartTransform(pipChange, scale, pos, crop); * t.setScale(pipChange.getLeash(), scale.x, scale.y); * t.setPosition(pipChange.getLeash(), pos.x, pos.y); * t.setCrop(pipChange.getLeash(), crop); * calcStartTransform(pipTask, scale, pos, crop); * t.setScale(pipTask.getLeash(), scale.x, scale.y); * t.setPosition(pipTask.getLeash(), pos.x, pos.y); * t.setCrop(pipTask.getLeash(), crop); * * @see calcEndTransform */ @JvmStatic fun calcStartTransform(pipChange: TransitionInfo.Change, outScale: PointF, fun calcStartTransform(pipTask: TransitionInfo.Change, outScale: PointF, outPos: PointF, outCrop: Rect) { val startBounds = pipChange.startAbsBounds val taskEndBounds = pipChange.endAbsBounds val startBounds = pipTask.startAbsBounds val taskEndBounds = pipTask.endAbsBounds // For now, pip activity bounds always matches task bounds. If this ever changes, we'll // need to get the activity offset. val endBounds = taskEndBounds var hintRect = pipChange.taskInfo?.pictureInPictureParams?.sourceRectHint var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint if (hintRect == null) { hintRect = Rect(startBounds) hintRect.offsetTo(0, 0) Loading Loading @@ -226,8 +284,8 @@ object PipUtils { + startBounds.left + hintRect.left) val endTaskPosForStartY = (-(endBounds.top - taskEndBounds.top) * endToHintScaleY + startBounds.top + hintRect.top) outScale[endToHintScaleX] = endToHintScaleY outPos[endTaskPosForStartX] = endTaskPosForStartY outScale.set(endToHintScaleX, endToHintScaleY) outPos.set(endTaskPosForStartX, endTaskPosForStartY) // now need to set crop to reveal the non-hint stuff. Again, hintrect is relative, so // we must apply outsets to reveal the *activity* content which is *inside* the task Loading services/core/java/com/android/server/wm/Transition.java +7 −52 Original line number Diff line number Diff line Loading @@ -547,13 +547,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (!ar.isVisible() || !ar.isVisibleRequested()) return; if (mConfigAtEndActivities == null) { mConfigAtEndActivities = new ArrayList<>(); } if (mConfigAtEndActivities.contains(ar)) { } else if (mConfigAtEndActivities.contains(ar)) { return; } mConfigAtEndActivities.add(ar); ar.pauseConfigurationDispatch(); snapshotStartState(ar); collect(ar); mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END; }); } Loading Loading @@ -1705,54 +1704,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; } void prepareConfigAtEnd(SurfaceControl.Transaction transact, ArrayList<ChangeInfo> targets) { if (mConfigAtEndActivities == null) return; for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { final ActivityRecord ar = mConfigAtEndActivities.get(i); if (!ar.isVisibleRequested()) continue; final SurfaceControl sc = ar.getSurfaceControl(); if (sc == null) continue; final Task task = ar.getTask(); if (task == null) continue; // If task isn't animating, then it means shell is animating activity directly (within // task), so don't do any setup. if (!containsChangeFor(task, targets)) continue; final ChangeInfo change = mChanges.get(ar); final Rect startBounds = change.mAbsoluteBounds; Rect hintRect = null; if (ar.getWindowingMode() == WINDOWING_MODE_PINNED && ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.getSourceRectHint() != null) { hintRect = ar.pictureInPictureArgs.getSourceRectHint(); } if (hintRect == null) { hintRect = new Rect(startBounds); hintRect.offsetTo(0, 0); } final Rect endBounds = ar.getBounds(); final Rect taskEndBounds = task.getBounds(); // FA = final activity bounds (absolute) // FT = final task bounds (absolute) // SA = start activity bounds (absolute) // H = source hint (relative to start activity bounds) // We want to transform the activity so that when the task is at FT, H overlaps with FA // This scales the activity such that the hint rect has the same dimensions // as the final activity bounds. float hintToEndScaleX = ((float) endBounds.width()) / ((float) hintRect.width()); float hintToEndScaleY = ((float) endBounds.height()) / ((float) hintRect.height()); // top-left needs to be (FA.tl - FT.tl) - H.tl * hintToEnd . H is relative to the // activity; so, for example, if shrinking H to FA (hintToEnd < 1), then the tl of the // shrunk SA is closer to H than expected, so we need to reduce how much we offset SA // to get H.tl to match. float startActPosInTaskEndX = (endBounds.left - taskEndBounds.left) - hintRect.left * hintToEndScaleX; float startActPosInTaskEndY = (endBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY; transact.setScale(sc, hintToEndScaleX, hintToEndScaleY); transact.setPosition(sc, startActPosInTaskEndX, startActPosInTaskEndY); } } static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) { for (int i = list.size() - 1; i >= 0; --i) { if (list.get(i).mContainer == wc) return true; Loading Loading @@ -1833,7 +1784,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Resolve the animating targets from the participants. mTargets = calculateTargets(mParticipants, mChanges); prepareConfigAtEnd(transaction, mTargets); // Check whether the participants were animated from back navigation. mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets, Loading Loading @@ -2669,6 +2619,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (reportIfNotTop(target)) { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " keep as target %s", target); } else if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) { // config-at-end activities do not match the end-state, so they should be treated // as independent. ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " keep as cfg-at-end target %s", target); } else { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " remove from targets %s", target); Loading services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +10 −0 Original line number Diff line number Diff line Loading @@ -2933,6 +2933,11 @@ public class TransitionTests extends WindowTestsBase { controller.requestStartTransition(transit, task, null, null); player.start(); // always include config-at-end activity since it is considered "independent" due to // changing at a different time. assertTrue(player.mLastReady.getChanges().stream() .anyMatch((change -> change.getActivityComponent() != null && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0))); assertTrue(activity.isConfigurationDispatchPaused()); player.finish(); assertFalse(activity.isConfigurationDispatchPaused()); Loading Loading @@ -2962,6 +2967,11 @@ public class TransitionTests extends WindowTestsBase { controller.requestStartTransition(transit, task, null, null); player.start(); // always include config-at-end activity since it is considered "independent" due to // changing at a different time. assertTrue(player.mLastReady.getChanges().stream() .anyMatch((change -> change.getActivityComponent() != null && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0))); assertTrue(activity.isConfigurationDispatchPaused()); player.finish(); assertFalse(activity.isConfigurationDispatchPaused()); Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +68 −10 Original line number Diff line number Diff line Loading @@ -176,27 +176,85 @@ object PipUtils { return (if (`val` >= 0f) ceil(`val`) else floor(`val`)).toInt() } /** * Calculates the transform to apply on a UNTRANSFORMED (config-at-end) Activity surface in * order for it's hint-rect to occupy the same task-relative position/dimensions as it would * have at the end of the transition (post-configuration). * * This is intended to be used in tandem with [calcStartTransform] below applied to the parent * task. Applying both transforms simultaneously should result in the appearance of nothing * having happened yet. * * Only the task should be animated (into it's identity state) and then WMCore will reset the * activity transform in sync with its new configuration upon finish. * * Usage example: * calcEndTransform(pipActivity, pipTask, scale, pos); * t.setScale(pipActivity.getLeash(), scale.x, scale.y); * t.setPosition(pipActivity.getLeash(), pos.x, pos.y); * * @see calcStartTransform */ @JvmStatic fun calcEndTransform(pipActivity: TransitionInfo.Change, pipTask: TransitionInfo.Change, outScale: PointF, outPos: PointF) { val actStartBounds = pipActivity.startAbsBounds val actEndBounds = pipActivity.endAbsBounds val taskEndBounds = pipTask.endAbsBounds var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint if (hintRect == null) { hintRect = Rect(actStartBounds) hintRect.offsetTo(0, 0) } // FA = final activity bounds (absolute) // FT = final task bounds (absolute) // SA = start activity bounds (absolute) // H = source hint (relative to start activity bounds) // We want to transform the activity so that when the task is at FT, H overlaps with FA // This scales the activity such that the hint rect has the same dimensions // as the final activity bounds. val hintToEndScaleX = (actEndBounds.width().toFloat()) / (hintRect.width().toFloat()) val hintToEndScaleY = (actEndBounds.height().toFloat()) / (hintRect.height().toFloat()) // top-left needs to be (FA.tl - FT.tl) - H.tl * hintToEnd . H is relative to the // activity; so, for example, if shrinking H to FA (hintToEnd < 1), then the tl of the // shrunk SA is closer to H than expected, so we need to reduce how much we offset SA // to get H.tl to match. val startActPosInTaskEndX = (actEndBounds.left - taskEndBounds.left) - hintRect.left * hintToEndScaleX val startActPosInTaskEndY = (actEndBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY outScale.set(hintToEndScaleX, hintToEndScaleY) outPos.set(startActPosInTaskEndX, startActPosInTaskEndY) } /** * Calculates the transform and crop to apply on a Task surface in order for the config-at-end * activity inside it (original-size activity transformed to match it's hint rect to the final * Task bounds) to occupy the same world-space position/dimensions as it had before the * transition. * * Intended to be used in tandem with [calcEndTransform]. * * Usage example: * calcStartTransform(pipChange, scale, pos, crop); * t.setScale(pipChange.getLeash(), scale.x, scale.y); * t.setPosition(pipChange.getLeash(), pos.x, pos.y); * t.setCrop(pipChange.getLeash(), crop); * calcStartTransform(pipTask, scale, pos, crop); * t.setScale(pipTask.getLeash(), scale.x, scale.y); * t.setPosition(pipTask.getLeash(), pos.x, pos.y); * t.setCrop(pipTask.getLeash(), crop); * * @see calcEndTransform */ @JvmStatic fun calcStartTransform(pipChange: TransitionInfo.Change, outScale: PointF, fun calcStartTransform(pipTask: TransitionInfo.Change, outScale: PointF, outPos: PointF, outCrop: Rect) { val startBounds = pipChange.startAbsBounds val taskEndBounds = pipChange.endAbsBounds val startBounds = pipTask.startAbsBounds val taskEndBounds = pipTask.endAbsBounds // For now, pip activity bounds always matches task bounds. If this ever changes, we'll // need to get the activity offset. val endBounds = taskEndBounds var hintRect = pipChange.taskInfo?.pictureInPictureParams?.sourceRectHint var hintRect = pipTask.taskInfo?.pictureInPictureParams?.sourceRectHint if (hintRect == null) { hintRect = Rect(startBounds) hintRect.offsetTo(0, 0) Loading Loading @@ -226,8 +284,8 @@ object PipUtils { + startBounds.left + hintRect.left) val endTaskPosForStartY = (-(endBounds.top - taskEndBounds.top) * endToHintScaleY + startBounds.top + hintRect.top) outScale[endToHintScaleX] = endToHintScaleY outPos[endTaskPosForStartX] = endTaskPosForStartY outScale.set(endToHintScaleX, endToHintScaleY) outPos.set(endTaskPosForStartX, endTaskPosForStartY) // now need to set crop to reveal the non-hint stuff. Again, hintrect is relative, so // we must apply outsets to reveal the *activity* content which is *inside* the task Loading
services/core/java/com/android/server/wm/Transition.java +7 −52 Original line number Diff line number Diff line Loading @@ -547,13 +547,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (!ar.isVisible() || !ar.isVisibleRequested()) return; if (mConfigAtEndActivities == null) { mConfigAtEndActivities = new ArrayList<>(); } if (mConfigAtEndActivities.contains(ar)) { } else if (mConfigAtEndActivities.contains(ar)) { return; } mConfigAtEndActivities.add(ar); ar.pauseConfigurationDispatch(); snapshotStartState(ar); collect(ar); mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END; }); } Loading Loading @@ -1705,54 +1704,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; } void prepareConfigAtEnd(SurfaceControl.Transaction transact, ArrayList<ChangeInfo> targets) { if (mConfigAtEndActivities == null) return; for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { final ActivityRecord ar = mConfigAtEndActivities.get(i); if (!ar.isVisibleRequested()) continue; final SurfaceControl sc = ar.getSurfaceControl(); if (sc == null) continue; final Task task = ar.getTask(); if (task == null) continue; // If task isn't animating, then it means shell is animating activity directly (within // task), so don't do any setup. if (!containsChangeFor(task, targets)) continue; final ChangeInfo change = mChanges.get(ar); final Rect startBounds = change.mAbsoluteBounds; Rect hintRect = null; if (ar.getWindowingMode() == WINDOWING_MODE_PINNED && ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.getSourceRectHint() != null) { hintRect = ar.pictureInPictureArgs.getSourceRectHint(); } if (hintRect == null) { hintRect = new Rect(startBounds); hintRect.offsetTo(0, 0); } final Rect endBounds = ar.getBounds(); final Rect taskEndBounds = task.getBounds(); // FA = final activity bounds (absolute) // FT = final task bounds (absolute) // SA = start activity bounds (absolute) // H = source hint (relative to start activity bounds) // We want to transform the activity so that when the task is at FT, H overlaps with FA // This scales the activity such that the hint rect has the same dimensions // as the final activity bounds. float hintToEndScaleX = ((float) endBounds.width()) / ((float) hintRect.width()); float hintToEndScaleY = ((float) endBounds.height()) / ((float) hintRect.height()); // top-left needs to be (FA.tl - FT.tl) - H.tl * hintToEnd . H is relative to the // activity; so, for example, if shrinking H to FA (hintToEnd < 1), then the tl of the // shrunk SA is closer to H than expected, so we need to reduce how much we offset SA // to get H.tl to match. float startActPosInTaskEndX = (endBounds.left - taskEndBounds.left) - hintRect.left * hintToEndScaleX; float startActPosInTaskEndY = (endBounds.top - taskEndBounds.top) - hintRect.top * hintToEndScaleY; transact.setScale(sc, hintToEndScaleX, hintToEndScaleY); transact.setPosition(sc, startActPosInTaskEndX, startActPosInTaskEndY); } } static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) { for (int i = list.size() - 1; i >= 0; --i) { if (list.get(i).mContainer == wc) return true; Loading Loading @@ -1833,7 +1784,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Resolve the animating targets from the participants. mTargets = calculateTargets(mParticipants, mChanges); prepareConfigAtEnd(transaction, mTargets); // Check whether the participants were animated from back navigation. mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets, Loading Loading @@ -2669,6 +2619,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (reportIfNotTop(target)) { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " keep as target %s", target); } else if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) { // config-at-end activities do not match the end-state, so they should be treated // as independent. ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " keep as cfg-at-end target %s", target); } else { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, " remove from targets %s", target); Loading
services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +10 −0 Original line number Diff line number Diff line Loading @@ -2933,6 +2933,11 @@ public class TransitionTests extends WindowTestsBase { controller.requestStartTransition(transit, task, null, null); player.start(); // always include config-at-end activity since it is considered "independent" due to // changing at a different time. assertTrue(player.mLastReady.getChanges().stream() .anyMatch((change -> change.getActivityComponent() != null && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0))); assertTrue(activity.isConfigurationDispatchPaused()); player.finish(); assertFalse(activity.isConfigurationDispatchPaused()); Loading Loading @@ -2962,6 +2967,11 @@ public class TransitionTests extends WindowTestsBase { controller.requestStartTransition(transit, task, null, null); player.start(); // always include config-at-end activity since it is considered "independent" due to // changing at a different time. assertTrue(player.mLastReady.getChanges().stream() .anyMatch((change -> change.getActivityComponent() != null && (change.getFlags() & TransitionInfo.FLAG_CONFIG_AT_END) != 0))); assertTrue(activity.isConfigurationDispatchPaused()); player.finish(); assertFalse(activity.isConfigurationDispatchPaused()); Loading