1 package com.android.systemui.controls.management 2 3 import android.content.ComponentName 4 import android.content.Intent 5 import android.os.Bundle 6 import android.service.controls.Control 7 import android.testing.TestableLooper 8 import android.view.View 9 import android.widget.Button 10 import android.window.OnBackInvokedCallback 11 import android.window.OnBackInvokedDispatcher 12 import androidx.test.ext.junit.runners.AndroidJUnit4 13 import androidx.test.filters.FlakyTest 14 import androidx.test.filters.SmallTest 15 import androidx.test.rule.ActivityTestRule 16 import com.android.systemui.res.R 17 import com.android.systemui.SysuiTestCase 18 import com.android.systemui.activity.SingleActivityFactory 19 import com.android.systemui.controls.ControlStatus 20 import com.android.systemui.controls.controller.ControlsController 21 import com.android.systemui.controls.controller.ControlsControllerImpl 22 import com.android.systemui.controls.controller.createLoadDataObject 23 import com.android.systemui.dagger.qualifiers.Main 24 import com.android.systemui.flags.FakeFeatureFlags 25 import com.android.systemui.settings.UserTracker 26 import com.android.systemui.util.mockito.any 27 import com.android.systemui.util.mockito.capture 28 import com.android.systemui.util.mockito.whenever 29 import com.google.common.truth.Truth.assertThat 30 import com.google.common.util.concurrent.MoreExecutors 31 import java.util.concurrent.CountDownLatch 32 import java.util.concurrent.Executor 33 import java.util.function.Consumer 34 import org.junit.Before 35 import org.junit.Rule 36 import org.junit.Test 37 import org.junit.runner.RunWith 38 import org.mockito.Answers 39 import org.mockito.ArgumentCaptor 40 import org.mockito.Captor 41 import org.mockito.Mock 42 import org.mockito.Mockito.eq 43 import org.mockito.Mockito.mock 44 import org.mockito.Mockito.verify 45 import org.mockito.MockitoAnnotations 46 47 @SmallTest 48 @RunWith(AndroidJUnit4::class) 49 @TestableLooper.RunWithLooper 50 class ControlsFavoritingActivityTest : SysuiTestCase() { 51 52 private companion object { 53 val TEST_COMPONENT = ComponentName("TestPackageName", "TestClassName") 54 val TEST_CONTROL = <lambda>null55 mock(Control::class.java, Answers.RETURNS_MOCKS)!!.apply { 56 whenever(structure).thenReturn(TEST_STRUCTURE) 57 } 58 val TEST_STRUCTURE: CharSequence = "TestStructure" 59 val TEST_APP: CharSequence = "TestApp" 60 waitForPostnull61 private fun View.waitForPost() { 62 val latch = CountDownLatch(1) 63 post(latch::countDown) 64 latch.await() 65 } 66 } 67 68 @Main private val executor: Executor = MoreExecutors.directExecutor() 69 private val featureFlags = FakeFeatureFlags() 70 71 @Mock lateinit var controller: ControlsControllerImpl 72 73 @Mock lateinit var userTracker: UserTracker 74 75 private var latch: CountDownLatch = CountDownLatch(1) 76 77 @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher 78 @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback> 79 @Captor 80 private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>> 81 82 @Rule 83 @JvmField 84 var activityRule = 85 ActivityTestRule( <lambda>null86 /* activityFactory= */ SingleActivityFactory { 87 TestableControlsFavoritingActivity( 88 executor, 89 controller, 90 userTracker, 91 mockDispatcher, 92 latch 93 ) 94 }, 95 /* initialTouchMode= */ false, 96 /* launchActivity= */ false, 97 ) 98 99 @Before setUpnull100 fun setUp() { 101 MockitoAnnotations.initMocks(this) 102 } 103 104 // b/259549854 to root-cause and fix 105 @FlakyTest 106 @Test testBackCallbackRegistrationAndUnregistrationnull107 fun testBackCallbackRegistrationAndUnregistration() { 108 launchActivity() 109 // 1. ensure that launching the activity results in it registering a callback 110 verify(mockDispatcher) 111 .registerOnBackInvokedCallback( 112 eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), 113 captureCallback.capture() 114 ) 115 activityRule.finishActivity() 116 latch.await() // ensure activity is finished 117 // 2. ensure that when the activity is finished, it unregisters the same callback 118 verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value) 119 } 120 121 @Test testButtonsnull122 fun testButtons() { 123 with(launchActivity()) { 124 val rearrangeButton = requireViewById<Button>(R.id.rearrange) 125 assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE) 126 assertThat(rearrangeButton.isEnabled).isFalse() 127 128 val otherAppsButton = requireViewById<Button>(R.id.other_apps) 129 otherAppsButton.waitForPost() 130 assertThat(otherAppsButton.visibility).isEqualTo(View.GONE) 131 } 132 } 133 134 @Test testRearrangePressed_savesAndlaunchesActivitynull135 fun testRearrangePressed_savesAndlaunchesActivity() { 136 with(launchActivity()) { 137 verify(controller).loadForComponent(any(), capture(controlsCallback), any()) 138 activityRule.runOnUiThread { 139 controlsCallback.value.accept( 140 createLoadDataObject( 141 listOf(ControlStatus(TEST_CONTROL, TEST_COMPONENT, true)), 142 emptyList(), 143 ) 144 ) 145 requireViewById<Button>(R.id.rearrange).performClick() 146 } 147 148 verify(controller).replaceFavoritesForStructure(any()) 149 with(startActivityData!!.intent) { 150 assertThat(component) 151 .isEqualTo(ComponentName(context, ControlsEditingActivity::class.java)) 152 assertThat( 153 getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java) 154 ) 155 .isEqualTo(TEST_COMPONENT) 156 assertThat(getCharSequenceExtra(ControlsEditingActivity.EXTRA_APP)) 157 .isEqualTo(TEST_APP) 158 assertThat(getBooleanExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, false)) 159 .isTrue() 160 assertThat(getCharSequenceExtra(ControlsEditingActivity.EXTRA_STRUCTURE)) 161 .isEqualTo("") 162 } 163 } 164 } 165 launchActivitynull166 private fun launchActivity( 167 componentName: ComponentName = TEST_COMPONENT, 168 structure: CharSequence = TEST_STRUCTURE, 169 app: CharSequence = TEST_APP, 170 source: Byte = ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR, 171 ): TestableControlsFavoritingActivity = 172 activityRule.launchActivity( 173 Intent().apply { 174 putExtra(Intent.EXTRA_COMPONENT_NAME, componentName) 175 putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure) 176 putExtra(ControlsFavoritingActivity.EXTRA_APP, app) 177 putExtra(ControlsFavoritingActivity.EXTRA_SOURCE, source) 178 } 179 ) 180 181 class TestableControlsFavoritingActivity( 182 executor: Executor, 183 controller: ControlsControllerImpl, 184 userTracker: UserTracker, 185 private val mockDispatcher: OnBackInvokedDispatcher, 186 private val latch: CountDownLatch 187 ) : 188 ControlsFavoritingActivity( 189 executor, 190 controller, 191 userTracker, 192 ) { 193 194 var triedToFinish = false 195 196 var startActivityData: StartActivityData? = null 197 private set 198 getOnBackInvokedDispatchernull199 override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher { 200 return mockDispatcher 201 } 202 onStopnull203 override fun onStop() { 204 super.onStop() 205 // ensures that test runner thread does not proceed until ui thread is done 206 latch.countDown() 207 } 208 startActivitynull209 override fun startActivity(intent: Intent) { 210 startActivityData = StartActivityData(intent, null) 211 } 212 startActivitynull213 override fun startActivity(intent: Intent, options: Bundle?) { 214 startActivityData = StartActivityData(intent, options) 215 } 216 animateExitAndFinishnull217 override fun animateExitAndFinish() { 218 triedToFinish = true 219 } 220 } 221 } 222