Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 20659908 authored by Evan Rosky's avatar Evan Rosky
Browse files

Report Activity as independent change when config-at-end

Config-at-end activities are actually changing independently
of their task. This means they should be collected and also
need to be reported even if they share changes with their
parent.

The upside of this is that WMCore no-longer needs to do shell
specific surface manipulations (pip pre-transforms) on the
activity surface since it is being sent to shell. Long-term
this also implies that we can remove some pip-specific logic
in core as well as some pip-specific communication.

Bug: 202201326
Test: atest TransitionTests#testConfigAtEnd
Flag: com.android.wm.shell.enable_pip2
Change-Id: I8800ce48e2b59c2f7928c8da3708bad712bedc5e
parent d0f95ff0
Loading
Loading
Loading
Loading
+68 −10
Original line number Diff line number Diff line
@@ -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)
@@ -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
+7 −52
Original line number Diff line number Diff line
@@ -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;
        });
    }
@@ -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;
@@ -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,
@@ -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);
+10 −0
Original line number Diff line number Diff line
@@ -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());
@@ -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());