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

Commit 5531f627 authored by William Xiao's avatar William Xiao
Browse files

Remove unnecessary animation cancellations when exiting low light

This CL removes the unnecessary cancellation in the transition
coordinator and also scales the dream overlay exit animation from
lasting >1s so that a self-cancellation isn't needed after the view is
no longer visible.

Also greatly simplifies the method we use to truncate the long
animations.

Test: atest LowLightDreamManagerTest LowLightTransitionCoordinatorTest TruncatedInterpolatorTest
Test: manually verified that enabling low light doesn't crash if
tapping during the animation
Bug: 285666217
Fixed: 285666217

Change-Id: Ib0dfe4fe4d030888607e1f03d57f1a084a5fba10
parent 17bb645a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.ComponentName
import android.util.Log
import com.android.dream.lowlight.dagger.LowLightDreamModule
import com.android.dream.lowlight.dagger.qualifiers.Application
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
@@ -103,6 +104,11 @@ class LowLightDreamManager @Inject constructor(
                )
            } catch (ex: TimeoutCancellationException) {
                Log.e(TAG, "timed out while waiting for low light animation", ex)
            } catch (ex: CancellationException) {
                Log.w(TAG, "low light transition animation cancelled")
                // Catch the cancellation so that we still set the system dream component if the
                // animation is cancelled, such as by a user tapping to wake as the transition to
                // low light happens.
            }
            dreamManager.setSystemDreamComponent(
                if (shouldEnterLowLight) lowLightDreamComponent else null
+0 −10
Original line number Diff line number Diff line
@@ -110,15 +110,5 @@ class LowLightTransitionCoordinator @Inject constructor() {
                }
            }
            animator.addListener(listener)
            continuation.invokeOnCancellation {
                try {
                    animator.removeListener(listener)
                    animator.cancel()
                } catch (exception: IndexOutOfBoundsException) {
                    // TODO(b/285666217): remove this try/catch once a proper fix is implemented.
                    // Cancelling the animator can cause an exception since we may be removing a
                    // listener during the cancellation. See b/285666217 for more details.
                }
            }
        }
}
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.dream.lowlight.util

import android.view.animation.Interpolator

/**
 * Interpolator wrapper that shortens another interpolator from its original duration to a portion
 * of that duration.
 *
 * For example, an `originalDuration` of 1000 and a `newDuration` of 200 results in an animation
 * that when played for 200ms is the exact same as the first 200ms of a 1000ms animation if using
 * the original interpolator.
 *
 * This is useful for the transition between the user dream and the low light clock as some
 * animations are defined in the spec to be longer than the total duration of the animation. For
 * example, the low light clock exit translation animation is defined to last >1s while the actual
 * fade out of the low light clock is only 250ms, meaning the clock isn't visible anymore after
 * 250ms.
 *
 * Since the dream framework currently only allows one dream to be visible and running, we use this
 * interpolator to play just the first 250ms of the translation animation. Simply reducing the
 * duration of the animation would result in the text exiting much faster than intended, so a custom
 * interpolator is needed.
 */
class TruncatedInterpolator(
    private val baseInterpolator: Interpolator,
    originalDuration: Float,
    newDuration: Float
) : Interpolator {
    private val scaleFactor: Float

    init {
        scaleFactor = newDuration / originalDuration
    }

    override fun getInterpolation(input: Float): Float {
        return baseInterpolator.getInterpolation(input * scaleFactor)
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ android_test {
        "androidx.test.runner",
        "androidx.test.rules",
        "androidx.test.ext.junit",
        "animationlib",
        "frameworks-base-testutils",
        "junit",
        "kotlinx_coroutines_test",
+15 −0
Original line number Diff line number Diff line
@@ -152,6 +152,21 @@ class LowLightDreamManagerTest {
        verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
    }

    @Test
    fun setAmbientLightMode_animationCancelled_SetsSystemDream() = testScope.runTest {
        mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
        runCurrent()
        cancelEnterAnimations()
        runCurrent()
        // Animation never finishes, but we should still set the system dream
        verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
    }

    private fun cancelEnterAnimations() {
        val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
        listener.onAnimationCancel(mEnterAnimator)
    }

    private fun completeEnterAnimations() {
        val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
        listener.onAnimationEnd(mEnterAnimator)
Loading