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