Loading packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +86 −5 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.view.ViewGroup import android.view.WindowManager import android.view.WindowManager import android.view.animation.Interpolator import android.view.animation.Interpolator import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.BinderThread import androidx.annotation.UiThread import androidx.annotation.UiThread import com.android.app.animation.Interpolators import com.android.app.animation.Interpolators Loading Loading @@ -149,6 +150,10 @@ class ActivityLaunchAnimator( override fun onLaunchAnimationProgress(linearProgress: Float) { override fun onLaunchAnimationProgress(linearProgress: Float) { listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } 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" "ActivityLaunchAnimator.callback must be set before using this animator" ) ) val runner = createRunner(controller) val runner = createRunner(controller) val runnerDelegate = runner.delegate!! val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the // 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 // If we expect an animation, post a timeout to cancel it in case the remote animation is // never started. // never started. if (willAnimate) { if (willAnimate) { runner.delegate.postTimeout() runnerDelegate.postTimeout() // Hide the keyguard using the launch animation instead of the default unlock animation. // Hide the keyguard using the launch animation instead of the default unlock animation. if (hideKeyguardWithAnimation) { if (hideKeyguardWithAnimation) { callback.hideKeyguardWithAnimation(runner) callback.hideKeyguardWithAnimation(runner) } } } else { // We need to make sure delegate references are dropped to avoid memory leaks. runner.dispose() } } } } Loading Loading @@ -344,6 +353,13 @@ class ActivityLaunchAnimator( */ */ fun onLaunchAnimationEnd() {} 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. */ /** Called when an activity launch animation made progress. */ fun onLaunchAnimationProgress(linearProgress: Float) {} fun onLaunchAnimationProgress(linearProgress: Float) {} } } Loading Loading @@ -426,6 +442,39 @@ class ActivityLaunchAnimator( fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} 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 @VisibleForTesting inner class Runner( inner class Runner( controller: Controller, controller: Controller, Loading @@ -436,11 +485,21 @@ class ActivityLaunchAnimator( listener: Listener? = null listener: Listener? = null ) : IRemoteAnimationRunner.Stub() { ) : IRemoteAnimationRunner.Stub() { private val context = controller.launchContainer.context 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 { init { delegate = delegate = AnimationDelegate(controller, callback, listener, launchAnimator, disableWmTimeout) AnimationDelegate( controller, callback, DelegatingAnimationCompletionListener(listener, this::dispose), launchAnimator, disableWmTimeout ) } } @BinderThread @BinderThread Loading @@ -451,14 +510,33 @@ class ActivityLaunchAnimator( nonApps: Array<out RemoteAnimationTarget>?, nonApps: Array<out RemoteAnimationTarget>?, finishedCallback: IRemoteAnimationFinishedCallback? finishedCallback: IRemoteAnimationFinishedCallback? ) { ) { val delegate = delegate context.mainExecutor.execute { 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) delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) } } } } } @BinderThread @BinderThread override fun onAnimationCancelled() { 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 @@ -584,6 +662,7 @@ class ActivityLaunchAnimator( ) ) } } controller.onLaunchAnimationCancelled() controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() return return } } Loading Loading @@ -821,6 +900,7 @@ class ActivityLaunchAnimator( Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]") Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]") } } controller.onLaunchAnimationCancelled() controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } } @UiThread @UiThread Loading @@ -842,6 +922,7 @@ class ActivityLaunchAnimator( ) ) } } controller.onLaunchAnimationCancelled() controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } } private fun IRemoteAnimationFinishedCallback.invoke() { private fun IRemoteAnimationFinishedCallback.invoke() { Loading packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt 0 → 100644 +50 −0 Original line number Original line 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 Original line Diff line number Diff line Loading @@ -166,6 +166,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } } @Test @Test Loading @@ -176,6 +179,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } } @Test @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 { private fun fakeWindow(): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val taskInfo = ActivityManager.RunningTaskInfo() val taskInfo = ActivityManager.RunningTaskInfo() Loading Loading
packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +86 −5 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.view.ViewGroup import android.view.WindowManager import android.view.WindowManager import android.view.animation.Interpolator import android.view.animation.Interpolator import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.BinderThread import androidx.annotation.UiThread import androidx.annotation.UiThread import com.android.app.animation.Interpolators import com.android.app.animation.Interpolators Loading Loading @@ -149,6 +150,10 @@ class ActivityLaunchAnimator( override fun onLaunchAnimationProgress(linearProgress: Float) { override fun onLaunchAnimationProgress(linearProgress: Float) { listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } 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" "ActivityLaunchAnimator.callback must be set before using this animator" ) ) val runner = createRunner(controller) val runner = createRunner(controller) val runnerDelegate = runner.delegate!! val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the // 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 // If we expect an animation, post a timeout to cancel it in case the remote animation is // never started. // never started. if (willAnimate) { if (willAnimate) { runner.delegate.postTimeout() runnerDelegate.postTimeout() // Hide the keyguard using the launch animation instead of the default unlock animation. // Hide the keyguard using the launch animation instead of the default unlock animation. if (hideKeyguardWithAnimation) { if (hideKeyguardWithAnimation) { callback.hideKeyguardWithAnimation(runner) callback.hideKeyguardWithAnimation(runner) } } } else { // We need to make sure delegate references are dropped to avoid memory leaks. runner.dispose() } } } } Loading Loading @@ -344,6 +353,13 @@ class ActivityLaunchAnimator( */ */ fun onLaunchAnimationEnd() {} 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. */ /** Called when an activity launch animation made progress. */ fun onLaunchAnimationProgress(linearProgress: Float) {} fun onLaunchAnimationProgress(linearProgress: Float) {} } } Loading Loading @@ -426,6 +442,39 @@ class ActivityLaunchAnimator( fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} 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 @VisibleForTesting inner class Runner( inner class Runner( controller: Controller, controller: Controller, Loading @@ -436,11 +485,21 @@ class ActivityLaunchAnimator( listener: Listener? = null listener: Listener? = null ) : IRemoteAnimationRunner.Stub() { ) : IRemoteAnimationRunner.Stub() { private val context = controller.launchContainer.context 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 { init { delegate = delegate = AnimationDelegate(controller, callback, listener, launchAnimator, disableWmTimeout) AnimationDelegate( controller, callback, DelegatingAnimationCompletionListener(listener, this::dispose), launchAnimator, disableWmTimeout ) } } @BinderThread @BinderThread Loading @@ -451,14 +510,33 @@ class ActivityLaunchAnimator( nonApps: Array<out RemoteAnimationTarget>?, nonApps: Array<out RemoteAnimationTarget>?, finishedCallback: IRemoteAnimationFinishedCallback? finishedCallback: IRemoteAnimationFinishedCallback? ) { ) { val delegate = delegate context.mainExecutor.execute { 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) delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) } } } } } @BinderThread @BinderThread override fun onAnimationCancelled() { 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 @@ -584,6 +662,7 @@ class ActivityLaunchAnimator( ) ) } } controller.onLaunchAnimationCancelled() controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() return return } } Loading Loading @@ -821,6 +900,7 @@ class ActivityLaunchAnimator( Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]") Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]") } } controller.onLaunchAnimationCancelled() controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } } @UiThread @UiThread Loading @@ -842,6 +922,7 @@ class ActivityLaunchAnimator( ) ) } } controller.onLaunchAnimationCancelled() controller.onLaunchAnimationCancelled() listener?.onLaunchAnimationCancelled() } } private fun IRemoteAnimationFinishedCallback.invoke() { private fun IRemoteAnimationFinishedCallback.invoke() { Loading
packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt 0 → 100644 +50 −0 Original line number Original line 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 Original line Diff line number Diff line Loading @@ -166,6 +166,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } } @Test @Test Loading @@ -176,6 +179,9 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { waitForIdleSync() waitForIdleSync() verify(controller).onLaunchAnimationCancelled() verify(controller).onLaunchAnimationCancelled() verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(controller, never()).onLaunchAnimationStart(anyBoolean()) verify(listener).onLaunchAnimationCancelled() verify(listener, never()).onLaunchAnimationStart() assertNull(runner.delegate) } } @Test @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 { private fun fakeWindow(): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val taskInfo = ActivityManager.RunningTaskInfo() val taskInfo = ActivityManager.RunningTaskInfo() Loading