1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.controls.management
18 
19 import android.app.Dialog
20 import android.content.ComponentName
21 import android.content.Intent
22 import android.content.pm.ApplicationInfo
23 import android.content.pm.ServiceInfo
24 import android.graphics.drawable.Drawable
25 import android.os.Bundle
26 import android.testing.TestableLooper
27 import android.window.OnBackInvokedCallback
28 import android.window.OnBackInvokedDispatcher
29 import androidx.test.ext.junit.runners.AndroidJUnit4
30 import androidx.test.filters.SmallTest
31 import androidx.test.rule.ActivityTestRule
32 import com.android.systemui.SysuiTestCase
33 import com.android.systemui.activity.SingleActivityFactory
34 import com.android.systemui.controls.ControlsServiceInfo
35 import com.android.systemui.controls.controller.ControlsController
36 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
37 import com.android.systemui.controls.ui.SelectedItem
38 import com.android.systemui.dagger.qualifiers.Background
39 import com.android.systemui.dagger.qualifiers.Main
40 import com.android.systemui.settings.UserTracker
41 import com.android.systemui.util.mockito.any
42 import com.android.systemui.util.mockito.argumentCaptor
43 import com.android.systemui.util.mockito.capture
44 import com.android.systemui.util.mockito.eq
45 import com.android.systemui.util.mockito.mock
46 import com.android.systemui.util.mockito.whenever
47 import com.google.common.truth.Truth.assertThat
48 import com.google.common.util.concurrent.MoreExecutors
49 import java.util.concurrent.CountDownLatch
50 import java.util.concurrent.Executor
51 import java.util.function.Consumer
52 import org.junit.Before
53 import org.junit.Rule
54 import org.junit.Test
55 import org.junit.runner.RunWith
56 import org.mockito.ArgumentCaptor
57 import org.mockito.ArgumentMatchers
58 import org.mockito.Captor
59 import org.mockito.Mock
60 import org.mockito.Mockito
61 import org.mockito.Mockito.doReturn
62 import org.mockito.Mockito.never
63 import org.mockito.Mockito.verify
64 import org.mockito.Mockito.verifyNoMoreInteractions
65 import org.mockito.MockitoAnnotations
66 
67 @SmallTest
68 @RunWith(AndroidJUnit4::class)
69 @TestableLooper.RunWithLooper
70 class ControlsProviderSelectorActivityTest : SysuiTestCase() {
71     @Main private val executor: Executor = MoreExecutors.directExecutor()
72 
73     @Background private val backExecutor: Executor = MoreExecutors.directExecutor()
74 
75     @Mock lateinit var listingController: ControlsListingController
76 
77     @Mock lateinit var controlsController: ControlsController
78 
79     @Mock lateinit var userTracker: UserTracker
80 
81     @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
82 
83     @Mock lateinit var dialogFactory: PanelConfirmationDialogFactory
84 
85     private var latch: CountDownLatch = CountDownLatch(1)
86 
87     @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
88     @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
89 
90     @Rule
91     @JvmField
92     var activityRule =
93         ActivityTestRule(
<lambda>null94             /* activityFactory= */ SingleActivityFactory {
95                 TestableControlsProviderSelectorActivity(
96                     executor,
97                     backExecutor,
98                     listingController,
99                     controlsController,
100                     userTracker,
101                     authorizedPanelsRepository,
102                     dialogFactory,
103                     mockDispatcher,
104                     latch
105                 )
106             },
107             /* initialTouchMode= */ false,
108             /* launchActivity= */ false,
109         )
110 
111     @Before
setUpnull112     fun setUp() {
113         MockitoAnnotations.initMocks(this)
114         val intent = Intent()
115         intent.putExtra(ControlsProviderSelectorActivity.BACK_SHOULD_EXIT, true)
116         activityRule.launchActivity(intent)
117     }
118 
119     @Test
testBackCallbackRegistrationAndUnregistrationnull120     fun testBackCallbackRegistrationAndUnregistration() {
121         // 1. ensure that launching the activity results in it registering a callback
122         verify(mockDispatcher)
123             .registerOnBackInvokedCallback(
124                 ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
125                 captureCallback.capture()
126             )
127         activityRule.finishActivity()
128         latch.await() // ensure activity is finished
129         // 2. ensure that when the activity is finished, it unregisters the same callback
130         verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
131     }
132 
133     @Test
testOnAppSelectedForNonPanelStartsFavoritingActivitynull134     fun testOnAppSelectedForNonPanelStartsFavoritingActivity() {
135         val info = ControlsServiceInfo(ComponentName("test_pkg", "service"), "", null)
136         activityRule.activity.onAppSelected(info)
137 
138         verifyNoMoreInteractions(dialogFactory)
139 
140         assertThat(activityRule.activity.lastStartedActivity?.component?.className)
141             .isEqualTo(ControlsFavoritingActivity::class.java.name)
142 
143         assertThat(activityRule.activity.triedToFinish).isFalse()
144     }
145 
146     @Test
testOnAppSelectedForPanelTriggersDialognull147     fun testOnAppSelectedForPanelTriggersDialog() {
148         val label = "label"
149         val info =
150             ControlsServiceInfo(
151                 ComponentName("test_pkg", "service"),
152                 label,
153                 ComponentName("test_pkg", "activity")
154             )
155 
156         val dialog: Dialog = mock()
157         whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
158 
159         activityRule.activity.onAppSelected(info)
160         verify(dialogFactory).createConfirmationDialog(any(), eq(label), any())
161         verify(dialog).show()
162 
163         assertThat(activityRule.activity.triedToFinish).isFalse()
164     }
165 
166     @Test
dialogAcceptAddsPackagenull167     fun dialogAcceptAddsPackage() {
168         val label = "label"
169         val info =
170             ControlsServiceInfo(
171                 ComponentName("test_pkg", "service"),
172                 label,
173                 ComponentName("test_pkg", "activity")
174             )
175 
176         val dialog: Dialog = mock()
177         whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
178 
179         activityRule.activity.onAppSelected(info)
180 
181         val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor()
182         verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor))
183 
184         captor.value.accept(true)
185 
186         val setCaptor: ArgumentCaptor<Set<String>> = argumentCaptor()
187         verify(authorizedPanelsRepository).addAuthorizedPanels(capture(setCaptor))
188         assertThat(setCaptor.value).containsExactly(info.componentName.packageName)
189         val selectedComponentCaptor: ArgumentCaptor<SelectedItem> = argumentCaptor()
190         verify(controlsController).setPreferredSelection(capture(selectedComponentCaptor))
191         assertThat(selectedComponentCaptor.value.componentName).isEqualTo(info.componentName)
192 
193         assertThat(activityRule.activity.triedToFinish).isTrue()
194     }
195 
196     @Test
dialogCancelDoesntAddPackagenull197     fun dialogCancelDoesntAddPackage() {
198         val label = "label"
199         val info =
200             ControlsServiceInfo(
201                 ComponentName("test_pkg", "service"),
202                 label,
203                 ComponentName("test_pkg", "activity")
204             )
205 
206         val dialog: Dialog = mock()
207         whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
208 
209         activityRule.activity.onAppSelected(info)
210 
211         val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor()
212         verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor))
213 
214         captor.value.accept(false)
215 
216         verify(authorizedPanelsRepository, never()).addAuthorizedPanels(any())
217 
218         assertThat(activityRule.activity.triedToFinish).isFalse()
219     }
220 
221     class TestableControlsProviderSelectorActivity(
222         executor: Executor,
223         backExecutor: Executor,
224         listingController: ControlsListingController,
225         controlsController: ControlsController,
226         userTracker: UserTracker,
227         authorizedPanelsRepository: AuthorizedPanelsRepository,
228         dialogFactory: PanelConfirmationDialogFactory,
229         private val mockDispatcher: OnBackInvokedDispatcher,
230         private val latch: CountDownLatch
231     ) :
232         ControlsProviderSelectorActivity(
233             executor,
234             backExecutor,
235             listingController,
236             controlsController,
237             userTracker,
238             authorizedPanelsRepository,
239             dialogFactory
240         ) {
241 
242         var lastStartedActivity: Intent? = null
243         var triedToFinish = false
244 
getOnBackInvokedDispatchernull245         override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
246             return mockDispatcher
247         }
248 
startActivitynull249         override fun startActivity(intent: Intent?, options: Bundle?) {
250             lastStartedActivity = intent
251         }
252 
onStopnull253         override fun onStop() {
254             super.onStop()
255             // ensures that test runner thread does not proceed until ui thread is done
256             latch.countDown()
257         }
258 
animateExitAndFinishnull259         override fun animateExitAndFinish() {
260             // Activity should only be finished from the rule.
261             triedToFinish = true
262         }
263     }
264 
265     companion object {
ControlsServiceInfonull266         private fun ControlsServiceInfo(
267             componentName: ComponentName,
268             label: CharSequence,
269             panelComponentName: ComponentName? = null
270         ): ControlsServiceInfo {
271             val serviceInfo =
272                 ServiceInfo().apply {
273                     applicationInfo = ApplicationInfo()
274                     packageName = componentName.packageName
275                     name = componentName.className
276                 }
277             return Mockito.spy(ControlsServiceInfo(mock(), serviceInfo)).apply {
278                 doReturn(label).`when`(this).loadLabel()
279                 doReturn(mock<Drawable>()).`when`(this).loadIcon()
280                 doReturn(panelComponentName).`when`(this).panelActivity
281             }
282         }
283     }
284 }
285