Loading core/java/android/window/TransitionFilter.java +21 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.ComponentName; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.WindowManager; Loading Loading @@ -180,6 +181,7 @@ public final class TransitionFilter implements Parcelable { public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY; public ComponentName mTopActivity; public IBinder mLaunchCookie; public Requirement() { } Loading @@ -193,6 +195,7 @@ public final class TransitionFilter implements Parcelable { mMustBeTask = in.readBoolean(); mOrder = in.readInt(); mTopActivity = in.readTypedObject(ComponentName.CREATOR); mLaunchCookie = in.readStrongBinder(); } /** Go through changes and find if at-least one change matches this filter */ Loading Loading @@ -231,6 +234,9 @@ public final class TransitionFilter implements Parcelable { if (mMustBeTask && change.getTaskInfo() == null) { continue; } if (!matchesCookie(change.getTaskInfo())) { continue; } return true; } return false; Loading @@ -247,13 +253,25 @@ public final class TransitionFilter implements Parcelable { return false; } private boolean matchesCookie(ActivityManager.RunningTaskInfo info) { if (mLaunchCookie == null) return true; if (info == null) return false; for (IBinder cookie : info.launchCookies) { if (mLaunchCookie.equals(cookie)) { return true; } } return false; } /** Check if the request matches this filter. It may generate false positives */ boolean matches(@NonNull TransitionRequestInfo request) { // Can't check modes/order since the transition hasn't been built at this point. if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true; return request.getTriggerTask() != null && request.getTriggerTask().getActivityType() == mActivityType && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */); && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */) && matchesCookie(request.getTriggerTask()); } @Override Loading @@ -267,6 +285,7 @@ public final class TransitionFilter implements Parcelable { dest.writeBoolean(mMustBeTask); dest.writeInt(mOrder); dest.writeTypedObject(mTopActivity, flags); dest.writeStrongBinder(mLaunchCookie); } @NonNull Loading Loading @@ -307,6 +326,7 @@ public final class TransitionFilter implements Parcelable { out.append(" mustBeTask=" + mMustBeTask); out.append(" order=" + containerOrderToString(mOrder)); out.append(" topActivity=").append(mTopActivity); out.append(" launchCookie=").append(mLaunchCookie); out.append("}"); return out.toString(); } Loading packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +208 −9 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.app.TaskInfo import android.graphics.Matrix import android.graphics.Rect import android.graphics.RectF import android.os.Binder import android.os.Build import android.os.Handler import android.os.Looper Loading @@ -36,7 +37,11 @@ import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_TO_BACK import android.view.animation.PathInterpolator import android.window.RemoteTransition import android.window.TransitionFilter import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.UiThread Loading @@ -44,6 +49,9 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.Flags.activityTransitionUseLargestWindow import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import com.android.wm.shell.shared.IShellTransitions import com.android.wm.shell.shared.ShellTransitions import java.util.concurrent.Executor import kotlin.math.roundToInt Loading @@ -59,6 +67,9 @@ constructor( /** The executor that runs on the main thread. */ private val mainExecutor: Executor, /** The object used to register ephemeral returns and long-lived transitions. */ private val transitionRegister: TransitionRegister? = null, /** The animator used when animating a View into an app. */ private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), Loading @@ -74,6 +85,36 @@ constructor( // TODO(b/301385865): Remove this flag. private val disableWmTimeout: Boolean = false, ) { @JvmOverloads constructor( mainExecutor: Executor, shellTransitions: ShellTransitions, transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), disableWmTimeout: Boolean = false, ) : this( mainExecutor, TransitionRegister.fromShellTransitions(shellTransitions), transitionAnimator, dialogToAppAnimator, disableWmTimeout, ) @JvmOverloads constructor( mainExecutor: Executor, iShellTransitions: IShellTransitions, transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), disableWmTimeout: Boolean = false, ) : this( mainExecutor, TransitionRegister.fromIShellTransitions(iShellTransitions), transitionAnimator, dialogToAppAnimator, disableWmTimeout, ) companion object { /** The timings when animating a View into an app. */ @JvmField Loading Loading @@ -233,6 +274,10 @@ constructor( } } if (animationAdapter != null && controller.transitionCookie != null) { registerEphemeralReturnAnimation(controller, transitionRegister) } val launchResult = intentStarter(animationAdapter) // Only animate if the app is not already on top and will be opened, unless we are on the Loading Loading @@ -302,6 +347,66 @@ constructor( } } /** * Uses [transitionRegister] to set up the return animation for the given [launchController]. * * De-registration is set up automatically once the return animation is run. * * TODO(b/339194555): automatically de-register when the launchable is detached. */ private fun registerEphemeralReturnAnimation( launchController: Controller, transitionRegister: TransitionRegister? ) { if (!returnAnimationFrameworkLibrary()) return var cleanUpRunnable: Runnable? = null val returnRunner = createRunner( object : DelegateTransitionAnimatorController(launchController) { override val isLaunching = false override fun onTransitionAnimationCancelled( newKeyguardOccludedState: Boolean? ) { super.onTransitionAnimationCancelled(newKeyguardOccludedState) cleanUp() } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { super.onTransitionAnimationEnd(isExpandingFullyAbove) cleanUp() } private fun cleanUp() { cleanUpRunnable?.run() } } ) // mTypeSet and mModes match back signals only, and not home. This is on purpose, because // we only want ephemeral return animations triggered in these scenarios. val filter = TransitionFilter().apply { mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) mRequirements = arrayOf( TransitionFilter.Requirement().apply { mLaunchCookie = launchController.transitionCookie mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) } ) } val transition = RemoteTransition( RemoteAnimationRunnerCompat.wrap(returnRunner), "${launchController.transitionCookie}_returnTransition" ) transitionRegister?.register(filter, transition) cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } } /** Add a [Listener] that can listen to transition animations. */ fun addListener(listener: Listener) { listeners.add(listener) Loading Loading @@ -386,8 +491,14 @@ constructor( * Note: The background of [view] should be a (rounded) rectangle so that it can be * properly animated. */ @JvmOverloads @JvmStatic fun fromView(view: View, cujType: Int? = null): Controller? { fun fromView( view: View, cujType: Int? = null, cookie: TransitionCookie? = null, returnCujType: Int? = null ): Controller? { // Make sure the View we launch from implements LaunchableView to avoid visibility // issues. if (view !is LaunchableView) { Loading @@ -408,7 +519,7 @@ constructor( return null } return GhostedViewTransitionAnimatorController(view, cujType) return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType) } } Loading @@ -431,6 +542,17 @@ constructor( val isBelowAnimatingWindow: Boolean get() = false /** * The cookie associated with the transition controlled by this [Controller]. * * This should be defined for all return [Controller] (when [isLaunching] is false) and for * their associated launch [Controller]s. * * For the recommended format, see [TransitionCookie]. */ val transitionCookie: TransitionCookie? get() = null /** * The intent was started. If [willAnimate] is false, nothing else will happen and the * animation will not be started. Loading Loading @@ -652,7 +774,7 @@ constructor( return } val window = findRootTaskIfPossible(apps) val window = findTargetWindowIfPossible(apps) if (window == null) { Log.i(TAG, "Aborting the animation as no window is opening") callback?.invoke() Loading @@ -676,7 +798,7 @@ constructor( startAnimation(window, navigationBar, callback) } private fun findRootTaskIfPossible( private fun findTargetWindowIfPossible( apps: Array<out RemoteAnimationTarget>? ): RemoteAnimationTarget? { if (apps == null) { Loading @@ -694,6 +816,19 @@ constructor( for (it in apps) { if (it.mode == targetMode) { if (activityTransitionUseLargestWindow()) { if (returnAnimationFrameworkLibrary()) { // If the controller contains a cookie, _only_ match if the candidate // contains the matching cookie. if ( controller.transitionCookie != null && it.taskInfo ?.launchCookies ?.contains(controller.transitionCookie) != true ) { continue } } if ( candidate == null || !it.hasAnimatingParent && candidate.hasAnimatingParent Loading Loading @@ -806,11 +941,7 @@ constructor( progress: Float, linearProgress: Float ) { // Apply the state to the window only if it is visible, i.e. when the // expanding view is *not* visible. if (!state.visible) { applyStateToWindow(window, state, linearProgress) } navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } listener?.onTransitionAnimationProgress(linearProgress) Loading Loading @@ -1048,4 +1179,72 @@ constructor( return (this.width() * this.height()) > (other.width() * other.height()) } } /** * Wraps one of the two methods we have to register remote transitions with WM Shell: * - for in-process registrations (e.g. System UI) we use [ShellTransitions] * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions] * * Important: each instance of this class must wrap exactly one of the two. */ class TransitionRegister private constructor( private val shellTransitions: ShellTransitions? = null, private val iShellTransitions: IShellTransitions? = null, ) { init { assert((shellTransitions != null).xor(iShellTransitions != null)) } companion object { /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */ fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister { return TransitionRegister(shellTransitions = shellTransitions) } /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */ fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister { return TransitionRegister(iShellTransitions = iShellTransitions) } } /** Register [remoteTransition] with WM Shell using the given [filter]. */ internal fun register( filter: TransitionFilter, remoteTransition: RemoteTransition, ) { shellTransitions?.registerRemote(filter, remoteTransition) iShellTransitions?.registerRemote(filter, remoteTransition) } /** Unregister [remoteTransition] from WM Shell. */ internal fun unregister(remoteTransition: RemoteTransition) { shellTransitions?.unregisterRemote(remoteTransition) iShellTransitions?.unregisterRemote(remoteTransition) } } /** * A cookie used to uniquely identify a task launched using an * [ActivityTransitionAnimator.Controller]. * * The [String] encapsulated by this class should be formatted in such a way to be unique across * the system, but reliably constant for the same associated launchable. * * Recommended naming scheme: * - DO use the fully qualified name of the class that owns the instance of the launchable, * along with a concise and precise description of the purpose of the launchable in question. * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that * will change if the instance is destroyed and re-created. * * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton" * * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same * launchable, and no static knowledge to adequately differentiate between them using a single * description. In this case, the recommendation is to append a unique identifier related to the * contents of the launchable. * * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256” */ data class TransitionCookie(private val cookie: String) : Binder() } packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt +31 −4 Original line number Diff line number Diff line Loading @@ -25,10 +25,30 @@ interface Expandable { * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated * (e.g. if it is currently not attached or visible). * * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * associated to the launch that will use this controller. * @param cookie The unique cookie associated with the launch that will use this controller. * This is required iff the a return animation should be included. * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * associated to the return animation that will use this controller. */ fun activityTransitionController(cujType: Int? = null): ActivityTransitionAnimator.Controller? fun activityTransitionController( launchCujType: Int? = null, cookie: ActivityTransitionAnimator.TransitionCookie? = null, returnCujType: Int? = null ): ActivityTransitionAnimator.Controller? /** * See [activityTransitionController] above. * * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that * don't use the return-related parameters. */ fun activityTransitionController( launchCujType: Int? = null ): ActivityTransitionAnimator.Controller? { return activityTransitionController(launchCujType, cookie = null, returnCujType = null) } /** * Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable] Loading @@ -48,9 +68,16 @@ interface Expandable { fun fromView(view: View): Expandable { return object : Expandable { override fun activityTransitionController( cujType: Int?, launchCujType: Int?, cookie: ActivityTransitionAnimator.TransitionCookie?, returnCujType: Int? ): ActivityTransitionAnimator.Controller? { return ActivityTransitionAnimator.Controller.fromView(view, cujType) return ActivityTransitionAnimator.Controller.fromView( view, launchCujType, cookie, returnCujType ) } override fun dialogTransitionController( Loading packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +15 −2 Original line number Diff line number Diff line Loading @@ -59,8 +59,12 @@ constructor( /** The view that will be ghosted and from which the background will be extracted. */ private val ghostedView: View, /** The [CujType] associated to this animation. */ private val cujType: Int? = null, /** The [CujType] associated to this launch animation. */ private val launchCujType: Int? = null, override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null, /** The [CujType] associated to this return animation. */ private val returnCujType: Int? = null, private var interactionJankMonitor: InteractionJankMonitor = InteractionJankMonitor.getInstance(), ) : ActivityTransitionAnimator.Controller { Loading Loading @@ -104,6 +108,15 @@ constructor( */ private val background: Drawable? /** CUJ identifier accounting for whether this controller is for a launch or a return. */ private val cujType: Int? get() = if (isLaunching) { launchCujType } else { returnCujType } init { // Make sure the View we launch from implements LaunchableView to avoid visibility issues. if (ghostedView !is LaunchableView) { Loading packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +22 −3 Original line number Diff line number Diff line Loading @@ -134,13 +134,15 @@ internal class ExpandableControllerImpl( override val expandable: Expandable = object : Expandable { override fun activityTransitionController( cujType: Int?, launchCujType: Int?, cookie: ActivityTransitionAnimator.TransitionCookie?, returnCujType: Int? ): ActivityTransitionAnimator.Controller? { if (!isComposed.value) { return null } return activityController(cujType) return activityController(launchCujType, cookie, returnCujType) } override fun dialogTransitionController( Loading Loading @@ -262,10 +264,27 @@ internal class ExpandableControllerImpl( } /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */ private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller { private fun activityController( launchCujType: Int?, cookie: ActivityTransitionAnimator.TransitionCookie?, returnCujType: Int? ): ActivityTransitionAnimator.Controller { val delegate = transitionController() return object : ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate { /** * CUJ identifier accounting for whether this controller is for a launch or a return. */ private val cujType: Int? get() = if (isLaunching) { launchCujType } else { returnCujType } override val transitionCookie = cookie override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { delegate.onTransitionAnimationStart(isExpandingFullyAbove) overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay Loading Loading
core/java/android/window/TransitionFilter.java +21 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.ComponentName; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.WindowManager; Loading Loading @@ -180,6 +181,7 @@ public final class TransitionFilter implements Parcelable { public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY; public ComponentName mTopActivity; public IBinder mLaunchCookie; public Requirement() { } Loading @@ -193,6 +195,7 @@ public final class TransitionFilter implements Parcelable { mMustBeTask = in.readBoolean(); mOrder = in.readInt(); mTopActivity = in.readTypedObject(ComponentName.CREATOR); mLaunchCookie = in.readStrongBinder(); } /** Go through changes and find if at-least one change matches this filter */ Loading Loading @@ -231,6 +234,9 @@ public final class TransitionFilter implements Parcelable { if (mMustBeTask && change.getTaskInfo() == null) { continue; } if (!matchesCookie(change.getTaskInfo())) { continue; } return true; } return false; Loading @@ -247,13 +253,25 @@ public final class TransitionFilter implements Parcelable { return false; } private boolean matchesCookie(ActivityManager.RunningTaskInfo info) { if (mLaunchCookie == null) return true; if (info == null) return false; for (IBinder cookie : info.launchCookies) { if (mLaunchCookie.equals(cookie)) { return true; } } return false; } /** Check if the request matches this filter. It may generate false positives */ boolean matches(@NonNull TransitionRequestInfo request) { // Can't check modes/order since the transition hasn't been built at this point. if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true; return request.getTriggerTask() != null && request.getTriggerTask().getActivityType() == mActivityType && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */); && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */) && matchesCookie(request.getTriggerTask()); } @Override Loading @@ -267,6 +285,7 @@ public final class TransitionFilter implements Parcelable { dest.writeBoolean(mMustBeTask); dest.writeInt(mOrder); dest.writeTypedObject(mTopActivity, flags); dest.writeStrongBinder(mLaunchCookie); } @NonNull Loading Loading @@ -307,6 +326,7 @@ public final class TransitionFilter implements Parcelable { out.append(" mustBeTask=" + mMustBeTask); out.append(" order=" + containerOrderToString(mOrder)); out.append(" topActivity=").append(mTopActivity); out.append(" launchCookie=").append(mLaunchCookie); out.append("}"); return out.toString(); } Loading
packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +208 −9 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.app.TaskInfo import android.graphics.Matrix import android.graphics.Rect import android.graphics.RectF import android.os.Binder import android.os.Build import android.os.Handler import android.os.Looper Loading @@ -36,7 +37,11 @@ import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_TO_BACK import android.view.animation.PathInterpolator import android.window.RemoteTransition import android.window.TransitionFilter import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.UiThread Loading @@ -44,6 +49,9 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.Flags.activityTransitionUseLargestWindow import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import com.android.wm.shell.shared.IShellTransitions import com.android.wm.shell.shared.ShellTransitions import java.util.concurrent.Executor import kotlin.math.roundToInt Loading @@ -59,6 +67,9 @@ constructor( /** The executor that runs on the main thread. */ private val mainExecutor: Executor, /** The object used to register ephemeral returns and long-lived transitions. */ private val transitionRegister: TransitionRegister? = null, /** The animator used when animating a View into an app. */ private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), Loading @@ -74,6 +85,36 @@ constructor( // TODO(b/301385865): Remove this flag. private val disableWmTimeout: Boolean = false, ) { @JvmOverloads constructor( mainExecutor: Executor, shellTransitions: ShellTransitions, transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), disableWmTimeout: Boolean = false, ) : this( mainExecutor, TransitionRegister.fromShellTransitions(shellTransitions), transitionAnimator, dialogToAppAnimator, disableWmTimeout, ) @JvmOverloads constructor( mainExecutor: Executor, iShellTransitions: IShellTransitions, transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), disableWmTimeout: Boolean = false, ) : this( mainExecutor, TransitionRegister.fromIShellTransitions(iShellTransitions), transitionAnimator, dialogToAppAnimator, disableWmTimeout, ) companion object { /** The timings when animating a View into an app. */ @JvmField Loading Loading @@ -233,6 +274,10 @@ constructor( } } if (animationAdapter != null && controller.transitionCookie != null) { registerEphemeralReturnAnimation(controller, transitionRegister) } val launchResult = intentStarter(animationAdapter) // Only animate if the app is not already on top and will be opened, unless we are on the Loading Loading @@ -302,6 +347,66 @@ constructor( } } /** * Uses [transitionRegister] to set up the return animation for the given [launchController]. * * De-registration is set up automatically once the return animation is run. * * TODO(b/339194555): automatically de-register when the launchable is detached. */ private fun registerEphemeralReturnAnimation( launchController: Controller, transitionRegister: TransitionRegister? ) { if (!returnAnimationFrameworkLibrary()) return var cleanUpRunnable: Runnable? = null val returnRunner = createRunner( object : DelegateTransitionAnimatorController(launchController) { override val isLaunching = false override fun onTransitionAnimationCancelled( newKeyguardOccludedState: Boolean? ) { super.onTransitionAnimationCancelled(newKeyguardOccludedState) cleanUp() } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { super.onTransitionAnimationEnd(isExpandingFullyAbove) cleanUp() } private fun cleanUp() { cleanUpRunnable?.run() } } ) // mTypeSet and mModes match back signals only, and not home. This is on purpose, because // we only want ephemeral return animations triggered in these scenarios. val filter = TransitionFilter().apply { mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) mRequirements = arrayOf( TransitionFilter.Requirement().apply { mLaunchCookie = launchController.transitionCookie mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) } ) } val transition = RemoteTransition( RemoteAnimationRunnerCompat.wrap(returnRunner), "${launchController.transitionCookie}_returnTransition" ) transitionRegister?.register(filter, transition) cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } } /** Add a [Listener] that can listen to transition animations. */ fun addListener(listener: Listener) { listeners.add(listener) Loading Loading @@ -386,8 +491,14 @@ constructor( * Note: The background of [view] should be a (rounded) rectangle so that it can be * properly animated. */ @JvmOverloads @JvmStatic fun fromView(view: View, cujType: Int? = null): Controller? { fun fromView( view: View, cujType: Int? = null, cookie: TransitionCookie? = null, returnCujType: Int? = null ): Controller? { // Make sure the View we launch from implements LaunchableView to avoid visibility // issues. if (view !is LaunchableView) { Loading @@ -408,7 +519,7 @@ constructor( return null } return GhostedViewTransitionAnimatorController(view, cujType) return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType) } } Loading @@ -431,6 +542,17 @@ constructor( val isBelowAnimatingWindow: Boolean get() = false /** * The cookie associated with the transition controlled by this [Controller]. * * This should be defined for all return [Controller] (when [isLaunching] is false) and for * their associated launch [Controller]s. * * For the recommended format, see [TransitionCookie]. */ val transitionCookie: TransitionCookie? get() = null /** * The intent was started. If [willAnimate] is false, nothing else will happen and the * animation will not be started. Loading Loading @@ -652,7 +774,7 @@ constructor( return } val window = findRootTaskIfPossible(apps) val window = findTargetWindowIfPossible(apps) if (window == null) { Log.i(TAG, "Aborting the animation as no window is opening") callback?.invoke() Loading @@ -676,7 +798,7 @@ constructor( startAnimation(window, navigationBar, callback) } private fun findRootTaskIfPossible( private fun findTargetWindowIfPossible( apps: Array<out RemoteAnimationTarget>? ): RemoteAnimationTarget? { if (apps == null) { Loading @@ -694,6 +816,19 @@ constructor( for (it in apps) { if (it.mode == targetMode) { if (activityTransitionUseLargestWindow()) { if (returnAnimationFrameworkLibrary()) { // If the controller contains a cookie, _only_ match if the candidate // contains the matching cookie. if ( controller.transitionCookie != null && it.taskInfo ?.launchCookies ?.contains(controller.transitionCookie) != true ) { continue } } if ( candidate == null || !it.hasAnimatingParent && candidate.hasAnimatingParent Loading Loading @@ -806,11 +941,7 @@ constructor( progress: Float, linearProgress: Float ) { // Apply the state to the window only if it is visible, i.e. when the // expanding view is *not* visible. if (!state.visible) { applyStateToWindow(window, state, linearProgress) } navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } listener?.onTransitionAnimationProgress(linearProgress) Loading Loading @@ -1048,4 +1179,72 @@ constructor( return (this.width() * this.height()) > (other.width() * other.height()) } } /** * Wraps one of the two methods we have to register remote transitions with WM Shell: * - for in-process registrations (e.g. System UI) we use [ShellTransitions] * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions] * * Important: each instance of this class must wrap exactly one of the two. */ class TransitionRegister private constructor( private val shellTransitions: ShellTransitions? = null, private val iShellTransitions: IShellTransitions? = null, ) { init { assert((shellTransitions != null).xor(iShellTransitions != null)) } companion object { /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */ fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister { return TransitionRegister(shellTransitions = shellTransitions) } /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */ fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister { return TransitionRegister(iShellTransitions = iShellTransitions) } } /** Register [remoteTransition] with WM Shell using the given [filter]. */ internal fun register( filter: TransitionFilter, remoteTransition: RemoteTransition, ) { shellTransitions?.registerRemote(filter, remoteTransition) iShellTransitions?.registerRemote(filter, remoteTransition) } /** Unregister [remoteTransition] from WM Shell. */ internal fun unregister(remoteTransition: RemoteTransition) { shellTransitions?.unregisterRemote(remoteTransition) iShellTransitions?.unregisterRemote(remoteTransition) } } /** * A cookie used to uniquely identify a task launched using an * [ActivityTransitionAnimator.Controller]. * * The [String] encapsulated by this class should be formatted in such a way to be unique across * the system, but reliably constant for the same associated launchable. * * Recommended naming scheme: * - DO use the fully qualified name of the class that owns the instance of the launchable, * along with a concise and precise description of the purpose of the launchable in question. * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that * will change if the instance is destroyed and re-created. * * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton" * * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same * launchable, and no static knowledge to adequately differentiate between them using a single * description. In this case, the recommendation is to append a unique identifier related to the * contents of the launchable. * * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256” */ data class TransitionCookie(private val cookie: String) : Binder() }
packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt +31 −4 Original line number Diff line number Diff line Loading @@ -25,10 +25,30 @@ interface Expandable { * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated * (e.g. if it is currently not attached or visible). * * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * associated to the launch that will use this controller. * @param cookie The unique cookie associated with the launch that will use this controller. * This is required iff the a return animation should be included. * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * associated to the return animation that will use this controller. */ fun activityTransitionController(cujType: Int? = null): ActivityTransitionAnimator.Controller? fun activityTransitionController( launchCujType: Int? = null, cookie: ActivityTransitionAnimator.TransitionCookie? = null, returnCujType: Int? = null ): ActivityTransitionAnimator.Controller? /** * See [activityTransitionController] above. * * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that * don't use the return-related parameters. */ fun activityTransitionController( launchCujType: Int? = null ): ActivityTransitionAnimator.Controller? { return activityTransitionController(launchCujType, cookie = null, returnCujType = null) } /** * Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable] Loading @@ -48,9 +68,16 @@ interface Expandable { fun fromView(view: View): Expandable { return object : Expandable { override fun activityTransitionController( cujType: Int?, launchCujType: Int?, cookie: ActivityTransitionAnimator.TransitionCookie?, returnCujType: Int? ): ActivityTransitionAnimator.Controller? { return ActivityTransitionAnimator.Controller.fromView(view, cujType) return ActivityTransitionAnimator.Controller.fromView( view, launchCujType, cookie, returnCujType ) } override fun dialogTransitionController( Loading
packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +15 −2 Original line number Diff line number Diff line Loading @@ -59,8 +59,12 @@ constructor( /** The view that will be ghosted and from which the background will be extracted. */ private val ghostedView: View, /** The [CujType] associated to this animation. */ private val cujType: Int? = null, /** The [CujType] associated to this launch animation. */ private val launchCujType: Int? = null, override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null, /** The [CujType] associated to this return animation. */ private val returnCujType: Int? = null, private var interactionJankMonitor: InteractionJankMonitor = InteractionJankMonitor.getInstance(), ) : ActivityTransitionAnimator.Controller { Loading Loading @@ -104,6 +108,15 @@ constructor( */ private val background: Drawable? /** CUJ identifier accounting for whether this controller is for a launch or a return. */ private val cujType: Int? get() = if (isLaunching) { launchCujType } else { returnCujType } init { // Make sure the View we launch from implements LaunchableView to avoid visibility issues. if (ghostedView !is LaunchableView) { Loading
packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +22 −3 Original line number Diff line number Diff line Loading @@ -134,13 +134,15 @@ internal class ExpandableControllerImpl( override val expandable: Expandable = object : Expandable { override fun activityTransitionController( cujType: Int?, launchCujType: Int?, cookie: ActivityTransitionAnimator.TransitionCookie?, returnCujType: Int? ): ActivityTransitionAnimator.Controller? { if (!isComposed.value) { return null } return activityController(cujType) return activityController(launchCujType, cookie, returnCujType) } override fun dialogTransitionController( Loading Loading @@ -262,10 +264,27 @@ internal class ExpandableControllerImpl( } /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */ private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller { private fun activityController( launchCujType: Int?, cookie: ActivityTransitionAnimator.TransitionCookie?, returnCujType: Int? ): ActivityTransitionAnimator.Controller { val delegate = transitionController() return object : ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate { /** * CUJ identifier accounting for whether this controller is for a launch or a return. */ private val cujType: Int? get() = if (isLaunching) { launchCujType } else { returnCujType } override val transitionCookie = cookie override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { delegate.onTransitionAnimationStart(isExpandingFullyAbove) overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay Loading