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.app.viewcapture.ViewCapture 35 import com.android.app.viewcapture.ViewCaptureAwareWindowManager 36 import com.android.internal.logging.InstanceId 37 import com.android.internal.logging.testing.UiEventLoggerFake 38 import com.android.systemui.SysuiTestCase 39 import com.android.systemui.dump.DumpManager 40 import com.android.systemui.res.R 41 import com.android.systemui.statusbar.CommandQueue 42 import com.android.systemui.statusbar.policy.ConfigurationController 43 import com.android.systemui.temporarydisplay.TemporaryViewUiEventLogger 44 import com.android.systemui.util.concurrency.FakeExecutor 45 import com.android.systemui.util.mockito.any 46 import com.android.systemui.util.mockito.eq 47 import com.android.systemui.util.time.FakeSystemClock 48 import com.android.systemui.util.view.ViewUtil 49 import com.android.systemui.util.wakelock.WakeLockFake 50 import com.google.common.truth.Truth.assertThat 51 import org.junit.Before 52 import org.junit.Test 53 import org.junit.runner.RunWith 54 import org.mockito.ArgumentCaptor 55 import org.mockito.Mock 56 import org.mockito.Mockito.never 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 private lateinit var packageManager: PackageManager 68 @Mock private lateinit var applicationInfo: ApplicationInfo 69 @Mock private lateinit var logger: MediaTttReceiverLogger 70 @Mock private lateinit var accessibilityManager: AccessibilityManager 71 @Mock private lateinit var configurationController: ConfigurationController 72 @Mock private lateinit var dumpManager: DumpManager 73 @Mock private lateinit var powerManager: PowerManager 74 @Mock private lateinit var viewUtil: ViewUtil 75 @Mock private lateinit var windowManager: WindowManager 76 @Mock private lateinit var commandQueue: CommandQueue 77 @Mock private lateinit var rippleController: MediaTttReceiverRippleController 78 @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture> 79 private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager 80 private lateinit var commandQueueCallback: CommandQueue.Callbacks 81 private lateinit var fakeAppIconDrawable: Drawable 82 private lateinit var uiEventLoggerFake: UiEventLoggerFake 83 private lateinit var receiverUiEventLogger: MediaTttReceiverUiEventLogger 84 private lateinit var temporaryViewUiEventLogger: TemporaryViewUiEventLogger 85 private lateinit var fakeClock: FakeSystemClock 86 private lateinit var fakeExecutor: FakeExecutor 87 private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder 88 private lateinit var fakeWakeLock: WakeLockFake 89 90 @Before setUpnull91 fun setUp() { 92 MockitoAnnotations.initMocks(this) 93 94 fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! 95 whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) 96 whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) 97 whenever( 98 packageManager.getApplicationInfo( 99 eq(PACKAGE_NAME), 100 any<PackageManager.ApplicationInfoFlags>(), 101 ) 102 ) 103 .thenReturn(applicationInfo) 104 context.setMockPackageManager(packageManager) 105 106 fakeClock = FakeSystemClock() 107 fakeExecutor = FakeExecutor(fakeClock) 108 109 uiEventLoggerFake = UiEventLoggerFake() 110 receiverUiEventLogger = MediaTttReceiverUiEventLogger(uiEventLoggerFake) 111 temporaryViewUiEventLogger = TemporaryViewUiEventLogger(uiEventLoggerFake) 112 113 fakeWakeLock = WakeLockFake() 114 fakeWakeLockBuilder = WakeLockFake.Builder(context) 115 fakeWakeLockBuilder.setWakeLock(fakeWakeLock) 116 117 viewCaptureAwareWindowManager = 118 ViewCaptureAwareWindowManager( 119 windowManager, 120 lazyViewCapture, 121 isViewCaptureEnabled = false, 122 ) 123 controllerReceiver = 124 FakeMediaTttChipControllerReceiver( 125 commandQueue, 126 context, 127 logger, 128 viewCaptureAwareWindowManager, 129 fakeExecutor, 130 accessibilityManager, 131 configurationController, 132 dumpManager, 133 powerManager, 134 Handler.getMain(), 135 receiverUiEventLogger, 136 viewUtil, 137 fakeWakeLockBuilder, 138 fakeClock, 139 rippleController, 140 temporaryViewUiEventLogger, 141 ) 142 controllerReceiver.start() 143 144 val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 145 verify(commandQueue).addCallback(callbackCaptor.capture()) 146 commandQueueCallback = callbackCaptor.value!! 147 } 148 149 @Test commandQueueCallback_closeToSender_triggersChipnull150 fun commandQueueCallback_closeToSender_triggersChip() { 151 val appName = "FakeAppName" 152 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 153 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 154 routeInfo, 155 /* appIcon= */ null, 156 appName, 157 ) 158 159 assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName) 160 assertThat(uiEventLoggerFake.eventId(0)) 161 .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER.id) 162 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 163 } 164 165 @Test commandQueueCallback_farFromSender_noChipShownnull166 fun commandQueueCallback_farFromSender_noChipShown() { 167 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 168 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 169 routeInfo, 170 null, 171 null, 172 ) 173 174 verify(windowManager, never()).addView(any(), any()) 175 assertThat(uiEventLoggerFake.eventId(0)) 176 .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER.id) 177 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 178 } 179 180 @Test commandQueueCallback_transferToReceiverSucceeded_noChipShownnull181 fun commandQueueCallback_transferToReceiverSucceeded_noChipShown() { 182 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 183 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, 184 routeInfo, 185 null, 186 null, 187 ) 188 189 verify(windowManager, never()).addView(any(), any()) 190 assertThat(uiEventLoggerFake.eventId(0)) 191 .isEqualTo( 192 MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED.id 193 ) 194 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 195 } 196 197 @Test commandQueueCallback_transferToReceiverFailed_noChipShownnull198 fun commandQueueCallback_transferToReceiverFailed_noChipShown() { 199 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 200 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED, 201 routeInfo, 202 null, 203 null, 204 ) 205 206 verify(windowManager, never()).addView(any(), any()) 207 assertThat(uiEventLoggerFake.eventId(0)) 208 .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id) 209 assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() 210 } 211 212 @Test commandQueueCallback_closeThenFar_chipShownThenHiddennull213 fun commandQueueCallback_closeThenFar_chipShownThenHidden() { 214 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 215 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 216 routeInfo, 217 null, 218 null, 219 ) 220 221 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 222 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 223 routeInfo, 224 null, 225 null, 226 ) 227 228 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 229 verify(windowManager).addView(viewCaptor.capture(), any()) 230 verify(windowManager).removeView(viewCaptor.value) 231 } 232 233 @Test commandQueueCallback_closeThenSucceeded_chipShownThenHiddennull234 fun commandQueueCallback_closeThenSucceeded_chipShownThenHidden() { 235 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 236 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 237 routeInfo, 238 null, 239 null, 240 ) 241 242 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 243 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, 244 routeInfo, 245 null, 246 null, 247 ) 248 249 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 250 verify(windowManager).addView(viewCaptor.capture(), any()) 251 verify(windowManager).removeView(viewCaptor.value) 252 } 253 254 @Test commandQueueCallback_closeThenSucceeded_sameViewInstanceIdnull255 fun commandQueueCallback_closeThenSucceeded_sameViewInstanceId() { 256 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 257 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 258 routeInfo, 259 null, 260 null, 261 ) 262 263 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 264 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, 265 routeInfo, 266 null, 267 null, 268 ) 269 270 assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(uiEventLoggerFake[1].instanceId) 271 } 272 273 @Test commandQueueCallback_closeThenFailed_chipShownThenHiddennull274 fun commandQueueCallback_closeThenFailed_chipShownThenHidden() { 275 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 276 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 277 routeInfo, 278 null, 279 null, 280 ) 281 282 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 283 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED, 284 routeInfo, 285 null, 286 null, 287 ) 288 289 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 290 verify(windowManager).addView(viewCaptor.capture(), any()) 291 verify(windowManager).removeView(viewCaptor.value) 292 } 293 294 @Test commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleasednull295 fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() { 296 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 297 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 298 routeInfo, 299 null, 300 null, 301 ) 302 303 assertThat(fakeWakeLock.isHeld).isTrue() 304 305 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 306 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 307 routeInfo, 308 null, 309 null, 310 ) 311 312 assertThat(fakeWakeLock.isHeld).isFalse() 313 } 314 315 @Test commandQueueCallback_closeThenFar_wakeLockNeverAcquirednull316 fun commandQueueCallback_closeThenFar_wakeLockNeverAcquired() { 317 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 318 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, 319 routeInfo, 320 null, 321 null, 322 ) 323 324 assertThat(fakeWakeLock.isHeld).isFalse() 325 } 326 327 @Test receivesNewStateFromCommandQueue_isLoggednull328 fun receivesNewStateFromCommandQueue_isLogged() { 329 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 330 StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, 331 routeInfo, 332 null, 333 null, 334 ) 335 336 verify(logger).logStateChange(any(), any(), any()) 337 } 338 339 @Test updateView_noOverrides_usesInfoFromAppIconnull340 fun updateView_noOverrides_usesInfoFromAppIcon() { 341 controllerReceiver.displayView( 342 ChipReceiverInfo( 343 routeInfo, 344 appIconDrawableOverride = null, 345 appNameOverride = null, 346 id = "id", 347 instanceId = InstanceId.fakeInstanceId(0), 348 ) 349 ) 350 351 val view = getChipView() 352 assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) 353 assertThat(view.getAppIconView().contentDescription) 354 .isEqualTo( 355 context.getString( 356 R.string.media_transfer_receiver_content_description_with_app_name, 357 APP_NAME, 358 ) 359 ) 360 } 361 362 @Test updateView_appIconOverride_usesOverridenull363 fun updateView_appIconOverride_usesOverride() { 364 val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!! 365 366 controllerReceiver.displayView( 367 ChipReceiverInfo( 368 routeInfo, 369 drawableOverride, 370 appNameOverride = null, 371 id = "id", 372 instanceId = InstanceId.fakeInstanceId(0), 373 ) 374 ) 375 376 val view = getChipView() 377 assertThat(view.getAppIconView().drawable).isEqualTo(drawableOverride) 378 } 379 380 @Test updateView_appNameOverride_usesOverridenull381 fun updateView_appNameOverride_usesOverride() { 382 val appNameOverride = "Sweet New App" 383 384 controllerReceiver.displayView( 385 ChipReceiverInfo( 386 routeInfo, 387 appIconDrawableOverride = null, 388 appNameOverride, 389 id = "id", 390 instanceId = InstanceId.fakeInstanceId(0), 391 ) 392 ) 393 394 val view = getChipView() 395 assertThat(view.getAppIconView().contentDescription).isEqualTo(appNameOverride) 396 } 397 398 @Test updateView_isAppIcon_usesAppIconPaddingnull399 fun updateView_isAppIcon_usesAppIconPadding() { 400 controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME)) 401 402 val chipView = getChipView() 403 assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(0) 404 assertThat(chipView.getAppIconView().paddingRight).isEqualTo(0) 405 assertThat(chipView.getAppIconView().paddingTop).isEqualTo(0) 406 assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(0) 407 } 408 409 @Test updateView_notAppIcon_usesGenericIconPaddingnull410 fun updateView_notAppIcon_usesGenericIconPadding() { 411 controllerReceiver.displayView(getChipReceiverInfo(packageName = null)) 412 413 val chipView = getChipView() 414 val expectedPadding = 415 context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_padding) 416 assertThat(chipView.getAppIconView().paddingLeft).isEqualTo(expectedPadding) 417 assertThat(chipView.getAppIconView().paddingRight).isEqualTo(expectedPadding) 418 assertThat(chipView.getAppIconView().paddingTop).isEqualTo(expectedPadding) 419 assertThat(chipView.getAppIconView().paddingBottom).isEqualTo(expectedPadding) 420 } 421 422 @Test commandQueueCallback_invalidStateParam_noChipShownnull423 fun commandQueueCallback_invalidStateParam_noChipShown() { 424 commandQueueCallback.updateMediaTapToTransferReceiverDisplay( 425 StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, 426 routeInfo, 427 null, 428 APP_NAME, 429 ) 430 431 verify(windowManager, never()).addView(any(), any()) 432 } 433 getChipViewnull434 private fun getChipView(): ViewGroup { 435 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 436 verify(windowManager).addView(viewCaptor.capture(), any()) 437 return viewCaptor.value as ViewGroup 438 } 439 getChipReceiverInfonull440 private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo { 441 val routeInfo = 442 MediaRoute2Info.Builder("id", "Test route name") 443 .addFeature("feature") 444 .setClientPackageName(packageName) 445 .build() 446 return ChipReceiverInfo( 447 routeInfo, 448 null, 449 null, 450 id = "id", 451 instanceId = InstanceId.fakeInstanceId(0), 452 ) 453 } 454 ViewGroupnull455 private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) 456 } 457 458 private const val APP_NAME = "Fake app name" 459 private const val PACKAGE_NAME = "com.android.systemui" 460 461 private val routeInfo = 462 MediaRoute2Info.Builder("id", "Test route name") 463 .addFeature("feature") 464 .setClientPackageName(PACKAGE_NAME) 465 .build() 466