Merge "Add jank CUJ instrumentation to DialogLaunchAnimator" into tm-qpr-dev am: 4b1750247e
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18652316 Change-Id: Ic9f9da7bd95cf8e76825a54415d9f5529e6e7caa Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -34,6 +34,9 @@ import android.view.WindowInsets
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import com.android.internal.jank.InteractionJankMonitor
|
||||||
|
import com.android.internal.jank.InteractionJankMonitor.Configuration
|
||||||
|
import com.android.internal.jank.InteractionJankMonitor.CujType
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val TAG = "DialogLaunchAnimator"
|
private const val TAG = "DialogLaunchAnimator"
|
||||||
@@ -50,6 +53,7 @@ private const val TAG = "DialogLaunchAnimator"
|
|||||||
*/
|
*/
|
||||||
class DialogLaunchAnimator @JvmOverloads constructor(
|
class DialogLaunchAnimator @JvmOverloads constructor(
|
||||||
private val dreamManager: IDreamManager,
|
private val dreamManager: IDreamManager,
|
||||||
|
private val interactionJankMonitor: InteractionJankMonitor,
|
||||||
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
|
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
|
||||||
private val isForTesting: Boolean = false
|
private val isForTesting: Boolean = false
|
||||||
) {
|
) {
|
||||||
@@ -89,12 +93,14 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
|||||||
fun showFromView(
|
fun showFromView(
|
||||||
dialog: Dialog,
|
dialog: Dialog,
|
||||||
view: View,
|
view: View,
|
||||||
animateBackgroundBoundsChange: Boolean = false
|
cuj: DialogCuj? = null,
|
||||||
|
animateBackgroundBoundsChange: Boolean = false,
|
||||||
) {
|
) {
|
||||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
throw IllegalStateException(
|
throw IllegalStateException(
|
||||||
"showFromView must be called from the main thread and dialog must be created in " +
|
"showFromView must be called from the main thread and dialog must be created in " +
|
||||||
"the main thread")
|
"the main thread"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the view we are launching from belongs to another dialog, then this means the caller
|
// If the view we are launching from belongs to another dialog, then this means the caller
|
||||||
@@ -113,14 +119,16 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
|||||||
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
||||||
|
|
||||||
val animatedDialog = AnimatedDialog(
|
val animatedDialog = AnimatedDialog(
|
||||||
launchAnimator,
|
launchAnimator,
|
||||||
dreamManager,
|
dreamManager,
|
||||||
animateFrom,
|
interactionJankMonitor,
|
||||||
onDialogDismissed = { openedDialogs.remove(it) },
|
animateFrom,
|
||||||
dialog = dialog,
|
onDialogDismissed = { openedDialogs.remove(it) },
|
||||||
animateBackgroundBoundsChange,
|
dialog = dialog,
|
||||||
animatedParent,
|
animateBackgroundBoundsChange,
|
||||||
isForTesting
|
animatedParent,
|
||||||
|
isForTesting,
|
||||||
|
cuj
|
||||||
)
|
)
|
||||||
|
|
||||||
openedDialogs.add(animatedDialog)
|
openedDialogs.add(animatedDialog)
|
||||||
@@ -143,8 +151,9 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
|||||||
?.dialogContentWithBackground
|
?.dialogContentWithBackground
|
||||||
?: throw IllegalStateException(
|
?: throw IllegalStateException(
|
||||||
"The animateFrom dialog was not animated using " +
|
"The animateFrom dialog was not animated using " +
|
||||||
"DialogLaunchAnimator.showFrom(View|Dialog)")
|
"DialogLaunchAnimator.showFrom(View|Dialog)"
|
||||||
showFromView(dialog, view, animateBackgroundBoundsChange)
|
)
|
||||||
|
showFromView(dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -270,9 +279,17 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CUJ interaction associated with opening the dialog.
|
||||||
|
*
|
||||||
|
* The optional tag indicates the specific dialog being opened.
|
||||||
|
*/
|
||||||
|
data class DialogCuj(@CujType val cujType: Int, val tag: String? = null)
|
||||||
|
|
||||||
private class AnimatedDialog(
|
private class AnimatedDialog(
|
||||||
private val launchAnimator: LaunchAnimator,
|
private val launchAnimator: LaunchAnimator,
|
||||||
private val dreamManager: IDreamManager,
|
private val dreamManager: IDreamManager,
|
||||||
|
private val interactionJankMonitor: InteractionJankMonitor,
|
||||||
|
|
||||||
/** The view that triggered the dialog after being tapped. */
|
/** The view that triggered the dialog after being tapped. */
|
||||||
var touchSurface: View,
|
var touchSurface: View,
|
||||||
@@ -295,14 +312,17 @@ private class AnimatedDialog(
|
|||||||
/**
|
/**
|
||||||
* Whether synchronization should be disabled, which can be useful if we are running in a test.
|
* Whether synchronization should be disabled, which can be useful if we are running in a test.
|
||||||
*/
|
*/
|
||||||
private val forceDisableSynchronization: Boolean
|
private val forceDisableSynchronization: Boolean,
|
||||||
|
|
||||||
|
/** Interaction to which the dialog animation is associated. */
|
||||||
|
private val cuj: DialogCuj? = null
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* The DecorView of this dialog window.
|
* The DecorView of this dialog window.
|
||||||
*
|
*
|
||||||
* Note that we access this DecorView lazily to avoid accessing it before the dialog is created,
|
* Note that we access this DecorView lazily to avoid accessing it before the dialog is created,
|
||||||
* which can sometimes cause crashes (e.g. with the Cast dialog).
|
* which can sometimes cause crashes (e.g. with the Cast dialog).
|
||||||
*/
|
*/
|
||||||
private val decorView by lazy { dialog.window!!.decorView as ViewGroup }
|
private val decorView by lazy { dialog.window!!.decorView as ViewGroup }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,6 +366,14 @@ private class AnimatedDialog(
|
|||||||
private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
|
private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
|
if (cuj != null) {
|
||||||
|
val config = Configuration.Builder.withView(cuj.cujType, touchSurface)
|
||||||
|
if (cuj.tag != null) {
|
||||||
|
config.setTag(cuj.tag)
|
||||||
|
}
|
||||||
|
interactionJankMonitor.begin(config)
|
||||||
|
}
|
||||||
|
|
||||||
// Create the dialog so that its onCreate() method is called, which usually sets the dialog
|
// Create the dialog so that its onCreate() method is called, which usually sets the dialog
|
||||||
// content.
|
// content.
|
||||||
dialog.create()
|
dialog.create()
|
||||||
@@ -430,9 +458,10 @@ private class AnimatedDialog(
|
|||||||
// Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
|
// Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
|
||||||
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
||||||
decorViewLayoutListener = View.OnLayoutChangeListener {
|
decorViewLayoutListener = View.OnLayoutChangeListener {
|
||||||
v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
||||||
if (window.attributes.width != MATCH_PARENT ||
|
if (window.attributes.width != MATCH_PARENT ||
|
||||||
window.attributes.height != MATCH_PARENT) {
|
window.attributes.height != MATCH_PARENT
|
||||||
|
) {
|
||||||
// The dialog size changed, copy its size to dialogContentWithBackground and
|
// The dialog size changed, copy its size to dialogContentWithBackground and
|
||||||
// make the dialog window full screen again.
|
// make the dialog window full screen again.
|
||||||
val layoutParams = dialogContentWithBackground.layoutParams
|
val layoutParams = dialogContentWithBackground.layoutParams
|
||||||
@@ -606,6 +635,7 @@ private class AnimatedDialog(
|
|||||||
dialogContentWithBackground!!
|
dialogContentWithBackground!!
|
||||||
.addOnLayoutChangeListener(backgroundLayoutListener)
|
.addOnLayoutChangeListener(backgroundLayoutListener)
|
||||||
}
|
}
|
||||||
|
cuj?.run { interactionJankMonitor.end(cujType) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,9 @@ public class DndTile extends QSTileImpl<BooleanState> {
|
|||||||
mUiHandler.post(() -> {
|
mUiHandler.post(() -> {
|
||||||
Dialog dialog = makeZenModeDialog();
|
Dialog dialog = makeZenModeDialog();
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
mDialogLaunchAnimator.showFromView(dialog, view, false);
|
mDialogLaunchAnimator.showFromView(dialog, view,
|
||||||
|
/* cuj= */ null,
|
||||||
|
/* animateBackgroundBoundsChange= */ false);
|
||||||
} else {
|
} else {
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import android.content.Context
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import com.android.internal.jank.InteractionJankMonitor
|
||||||
import com.android.internal.logging.UiEventLogger
|
import com.android.internal.logging.UiEventLogger
|
||||||
|
import com.android.systemui.animation.DialogCuj
|
||||||
import com.android.systemui.animation.DialogLaunchAnimator
|
import com.android.systemui.animation.DialogLaunchAnimator
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
@@ -45,6 +47,7 @@ class InternetDialogFactory @Inject constructor(
|
|||||||
private val keyguardStateController: KeyguardStateController
|
private val keyguardStateController: KeyguardStateController
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val INTERACTION_JANK_TAG = "internet"
|
||||||
var internetDialog: InternetDialog? = null
|
var internetDialog: InternetDialog? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +64,20 @@ class InternetDialogFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
internetDialog = InternetDialog(context, this, internetDialogController,
|
internetDialog = InternetDialog(
|
||||||
canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
|
context, this, internetDialogController,
|
||||||
executor, keyguardStateController)
|
canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
|
||||||
|
executor, keyguardStateController
|
||||||
|
)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
dialogLaunchAnimator.showFromView(internetDialog!!, view,
|
dialogLaunchAnimator.showFromView(
|
||||||
animateBackgroundBoundsChange = true)
|
internetDialog!!, view,
|
||||||
|
animateBackgroundBoundsChange = true,
|
||||||
|
cuj = DialogCuj(
|
||||||
|
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
|
||||||
|
INTERACTION_JANK_TAG
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
internetDialog?.show()
|
internetDialog?.show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.content.Context;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.service.dreams.IDreamManager;
|
import android.service.dreams.IDreamManager;
|
||||||
|
|
||||||
|
import com.android.internal.jank.InteractionJankMonitor;
|
||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||||
@@ -317,7 +318,8 @@ public interface CentralSurfacesDependenciesModule {
|
|||||||
*/
|
*/
|
||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) {
|
static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
|
||||||
return new DialogLaunchAnimator(dreamManager);
|
InteractionJankMonitor interactionJankMonitor) {
|
||||||
|
return new DialogLaunchAnimator(dreamManager, interactionJankMonitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.internal.jank.InteractionJankMonitor
|
||||||
import com.android.internal.policy.DecorView
|
import com.android.internal.policy.DecorView
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import junit.framework.Assert.assertEquals
|
import junit.framework.Assert.assertEquals
|
||||||
@@ -29,6 +30,8 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.any
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -40,12 +43,14 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
private val attachedViews = mutableSetOf<View>()
|
private val attachedViews = mutableSetOf<View>()
|
||||||
|
|
||||||
@Mock lateinit var dreamManager: IDreamManager
|
@Mock lateinit var dreamManager: IDreamManager
|
||||||
|
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
|
||||||
@get:Rule val rule = MockitoJUnit.rule()
|
@get:Rule val rule = MockitoJUnit.rule()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
dialogLaunchAnimator = DialogLaunchAnimator(
|
dialogLaunchAnimator = DialogLaunchAnimator(
|
||||||
dreamManager, launchAnimator, isForTesting = true)
|
dreamManager, interactionJankMonitor, launchAnimator, isForTesting = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -90,7 +95,8 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// The dialog content is inside this fake window view.
|
// The dialog content is inside this fake window view.
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
dialogContentWithBackground.findViewByPredicate { it === dialog.contentView })
|
dialogContentWithBackground.findViewByPredicate { it === dialog.contentView }
|
||||||
|
)
|
||||||
|
|
||||||
// Clicking the transparent background should dismiss the dialog.
|
// Clicking the transparent background should dismiss the dialog.
|
||||||
runOnMainThreadAndWaitForIdleSync {
|
runOnMainThreadAndWaitForIdleSync {
|
||||||
@@ -161,6 +167,24 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
assertNotEquals(0, dialog.window.attributes.windowAnimations)
|
assertNotEquals(0, dialog.window.attributes.windowAnimations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCujSpecificationLogsInteraction() {
|
||||||
|
val touchSurface = createTouchSurface()
|
||||||
|
return runOnMainThreadAndWaitForIdleSync {
|
||||||
|
val dialog = TestDialog(context)
|
||||||
|
dialogLaunchAnimator.showFromView(
|
||||||
|
dialog, touchSurface,
|
||||||
|
cuj = DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(interactionJankMonitor).begin(
|
||||||
|
any()
|
||||||
|
)
|
||||||
|
verify(interactionJankMonitor)
|
||||||
|
.end(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createAndShowDialog(): TestDialog {
|
private fun createAndShowDialog(): TestDialog {
|
||||||
val touchSurface = createTouchSurface()
|
val touchSurface = createTouchSurface()
|
||||||
return runOnMainThreadAndWaitForIdleSync {
|
return runOnMainThreadAndWaitForIdleSync {
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ import com.android.systemui.qs.logging.QSLogger
|
|||||||
import com.android.systemui.statusbar.policy.ZenModeController
|
import com.android.systemui.statusbar.policy.ZenModeController
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.mockito.eq
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.mockito.nullable
|
||||||
import com.android.systemui.util.settings.FakeSettings
|
import com.android.systemui.util.settings.FakeSettings
|
||||||
import com.android.systemui.util.settings.SecureSettings
|
import com.android.systemui.util.settings.SecureSettings
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.io.File
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -49,9 +51,8 @@ import org.mockito.Mock
|
|||||||
import org.mockito.Mockito.anyBoolean
|
import org.mockito.Mockito.anyBoolean
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
import java.io.File
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@@ -173,7 +174,7 @@ class DndTileTest : SysuiTestCase() {
|
|||||||
tile.handleClick(view)
|
tile.handleClick(view)
|
||||||
testableLooper.processAllMessages()
|
testableLooper.processAllMessages()
|
||||||
|
|
||||||
verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean())
|
verify(dialogLaunchAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -187,6 +188,6 @@ class DndTileTest : SysuiTestCase() {
|
|||||||
tile.handleClick(view)
|
tile.handleClick(view)
|
||||||
testableLooper.processAllMessages()
|
testableLooper.processAllMessages()
|
||||||
|
|
||||||
verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean())
|
verify(dialogLaunchAnimator, never()).showFromView(any(), any(), nullable(), anyBoolean())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user