Merge "Do not animate dialogs into activites when locked (1/n)" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4dd2341342
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -77,12 +77,13 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
||||
// We start in a new thread so that we can ensure that the callbacks are called in the main
|
||||
// thread.
|
||||
thread {
|
||||
animator.startIntentWithAnimation(
|
||||
animator.startIntentWithAnimation(
|
||||
controller = controller,
|
||||
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,17 +225,17 @@ 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,
|
||||
right = 400,
|
||||
topCornerRadius = 10f,
|
||||
bottomCornerRadius = 20f
|
||||
)
|
||||
)
|
||||
|
||||
private fun assertOnMainThread() {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.android.systemui.animation
|
||||
|
||||
/**
|
||||
* A [LaunchAnimator.Timings] to be used in tests.
|
||||
*
|
||||
* Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
|
||||
* when computing the progress of a sub-animation (the contents fade in/out).
|
||||
*/
|
||||
val TEST_TIMINGS = LaunchAnimator.Timings(
|
||||
totalDuration = 0L,
|
||||
contentBeforeFadeOutDelay = 1L,
|
||||
contentBeforeFadeOutDuration = 1L,
|
||||
contentAfterFadeInDelay = 1L,
|
||||
contentAfterFadeInDuration = 1L
|
||||
)
|
||||
|
||||
/** A [LaunchAnimator.Interpolators] to be used in tests. */
|
||||
val TEST_INTERPOLATORS = LaunchAnimator.Interpolators(
|
||||
positionInterpolator = Interpolators.STANDARD,
|
||||
positionXInterpolator = Interpolators.STANDARD,
|
||||
contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
|
||||
contentAfterFadeInInterpolator = Interpolators.STANDARD
|
||||
)
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.systemui;
|
||||
|
||||
import static com.android.systemui.animation.FakeDialogLaunchAnimatorKt.fakeDialogLaunchAnimator;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -34,6 +36,7 @@ import androidx.test.uiautomator.UiDevice;
|
||||
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.broadcast.FakeBroadcastDispatcher;
|
||||
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
|
||||
@@ -119,6 +122,7 @@ public abstract class SysuiTestCase {
|
||||
// is missing (constructing the actual one would throw).
|
||||
// TODO(b/219008720): Remove this.
|
||||
mDependency.injectMockDependency(SystemUIDialogManager.class);
|
||||
mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator());
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.animation
|
||||
|
||||
import com.android.internal.jank.InteractionJankMonitor
|
||||
import org.mockito.Mockito.mock
|
||||
|
||||
/** A [DialogLaunchAnimator] to be used in tests. */
|
||||
@JvmOverloads
|
||||
fun fakeDialogLaunchAnimator(
|
||||
isUnlocked: Boolean = true,
|
||||
isShowingAlternateAuthOnUnlock: Boolean = false,
|
||||
interactionJankMonitor: InteractionJankMonitor = mock(InteractionJankMonitor::class.java),
|
||||
): DialogLaunchAnimator {
|
||||
return DialogLaunchAnimator(
|
||||
FakeCallback(
|
||||
isUnlocked = isUnlocked,
|
||||
isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock,
|
||||
),
|
||||
interactionJankMonitor,
|
||||
fakeLaunchAnimator(),
|
||||
isForTesting = true,
|
||||
)
|
||||
}
|
||||
|
||||
private class FakeCallback(
|
||||
private val isDreaming: Boolean = false,
|
||||
private val isUnlocked: Boolean = true,
|
||||
private val isShowingAlternateAuthOnUnlock: Boolean = false,
|
||||
) : DialogLaunchAnimator.Callback {
|
||||
override fun isDreaming(): Boolean = isDreaming
|
||||
override fun isUnlocked(): Boolean = isUnlocked
|
||||
override fun isShowingAlternateAuthOnUnlock() = isShowingAlternateAuthOnUnlock
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.animation
|
||||
|
||||
/** A [LaunchAnimator] to be used in tests. */
|
||||
fun fakeLaunchAnimator(): LaunchAnimator {
|
||||
return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [LaunchAnimator.Timings] to be used in tests.
|
||||
*
|
||||
* Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
|
||||
* when computing the progress of a sub-animation (the contents fade in/out).
|
||||
*/
|
||||
private val TEST_TIMINGS =
|
||||
LaunchAnimator.Timings(
|
||||
totalDuration = 0L,
|
||||
contentBeforeFadeOutDelay = 1L,
|
||||
contentBeforeFadeOutDuration = 1L,
|
||||
contentAfterFadeInDelay = 1L,
|
||||
contentAfterFadeInDuration = 1L
|
||||
)
|
||||
|
||||
/** A [LaunchAnimator.Interpolators] to be used in tests. */
|
||||
private val TEST_INTERPOLATORS =
|
||||
LaunchAnimator.Interpolators(
|
||||
positionInterpolator = Interpolators.STANDARD,
|
||||
positionXInterpolator = Interpolators.STANDARD,
|
||||
contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
|
||||
contentAfterFadeInInterpolator = Interpolators.STANDARD
|
||||
)
|
||||
Reference in New Issue
Block a user