From 8d7127506073b4d3fe92df73a59987361e844e58 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Thu, 3 Feb 2022 18:29:47 -0800 Subject: [PATCH] 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 --- core/java/android/window/TransitionInfo.java | 2 +- .../animation/RemoteTransitionAdapter.kt | 401 ++++++++++++++++++ .../systemui/statusbar/phone/StatusBar.java | 9 +- 3 files changed, 410 insertions(+), 2 deletions(-) create mode 100644 packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 3fa62e017976d..5ca29e6a6cfbf 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -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; } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt new file mode 100644 index 0000000000000..7ef0901a27c98 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -0,0 +1,401 @@ +/* + * 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? + ): Array { + val out = ArrayList() + 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() + 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(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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 2f3300a53ad6e..6f5bb59f0a9ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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) { - options = ActivityOptions.makeRemoteAnimation(animationAdapter); + if (ENABLE_SHELL_TRANSITIONS) { + options = ActivityOptions.makeRemoteTransition( + RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter)); + } else { + options = ActivityOptions.makeRemoteAnimation(animationAdapter); + } } else { options = ActivityOptions.makeBasic(); }