• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.statusbar.phone
18 
19 import android.app.AlarmManager
20 import android.app.AutomaticZenRule
21 import android.app.NotificationManager
22 import android.app.admin.DevicePolicyManager
23 import android.app.admin.DevicePolicyResourcesManager
24 import android.content.SharedPreferences
25 import android.net.Uri
26 import android.os.UserManager
27 import android.platform.test.annotations.DisableFlags
28 import android.platform.test.annotations.EnableFlags
29 import android.provider.Settings
30 import android.service.notification.SystemZenRules
31 import android.service.notification.ZenModeConfig
32 import android.telecom.TelecomManager
33 import android.testing.TestableLooper
34 import android.testing.TestableLooper.RunWithLooper
35 import androidx.test.ext.junit.runners.AndroidJUnit4
36 import androidx.test.filters.SmallTest
37 import com.android.internal.statusbar.StatusBarIcon
38 import com.android.settingslib.notification.modes.TestModeBuilder
39 import com.android.systemui.SysuiTestCase
40 import com.android.systemui.broadcast.BroadcastDispatcher
41 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
42 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
43 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
44 import com.android.systemui.kosmos.testScope
45 import com.android.systemui.privacy.PrivacyItemController
46 import com.android.systemui.privacy.logging.PrivacyLogger
47 import com.android.systemui.settings.UserTracker
48 import com.android.systemui.statusbar.CommandQueue
49 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
50 import com.android.systemui.statusbar.policy.BluetoothController
51 import com.android.systemui.statusbar.policy.DataSaverController
52 import com.android.systemui.statusbar.policy.DeviceProvisionedController
53 import com.android.systemui.statusbar.policy.HotspotController
54 import com.android.systemui.statusbar.policy.KeyguardStateController
55 import com.android.systemui.statusbar.policy.LocationController
56 import com.android.systemui.statusbar.policy.NextAlarmController
57 import com.android.systemui.statusbar.policy.RotationLockController
58 import com.android.systemui.statusbar.policy.SensorPrivacyController
59 import com.android.systemui.statusbar.policy.UserInfoController
60 import com.android.systemui.statusbar.policy.ZenModeController
61 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
62 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
63 import com.android.systemui.testKosmos
64 import com.android.systemui.util.RingerModeTracker
65 import com.android.systemui.util.concurrency.FakeExecutor
66 import com.android.systemui.util.kotlin.JavaAdapter
67 import com.android.systemui.util.mockito.capture
68 import com.android.systemui.util.time.DateFormatUtil
69 import com.android.systemui.util.time.FakeSystemClock
70 import kotlinx.coroutines.flow.Flow
71 import kotlinx.coroutines.flow.MutableStateFlow
72 import kotlinx.coroutines.test.runCurrent
73 import kotlinx.coroutines.test.runTest
74 import org.junit.Before
75 import org.junit.Test
76 import org.junit.runner.RunWith
77 import org.mockito.Answers
78 import org.mockito.ArgumentCaptor
79 import org.mockito.Captor
80 import org.mockito.Mock
81 import org.mockito.Mockito.anyInt
82 import org.mockito.Mockito.anyString
83 import org.mockito.Mockito.clearInvocations
84 import org.mockito.Mockito.inOrder
85 import org.mockito.Mockito.never
86 import org.mockito.Mockito.verify
87 import org.mockito.Mockito.`when` as whenever
88 import org.mockito.MockitoAnnotations
89 import org.mockito.kotlin.any
90 import org.mockito.kotlin.eq
91 import org.mockito.kotlin.reset
92 
93 @RunWith(AndroidJUnit4::class)
94 @RunWithLooper
95 @SmallTest
96 class PhoneStatusBarPolicyTest : SysuiTestCase() {
97 
98     private val kosmos = testKosmos()
99     private val zenModeRepository = kosmos.fakeZenModeRepository
100 
101     companion object {
102         private const val ZEN_SLOT = "zen"
103         private const val ALARM_SLOT = "alarm"
104         private const val CONNECTED_DISPLAY_SLOT = "connected_display"
105         private const val MANAGED_PROFILE_SLOT = "managed_profile"
106     }
107 
108     @Mock private lateinit var iconController: StatusBarIconController
109     @Mock private lateinit var commandQueue: CommandQueue
110     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
111     @Mock private lateinit var hotspotController: HotspotController
112     @Mock private lateinit var bluetoothController: BluetoothController
113     @Mock private lateinit var nextAlarmController: NextAlarmController
114     @Mock private lateinit var userInfoController: UserInfoController
115     @Mock private lateinit var rotationLockController: RotationLockController
116     @Mock private lateinit var dataSaverController: DataSaverController
117     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
118     @Mock private lateinit var keyguardStateController: KeyguardStateController
119     @Mock private lateinit var locationController: LocationController
120     @Mock private lateinit var sensorPrivacyController: SensorPrivacyController
121     @Mock private lateinit var alarmManager: AlarmManager
122     @Mock private lateinit var userManager: UserManager
123     @Mock private lateinit var userTracker: UserTracker
124     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
125     @Mock private lateinit var devicePolicyManagerResources: DevicePolicyResourcesManager
126     @Mock private lateinit var telecomManager: TelecomManager
127     @Mock private lateinit var sharedPreferences: SharedPreferences
128     @Mock private lateinit var dateFormatUtil: DateFormatUtil
129     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
130     private lateinit var ringerModeTracker: RingerModeTracker
131     @Mock private lateinit var privacyItemController: PrivacyItemController
132     @Mock private lateinit var privacyLogger: PrivacyLogger
133     @Captor
134     private lateinit var alarmCallbackCaptor:
135         ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
136 
137     private val testScope = kosmos.testScope
138     private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
139     private val zenModeController = FakeZenModeController()
140 
141     private lateinit var executor: FakeExecutor
142     private lateinit var statusBarPolicy: PhoneStatusBarPolicy
143     private lateinit var testableLooper: TestableLooper
144 
145     @Before
setUpnull146     fun setUp() {
147         MockitoAnnotations.initMocks(this)
148         executor = FakeExecutor(FakeSystemClock())
149         testableLooper = TestableLooper.get(this)
150         context.orCreateTestableResources.addOverride(
151             com.android.internal.R.string.status_bar_alarm_clock,
152             ALARM_SLOT,
153         )
154         context.orCreateTestableResources.addOverride(
155             com.android.internal.R.string.status_bar_managed_profile,
156             MANAGED_PROFILE_SLOT,
157         )
158         whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources)
159         whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("")
160         statusBarPolicy = createStatusBarPolicy()
161     }
162 
163     @Test
testDeviceNotProvisioned_alarmIconNotShownnull164     fun testDeviceNotProvisioned_alarmIconNotShown() {
165         val alarmInfo = createAlarmInfo()
166 
167         whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
168         statusBarPolicy.init()
169         verify(nextAlarmController).addCallback(capture(alarmCallbackCaptor))
170 
171         whenever(alarmManager.getNextAlarmClock(anyInt())).thenReturn(alarmInfo)
172 
173         alarmCallbackCaptor.value.onNextAlarmChanged(alarmInfo)
174         verify(iconController, never()).setIconVisibility(ALARM_SLOT, true)
175     }
176 
177     @Test
testDeviceProvisioned_alarmIconShownnull178     fun testDeviceProvisioned_alarmIconShown() {
179         val alarmInfo = createAlarmInfo()
180 
181         whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
182         statusBarPolicy.init()
183 
184         verify(nextAlarmController).addCallback(capture(alarmCallbackCaptor))
185         whenever(alarmManager.getNextAlarmClock(anyInt())).thenReturn(alarmInfo)
186 
187         alarmCallbackCaptor.value.onNextAlarmChanged(alarmInfo)
188         verify(iconController).setIconVisibility(ALARM_SLOT, true)
189     }
190 
191     @Test
testDeviceProvisionedChanged_alarmIconShownAfterCurrentUserSetupnull192     fun testDeviceProvisionedChanged_alarmIconShownAfterCurrentUserSetup() {
193         val alarmInfo = createAlarmInfo()
194 
195         whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
196         statusBarPolicy.init()
197 
198         verify(nextAlarmController).addCallback(capture(alarmCallbackCaptor))
199         whenever(alarmManager.getNextAlarmClock(anyInt())).thenReturn(alarmInfo)
200 
201         alarmCallbackCaptor.value.onNextAlarmChanged(alarmInfo)
202         verify(iconController, never()).setIconVisibility(ALARM_SLOT, true)
203 
204         whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
205         statusBarPolicy.onUserSetupChanged()
206         verify(iconController).setIconVisibility(ALARM_SLOT, true)
207     }
208 
209     @Test
testAppTransitionFinished_doesNotShowManagedProfileIconnull210     fun testAppTransitionFinished_doesNotShowManagedProfileIcon() {
211         whenever(userManager.isProfile(anyInt())).thenReturn(true)
212         whenever(userManager.getUserStatusBarIconResId(anyInt())).thenReturn(0 /* ID_NULL */)
213         whenever(keyguardStateController.isShowing).thenReturn(false)
214         statusBarPolicy.appTransitionFinished(0)
215         // The above call posts to bgExecutor and then back to mainExecutor
216         executor.advanceClockToLast()
217         executor.runAllReady()
218         executor.advanceClockToLast()
219         executor.runAllReady()
220         verify(iconController, never()).setIconVisibility(MANAGED_PROFILE_SLOT, true)
221     }
222 
223     @Test
testAppTransitionFinished_showsManagedProfileIconnull224     fun testAppTransitionFinished_showsManagedProfileIcon() {
225         whenever(userManager.isProfile(anyInt())).thenReturn(true)
226         whenever(userManager.getUserStatusBarIconResId(anyInt())).thenReturn(100)
227         whenever(keyguardStateController.isShowing).thenReturn(false)
228         statusBarPolicy.appTransitionFinished(0)
229         // The above call posts to bgExecutor and then back to mainExecutor
230         executor.advanceClockToLast()
231         executor.runAllReady()
232         executor.advanceClockToLast()
233         executor.runAllReady()
234         verify(iconController).setIconVisibility(MANAGED_PROFILE_SLOT, true)
235     }
236 
237     @Test
connectedDisplay_connected_iconShownnull238     fun connectedDisplay_connected_iconShown() =
239         testScope.runTest {
240             statusBarPolicy.init()
241             clearInvocations(iconController)
242 
243             fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
244             runCurrent()
245 
246             verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
247         }
248 
249     @Test
connectedDisplay_disconnected_iconHiddennull250     fun connectedDisplay_disconnected_iconHidden() =
251         testScope.runTest {
252             statusBarPolicy.init()
253             clearInvocations(iconController)
254 
255             fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED)
256             runCurrent()
257 
258             verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
259         }
260 
261     @Test
connectedDisplay_disconnectedThenConnected_iconShownnull262     fun connectedDisplay_disconnectedThenConnected_iconShown() =
263         testScope.runTest {
264             statusBarPolicy.init()
265             clearInvocations(iconController)
266 
267             fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
268             runCurrent()
269             fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED)
270             runCurrent()
271             fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
272             runCurrent()
273 
274             inOrder(iconController).apply {
275                 verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
276                 verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
277                 verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
278             }
279         }
280 
281     @Test
connectedDisplay_connectSecureDisplay_iconShownnull282     fun connectedDisplay_connectSecureDisplay_iconShown() =
283         testScope.runTest {
284             statusBarPolicy.init()
285             clearInvocations(iconController)
286 
287             fakeConnectedDisplayStateProvider.setState(State.CONNECTED_SECURE)
288             runCurrent()
289 
290             verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
291         }
292 
293     @Test
294     @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
zenModeInteractorActiveModeChanged_showsModeIconnull295     fun zenModeInteractorActiveModeChanged_showsModeIcon() =
296         testScope.runTest {
297             statusBarPolicy.init()
298             reset(iconController)
299 
300             zenModeRepository.addModes(
301                 listOf(
302                     TestModeBuilder()
303                         .setId("bedtime")
304                         .setName("Bedtime Mode")
305                         .setType(AutomaticZenRule.TYPE_BEDTIME)
306                         .setActive(true)
307                         .setPackage(mContext.packageName)
308                         .setIconResId(android.R.drawable.ic_lock_lock)
309                         .build(),
310                     TestModeBuilder()
311                         .setId("other")
312                         .setName("Other Mode")
313                         .setType(AutomaticZenRule.TYPE_OTHER)
314                         .setActive(true)
315                         .setPackage(SystemZenRules.PACKAGE_ANDROID)
316                         .setIconResId(android.R.drawable.ic_media_play)
317                         .build(),
318                 )
319             )
320             runCurrent()
321 
322             verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
323             verify(iconController)
324                 .setResourceIcon(
325                     eq(ZEN_SLOT),
326                     eq(mContext.packageName),
327                     eq(android.R.drawable.ic_lock_lock),
328                     any(), // non-null
329                     eq("Bedtime Mode is on"),
330                     eq(StatusBarIcon.Shape.FIXED_SPACE),
331                 )
332 
333             zenModeRepository.deactivateMode("bedtime")
334             runCurrent()
335 
336             verify(iconController)
337                 .setResourceIcon(
338                     eq(ZEN_SLOT),
339                     eq(null),
340                     eq(android.R.drawable.ic_media_play),
341                     any(), // non-null
342                     eq("Other Mode is on"),
343                     eq(StatusBarIcon.Shape.FIXED_SPACE),
344                 )
345 
346             zenModeRepository.deactivateMode("other")
347             runCurrent()
348 
349             verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
350         }
351 
352     @Test
353     @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS)
zenModeControllerOnGlobalZenChanged_doesNotUpdateDndIconnull354     fun zenModeControllerOnGlobalZenChanged_doesNotUpdateDndIcon() {
355         statusBarPolicy.init()
356         reset(iconController)
357 
358         zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
359 
360         verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
361         verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
362         verify(iconController, never())
363             .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any(), any())
364     }
365 
366     @Test
367     @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
zenModeInteractorActiveModeChanged_withFlagDisabled_ignorednull368     fun zenModeInteractorActiveModeChanged_withFlagDisabled_ignored() =
369         testScope.runTest {
370             statusBarPolicy.init()
371             reset(iconController)
372 
373             zenModeRepository.addMode(id = "Bedtime", active = true)
374             runCurrent()
375 
376             verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any())
377             verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any())
378             verify(iconController, never())
379                 .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any(), any())
380         }
381 
382     @Test
383     @DisableFlags(android.app.Flags.FLAG_MODES_UI_ICONS)
zenModeControllerOnGlobalZenChanged_withFlagDisabled_updatesDndIconnull384     fun zenModeControllerOnGlobalZenChanged_withFlagDisabled_updatesDndIcon() {
385         statusBarPolicy.init()
386         reset(iconController)
387 
388         zenModeController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null)
389 
390         verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
391         verify(iconController).setIcon(eq(ZEN_SLOT), anyInt(), eq("Priority only"))
392 
393         zenModeController.setZen(Settings.Global.ZEN_MODE_OFF, null, null)
394 
395         verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
396     }
397 
createAlarmInfonull398     private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
399         return AlarmManager.AlarmClockInfo(10L, null)
400     }
401 
createStatusBarPolicynull402     private fun createStatusBarPolicy(): PhoneStatusBarPolicy {
403         return PhoneStatusBarPolicy(
404             iconController,
405             commandQueue,
406             broadcastDispatcher,
407             executor,
408             executor,
409             testableLooper.looper,
410             context.resources,
411             hotspotController,
412             bluetoothController,
413             nextAlarmController,
414             userInfoController,
415             rotationLockController,
416             dataSaverController,
417             zenModeController,
418             deviceProvisionedController,
419             keyguardStateController,
420             locationController,
421             sensorPrivacyController,
422             alarmManager,
423             userManager,
424             userTracker,
425             devicePolicyManager,
426             telecomManager,
427             /* displayId = */ 0,
428             sharedPreferences,
429             dateFormatUtil,
430             ringerModeTracker,
431             privacyItemController,
432             privacyLogger,
433             fakeConnectedDisplayStateProvider,
434             kosmos.zenModeInteractor,
435             JavaAdapter(testScope.backgroundScope),
436         )
437     }
438 
439     private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
440         private val flow = MutableStateFlow(State.DISCONNECTED)
441 
setStatenull442         fun setState(value: State) {
443             flow.value = value
444         }
445 
446         override val connectedDisplayState: Flow<State>
447             get() = flow
448 
449         override val connectedDisplayAddition: Flow<Unit>
450             get() = TODO("Not yet implemented")
451 
452         override val pendingDisplay: Flow<PendingDisplay?>
453             get() = TODO("Not yet implemented")
454 
455         override val concurrentDisplaysInProgress: Flow<Boolean>
456             get() = TODO("Not yet implemented")
457     }
458 
459     private class FakeZenModeController : ZenModeController {
460 
461         private val callbacks = mutableListOf<ZenModeController.Callback>()
462         private var zen = Settings.Global.ZEN_MODE_OFF
463         private var consolidatedPolicy = NotificationManager.Policy(0, 0, 0)
464 
addCallbacknull465         override fun addCallback(listener: ZenModeController.Callback) {
466             callbacks.add(listener)
467         }
468 
removeCallbacknull469         override fun removeCallback(listener: ZenModeController.Callback) {
470             callbacks.remove(listener)
471         }
472 
setZennull473         override fun setZen(zen: Int, conditionId: Uri?, reason: String?) {
474             this.zen = zen
475             callbacks.forEach { it.onZenChanged(zen) }
476         }
477 
getZennull478         override fun getZen(): Int = zen
479 
480         override fun getConfig(): ZenModeConfig = throw NotImplementedError()
481 
482         override fun getConsolidatedPolicy(): NotificationManager.Policy = consolidatedPolicy
483 
484         override fun getNextAlarm() = throw NotImplementedError()
485 
486         override fun isZenAvailable() = throw NotImplementedError()
487 
488         override fun getCurrentUser() = throw NotImplementedError()
489     }
490 }
491