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

Commit 382622fb authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Do not animate dialogs into activites when locked (1/n)

This CL ensures that we don't try to animate a dialog into an Activity
if the device is currently locked.

Test: atest DialogLaunchAnimatorTest
Bug: 240572073
Change-Id: I5adf650cf9f42781803dce1bebe14b2a78f43fff
parent fe1eba3a
Loading
Loading
Loading
Loading
+27 −7
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.app.Dialog
import android.graphics.Color
import android.graphics.Rect
import android.os.Looper
import android.service.dreams.IDreamManager
import android.util.Log
import android.util.MathUtils
import android.view.GhostView
@@ -54,7 +53,7 @@ private const val TAG = "DialogLaunchAnimator"
class DialogLaunchAnimator
@JvmOverloads
constructor(
    private val dreamManager: IDreamManager,
    private val callback: Callback,
    private val interactionJankMonitor: InteractionJankMonitor,
    private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
    private val isForTesting: Boolean = false
@@ -126,7 +125,7 @@ constructor(
        val animatedDialog =
            AnimatedDialog(
                launchAnimator,
                dreamManager,
                callback,
                interactionJankMonitor,
                animateFrom,
                onDialogDismissed = { openedDialogs.remove(it) },
@@ -194,8 +193,12 @@ constructor(

        val dialog = animatedDialog.dialog

        // Don't animate if the dialog is not showing.
        if (!dialog.isShowing) {
        // Don't animate if the dialog is not showing or if we are locked and going to show the
        // bouncer.
        if (
            !dialog.isShowing ||
            (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock())
        ) {
            return null
        }

@@ -285,6 +288,23 @@ constructor(
            ?.let { it.touchSurface = it.prepareForStackDismiss() }
        dialog.dismiss()
    }

    interface Callback {
        /** Whether the device is currently in dreaming (screensaver) mode. */
        fun isDreaming(): Boolean

        /**
         * Whether the device is currently unlocked, i.e. if it is *not* on the keyguard or if the
         * keyguard can be dismissed.
         */
        fun isUnlocked(): Boolean

        /**
         * Whether we are going to show alternate authentication (like UDFPS) instead of the
         * traditional bouncer when unlocking the device.
         */
        fun isShowingAlternateAuthOnUnlock(): Boolean
    }
}

/**
@@ -296,7 +316,7 @@ data class DialogCuj(@CujType val cujType: Int, val tag: String? = null)

private class AnimatedDialog(
    private val launchAnimator: LaunchAnimator,
    private val dreamManager: IDreamManager,
    private val callback: DialogLaunchAnimator.Callback,
    private val interactionJankMonitor: InteractionJankMonitor,

    /** The view that triggered the dialog after being tapped. */
@@ -850,7 +870,7 @@ private class AnimatedDialog(

        // If we are dreaming, the dialog was probably closed because of that so we don't animate
        // into the touchSurface.
        if (dreamManager.isDreaming) {
        if (callback.isDreaming()) {
            return false
        }

+28 −1
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@ package com.android.systemui.statusbar.dagger;
import android.app.IActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
import android.service.dreams.IDreamManager;
import android.util.Log;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
@@ -60,10 +62,12 @@ import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconList;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tracing.ProtoTracer;
@@ -274,7 +278,30 @@ public interface CentralSurfacesDependenciesModule {
    @Provides
    @SysUISingleton
    static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
            KeyguardStateController keyguardStateController,
            Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManager,
            InteractionJankMonitor interactionJankMonitor) {
        return new DialogLaunchAnimator(dreamManager, interactionJankMonitor);
        DialogLaunchAnimator.Callback callback = new DialogLaunchAnimator.Callback() {
            @Override
            public boolean isDreaming() {
                try {
                    return dreamManager.isDreaming();
                } catch (RemoteException e) {
                    Log.e("DialogLaunchAnimator.Callback", "dreamManager.isDreaming failed", e);
                    return false;
                }
            }

            @Override
            public boolean isUnlocked() {
                return keyguardStateController.isUnlocked();
            }

            @Override
            public boolean isShowingAlternateAuthOnUnlock() {
                return statusBarKeyguardViewManager.get().shouldShowAltAuth();
            }
        };
        return new DialogLaunchAnimator(callback, interactionJankMonitor);
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -471,7 +471,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
        showBouncer(scrimmed);
    }

    private boolean shouldShowAltAuth() {
    /** Whether we should show the alternate authentication instead of the traditional bouncer. */
    public boolean shouldShowAltAuth() {
        return mAlternateAuthInterceptor != null
                && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);
    }
+29 −17
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -34,19 +35,18 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import kotlin.concurrent.thread

@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() {
    private val launchContainer = LinearLayout(mContext)
    private val testLaunchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
    private val testLaunchAnimator = fakeLaunchAnimator()
    @Mock lateinit var callback: ActivityLaunchAnimator.Callback
    @Mock lateinit var listener: ActivityLaunchAnimator.Listener
    @Spy private val controller = TestLaunchAnimatorController(launchContainer)
@@ -82,7 +82,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
                    animate = animate,
                    intentStarter = intentStarter
                )
        }.join()
            }
            .join()
    }

    @Test
@@ -197,14 +198,25 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
        val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
        val taskInfo = ActivityManager.RunningTaskInfo()
        taskInfo.topActivity = ComponentName("com.android.systemui", "FakeActivity")
        taskInfo.topActivityInfo = ActivityInfo().apply {
            applicationInfo = ApplicationInfo()
        }
        taskInfo.topActivityInfo = ActivityInfo().apply { applicationInfo = ApplicationInfo() }

        return RemoteAnimationTarget(
                0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0,
                Point(), Rect(), bounds, WindowConfiguration(), false, SurfaceControl(), Rect(),
                taskInfo, false
            0,
            RemoteAnimationTarget.MODE_OPENING,
            SurfaceControl(),
            false,
            Rect(),
            Rect(),
            0,
            Point(),
            Rect(),
            bounds,
            WindowConfiguration(),
            false,
            SurfaceControl(),
            Rect(),
            taskInfo,
            false
        )
    }
}
@@ -213,10 +225,10 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
 * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called
 * outside of the main thread.
 */
private class TestLaunchAnimatorController(
    override var launchContainer: ViewGroup
) : ActivityLaunchAnimator.Controller {
    override fun createAnimatorState() = LaunchAnimator.State(
private class TestLaunchAnimatorController(override var launchContainer: ViewGroup) :
    ActivityLaunchAnimator.Controller {
    override fun createAnimatorState() =
        LaunchAnimator.State(
            top = 100,
            bottom = 200,
            left = 300,
+22 −8
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@ import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.service.dreams.IDreamManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -38,19 +37,16 @@ import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class DialogLaunchAnimatorTest : SysuiTestCase() {
    private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
    private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
    private val attachedViews = mutableSetOf<View>()

    @Mock lateinit var dreamManager: IDreamManager
    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
    @get:Rule val rule = MockitoJUnit.rule()

    @Before
    fun setUp() {
        dialogLaunchAnimator = DialogLaunchAnimator(
            dreamManager, interactionJankMonitor, launchAnimator, isForTesting = true
        )
        dialogLaunchAnimator =
            fakeDialogLaunchAnimator(interactionJankMonitor = interactionJankMonitor)
    }

    @After
@@ -152,6 +148,22 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
        assertNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView))
    }

    @Test
    fun testActivityLaunchWhenLockedWithoutAlternateAuth() {
        val dialogLaunchAnimator =
            fakeDialogLaunchAnimator(isUnlocked = false, isShowingAlternateAuthOnUnlock = false)
        val dialog = createAndShowDialog(dialogLaunchAnimator)
        assertNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView))
    }

    @Test
    fun testActivityLaunchWhenLockedWithAlternateAuth() {
        val dialogLaunchAnimator =
            fakeDialogLaunchAnimator(isUnlocked = false, isShowingAlternateAuthOnUnlock = true)
        val dialog = createAndShowDialog(dialogLaunchAnimator)
        assertNotNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView))
    }

    @Test
    fun testDialogAnimationIsChangedByAnimator() {
        // Important: the power menu animation relies on this behavior to know when to animate (see
@@ -193,11 +205,13 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
        verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN)
    }

    private fun createAndShowDialog(): TestDialog {
    private fun createAndShowDialog(
        animator: DialogLaunchAnimator = dialogLaunchAnimator,
    ): TestDialog {
        val touchSurface = createTouchSurface()
        return runOnMainThreadAndWaitForIdleSync {
            val dialog = TestDialog(context)
            dialogLaunchAnimator.showFromView(dialog, touchSurface)
            animator.showFromView(dialog, touchSurface)
            dialog
        }
    }
Loading