1 /* 2 * Copyright (C) 2021 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.media.taptotransfer.receiver 18 19 import android.app.StatusBarManager 20 import android.content.pm.ApplicationInfo 21 import android.content.pm.PackageManager 22 import android.graphics.drawable.Drawable 23 import android.media.MediaRoute2Info 24 import android.os.Handler 25 import android.os.PowerManager 26 import android.testing.TestableLooper 27 import android.view.View 28 import android.view.ViewGroup 29 import android.view.WindowManager 30 import android.view.accessibility.AccessibilityManager 31 import android.widget.ImageView 32 import androidx.test.ext.junit.runners.AndroidJUnit4 33 import androidx.test.filters.SmallTest 34 import com.android.internal.logging.InstanceId 35 import com.android.internal.logging.testing.UiEventLoggerFake 36 import com.android.systemui.res.R 37 import com.android.systemui.SysuiTestCase 38 import com.android.systemui.dump.DumpManager 39 import com.android.systemui.media.taptotransfer.MediaTttFlags 40 import com.android.systemui.statusbar.CommandQueue 41 import com.android.systemui.statusbar.policy.ConfigurationController 42 import com.android.systemui.temporarydisplay.TemporaryViewUiEventLogger 43 import com.android.systemui.util.concurrency.FakeExecutor 44 import com.android.systemui.util.mockito.any 45 import com.android.systemui.util.mockito.eq 46 import com.android.systemui.util.time.FakeSystemClock 47 import com.android.systemui.util.view.ViewUtil 48 import com.android.systemui.util.wakelock.WakeLockFake 49 import com.google.common.truth.Truth.assertThat 50 import org.junit.Before 51 import org.junit.Test 52 import org.junit.runner.RunWith 53 import org.mockito.ArgumentCaptor 54 import org.mockito.Mock 55 import org.mockito.Mockito.never 56 import org.mockito.Mockito.reset 57 import org.mockito.Mockito.verify 58 import org.mockito.Mockito.`when` as whenever 59 import org.mockito.MockitoAnnotations 60 61 @SmallTest 62 @RunWith(AndroidJUnit4::class) 63 @TestableLooper.RunWithLooper 64 class MediaTttChipControllerReceiverTest : SysuiTestCase() { 65 private lateinit var controllerReceiver: MediaTttChipControllerReceiver 66 67 @Mock 68 private lateinit var packageManager: PackageManager 69 @Mock 70 private lateinit var applicationInfo: ApplicationInfo 71 @Mock 72 private lateinit var logger: MediaTttReceiverLogger 73 @Mock 74 private lateinit var accessibilityManager: AccessibilityManager 75 @Mock 76 private lateinit var configurationController: ConfigurationController 77 @Mock 78 private lateinit var dumpManager: DumpManager 79 @Mock 80 private lateinit var mediaTttFlags: MediaTttFlags 81 @Mock 82 private lateinit var powerManager: PowerManager 83 @Mock 84 private lateinit var viewUtil: ViewUtil 85 @Mock 86 private lateinit var windowManager: WindowManager 87 @Mock 88 private lateinit var commandQueue: CommandQueue 89 @Mock 90 private lateinit var rippleController: MediaTttReceiverRippleController 91 private lateinit var commandQueueCallback: CommandQueue.Callbacks 92 private lateinit var fakeAppIconDrawable: Drawable 93 private lateinit var uiEventLoggerFake: UiEventLoggerFake 94 private lateinit var receiverUiEventLogger: MediaTttReceiverUiEventLogger 95 private lateinit var temporaryViewUiEventLogger: TemporaryViewUiEventLogger 96 private lateinit var fakeClock: FakeSystemClock 97 private lateinit var fakeExecutor: FakeExecutor 98 private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder 99 private lateinit var fakeWakeLock: WakeLockFake 100 101 @Before setUpnull102 fun setUp() { 103 MockitoAnnotations.initMocks(this) 104 whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true) 105 106 fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! 107 whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) 108 whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) 109 whenever(packageManager.getApplicationInfo( 110 eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() 111 )).thenReturn(applicationInfo) 112 context.setMockPackageManager(packageManager) 113 114 fakeClock = FakeSystemClock() 115 fakeExecutor = FakeExecutor(fakeClock) 116 117 uiEventLoggerFake = UiEventLoggerFake() 118 receiverUiEventLogger = MediaTttReceiverUiEventLogger(uiEventLoggerFake) 119 temporaryViewUiEventLogger = TemporaryViewUiEventLogger(uiEventLoggerFake) 120 121 fakeWakeLock = WakeLockFake() 122 fakeWakeLockBuilder = WakeLockFake.Builder(context) 123 fakeWakeLockBuilder.setWakeLock(fakeWakeLock) 124 125 controllerReceiver = FakeMediaTttChipControllerReceiver( 126 commandQueue, 127 context, 128 logger, 129 windowManager, 130 fakeExecutor, 131 accessibilityManager, 132 configurationController, 133 dumpManager, 134 powerManager, 135 Handler.getMain(), 136 mediaTttFlags, 137 receiverUiEventLogger, 138 viewUtil, 139 fakeWakeLockBuilder, 140 fakeClock, 141 rippleController, 142 temporaryViewUiEventLogger, 143 ) 144 controllerReceiver.start() 145 146 val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 147 verify(commandQueue).addCallback(callbackCaptor.capture()) 148 commandQueueCallback = callbackCaptor.value!! 149 } 150 151 @Test commandQueueCallback_flagOff_noCallbackAddednull152 fun commandQueueCallback_flagOff_noCallbackAdded() { 153 reset(commandQueue) 154 whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false) 155 156 controllerReceiver = MediaTttChipControllerReceiver( 157 commandQueue, 158 context, 159 logger, 160 windowManager, 161 FakeExecutor(FakeSystemClock()), 162 accessibilityManager, 163 configurationController, 164 dumpManager, 165 powerManager, 166 Handler.getMain(), 167 mediaTttFlags, 168 receiverUiEventLogger, 169 viewUtil, 170 fakeWakeLockBuilder, 171 fakeClock, 172 rippleController, 173 temporaryViewUiEventLogger, 174 ) 175 controllerReceiver.start() 176 177 verify(commandQueue, never()).addCallback(any()) 178 } 179 180 @Test commandQueueCallback_closeToSender_triggersChipnull181 fun commandQueueCallback_closeToSender_triggersChip() { 182 val appName = "FakeAppName" 183 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 184 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 185 routeInfo, 186 /* appIcon= */ null, 187 appName 188 ) 189 190 assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName) 191 assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( 192 MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER.id 193 ) 194 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 195 } 196 197 @Test commandQueueCallback_farFromSender_noChipShownnull198 fun commandQueueCallback_farFromSender_noChipShown() { 199 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 200 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 201 routeInfo, 202 null, 203 null 204 ) 205 206 verify(windowManager, never()).addView(any(), any()) 207 assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( 208 MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER.id 209 ) 210 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 211 } 212 213 @Test commandQueueCallback_transferToReceiverSucceeded_noChipShownnull214 fun commandQueueCallback_transferToReceiverSucceeded_noChipShown() { 215 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 216 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, 217 routeInfo, 218 null, 219 null 220 ) 221 222 verify(windowManager, never()).addView(any(), any()) 223 assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( 224 MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED.id 225 ) 226 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 227 } 228 229 @Test commandQueueCallback_transferToReceiverFailed_noChipShownnull230 fun commandQueueCallback_transferToReceiverFailed_noChipShown() { 231 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 232 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED, 233 routeInfo, 234 null, 235 null 236 ) 237 238 verify(windowManager, never()).addView(any(), any()) 239 assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( 240 MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id 241 ) 242 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 243 } 244 245 @Test commandQueueCallback_closeThenFar_chipShownThenHiddennull246 fun commandQueueCallback_closeThenFar_chipShownThenHidden() { 247 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 248 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 249 routeInfo, 250 null, 251 null 252 ) 253 254 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 255 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 256 routeInfo, 257 null, 258 null 259 ) 260 261 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 262 verify(windowManager).addView(viewCaptor.capture(), any()) 263 verify(windowManager).removeView(viewCaptor.value) 264 } 265 266 @Test commandQueueCallback_closeThenSucceeded_chipShownThenHiddennull267 fun commandQueueCallback_closeThenSucceeded_chipShownThenHidden() { 268 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 269 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 270 routeInfo, 271 null, 272 null 273 ) 274 275 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 276 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, 277 routeInfo, 278 null, 279 null 280 ) 281 282 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 283 verify(windowManager).addView(viewCaptor.capture(), any()) 284 verify(windowManager).removeView(viewCaptor.value) 285 } 286 287 @Test commandQueueCallback_closeThenSucceeded_sameViewInstanceIdnull288 fun commandQueueCallback_closeThenSucceeded_sameViewInstanceId() { 289 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 290 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 291 routeInfo, 292 null, 293 null 294 ) 295 296 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 297 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, 298 routeInfo, 299 null, 300 null 301 ) 302 303 assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(uiEventLoggerFake[1].instanceId) 304 } 305 306 @Test commandQueueCallback_closeThenFailed_chipShownThenHiddennull307 fun commandQueueCallback_closeThenFailed_chipShownThenHidden() { 308 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 309 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 310 routeInfo, 311 null, 312 null 313 ) 314 315 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 316 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED, 317 routeInfo, 318 null, 319 null 320 ) 321 322 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 323 verify(windowManager).addView(viewCaptor.capture(), any()) 324 verify(windowManager).removeView(viewCaptor.value) 325 } 326 327 @Test commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleasednull328 fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() { 329 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 330 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 331 routeInfo, 332 null, 333 null 334 ) 335 336 assertThat(fakeWakeLock.isHeld).isTrue() 337 338 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 339 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 340 routeInfo, 341 null, 342 null 343 ) 344 345 assertThat(fakeWakeLock.isHeld).isFalse() 346 } 347 348 @Test commandQueueCallback_closeThenFar_wakeLockNeverAcquirednull349 fun commandQueueCallback_closeThenFar_wakeLockNeverAcquired() { 350 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 351 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 352 routeInfo, 353 null, 354 null 355 ) 356 357 assertThat(fakeWakeLock.isHeld).isFalse() 358 } 359 360 @Test receivesNewStateFromCommandQueue_isLoggednull361 fun receivesNewStateFromCommandQueue_isLogged() { 362 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 363 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 364 routeInfo, 365 null, 366 null 367 ) 368 369 verify(logger).logStateChange(any(), any(), any()) 370 } 371 372 @Test updateView_noOverrides_usesInfoFromAppIconnull373 fun updateView_noOverrides_usesInfoFromAppIcon() { 374 controllerReceiver.displayView( 375 ChipReceiverInfo( 376 routeInfo, 377 appIconDrawableOverride = null, 378 appNameOverride = null, 379 id = "id", 380 instanceId = InstanceId.fakeInstanceId(0), 381 ) 382 ) 383 384 val view = getChipView() 385 assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) 386 assertThat(view.getAppIconView().contentDescription) 387 .isEqualTo(context.getString( 388 R.string.media_transfer_receiver_content_description_with_app_name, 389 APP_NAME, 390 )) 391 } 392 393 @Test updateView_appIconOverride_usesOverridenull394 fun updateView_appIconOverride_usesOverride() { 395 val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!! 396 397 controllerReceiver.displayView( 398 ChipReceiverInfo( 399 routeInfo, 400 drawableOverride, 401 appNameOverride = null, 402 id = "id", 403 instanceId = InstanceId.fakeInstanceId(0), 404 ) 405 ) 406 407 val view = getChipView() 408 assertThat(view.getAppIconView().drawable).isEqualTo(drawableOverride) 409 } 410 411 @Test updateView_appNameOverride_usesOverridenull412 fun updateView_appNameOverride_usesOverride() { 413 val appNameOverride = "Sweet New App" 414 415 controllerReceiver.displayView( 416 ChipReceiverInfo( 417 routeInfo, 418 appIconDrawableOverride = null, 419 appNameOverride, 420 id = "id", 421 instanceId = InstanceId.fakeInstanceId(0), 422 ) 423 ) 424 425 val view = getChipView() 426 assertThat(view.getAppIconView().contentDescription).isEqualTo(appNameOverride) 427 } 428 429 @Test updateView_isAppIcon_usesAppIconPaddingnull430 fun updateView_isAppIcon_usesAppIconPadding() { 431 controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME)) 432 433 val chipView = getChipView() 434 assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(0) 435 assertThat(chipView.getAppIconView().paddingRight).isEqualTo(0) 436 assertThat(chipView.getAppIconView().paddingTop).isEqualTo(0) 437 assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(0) 438 } 439 440 @Test updateView_notAppIcon_usesGenericIconPaddingnull441 fun updateView_notAppIcon_usesGenericIconPadding() { 442 controllerReceiver.displayView(getChipReceiverInfo(packageName = null)) 443 444 val chipView = getChipView() 445 val expectedPadding = 446 context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding) 447 assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(expectedPadding) 448 assertThat(chipView.getAppIconView().paddingRight).isEqualTo(expectedPadding) 449 assertThat(chipView.getAppIconView().paddingTop).isEqualTo(expectedPadding) 450 assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(expectedPadding) 451 } 452 453 @Test commandQueueCallback_invalidStateParam_noChipShownnull454 fun commandQueueCallback_invalidStateParam_noChipShown() { 455 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 456 StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, 457 routeInfo, 458 null, 459 APP_NAME 460 ) 461 462 verify(windowManager, never()).addView(any(), any()) 463 } 464 getChipViewnull465 private fun getChipView(): ViewGroup { 466 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 467 verify(windowManager).addView(viewCaptor.capture(), any()) 468 return viewCaptor.value as ViewGroup 469 } 470 getChipReceiverInfonull471 private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo { 472 val routeInfo = MediaRoute2Info.Builder("id", "Test route name") 473 .addFeature("feature") 474 .setClientPackageName(packageName) 475 .build() 476 return ChipReceiverInfo( 477 routeInfo, 478 null, 479 null, 480 id = "id", 481 instanceId = InstanceId.fakeInstanceId(0), 482 ) 483 } 484 ViewGroupnull485 private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) 486 } 487 488 private const val APP_NAME = "Fake app name" 489 private const val PACKAGE_NAME = "com.android.systemui" 490 491 private val routeInfo = MediaRoute2Info.Builder("id", "Test route name") 492 .addFeature("feature") 493 .setClientPackageName(PACKAGE_NAME) 494 .build() 495