1 package com.android.systemui.animation 2 3 import android.app.Dialog 4 import android.content.Context 5 import android.graphics.Color 6 import android.graphics.drawable.ColorDrawable 7 import android.os.Bundle 8 import android.service.dreams.IDreamManager 9 import android.testing.AndroidTestingRunner 10 import android.testing.TestableLooper 11 import android.testing.ViewUtils 12 import android.view.View 13 import android.view.ViewGroup 14 import android.view.ViewGroup.LayoutParams.MATCH_PARENT 15 import android.view.WindowManager 16 import android.widget.LinearLayout 17 import androidx.test.filters.SmallTest 18 import com.android.internal.policy.DecorView 19 import com.android.systemui.SysuiTestCase 20 import junit.framework.Assert.assertEquals 21 import junit.framework.Assert.assertFalse 22 import junit.framework.Assert.assertNotNull 23 import junit.framework.Assert.assertTrue 24 import org.junit.After 25 import org.junit.Before 26 import org.junit.Rule 27 import org.junit.Test 28 import org.junit.runner.RunWith 29 import org.mockito.Mock 30 import org.mockito.junit.MockitoJUnit 31 32 @SmallTest 33 @RunWith(AndroidTestingRunner::class) 34 @TestableLooper.RunWithLooper 35 class DialogLaunchAnimatorTest : SysuiTestCase() { 36 private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) 37 private lateinit var dialogLaunchAnimator: DialogLaunchAnimator 38 private val attachedViews = mutableSetOf<View>() 39 40 @Mock lateinit var dreamManager: IDreamManager 41 @get:Rule val rule = MockitoJUnit.rule() 42 43 @Before 44 fun setUp() { 45 dialogLaunchAnimator = DialogLaunchAnimator( 46 dreamManager, launchAnimator, isForTesting = true) 47 } 48 49 @After 50 fun tearDown() { 51 runOnMainThreadAndWaitForIdleSync { 52 attachedViews.forEach { 53 ViewUtils.detachView(it) 54 } 55 } 56 } 57 58 @Test 59 fun testShowDialogFromView() { 60 // Show the dialog. showFromView() must be called on the main thread with a dialog created 61 // on the main thread too. 62 val dialog = createAndShowDialog() 63 64 assertTrue(dialog.isShowing) 65 66 // The dialog is now fullscreen. 67 val window = dialog.window 68 val decorView = window.decorView as DecorView 69 assertEquals(MATCH_PARENT, window.attributes.width) 70 assertEquals(MATCH_PARENT, window.attributes.height) 71 assertEquals(MATCH_PARENT, decorView.layoutParams.width) 72 assertEquals(MATCH_PARENT, decorView.layoutParams.height) 73 74 // The single DecorView child is a transparent fullscreen view that will dismiss the dialog 75 // when clicked. 76 assertEquals(1, decorView.childCount) 77 val transparentBackground = decorView.getChildAt(0) as ViewGroup 78 assertEquals(MATCH_PARENT, transparentBackground.layoutParams.width) 79 assertEquals(MATCH_PARENT, transparentBackground.layoutParams.height) 80 81 // The single transparent background child is a fake window with the same size and 82 // background as the dialog initially had. 83 assertEquals(1, transparentBackground.childCount) 84 val dialogContentWithBackground = transparentBackground.getChildAt(0) as ViewGroup 85 assertEquals(TestDialog.DIALOG_WIDTH, dialogContentWithBackground.layoutParams.width) 86 assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentWithBackground.layoutParams.height) 87 assertEquals(dialog.windowBackground, dialogContentWithBackground.background) 88 89 // The dialog content is inside this fake window view. 90 assertNotNull( 91 dialogContentWithBackground.findViewByPredicate { it === dialog.contentView }) 92 93 // Clicking the transparent background should dismiss the dialog. 94 runOnMainThreadAndWaitForIdleSync { 95 // TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and 96 // make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in 97 // Forrest. 98 dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() 99 100 transparentBackground.performClick() 101 } 102 assertFalse(dialog.isShowing) 103 } 104 105 @Test 106 fun testStackedDialogsDismissesAll() { 107 val firstDialog = createAndShowDialog() 108 val secondDialog = createDialogAndShowFromDialog(firstDialog) 109 110 assertTrue(firstDialog.isShowing) 111 assertTrue(secondDialog.isShowing) 112 runOnMainThreadAndWaitForIdleSync { 113 dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() 114 dialogLaunchAnimator.dismissStack(secondDialog) 115 } 116 117 assertFalse(firstDialog.isShowing) 118 assertFalse(secondDialog.isShowing) 119 } 120 121 private fun createAndShowDialog(): TestDialog { 122 return runOnMainThreadAndWaitForIdleSync { 123 val touchSurfaceRoot = LinearLayout(context) 124 val touchSurface = View(context) 125 touchSurfaceRoot.addView(touchSurface) 126 127 // We need to attach the root to the window manager otherwise the exit animation will 128 // be skipped 129 ViewUtils.attachView(touchSurfaceRoot) 130 attachedViews.add(touchSurfaceRoot) 131 132 val dialog = TestDialog(context) 133 dialogLaunchAnimator.showFromView(dialog, touchSurface) 134 dialog 135 } 136 } 137 138 private fun createDialogAndShowFromDialog(animateFrom: Dialog): TestDialog { 139 return runOnMainThreadAndWaitForIdleSync { 140 val dialog = TestDialog(context) 141 dialogLaunchAnimator.showFromDialog(dialog, animateFrom) 142 dialog 143 } 144 } 145 146 private fun <T : Any> runOnMainThreadAndWaitForIdleSync(f: () -> T): T { 147 lateinit var result: T 148 context.mainExecutor.execute { 149 result = f() 150 } 151 waitForIdleSync() 152 return result 153 } 154 155 private class TestDialog(context: Context) : Dialog(context) { 156 companion object { 157 const val DIALOG_WIDTH = 100 158 const val DIALOG_HEIGHT = 200 159 } 160 161 val contentView = View(context) 162 val windowBackground = ColorDrawable(Color.RED) 163 164 init { 165 // We need to set the window type for dialogs shown by SysUI, otherwise WM will throw. 166 window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) 167 } 168 169 override fun onCreate(savedInstanceState: Bundle?) { 170 super.onCreate(savedInstanceState) 171 setContentView(contentView) 172 173 window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT) 174 window.setBackgroundDrawable(windowBackground) 175 } 176 } 177 }