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

Commit 8d712750 authored by Evan Rosky's avatar Evan Rosky
Browse files

Adapt shell transitions into legacy remote animations in systemui

systemui also defines several remote animations. Given the number
and complexity of animation entry-points in sysui, insert a
cheeky hack to swap remotetransitions in for remoteanimations
when shell transitions are enabled.

Bug: 217810915
Test: Open quick-settings, poke Settings icon. Observe animation
      and no paus.
Change-Id: I462034210f28051a03812eb6ae650fa1f0446ef5
parent ca4633dd
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -512,7 +512,7 @@ public final class TransitionInfo implements Parcelable {
        }

        /** @return the task info or null if this isn't a task */
        @NonNull
        @Nullable
        public ActivityManager.RunningTaskInfo getTaskInfo() {
            return mTaskInfo;
        }
+401 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.animation

import android.annotation.SuppressLint
import android.app.WindowConfiguration
import android.graphics.Point
import android.graphics.Rect
import android.os.IBinder
import android.os.RemoteException
import android.util.ArrayMap
import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransition
import android.window.TransitionInfo

class RemoteTransitionAdapter {
    companion object {
        /**
         * Almost a copy of Transitions#setupStartState.
         * TODO: remove when there is proper cross-process transaction sync.
         */
        @SuppressLint("NewApi")
        private fun setupLeash(
            leash: SurfaceControl,
            change: TransitionInfo.Change,
            layer: Int,
            info: TransitionInfo,
            t: SurfaceControl.Transaction
        ) {
            val isOpening = info.type == WindowManager.TRANSIT_OPEN ||
                    info.type == WindowManager.TRANSIT_TO_FRONT
            // Put animating stuff above this line and put static stuff below it.
            val zSplitLine = info.changes.size
            // changes should be ordered top-to-bottom in z
            val mode = change.mode

            // Launcher animates leaf tasks directly, so always reparent all task leashes to root.
            t.reparent(leash, info.rootLeash)
            t.setPosition(leash, (change.startAbsBounds.left - info.rootOffset.x).toFloat(), (
                    change.startAbsBounds.top - info.rootOffset.y).toFloat())
            t.show(leash)
            // Put all the OPEN/SHOW on top
            if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) {
                if (isOpening) {
                    t.setLayer(leash, zSplitLine + info.changes.size - layer)
                    if (change.flags
                            and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0) {
                        // if transferred, it should be left visible.
                        t.setAlpha(leash, 0f)
                    }
                } else {
                    // put on bottom and leave it visible
                    t.setLayer(leash, zSplitLine - layer)
                }
            } else if (mode == WindowManager.TRANSIT_CLOSE ||
                    mode == WindowManager.TRANSIT_TO_BACK) {
                if (isOpening) {
                    // put on bottom and leave visible
                    t.setLayer(leash, zSplitLine - layer)
                } else {
                    // put on top
                    t.setLayer(leash, zSplitLine + info.changes.size - layer)
                }
            } else { // CHANGE
                t.setLayer(leash, zSplitLine + info.changes.size - layer)
            }
        }

        @SuppressLint("NewApi")
        private fun createLeash(
            info: TransitionInfo,
            change: TransitionInfo.Change,
            order: Int,
            t: SurfaceControl.Transaction
        ): SurfaceControl {
            // TODO: once we can properly sync transactions across process, then get rid of this.
            if (change.parent != null && change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
                // Special case for wallpaper atm. Normally these are left alone; but, a quirk of
                // making leashes means we have to handle them specially.
                return change.leash
            }
            val leashSurface = SurfaceControl.Builder()
                    .setName(change.leash.toString() + "_transition-leash")
                    .setContainerLayer().setParent(if (change.parent == null)
                            info.rootLeash else info.getChange(change.parent!!)!!.leash).build()
            // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
            setupLeash(leashSurface, change, info.changes.size - order, info, t)
            t.reparent(change.leash, leashSurface)
            t.setAlpha(change.leash, 1.0f)
            t.show(change.leash)
            t.setPosition(change.leash, 0f, 0f)
            t.setLayer(change.leash, 0)
            return leashSurface
        }

        private fun newModeToLegacyMode(newMode: Int): Int {
            return when (newMode) {
                WindowManager.TRANSIT_OPEN, WindowManager.TRANSIT_TO_FRONT
                        -> RemoteAnimationTarget.MODE_OPENING
                WindowManager.TRANSIT_CLOSE, WindowManager.TRANSIT_TO_BACK
                        -> RemoteAnimationTarget.MODE_CLOSING
                else -> RemoteAnimationTarget.MODE_CHANGING
            }
        }

        private fun rectOffsetTo(rect: Rect, offset: Point): Rect {
            val out = Rect(rect)
            out.offsetTo(offset.x, offset.y)
            return out
        }

        fun createTarget(
            change: TransitionInfo.Change,
            order: Int,
            info: TransitionInfo,
            t: SurfaceControl.Transaction
        ): RemoteAnimationTarget {
            return RemoteAnimationTarget(
                    /* taskId */ if (change.taskInfo != null) change.taskInfo!!.taskId else -1,
                    /* mode */ newModeToLegacyMode(change.mode),
                    /* leash */ createLeash(info, change, order, t),
                    /* isTranslucent */ (change.flags and TransitionInfo.FLAG_TRANSLUCENT != 0 ||
                    change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0),
                    /* clipRect */ null,
                    /* contentInsets */ Rect(0, 0, 0, 0),
                    /* prefixOrderIndex */ order,
                    /* position */ null,
                    /* localBounds */ rectOffsetTo(change.endAbsBounds, change.endRelOffset),
                    /* screenSpaceBounds */ Rect(change.endAbsBounds),
                    /* windowConfig */ if (change.taskInfo != null)
                            change.taskInfo!!.configuration.windowConfiguration else
                                    WindowConfiguration(),
                    /* isNotInRecents */ if (change.taskInfo != null)
                            !change.taskInfo!!.isRunning else true,
                    /* startLeash */ null,
                    /* startBounds */ Rect(change.startAbsBounds),
                    /* taskInfo */ change.taskInfo,
                    /* allowEnterPip */ change.allowEnterPip,
                    /* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE)
        }

        /**
         * Represents a TransitionInfo object as an array of old-style targets
         *
         * @param wallpapers If true, this will return wallpaper targets; otherwise it returns
         * non-wallpaper targets.
         * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should
         * be populated by this function. If null, it is ignored.
         */
        fun wrapTargets(
            info: TransitionInfo,
            wallpapers: Boolean,
            t: SurfaceControl.Transaction,
            leashMap: ArrayMap<SurfaceControl, SurfaceControl>?
        ): Array<RemoteAnimationTarget> {
            val out = ArrayList<RemoteAnimationTarget>()
            for (i in info.changes.indices) {
                val change = info.changes[i]
                val changeIsWallpaper = change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0
                if (wallpapers != changeIsWallpaper) continue
                out.add(createTarget(change, info.changes.size - i, info, t))
                if (leashMap != null) {
                    leashMap[change.leash] = out[out.size - 1].leash
                }
            }
            return out.toTypedArray()
        }

        @JvmStatic
        fun adaptRemoteRunner(
            runner: IRemoteAnimationRunner
        ): IRemoteTransition.Stub {
            return object : IRemoteTransition.Stub() {
                override fun startAnimation(
                    token: IBinder,
                    info: TransitionInfo,
                    t: SurfaceControl.Transaction,
                    finishCallback: IRemoteTransitionFinishedCallback
                ) {
                    val leashMap = ArrayMap<SurfaceControl, SurfaceControl>()
                    val appsCompat = wrapTargets(info, false /* wallpapers */, t, leashMap)
                    val wallpapersCompat = wrapTargets(info, true /* wallpapers */, t, leashMap)
                    // TODO(bc-unlock): Build wrapped object for non-apps target.
                    val nonAppsCompat = arrayOfNulls<RemoteAnimationTarget>(0)

                    // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
                    var isReturnToHome = false
                    var launcherTask: TransitionInfo.Change? = null
                    var wallpaper: TransitionInfo.Change? = null
                    var launcherLayer = 0
                    var rotateDelta = 0
                    var displayW = 0f
                    var displayH = 0f
                    for (i in info.changes.indices.reversed()) {
                        val change = info.changes[i]
                        if (change.taskInfo != null &&
                                change.taskInfo!!.activityType
                                        == WindowConfiguration.ACTIVITY_TYPE_HOME) {
                            isReturnToHome = (change.mode == WindowManager.TRANSIT_OPEN ||
                                    change.mode == WindowManager.TRANSIT_TO_FRONT)
                            launcherTask = change
                            launcherLayer = info.changes.size - i
                        } else if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
                            wallpaper = change
                        }
                        if (change.parent == null && change.endRotation >= 0 &&
                                change.endRotation != change.startRotation) {
                            rotateDelta = change.endRotation - change.startRotation
                            displayW = change.endAbsBounds.width().toFloat()
                            displayH = change.endAbsBounds.height().toFloat()
                        }
                    }

                    // Prepare for rotation if there is one
                    val counterLauncher = CounterRotator()
                    val counterWallpaper = CounterRotator()
                    if (launcherTask != null && rotateDelta != 0 && launcherTask.parent != null) {
                        counterLauncher.setup(t, info.getChange(launcherTask.parent!!)!!.leash,
                                rotateDelta, displayW, displayH)
                        if (counterLauncher.surface != null) {
                            t.setLayer(counterLauncher.surface!!, launcherLayer)
                        }
                    }
                    if (isReturnToHome) {
                        if (counterLauncher.surface != null) {
                            t.setLayer(counterLauncher.surface!!, info.changes.size * 3)
                        }
                        // Need to "boost" the closing things since that's what launcher expects.
                        for (i in info.changes.indices.reversed()) {
                            val change = info.changes[i]
                            val leash = leashMap[change.leash]
                            val mode = info.changes[i].mode
                            // Only deal with independent layers
                            if (!TransitionInfo.isIndependent(change, info)) continue
                            if (mode == WindowManager.TRANSIT_CLOSE ||
                                    mode == WindowManager.TRANSIT_TO_BACK) {
                                t.setLayer(leash!!, info.changes.size * 3 - i)
                                counterLauncher.addChild(t, leash)
                            }
                        }
                        // Make wallpaper visible immediately since sysui apparently won't do this.
                        for (i in wallpapersCompat.indices.reversed()) {
                            t.show(wallpapersCompat[i].leash)
                            t.setAlpha(wallpapersCompat[i].leash, 1f)
                        }
                    } else {
                        if (launcherTask != null) {
                            counterLauncher.addChild(t, leashMap[launcherTask.leash])
                        }
                        if (wallpaper != null && rotateDelta != 0 && wallpaper.parent != null) {
                            counterWallpaper.setup(t, info.getChange(wallpaper.parent!!)!!.leash,
                                    rotateDelta, displayW, displayH)
                            if (counterWallpaper.surface != null) {
                                t.setLayer(counterWallpaper.surface!!, -1)
                                counterWallpaper.addChild(t, leashMap[wallpaper.leash])
                            }
                        }
                    }
                    t.apply()
                    val animationFinishedCallback = object : IRemoteAnimationFinishedCallback {
                        override fun onAnimationFinished() {
                            val finishTransaction = SurfaceControl.Transaction()
                            counterLauncher.cleanUp(finishTransaction)
                            counterWallpaper.cleanUp(finishTransaction)
                            // Release surface references now. This is apparently to free GPU memory
                            // while doing quick operations (eg. during CTS).
                            for (i in info.changes.indices.reversed()) {
                                info.changes[i].leash.release()
                            }
                            for (i in leashMap.size - 1 downTo 0) {
                                leashMap.valueAt(i).release()
                            }
                            try {
                                finishCallback.onTransitionFinished(null /* wct */,
                                        finishTransaction)
                            } catch (e: RemoteException) {
                                Log.e("ActivityOptionsCompat", "Failed to call app controlled" +
                                        " animation finished callback", e)
                            }
                        }

                        override fun asBinder(): IBinder? {
                            return null
                        }
                    }
                    // TODO(bc-unlcok): Pass correct transit type.
                    runner.onAnimationStart(
                            WindowManager.TRANSIT_OLD_NONE,
                            appsCompat, wallpapersCompat, nonAppsCompat,
                            animationFinishedCallback)
                }

                override fun mergeAnimation(
                    token: IBinder,
                    info: TransitionInfo,
                    t: SurfaceControl.Transaction,
                    mergeTarget: IBinder,
                    finishCallback: IRemoteTransitionFinishedCallback
                ) {
                    // TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
                    //       ignore any incoming merges.
                }
            }
        }

        @JvmStatic
        fun adaptRemoteAnimation(
            adapter: RemoteAnimationAdapter
        ): RemoteTransition {
            return RemoteTransition(adaptRemoteRunner(adapter.runner), adapter.callingApplication)
        }
    }

    /**
     * Utility class that takes care of counter-rotating surfaces during a transition animation.
     */
    class CounterRotator {
        /** Gets the surface with the counter-rotation.  */
        var surface: SurfaceControl? = null
            private set

        /**
         * Sets up this rotator.
         *
         * @param rotateDelta is the forward rotation change (the rotation the display is making).
         * @param displayW (and H) Is the size of the rotating display.
         */
        fun setup(
            t: SurfaceControl.Transaction,
            parent: SurfaceControl,
            rotateDelta: Int,
            displayW: Float,
            displayH: Float
        ) {
            var rotateDelta = rotateDelta
            if (rotateDelta == 0) return
            // We want to counter-rotate, so subtract from 4
            rotateDelta = 4 - (rotateDelta + 4) % 4
            surface = SurfaceControl.Builder()
                    .setName("Transition Unrotate")
                    .setContainerLayer()
                    .setParent(parent)
                    .build()
            // column-major
            when (rotateDelta) {
                1 -> {
                    t.setMatrix(surface, 0f, 1f, -1f, 0f)
                    t.setPosition(surface!!, displayW, 0f)
                }
                2 -> {
                    t.setMatrix(surface, -1f, 0f, 0f, -1f)
                    t.setPosition(surface!!, displayW, displayH)
                }
                3 -> {
                    t.setMatrix(surface, 0f, -1f, 1f, 0f)
                    t.setPosition(surface!!, 0f, displayH)
                }
            }
            t.show(surface)
        }

        /**
         * Adds a surface that needs to be counter-rotate.
         */
        fun addChild(t: SurfaceControl.Transaction, child: SurfaceControl?) {
            if (surface == null) return
            t.reparent(child!!, surface)
        }

        /**
         * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the
         * counter rotation surface.
         */
        fun cleanUp(finishTransaction: SurfaceControl.Transaction) {
            if (surface == null) return
            finishTransaction.remove(surface!!)
        }
    }
}
 No newline at end of file
+8 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;

import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -139,6 +140,7 @@ import com.android.systemui.R;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DelegateLaunchAnimatorController;
import com.android.systemui.animation.RemoteTransitionAdapter;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -4098,7 +4100,12 @@ public class StatusBar extends CoreStartable implements
            @Nullable RemoteAnimationAdapter animationAdapter) {
        ActivityOptions options;
        if (animationAdapter != null) {
            if (ENABLE_SHELL_TRANSITIONS) {
                options = ActivityOptions.makeRemoteTransition(
                        RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter));
            } else {
                options = ActivityOptions.makeRemoteAnimation(animationAdapter);
            }
        } else {
            options = ActivityOptions.makeBasic();
        }