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