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