Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 782fcf6c authored by Jernej Virag's avatar Jernej Virag Committed by Android (Google) Code Review
Browse files

Merge "Fix Notification animation controller memory leak" into main

parents 088cb229 0e46bbbd
Loading
Loading
Loading
Loading
+86 −5
Original line number Original line Diff line number Diff line
@@ -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
@@ -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() }
            }
        }
        }


    /**
    /**
@@ -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
@@ -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()
        }
        }
    }
    }


@@ -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) {}
    }
    }
@@ -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,
@@ -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
@@ -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 }
        }
        }
    }
    }


@@ -584,6 +662,7 @@ class ActivityLaunchAnimator(
                    )
                    )
                }
                }
                controller.onLaunchAnimationCancelled()
                controller.onLaunchAnimationCancelled()
                listener?.onLaunchAnimationCancelled()
                return
                return
            }
            }


@@ -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
@@ -842,6 +922,7 @@ class ActivityLaunchAnimator(
                )
                )
            }
            }
            controller.onLaunchAnimationCancelled()
            controller.onLaunchAnimationCancelled()
            listener?.onLaunchAnimationCancelled()
        }
        }


        private fun IRemoteAnimationFinishedCallback.invoke() {
        private fun IRemoteAnimationFinishedCallback.invoke() {
+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)
        }
    }
}
+15 −0
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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()