1 package com.android.systemui.qs
2 
3 import android.content.BroadcastReceiver
4 import android.content.Context
5 import android.content.Intent
6 import android.content.IntentFilter
7 import android.permission.PermissionManager
8 import android.safetycenter.SafetyCenterManager
9 import android.testing.AndroidTestingRunner
10 import android.view.View
11 import androidx.test.filters.SmallTest
12 import com.android.internal.logging.UiEventLogger
13 import com.android.systemui.SysuiTestCase
14 import com.android.systemui.appops.AppOpsController
15 import com.android.systemui.broadcast.BroadcastDispatcher
16 import com.android.systemui.flags.FeatureFlags
17 import com.android.systemui.flags.Flags
18 import com.android.systemui.plugins.ActivityStarter
19 import com.android.systemui.privacy.OngoingPrivacyChip
20 import com.android.systemui.privacy.PrivacyDialogController
21 import com.android.systemui.privacy.PrivacyDialogControllerV2
22 import com.android.systemui.privacy.PrivacyItemController
23 import com.android.systemui.privacy.logging.PrivacyLogger
24 import com.android.systemui.statusbar.phone.StatusIconContainer
25 import com.android.systemui.statusbar.policy.DeviceProvisionedController
26 import com.android.systemui.util.concurrency.FakeExecutor
27 import com.android.systemui.util.mockito.any
28 import com.android.systemui.util.mockito.argumentCaptor
29 import com.android.systemui.util.mockito.capture
30 import com.android.systemui.util.mockito.eq
31 import com.android.systemui.util.mockito.nullable
32 import com.android.systemui.util.time.FakeSystemClock
33 import org.junit.Before
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 import org.mockito.ArgumentMatchers.anyInt
37 import org.mockito.Mock
38 import org.mockito.Mockito
39 import org.mockito.Mockito.never
40 import org.mockito.Mockito.times
41 import org.mockito.Mockito.verify
42 import org.mockito.MockitoAnnotations
43 import org.mockito.Mockito.`when` as whenever
44 
45 private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
46 private fun <T> any(): T = Mockito.any<T>()
47 
48 @SmallTest
49 @RunWith(AndroidTestingRunner::class)
50 class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
51 
52     @Mock
53     private lateinit var privacyItemController: PrivacyItemController
54     @Mock
55     private lateinit var uiEventLogger: UiEventLogger
56     @Mock
57     private lateinit var privacyChip: OngoingPrivacyChip
58     @Mock
59     private lateinit var privacyDialogController: PrivacyDialogController
60     @Mock
61     private lateinit var privacyDialogControllerV2: PrivacyDialogControllerV2
62     @Mock
63     private lateinit var privacyLogger: PrivacyLogger
64     @Mock
65     private lateinit var iconContainer: StatusIconContainer
66     @Mock
67     private lateinit var permissionManager: PermissionManager
68     @Mock
69     private lateinit var activityStarter: ActivityStarter
70     @Mock
71     private lateinit var appOpsController: AppOpsController
72     @Mock
73     private lateinit var broadcastDispatcher: BroadcastDispatcher
74     @Mock
75     private lateinit var safetyCenterManager: SafetyCenterManager
76     @Mock
77     private lateinit var deviceProvisionedController: DeviceProvisionedController
78     @Mock
79     private lateinit var featureFlags: FeatureFlags
80 
81     private val uiExecutor = FakeExecutor(FakeSystemClock())
82     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
83     private lateinit var cameraSlotName: String
84     private lateinit var microphoneSlotName: String
85     private lateinit var locationSlotName: String
86     private lateinit var controller: HeaderPrivacyIconsController
87 
88     @Before
89     fun setUp() {
90         MockitoAnnotations.initMocks(this)
91         whenever(privacyChip.context).thenReturn(context)
92         whenever(privacyChip.resources).thenReturn(context.resources)
93         whenever(privacyChip.isAttachedToWindow).thenReturn(true)
94         whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
95 
96         cameraSlotName = context.getString(com.android.internal.R.string.status_bar_camera)
97         microphoneSlotName = context.getString(com.android.internal.R.string.status_bar_microphone)
98         locationSlotName = context.getString(com.android.internal.R.string.status_bar_location)
99 
100         controller = HeaderPrivacyIconsController(
101                 privacyItemController,
102                 uiEventLogger,
103                 privacyChip,
104                 privacyDialogController,
105                 privacyDialogControllerV2,
106                 privacyLogger,
107                 iconContainer,
108                 permissionManager,
109                 backgroundExecutor,
110                 uiExecutor,
111                 activityStarter,
112                 appOpsController,
113                 broadcastDispatcher,
114                 safetyCenterManager,
115                 deviceProvisionedController,
116                 featureFlags
117         )
118 
119         backgroundExecutor.runAllReady()
120     }
121 
122     @Test
123     fun testIgnoredSlotsOnParentVisible_noIndicators() {
124         setPrivacyController(micCamera = false, location = false)
125 
126         controller.onParentVisible()
127 
128         verify(iconContainer).removeIgnoredSlot(cameraSlotName)
129         verify(iconContainer).removeIgnoredSlot(microphoneSlotName)
130         verify(iconContainer).removeIgnoredSlot(locationSlotName)
131     }
132 
133     @Test
134     fun testIgnoredSlotsOnParentVisible_onlyMicCamera() {
135         setPrivacyController(micCamera = true, location = false)
136 
137         controller.onParentVisible()
138 
139         verify(iconContainer).addIgnoredSlot(cameraSlotName)
140         verify(iconContainer).addIgnoredSlot(microphoneSlotName)
141         verify(iconContainer).removeIgnoredSlot(locationSlotName)
142     }
143 
144     @Test
145     fun testIgnoredSlotsOnParentVisible_onlyLocation() {
146         setPrivacyController(micCamera = false, location = true)
147 
148         controller.onParentVisible()
149 
150         verify(iconContainer).removeIgnoredSlot(cameraSlotName)
151         verify(iconContainer).removeIgnoredSlot(microphoneSlotName)
152         verify(iconContainer).addIgnoredSlot(locationSlotName)
153     }
154 
155     @Test
156     fun testIgnoredSlotsOnParentVisible_locationMicCamera() {
157         setPrivacyController(micCamera = true, location = true)
158 
159         controller.onParentVisible()
160 
161         verify(iconContainer).addIgnoredSlot(cameraSlotName)
162         verify(iconContainer).addIgnoredSlot(microphoneSlotName)
163         verify(iconContainer).addIgnoredSlot(locationSlotName)
164     }
165 
166     @Test
167     fun testPrivacyChipClickedWhenNewDialogDisabledAndSafetyCenterDisabled() {
168         whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(false)
169         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
170         controller.onParentVisible()
171         val captor = argumentCaptor<View.OnClickListener>()
172         verify(privacyChip).setOnClickListener(capture(captor))
173         captor.value.onClick(privacyChip)
174         verify(privacyDialogController).showDialog(any(Context::class.java))
175         verify(privacyDialogControllerV2, never())
176             .showDialog(any(Context::class.java), any(View::class.java))
177     }
178 
179     @Test
180     fun testPrivacyChipClickedWhenNewDialogEnabledAndSafetyCenterDisabled() {
181         whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(true)
182         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
183         controller.onParentVisible()
184         val captor = argumentCaptor<View.OnClickListener>()
185         verify(privacyChip).setOnClickListener(capture(captor))
186         captor.value.onClick(privacyChip)
187         verify(privacyDialogController).showDialog(any(Context::class.java))
188         verify(privacyDialogControllerV2, never())
189                 .showDialog(any(Context::class.java), any(View::class.java))
190     }
191 
192     @Test
193     fun testPrivacyChipClickedWhenNewDialogDisabledAndSafetyCenterEnabled() {
194         whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(false)
195         val receiverCaptor = argumentCaptor<BroadcastReceiver>()
196         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
197         verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
198                 any(), any(), nullable(), anyInt(), nullable())
199         receiverCaptor.value.onReceive(
200                 context,
201                 Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
202         )
203         backgroundExecutor.runAllReady()
204         controller.onParentVisible()
205         val captor = argumentCaptor<View.OnClickListener>()
206         verify(privacyChip).setOnClickListener(capture(captor))
207         captor.value.onClick(privacyChip)
208         verify(privacyDialogController, never()).showDialog(any(Context::class.java))
209         verify(privacyDialogControllerV2, never())
210             .showDialog(any(Context::class.java), any(View::class.java))
211     }
212 
213     @Test
214     fun testPrivacyChipClickedWhenNewDialogEnabledAndSafetyCenterEnabled() {
215         whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(true)
216         val receiverCaptor = argumentCaptor<BroadcastReceiver>()
217         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
218         verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
219                 any(), any(), nullable(), anyInt(), nullable())
220         receiverCaptor.value.onReceive(
221                 context,
222                 Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
223         )
224         backgroundExecutor.runAllReady()
225         controller.onParentVisible()
226         val captor = argumentCaptor<View.OnClickListener>()
227         verify(privacyChip).setOnClickListener(capture(captor))
228         captor.value.onClick(privacyChip)
229         verify(privacyDialogControllerV2).showDialog(any(Context::class.java), eq(privacyChip))
230         verify(privacyDialogController, never()).showDialog(any(Context::class.java))
231     }
232 
233     @Test
234     fun testBroadcastReceiverUnregisteredWhenChipDetached() {
235         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
236         controller.attachStateChangeListener.onViewDetachedFromWindow(privacyChip)
237         backgroundExecutor.runAllReady()
238         val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
239         verify(broadcastDispatcher).unregisterReceiver(capture(broadcastReceiverCaptor))
240     }
241 
242     @Test
243     fun testBroadcastReceiverRegisteredWhenChipAttached() {
244         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
245         controller.attachStateChangeListener.onViewAttachedToWindow(privacyChip)
246         backgroundExecutor.runAllReady()
247         val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
248         val intentFilterCaptor = argumentCaptor<IntentFilter>()
249         // Broadcast receiver is registered on init and when privacy chip is attached
250         verify(broadcastDispatcher, times(2)).registerReceiver(
251             capture(broadcastReceiverCaptor),
252             capture(intentFilterCaptor), any(), nullable(), anyInt(), nullable()
253         )
254     }
255 
256     @Test
257     fun testNoDialogWhenDeviceNotProvisioned() {
258         whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
259         controller.onParentVisible()
260 
261         val captor = argumentCaptor<View.OnClickListener>()
262         verify(privacyChip).setOnClickListener(capture(captor))
263 
264         captor.value.onClick(privacyChip)
265         verify(privacyDialogController, never()).showDialog(any(Context::class.java))
266     }
267 
268     private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
269         whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera)
270         whenever(privacyItemController.locationAvailable).thenReturn(location)
271     }
272 }