Loading packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +86 −5 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.view.ViewGroup import android.view.WindowManager import android.view.animation.Interpolator import android.view.animation.PathInterpolator import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.UiThread import com.android.app.animation.Interpolators Loading Loading @@ -149,6 +150,10 @@ class ActivityLaunchAnimator( override fun onLaunchAnimationProgress(linearProgress: Float) { listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } } override fun onLaunchAnimationCancelled() { listeners.forEach { it.onLaunchAnimationCancelled() } } } /** Loading Loading @@ -191,6 +196,7 @@ class ActivityLaunchAnimator( "ActivityLaunchAnimator.callback must be set before using this animator" ) val runner = createRunner(controller) val runnerDelegate = runner.delegate!! val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the Loading Loading @@ -241,12 +247,15 @@ class ActivityLaunchAnimator( // If we expect an animation, post a timeout to cancel it in case the remote animation is // never started. if (willAnimate) { runner.delegate.postTimeout() runnerDelegate.postTimeout() // Hide the keyguard using the launch animation instead of the default unlock animation. if (hideKeyguardWithAnimation) { callback.hideKeyguardWithAnimation(runner) } } else { // We need to make sure delegate references are dropped to avoid memory leaks. runner.dispose() } } Loading Loading @@ -345,6 +354,13 @@ class ActivityLaunchAnimator( */ @JvmDefault fun onLaunchAnimationEnd() {} /** * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called * before the cancellation. */ fun onLaunchAnimationCancelled() {} /** Called when an activity launch animation made progress. */ @JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {} } Loading Loading @@ -427,6 +443,39 @@ class ActivityLaunchAnimator( fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} } /** * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all * events to the passed [delegate]. */ @VisibleForTesting inner class DelegatingAnimationCompletionListener( private val delegate: Listener?, private val onAnimationComplete: () -> Unit ) : Listener { var cancelled = false override fun onLaunchAnimationStart() { delegate?.onLaunchAnimationStart() } override fun onLaunchAnimationProgress(linearProgress: Float) { delegate?.onLaunchAnimationProgress(linearProgress) } override fun onLaunchAnimationEnd() { delegate?.onLaunchAnimationEnd() if (!cancelled) { onAnimationComplete.invoke() } } override fun onLaunchAnimationCancelled() { cancelled = true delegate?.onLaunchAnimationCancelled() onAnimationComplete.invoke() } } @VisibleForTesting inner class Runner( controller: Controller, Loading @@ -437,11 +486,21 @@ class ActivityLaunchAnimator( listener: Listener? = null ) : IRemoteAnimationRunner.Stub() { private val context = controller.launchContainer.context internal val delegate: AnimationDelegate // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, // etc.) are possible. So we need to make sure we drop any references that might // transitively cause leaks when we're done with animation. @VisibleForTesting var delegate: AnimationDelegate? init { delegate = AnimationDelegate(controller, callback, listener, launchAnimator, disableWmTimeout) AnimationDelegate( controller, callback, DelegatingAnimationCompletionListener(listener, this::dispose), launchAnimator, disableWmTimeout ) } @BinderThread Loading @@ -452,14 +511,33 @@ class ActivityLaunchAnimator( nonApps: Array<out RemoteAnimationTarget>?, finishedCallback: IRemoteAnimationFinishedCallback? ) { val delegate = delegate context.mainExecutor.execute { if (delegate == null) { Log.i(TAG, "onAnimationStart called after completion") // Animation started too late and timed out already. We need to still // signal back that we're done with it. finishedCallback?.onAnimationFinished() } else { delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) } } } @BinderThread override fun onAnimationCancelled() { context.mainExecutor.execute { delegate.onAnimationCancelled() } val delegate = delegate context.mainExecutor.execute { delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion") delegate?.onAnimationCancelled() } } @AnyThread fun dispose() { // Drop references to animation controller once we're done with the animation // to avoid leaking. context.mainExecutor.execute { delegate = null } } } Loading Loading @@ -585,6 +663,7 @@ class ActivityLaunchAnimator( ) } controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() return } Loading Loading @@ -822,6 +901,7 @@ class ActivityLaunchAnimator( Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]") } controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } @UiThread Loading @@ -843,6 +923,7 @@ class ActivityLaunchAnimator( ) } controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } private fun IRemoteAnimationFinishedCallback.invoke() { Loading packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt 0 → 100644 +50 −0 Original line number Diff line number Diff line package com.android.systemui.util import java.lang.ref.SoftReference import java.lang.ref.WeakReference import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /** * Creates a Kotlin idiomatic weak reference. * * Usage: * ``` * var weakReferenceObj: Object? by weakReference(null) * weakReferenceObj = Object() * ``` */ fun <T> weakReference(obj: T? = null): ReadWriteProperty<Any?, T?> { return object : ReadWriteProperty<Any?, T?> { var weakRef = WeakReference<T?>(obj) override fun getValue(thisRef: Any?, property: KProperty<*>): T? { return weakRef.get() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { weakRef = WeakReference(value) } } } /** * Creates a Kotlin idiomatic soft reference. * * Usage: * ``` * var softReferenceObj: Object? by softReference(null) * softReferenceObj = Object() * ``` */ fun <T> softReference(obj: T? = null): ReadWriteProperty<Any?, T?> { return object : ReadWriteProperty<Any?, T?> { var softRef = SoftReference<T?>(obj) override fun getValue(thisRef: Any?, property: KProperty<*>): T? { return softRef.get() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { softRef = SoftReference(value) } } } packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +15 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } @Test Loading @@ -176,6 +179,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } @Test Loading @@ -194,6 +200,15 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { } } @Test fun disposeRunner_delegateDereferenced() { val runner = activityLaunchAnimator.createRunner(controller) assertNotNull(runner.delegate) runner.dispose() waitForIdleSync() assertNull(runner.delegate) } private fun fakeWindow(): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val taskInfo = ActivityManager.RunningTaskInfo() Loading Loading
packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +86 −5 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.view.ViewGroup import android.view.WindowManager import android.view.animation.Interpolator import android.view.animation.PathInterpolator import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.UiThread import com.android.app.animation.Interpolators Loading Loading @@ -149,6 +150,10 @@ class ActivityLaunchAnimator( override fun onLaunchAnimationProgress(linearProgress: Float) { listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } } override fun onLaunchAnimationCancelled() { listeners.forEach { it.onLaunchAnimationCancelled() } } } /** Loading Loading @@ -191,6 +196,7 @@ class ActivityLaunchAnimator( "ActivityLaunchAnimator.callback must be set before using this animator" ) val runner = createRunner(controller) val runnerDelegate = runner.delegate!! val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the Loading Loading @@ -241,12 +247,15 @@ class ActivityLaunchAnimator( // If we expect an animation, post a timeout to cancel it in case the remote animation is // never started. if (willAnimate) { runner.delegate.postTimeout() runnerDelegate.postTimeout() // Hide the keyguard using the launch animation instead of the default unlock animation. if (hideKeyguardWithAnimation) { callback.hideKeyguardWithAnimation(runner) } } else { // We need to make sure delegate references are dropped to avoid memory leaks. runner.dispose() } } Loading Loading @@ -345,6 +354,13 @@ class ActivityLaunchAnimator( */ @JvmDefault fun onLaunchAnimationEnd() {} /** * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called * before the cancellation. */ fun onLaunchAnimationCancelled() {} /** Called when an activity launch animation made progress. */ @JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {} } Loading Loading @@ -427,6 +443,39 @@ class ActivityLaunchAnimator( fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} } /** * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all * events to the passed [delegate]. */ @VisibleForTesting inner class DelegatingAnimationCompletionListener( private val delegate: Listener?, private val onAnimationComplete: () -> Unit ) : Listener { var cancelled = false override fun onLaunchAnimationStart() { delegate?.onLaunchAnimationStart() } override fun onLaunchAnimationProgress(linearProgress: Float) { delegate?.onLaunchAnimationProgress(linearProgress) } override fun onLaunchAnimationEnd() { delegate?.onLaunchAnimationEnd() if (!cancelled) { onAnimationComplete.invoke() } } override fun onLaunchAnimationCancelled() { cancelled = true delegate?.onLaunchAnimationCancelled() onAnimationComplete.invoke() } } @VisibleForTesting inner class Runner( controller: Controller, Loading @@ -437,11 +486,21 @@ class ActivityLaunchAnimator( listener: Listener? = null ) : IRemoteAnimationRunner.Stub() { private val context = controller.launchContainer.context internal val delegate: AnimationDelegate // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, // etc.) are possible. So we need to make sure we drop any references that might // transitively cause leaks when we're done with animation. @VisibleForTesting var delegate: AnimationDelegate? init { delegate = AnimationDelegate(controller, callback, listener, launchAnimator, disableWmTimeout) AnimationDelegate( controller, callback, DelegatingAnimationCompletionListener(listener, this::dispose), launchAnimator, disableWmTimeout ) } @BinderThread Loading @@ -452,14 +511,33 @@ class ActivityLaunchAnimator( nonApps: Array<out RemoteAnimationTarget>?, finishedCallback: IRemoteAnimationFinishedCallback? ) { val delegate = delegate context.mainExecutor.execute { if (delegate == null) { Log.i(TAG, "onAnimationStart called after completion") // Animation started too late and timed out already. We need to still // signal back that we're done with it. finishedCallback?.onAnimationFinished() } else { delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) } } } @BinderThread override fun onAnimationCancelled() { context.mainExecutor.execute { delegate.onAnimationCancelled() } val delegate = delegate context.mainExecutor.execute { delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion") delegate?.onAnimationCancelled() } } @AnyThread fun dispose() { // Drop references to animation controller once we're done with the animation // to avoid leaking. context.mainExecutor.execute { delegate = null } } } Loading Loading @@ -585,6 +663,7 @@ class ActivityLaunchAnimator( ) } controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() return } Loading Loading @@ -822,6 +901,7 @@ class ActivityLaunchAnimator( Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]") } controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } @UiThread Loading @@ -843,6 +923,7 @@ class ActivityLaunchAnimator( ) } controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } private fun IRemoteAnimationFinishedCallback.invoke() { Loading
packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt 0 → 100644 +50 −0 Original line number Diff line number Diff line package com.android.systemui.util import java.lang.ref.SoftReference import java.lang.ref.WeakReference import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /** * Creates a Kotlin idiomatic weak reference. * * Usage: * ``` * var weakReferenceObj: Object? by weakReference(null) * weakReferenceObj = Object() * ``` */ fun <T> weakReference(obj: T? = null): ReadWriteProperty<Any?, T?> { return object : ReadWriteProperty<Any?, T?> { var weakRef = WeakReference<T?>(obj) override fun getValue(thisRef: Any?, property: KProperty<*>): T? { return weakRef.get() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { weakRef = WeakReference(value) } } } /** * Creates a Kotlin idiomatic soft reference. * * Usage: * ``` * var softReferenceObj: Object? by softReference(null) * softReferenceObj = Object() * ``` */ fun <T> softReference(obj: T? = null): ReadWriteProperty<Any?, T?> { return object : ReadWriteProperty<Any?, T?> { var softRef = SoftReference<T?>(obj) override fun getValue(thisRef: Any?, property: KProperty<*>): T? { return softRef.get() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { softRef = SoftReference(value) } } }
packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +15 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } @Test Loading @@ -176,6 +179,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } @Test Loading @@ -194,6 +200,15 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { } } @Test fun disposeRunner_delegateDereferenced() { val runner = activityLaunchAnimator.createRunner(controller) assertNotNull(runner.delegate) runner.dispose() waitForIdleSync() assertNull(runner.delegate) } private fun fakeWindow(): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val taskInfo = ActivityManager.RunningTaskInfo() Loading