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