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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<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!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user