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.media.taptotransfer.sender
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.PowerManager
25 import android.os.VibrationAttributes
26 import android.os.VibrationEffect
27 import android.testing.TestableLooper
28 import android.view.View
29 import android.view.ViewGroup
30 import android.view.WindowManager
31 import android.view.accessibility.AccessibilityManager
32 import android.widget.ImageView
33 import android.widget.TextView
34 import androidx.test.ext.junit.runners.AndroidJUnit4
35 import androidx.test.filters.SmallTest
36 import com.android.app.viewcapture.ViewCapture
37 import com.android.app.viewcapture.ViewCaptureAwareWindowManager
38 import com.android.internal.logging.testing.UiEventLoggerFake
39 import com.android.internal.statusbar.IUndoMediaTransferCallback
40 import com.android.systemui.SysuiTestCase
41 import com.android.systemui.classifier.FalsingCollector
42 import com.android.systemui.common.shared.model.Text.Companion.loadText
43 import com.android.systemui.dump.DumpManager
44 import com.android.systemui.plugins.FalsingManager
45 import com.android.systemui.res.R
46 import com.android.systemui.statusbar.CommandQueue
47 import com.android.systemui.statusbar.VibratorHelper
48 import com.android.systemui.statusbar.policy.ConfigurationController
49 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
50 import com.android.systemui.temporarydisplay.TemporaryViewUiEventLogger
51 import com.android.systemui.temporarydisplay.chipbar.ChipbarAnimator
52 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
53 import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
54 import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
55 import com.android.systemui.util.concurrency.FakeExecutor
56 import com.android.systemui.util.mockito.any
57 import com.android.systemui.util.mockito.argumentCaptor
58 import com.android.systemui.util.mockito.capture
59 import com.android.systemui.util.mockito.eq
60 import com.android.systemui.util.mockito.mock
61 import com.android.systemui.util.time.FakeSystemClock
62 import com.android.systemui.util.view.ViewUtil
63 import com.android.systemui.util.wakelock.WakeLockFake
64 import com.google.common.truth.Truth.assertThat
65 import org.junit.Before
66 import org.junit.Test
67 import org.junit.runner.RunWith
68 import org.mockito.ArgumentCaptor
69 import org.mockito.Mock
70 import org.mockito.Mockito.atLeast
71 import org.mockito.Mockito.never
72 import org.mockito.Mockito.reset
73 import org.mockito.Mockito.verify
74 import org.mockito.Mockito.`when` as whenever
75 import org.mockito.MockitoAnnotations
76 
77 @SmallTest
78 @RunWith(AndroidJUnit4::class)
79 @TestableLooper.RunWithLooper
80 class MediaTttSenderCoordinatorTest : SysuiTestCase() {
81 
82     // Note: This tests are a bit like integration tests because they use a real instance of
83     //   [ChipbarCoordinator] and verify that the coordinator displays the correct view, based on
84     //   the inputs from [MediaTttSenderCoordinator].
85 
86     private lateinit var underTest: MediaTttSenderCoordinator
87 
88     @Mock private lateinit var accessibilityManager: AccessibilityManager
89     @Mock private lateinit var applicationInfo: ApplicationInfo
90     @Mock private lateinit var commandQueue: CommandQueue
91     @Mock private lateinit var configurationController: ConfigurationController
92     @Mock private lateinit var dumpManager: DumpManager
93     @Mock private lateinit var falsingManager: FalsingManager
94     @Mock private lateinit var falsingCollector: FalsingCollector
95     @Mock private lateinit var chipbarLogger: ChipbarLogger
96     @Mock private lateinit var logger: MediaTttSenderLogger
97     @Mock private lateinit var packageManager: PackageManager
98     @Mock private lateinit var powerManager: PowerManager
99     @Mock private lateinit var viewUtil: ViewUtil
100     @Mock private lateinit var windowManager: WindowManager
101     @Mock private lateinit var vibratorHelper: VibratorHelper
102     @Mock private lateinit var swipeHandler: SwipeChipbarAwayGestureHandler
103     @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
104     private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
105     private lateinit var fakeWakeLock: WakeLockFake
106     private lateinit var chipbarCoordinator: ChipbarCoordinator
107     private lateinit var commandQueueCallback: CommandQueue.Callbacks
108     private lateinit var fakeAppIconDrawable: Drawable
109     private lateinit var fakeClock: FakeSystemClock
110     private lateinit var fakeExecutor: FakeExecutor
111     private lateinit var uiEventLoggerFake: UiEventLoggerFake
112     private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
113     private lateinit var tempViewUiEventLogger: TemporaryViewUiEventLogger
114     private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
115 
116     @Before
setUpnull117     fun setUp() {
118         MockitoAnnotations.initMocks(this)
119         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
120 
121         fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
122         whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
123         whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
124         whenever(
125                 packageManager.getApplicationInfo(
126                     eq(PACKAGE_NAME),
127                     any<PackageManager.ApplicationInfoFlags>(),
128                 )
129             )
130             .thenReturn(applicationInfo)
131         context.setMockPackageManager(packageManager)
132 
133         fakeClock = FakeSystemClock()
134         fakeExecutor = FakeExecutor(fakeClock)
135 
136         fakeWakeLock = WakeLockFake()
137         fakeWakeLockBuilder = WakeLockFake.Builder(context)
138         fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
139 
140         uiEventLoggerFake = UiEventLoggerFake()
141         uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
142         tempViewUiEventLogger = TemporaryViewUiEventLogger(uiEventLoggerFake)
143 
144         chipbarCoordinator =
145             ChipbarCoordinator(
146                 context,
147                 chipbarLogger,
148                 ViewCaptureAwareWindowManager(
149                     windowManager,
150                     lazyViewCapture,
151                     isViewCaptureEnabled = false,
152                 ),
153                 fakeExecutor,
154                 accessibilityManager,
155                 configurationController,
156                 dumpManager,
157                 powerManager,
158                 ChipbarAnimator(),
159                 falsingManager,
160                 falsingCollector,
161                 swipeHandler,
162                 viewUtil,
163                 vibratorHelper,
164                 fakeWakeLockBuilder,
165                 fakeClock,
166                 tempViewUiEventLogger,
167             )
168         chipbarCoordinator.start()
169 
170         underTest =
171             MediaTttSenderCoordinator(
172                 chipbarCoordinator,
173                 commandQueue,
174                 context,
175                 dumpManager,
176                 logger,
177                 uiEventLogger,
178             )
179         underTest.start()
180 
181         setCommandQueueCallback()
182     }
183 
184     @Test
commandQueueCallback_almostCloseToStartCast_triggersCorrectChipnull185     fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
186         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
187             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
188             routeInfo,
189             null,
190         )
191 
192         val chipbarView = getChipbarView()
193         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
194         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
195         assertThat(chipbarView.getChipText())
196             .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
197         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
198         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
199         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
200         assertThat(uiEventLoggerFake.eventId(0))
201             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id)
202         verify(vibratorHelper)
203             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
204     }
205 
206     @Test
commandQueueCallback_almostCloseToStartCast_deviceNameBlank_showsDefaultDeviceNamenull207     fun commandQueueCallback_almostCloseToStartCast_deviceNameBlank_showsDefaultDeviceName() {
208         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
209             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
210             routeInfoWithBlankDeviceName,
211             null,
212         )
213 
214         val chipbarView = getChipbarView()
215         assertThat(chipbarView.getChipText())
216             .contains(context.getString(R.string.media_ttt_default_device_type))
217         assertThat(chipbarView.getChipText())
218             .isNotEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
219     }
220 
221     @Test
commandQueueCallback_almostCloseToEndCast_triggersCorrectChipnull222     fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
223         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
224             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
225             routeInfo,
226             null,
227         )
228 
229         val chipbarView = getChipbarView()
230         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
231         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
232         assertThat(chipbarView.getChipText())
233             .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText())
234         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
235         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
236         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
237         assertThat(uiEventLoggerFake.eventId(0))
238             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id)
239         verify(vibratorHelper)
240             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
241     }
242 
243     @Test
commandQueueCallback_transferToReceiverTriggered_triggersCorrectChipnull244     fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
245         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
246             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
247             routeInfo,
248             null,
249         )
250 
251         val chipbarView = getChipbarView()
252         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
253         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
254         assertThat(chipbarView.getChipText())
255             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
256         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
257         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
258         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
259         assertThat(uiEventLoggerFake.eventId(0))
260             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id)
261         verify(vibratorHelper)
262             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
263     }
264 
265     @Test
commandQueueCallback_transferToReceiverTriggered_deviceNameBlank_showsDefaultDeviceNamenull266     fun commandQueueCallback_transferToReceiverTriggered_deviceNameBlank_showsDefaultDeviceName() {
267         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
268             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
269             routeInfoWithBlankDeviceName,
270             null,
271         )
272 
273         val chipbarView = getChipbarView()
274         assertThat(chipbarView.getChipText())
275             .contains(context.getString(R.string.media_ttt_default_device_type))
276         assertThat(chipbarView.getChipText())
277             .isNotEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
278     }
279 
280     @Test
commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChipnull281     fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
282         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
283             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
284             routeInfo,
285             null,
286         )
287 
288         val chipbarView = getChipbarView()
289         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
290         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
291         assertThat(chipbarView.getChipText())
292             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
293         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
294         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
295         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
296         assertThat(uiEventLoggerFake.eventId(0))
297             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id)
298         verify(vibratorHelper)
299             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
300     }
301 
302     @Test
commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChipnull303     fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
304         displayReceiverTriggered()
305         reset(vibratorHelper)
306         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
307             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
308             routeInfo,
309             null,
310         )
311 
312         val chipbarView = getChipbarView()
313         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
314         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
315         assertThat(chipbarView.getChipText())
316             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
317         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
318         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
319         // Event index 2 since initially displaying the triggered chip would also log two events.
320         assertThat(uiEventLoggerFake.eventId(2))
321             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
322         verify(vibratorHelper, never())
323             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
324     }
325 
326     @Test
commandQueueCallback_transferToReceiverSucceeded_sameViewInstanceIdnull327     fun commandQueueCallback_transferToReceiverSucceeded_sameViewInstanceId() {
328         displayReceiverTriggered()
329         reset(vibratorHelper)
330         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
331             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
332             routeInfo,
333             null,
334         )
335 
336         // Event index 2 since initially displaying the triggered chip would also log two events.
337         assertThat(uiEventLoggerFake.eventId(2))
338             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
339         verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
340         assertThat(uiEventLoggerFake.logs[0].instanceId)
341             .isEqualTo(uiEventLoggerFake.logs[2].instanceId)
342     }
343 
344     @Test
transferToReceiverSucceeded_nullUndoCallback_noUndonull345     fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
346         displayReceiverTriggered()
347         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
348             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
349             routeInfo,
350             /* undoCallback= */ null,
351         )
352 
353         val chipbarView = getChipbarView()
354         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
355     }
356 
357     @Test
transferToReceiverSucceeded_withUndoRunnable_undoVisiblenull358     fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
359         displayReceiverTriggered()
360         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
361             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
362             routeInfo,
363             /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
364                 override fun onUndoTriggered() {}
365             },
366         )
367 
368         val chipbarView = getChipbarView()
369         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
370         assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
371     }
372 
373     @Test
transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggerednull374     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
375         var undoCallbackCalled = false
376         displayReceiverTriggered()
377         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
378             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
379             routeInfo,
380             /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
381                 override fun onUndoTriggered() {
382                     undoCallbackCalled = true
383                 }
384             },
385         )
386 
387         getChipbarView().getUndoButton().performClick()
388 
389         // Event index 3 since initially displaying the triggered and succeeded chip would also log
390         // events.
391         assertThat(uiEventLoggerFake.eventId(3))
392             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
393         assertThat(undoCallbackCalled).isTrue()
394         assertThat(getChipbarView().getChipText())
395             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
396     }
397 
398     @Test
commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChipnull399     fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
400         displayThisDeviceTriggered()
401         reset(vibratorHelper)
402         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
403             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
404             routeInfo,
405             null,
406         )
407 
408         val chipbarView = getChipbarView()
409         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
410         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
411         assertThat(chipbarView.getChipText())
412             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
413         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
414         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
415         // Event index 2 since initially displaying the triggered chip would also log two events.
416         assertThat(uiEventLoggerFake.eventId(2))
417             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
418         verify(vibratorHelper, never())
419             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
420     }
421 
422     @Test
transferToThisDeviceSucceeded_nullUndoCallback_noUndonull423     fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
424         displayThisDeviceTriggered()
425         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
426             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
427             routeInfo,
428             /* undoCallback= */ null,
429         )
430 
431         val chipbarView = getChipbarView()
432         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
433     }
434 
435     @Test
transferToThisDeviceSucceeded_withUndoRunnable_undoVisiblenull436     fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
437         displayThisDeviceTriggered()
438         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
439             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
440             routeInfo,
441             /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
442                 override fun onUndoTriggered() {}
443             },
444         )
445 
446         val chipbarView = getChipbarView()
447         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
448         assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
449     }
450 
451     @Test
transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggerednull452     fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
453         var undoCallbackCalled = false
454         displayThisDeviceTriggered()
455         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
456             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
457             routeInfo,
458             /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
459                 override fun onUndoTriggered() {
460                     undoCallbackCalled = true
461                 }
462             },
463         )
464 
465         getChipbarView().getUndoButton().performClick()
466 
467         // Event index 3 since initially displaying the triggered and succeeded chip would also log
468         // events.
469         assertThat(uiEventLoggerFake.eventId(3))
470             .isEqualTo(
471                 MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
472             )
473         assertThat(undoCallbackCalled).isTrue()
474         assertThat(getChipbarView().getChipText())
475             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
476     }
477 
478     @Test
commandQueueCallback_transferToReceiverFailed_triggersCorrectChipnull479     fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
480         displayReceiverTriggered()
481         reset(vibratorHelper)
482         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
483             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
484             routeInfo,
485             null,
486         )
487 
488         val chipbarView = getChipbarView()
489         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
490         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
491         assertThat(chipbarView.getChipText())
492             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
493         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
494         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
495         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
496         // Event index 2 since initially displaying the triggered chip would also log two events.
497         assertThat(uiEventLoggerFake.eventId(2))
498             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
499         verify(vibratorHelper)
500             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
501     }
502 
503     @Test
commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChipnull504     fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
505         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
506             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
507             routeInfo,
508             null,
509         )
510         reset(vibratorHelper)
511         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
512             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
513             routeInfo,
514             null,
515         )
516 
517         val chipbarView = getChipbarView()
518         assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
519         assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
520         assertThat(chipbarView.getChipText())
521             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
522         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
523         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
524         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
525         // Event index 1 since initially displaying the triggered chip would also log an event.
526         assertThat(uiEventLoggerFake.eventId(2))
527             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
528         verify(vibratorHelper)
529             .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>())
530     }
531 
532     @Test
commandQueueCallback_farFromReceiver_noChipShownnull533     fun commandQueueCallback_farFromReceiver_noChipShown() {
534         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
535             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
536             routeInfo,
537             null,
538         )
539 
540         verify(windowManager, never()).addView(any(), any())
541         assertThat(uiEventLoggerFake.eventId(0))
542             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id)
543     }
544 
545     @Test
commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHiddennull546     fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
547         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
548             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
549             routeInfo,
550             null,
551         )
552 
553         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
554             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
555             routeInfo,
556             null,
557         )
558 
559         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
560         verify(windowManager).addView(viewCaptor.capture(), any())
561         verify(windowManager).removeView(viewCaptor.value)
562         verify(logger).logStateMapRemoval(eq(DEFAULT_ID), any())
563     }
564 
565     @Test
commandQueueCallback_almostCloseThenFarFromReceiver_wakeLockAcquiredThenReleasednull566     fun commandQueueCallback_almostCloseThenFarFromReceiver_wakeLockAcquiredThenReleased() {
567         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
568             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
569             routeInfo,
570             null,
571         )
572 
573         assertThat(fakeWakeLock.isHeld).isTrue()
574 
575         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
576             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
577             routeInfo,
578             null,
579         )
580 
581         assertThat(fakeWakeLock.isHeld).isFalse()
582     }
583 
584     @Test
commandQueueCallback_FarFromReceiver_wakeLockNeverReleasednull585     fun commandQueueCallback_FarFromReceiver_wakeLockNeverReleased() {
586         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
587             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
588             routeInfo,
589             null,
590         )
591 
592         assertThat(fakeWakeLock.isHeld).isFalse()
593     }
594 
595     @Test
commandQueueCallback_invalidStateParam_noChipShownnull596     fun commandQueueCallback_invalidStateParam_noChipShown() {
597         commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null)
598 
599         verify(windowManager, never()).addView(any(), any())
600     }
601 
602     @Test
commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLoggednull603     fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
604         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
605             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
606             routeInfo,
607             null,
608         )
609         verify(windowManager).addView(any(), any())
610         reset(windowManager)
611 
612         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
613             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
614             routeInfo,
615             null,
616         )
617 
618         verify(logger).logInvalidStateTransitionError(any(), any())
619         verify(windowManager, never()).addView(any(), any())
620     }
621 
622     @Test
commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLoggednull623     fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
624         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
625             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
626             routeInfo,
627             null,
628         )
629         verify(windowManager).addView(any(), any())
630         reset(windowManager)
631 
632         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
633             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
634             routeInfo,
635             null,
636         )
637 
638         verify(logger).logInvalidStateTransitionError(any(), any())
639         verify(windowManager, never()).addView(any(), any())
640     }
641 
642     @Test
commandQueueCallback_receiverSucceededThenThisDeviceSucceeded_invalidTransitionLoggednull643     fun commandQueueCallback_receiverSucceededThenThisDeviceSucceeded_invalidTransitionLogged() {
644         displayReceiverTriggered()
645         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
646             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
647             routeInfo,
648             null,
649         )
650         reset(windowManager)
651 
652         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
653             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
654             routeInfo,
655             null,
656         )
657 
658         verify(logger).logInvalidStateTransitionError(any(), any())
659         verify(windowManager, never()).addView(any(), any())
660     }
661 
662     @Test
commandQueueCallback_thisDeviceSucceededThenReceiverSucceeded_invalidTransitionLoggednull663     fun commandQueueCallback_thisDeviceSucceededThenReceiverSucceeded_invalidTransitionLogged() {
664         displayThisDeviceTriggered()
665         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
666             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
667             routeInfo,
668             null,
669         )
670         reset(windowManager)
671 
672         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
673             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
674             routeInfo,
675             null,
676         )
677 
678         verify(logger).logInvalidStateTransitionError(any(), any())
679         verify(windowManager, never()).addView(any(), any())
680     }
681 
682     @Test
commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLoggednull683     fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
684         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
685             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
686             routeInfo,
687             null,
688         )
689         verify(windowManager).addView(any(), any())
690         reset(windowManager)
691 
692         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
693             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
694             routeInfo,
695             null,
696         )
697 
698         verify(logger).logInvalidStateTransitionError(any(), any())
699         verify(windowManager, never()).addView(any(), any())
700     }
701 
702     @Test
commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLoggednull703     fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
704         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
705             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
706             routeInfo,
707             null,
708         )
709         verify(windowManager).addView(any(), any())
710         reset(windowManager)
711 
712         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
713             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
714             routeInfo,
715             null,
716         )
717 
718         verify(logger).logInvalidStateTransitionError(any(), any())
719         verify(windowManager, never()).addView(any(), any())
720     }
721 
722     @Test
commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLoggednull723     fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
724         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
725             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
726             routeInfo,
727             null,
728         )
729         verify(windowManager).addView(any(), any())
730         reset(windowManager)
731 
732         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
733             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
734             routeInfo,
735             null,
736         )
737 
738         verify(logger).logInvalidStateTransitionError(any(), any())
739         verify(windowManager, never()).addView(any(), any())
740     }
741 
742     @Test
commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLoggednull743     fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
744         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
745             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
746             routeInfo,
747             null,
748         )
749         verify(windowManager).addView(any(), any())
750         reset(windowManager)
751 
752         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
753             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
754             routeInfo,
755             null,
756         )
757 
758         verify(logger).logInvalidStateTransitionError(any(), any())
759         verify(windowManager, never()).addView(any(), any())
760     }
761 
762     /** Regression test for b/266217596. */
763     @Test
toReceiver_triggeredThenFar_thenSucceeded_updatesToSucceedednull764     fun toReceiver_triggeredThenFar_thenSucceeded_updatesToSucceeded() {
765         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
766             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
767             routeInfo,
768             null,
769         )
770 
771         // WHEN a FAR command comes in
772         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
773             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
774             routeInfo,
775             null,
776         )
777 
778         // THEN it is ignored and the chipbar is stilled displayed
779         val chipbarView = getChipbarView()
780         assertThat(chipbarView.getChipText())
781             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
782         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
783         verify(windowManager, never()).removeView(any())
784 
785         // WHEN a SUCCEEDED command comes in
786         val succeededRouteInfo =
787             MediaRoute2Info.Builder(DEFAULT_ID, "Tablet Succeeded")
788                 .addFeature("feature")
789                 .setClientPackageName(PACKAGE_NAME)
790                 .build()
791         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
792             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
793             succeededRouteInfo,
794             /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
795                 override fun onUndoTriggered() {}
796             },
797         )
798 
799         // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded
800         // state. (The "invalid transition" would be FAR => SUCCEEDED.)
801         assertThat(chipbarView.getChipText())
802             .isEqualTo(
803                 ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText(
804                     "Tablet Succeeded"
805                 )
806             )
807         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
808         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
809     }
810 
811     /** Regression test for b/266217596. */
812     @Test
toThisDevice_triggeredThenFar_thenSucceeded_updatesToSucceedednull813     fun toThisDevice_triggeredThenFar_thenSucceeded_updatesToSucceeded() {
814         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
815             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
816             routeInfo,
817             null,
818         )
819 
820         // WHEN a FAR command comes in
821         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
822             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
823             routeInfo,
824             null,
825         )
826 
827         // THEN it is ignored and the chipbar is stilled displayed
828         val chipbarView = getChipbarView()
829         assertThat(chipbarView.getChipText())
830             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
831         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
832         verify(windowManager, never()).removeView(any())
833 
834         // WHEN a SUCCEEDED command comes in
835         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
836             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
837             routeInfo,
838             /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
839                 override fun onUndoTriggered() {}
840             },
841         )
842 
843         // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded
844         // state. (The "invalid transition" would be FAR => SUCCEEDED.)
845         assertThat(chipbarView.getChipText())
846             .isEqualTo(
847                 ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText(
848                     "Tablet Succeeded"
849                 )
850             )
851         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
852         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
853     }
854 
855     @Test
receivesNewStateFromCommandQueue_isLoggednull856     fun receivesNewStateFromCommandQueue_isLogged() {
857         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
858             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
859             routeInfo,
860             null,
861         )
862 
863         verify(logger).logStateChange(any(), any(), any())
864     }
865 
866     @Test
transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayedButStillTimesOutnull867     fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayedButStillTimesOut() {
868         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
869             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
870             routeInfo,
871             null,
872         )
873 
874         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
875             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
876             routeInfo,
877             null,
878         )
879         fakeExecutor.runAllReady()
880 
881         verify(windowManager, never()).removeView(any())
882         verify(logger).logRemovalBypass(any(), any())
883 
884         fakeClock.advanceTime(TIMEOUT + 1L)
885 
886         verify(windowManager).removeView(any())
887     }
888 
889     @Test
transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayedButDoesTimeOutnull890     fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
891         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
892             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
893             routeInfo,
894             null,
895         )
896 
897         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
898             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
899             routeInfo,
900             null,
901         )
902         fakeExecutor.runAllReady()
903 
904         verify(windowManager, never()).removeView(any())
905         verify(logger).logRemovalBypass(any(), any())
906 
907         fakeClock.advanceTime(TIMEOUT + 1L)
908 
909         verify(windowManager).removeView(any())
910     }
911 
912     @Test
transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOutnull913     fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
914         displayReceiverTriggered()
915         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
916             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
917             routeInfo,
918             null,
919         )
920 
921         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
922             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
923             routeInfo,
924             null,
925         )
926         fakeExecutor.runAllReady()
927 
928         verify(windowManager, never()).removeView(any())
929         verify(logger).logRemovalBypass(any(), any())
930 
931         fakeClock.advanceTime(TIMEOUT + 1L)
932 
933         verify(windowManager).removeView(any())
934     }
935 
936     @Test
transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOutnull937     fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
938         displayThisDeviceTriggered()
939         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
940             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
941             routeInfo,
942             null,
943         )
944 
945         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
946             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
947             routeInfo,
948             null,
949         )
950         fakeExecutor.runAllReady()
951 
952         verify(windowManager, never()).removeView(any())
953         verify(logger).logRemovalBypass(any(), any())
954 
955         fakeClock.advanceTime(TIMEOUT + 1L)
956 
957         verify(windowManager).removeView(any())
958     }
959 
960     @Test
transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOutnull961     fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
962         displayReceiverTriggered()
963         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
964             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
965             routeInfo,
966             object : IUndoMediaTransferCallback.Stub() {
967                 override fun onUndoTriggered() {}
968             },
969         )
970         val chipbarView = getChipbarView()
971         assertThat(chipbarView.getChipText())
972             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
973 
974         // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
975         // verify that the new state it triggers operates just like any other state.
976         getChipbarView().getUndoButton().performClick()
977         fakeExecutor.runAllReady()
978 
979         // Verify that the click updated us to the triggered state
980         assertThat(chipbarView.getChipText())
981             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
982 
983         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
984             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
985             routeInfo,
986             null,
987         )
988         fakeExecutor.runAllReady()
989 
990         // Verify that we didn't remove the chipbar because it's in the triggered state
991         verify(windowManager, never()).removeView(any())
992         verify(logger).logRemovalBypass(any(), any())
993 
994         fakeClock.advanceTime(TIMEOUT + 1L)
995 
996         // Verify we eventually remove the chipbar
997         verify(windowManager).removeView(any())
998     }
999 
1000     @Test
transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOutnull1001     fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
1002         displayThisDeviceTriggered()
1003         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1004             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
1005             routeInfo,
1006             object : IUndoMediaTransferCallback.Stub() {
1007                 override fun onUndoTriggered() {}
1008             },
1009         )
1010         val chipbarView = getChipbarView()
1011         assertThat(chipbarView.getChipText())
1012             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
1013 
1014         // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
1015         // verify that the new state it triggers operates just like any other state.
1016         getChipbarView().getUndoButton().performClick()
1017         fakeExecutor.runAllReady()
1018 
1019         // Verify that the click updated us to the triggered state
1020         assertThat(chipbarView.getChipText())
1021             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
1022 
1023         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1024             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
1025             routeInfo,
1026             null,
1027         )
1028         fakeExecutor.runAllReady()
1029 
1030         // Verify that we didn't remove the chipbar because it's in the triggered state
1031         verify(windowManager, never()).removeView(any())
1032         verify(logger).logRemovalBypass(any(), any())
1033 
1034         fakeClock.advanceTime(TIMEOUT + 1L)
1035 
1036         // Verify we eventually remove the chipbar
1037         verify(windowManager).removeView(any())
1038     }
1039 
1040     @Test
newState_viewListenerRegisterednull1041     fun newState_viewListenerRegistered() {
1042         val mockChipbarCoordinator = mock<ChipbarCoordinator>()
1043         whenever(mockChipbarCoordinator.tempViewUiEventLogger).thenReturn(tempViewUiEventLogger)
1044         underTest =
1045             MediaTttSenderCoordinator(
1046                 mockChipbarCoordinator,
1047                 commandQueue,
1048                 context,
1049                 dumpManager,
1050                 logger,
1051                 uiEventLogger,
1052             )
1053         underTest.start()
1054         // Re-set the command queue callback since we've created a new [MediaTttSenderCoordinator]
1055         // with a new callback.
1056         setCommandQueueCallback()
1057 
1058         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1059             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1060             routeInfo,
1061             null,
1062         )
1063 
1064         verify(mockChipbarCoordinator).registerListener(any())
1065     }
1066 
1067     @Test
onInfoPermanentlyRemoved_viewListenerUnregisterednull1068     fun onInfoPermanentlyRemoved_viewListenerUnregistered() {
1069         val mockChipbarCoordinator = mock<ChipbarCoordinator>()
1070         whenever(mockChipbarCoordinator.tempViewUiEventLogger).thenReturn(tempViewUiEventLogger)
1071         underTest =
1072             MediaTttSenderCoordinator(
1073                 mockChipbarCoordinator,
1074                 commandQueue,
1075                 context,
1076                 dumpManager,
1077                 logger,
1078                 uiEventLogger,
1079             )
1080         underTest.start()
1081         setCommandQueueCallback()
1082 
1083         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1084             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1085             routeInfo,
1086             null,
1087         )
1088 
1089         val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
1090         verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
1091 
1092         // WHEN the listener is notified that the view has been removed
1093         listenerCaptor.value.onInfoPermanentlyRemoved(DEFAULT_ID, "reason")
1094 
1095         // THEN the media coordinator unregisters the listener
1096         verify(logger).logStateMapRemoval(DEFAULT_ID, "reason")
1097         verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value)
1098     }
1099 
1100     @Test
onInfoPermanentlyRemoved_wrongId_viewListenerNotUnregisterednull1101     fun onInfoPermanentlyRemoved_wrongId_viewListenerNotUnregistered() {
1102         val mockChipbarCoordinator = mock<ChipbarCoordinator>()
1103         whenever(mockChipbarCoordinator.tempViewUiEventLogger).thenReturn(tempViewUiEventLogger)
1104         underTest =
1105             MediaTttSenderCoordinator(
1106                 mockChipbarCoordinator,
1107                 commandQueue,
1108                 context,
1109                 dumpManager,
1110                 logger,
1111                 uiEventLogger,
1112             )
1113         underTest.start()
1114         setCommandQueueCallback()
1115 
1116         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1117             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1118             routeInfo,
1119             null,
1120         )
1121 
1122         val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
1123         verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
1124 
1125         // WHEN the listener is notified that a different view has been removed
1126         listenerCaptor.value.onInfoPermanentlyRemoved("differentViewId", "reason")
1127 
1128         // THEN the media coordinator doesn't unregister the listener
1129         verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
1130     }
1131 
1132     @Test
farFromReceiverState_viewListenerUnregisterednull1133     fun farFromReceiverState_viewListenerUnregistered() {
1134         val mockChipbarCoordinator = mock<ChipbarCoordinator>()
1135         whenever(mockChipbarCoordinator.tempViewUiEventLogger).thenReturn(tempViewUiEventLogger)
1136         underTest =
1137             MediaTttSenderCoordinator(
1138                 mockChipbarCoordinator,
1139                 commandQueue,
1140                 context,
1141                 dumpManager,
1142                 logger,
1143                 uiEventLogger,
1144             )
1145         underTest.start()
1146         setCommandQueueCallback()
1147 
1148         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1149             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1150             routeInfo,
1151             null,
1152         )
1153 
1154         val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
1155         verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
1156 
1157         // WHEN we go to the FAR_FROM_RECEIVER state
1158         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1159             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
1160             routeInfo,
1161             null,
1162         )
1163 
1164         // THEN the media coordinator unregisters the listener
1165         verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value)
1166     }
1167 
1168     @Test
statesWithDifferentIds_onInfoPermanentlyRemovedForOneId_viewListenerNotUnregisterednull1169     fun statesWithDifferentIds_onInfoPermanentlyRemovedForOneId_viewListenerNotUnregistered() {
1170         val mockChipbarCoordinator = mock<ChipbarCoordinator>()
1171         whenever(mockChipbarCoordinator.tempViewUiEventLogger).thenReturn(tempViewUiEventLogger)
1172         underTest =
1173             MediaTttSenderCoordinator(
1174                 mockChipbarCoordinator,
1175                 commandQueue,
1176                 context,
1177                 dumpManager,
1178                 logger,
1179                 uiEventLogger,
1180             )
1181         underTest.start()
1182         setCommandQueueCallback()
1183 
1184         // WHEN there are two different media transfers with different IDs
1185         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1186             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1187             MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME)
1188                 .addFeature("feature")
1189                 .setClientPackageName(PACKAGE_NAME)
1190                 .build(),
1191             null,
1192         )
1193         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1194             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1195             MediaRoute2Info.Builder("route2", OTHER_DEVICE_NAME)
1196                 .addFeature("feature")
1197                 .setClientPackageName(PACKAGE_NAME)
1198                 .build(),
1199             null,
1200         )
1201 
1202         val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
1203         verify(mockChipbarCoordinator, atLeast(1)).registerListener(capture(listenerCaptor))
1204 
1205         // THEN one of them is removed
1206         listenerCaptor.value.onInfoPermanentlyRemoved("route1", "reason")
1207 
1208         // THEN the media coordinator doesn't unregister the listener (since route2 is still active)
1209         verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
1210         verify(logger).logStateMapRemoval("route1", "reason")
1211     }
1212 
1213     /** Regression test for b/266218672. */
1214     @Test
twoIdsDisplayed_oldIdIsFar_viewStillDisplayednull1215     fun twoIdsDisplayed_oldIdIsFar_viewStillDisplayed() {
1216         // WHEN there are two different media transfers with different IDs
1217         val route1 =
1218             MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME)
1219                 .addFeature("feature")
1220                 .setClientPackageName(PACKAGE_NAME)
1221                 .build()
1222         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1223             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1224             route1,
1225             null,
1226         )
1227         verify(windowManager).addView(any(), any())
1228         reset(windowManager)
1229 
1230         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1231             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
1232             MediaRoute2Info.Builder("route2", "Route 2 name")
1233                 .addFeature("feature")
1234                 .setClientPackageName(PACKAGE_NAME)
1235                 .build(),
1236             null,
1237         )
1238         val newView = getChipbarView()
1239 
1240         // WHEN there's a FAR event for the earlier one
1241         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1242             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
1243             route1,
1244             null,
1245         )
1246 
1247         // THEN it's ignored and the more recent one is still displayed
1248         assertThat(newView.getChipText())
1249             .isEqualTo(
1250                 ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText("Route 2 name")
1251             )
1252     }
1253 
1254     /** Regression test for b/266218672. */
1255     @Test
receiverSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToEndnull1256     fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToEnd() {
1257         displayReceiverTriggered()
1258         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1259             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
1260             routeInfo,
1261             null,
1262         )
1263 
1264         fakeClock.advanceTime(TIMEOUT + 1L)
1265         verify(windowManager).removeView(any())
1266 
1267         reset(windowManager)
1268 
1269         // WHEN we try to show ALMOST_CLOSE_TO_END
1270         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1271             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
1272             routeInfo,
1273             null,
1274         )
1275 
1276         // THEN it succeeds
1277         val chipbarView = getChipbarView()
1278         assertThat(chipbarView.getChipText())
1279             .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText())
1280     }
1281 
1282     /** Regression test for b/266218672. */
1283     @Test
receiverSucceededThenTimedOut_internalStateResetAndCanDisplayReceiverTriggerednull1284     fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayReceiverTriggered() {
1285         displayReceiverTriggered()
1286         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1287             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
1288             routeInfo,
1289             null,
1290         )
1291 
1292         fakeClock.advanceTime(TIMEOUT + 1L)
1293         verify(windowManager).removeView(any())
1294 
1295         reset(windowManager)
1296 
1297         // WHEN we try to show RECEIVER_TRIGGERED
1298         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1299             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
1300             routeInfo,
1301             null,
1302         )
1303 
1304         // THEN it succeeds
1305         val chipbarView = getChipbarView()
1306         assertThat(chipbarView.getChipText())
1307             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
1308         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
1309     }
1310 
1311     /** Regression test for b/266218672. */
1312     @Test
toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToStartnull1313     fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToStart() {
1314         displayThisDeviceTriggered()
1315         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1316             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
1317             routeInfo,
1318             null,
1319         )
1320 
1321         fakeClock.advanceTime(TIMEOUT + 1L)
1322         verify(windowManager).removeView(any())
1323 
1324         reset(windowManager)
1325 
1326         // WHEN we try to show ALMOST_CLOSE_TO_START
1327         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1328             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
1329             routeInfo,
1330             null,
1331         )
1332 
1333         // THEN it succeeds
1334         val chipbarView = getChipbarView()
1335         assertThat(chipbarView.getChipText())
1336             .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
1337     }
1338 
1339     /** Regression test for b/266218672. */
1340     @Test
toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayThisDeviceTriggerednull1341     fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayThisDeviceTriggered() {
1342         displayThisDeviceTriggered()
1343         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1344             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
1345             routeInfo,
1346             null,
1347         )
1348 
1349         fakeClock.advanceTime(TIMEOUT + 1L)
1350         verify(windowManager).removeView(any())
1351 
1352         reset(windowManager)
1353 
1354         // WHEN we try to show THIS_DEVICE_TRIGGERED
1355         val newRouteInfo =
1356             MediaRoute2Info.Builder(DEFAULT_ID, "New Name")
1357                 .addFeature("feature")
1358                 .setClientPackageName(PACKAGE_NAME)
1359                 .build()
1360         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1361             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
1362             newRouteInfo,
1363             null,
1364         )
1365 
1366         // THEN it succeeds
1367         val chipbarView = getChipbarView()
1368         assertThat(chipbarView.getChipText())
1369             .isEqualTo(
1370                 ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText("New Name")
1371             )
1372         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
1373     }
1374 
1375     @Test
almostClose_hasLongTimeout_eventuallyTimesOutnull1376     fun almostClose_hasLongTimeout_eventuallyTimesOut() {
1377         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
1378             it.arguments[0]
1379         }
1380 
1381         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1382             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
1383             routeInfo,
1384             null,
1385         )
1386 
1387         // WHEN the default timeout has passed
1388         fakeClock.advanceTime(defaultTimeout + 1L)
1389 
1390         // THEN the view is still on-screen because it has a long timeout
1391         verify(windowManager, never()).removeView(any())
1392 
1393         // WHEN a very long amount of time has passed
1394         fakeClock.advanceTime(5L * defaultTimeout)
1395 
1396         // THEN the view does time out
1397         verify(windowManager).removeView(any())
1398     }
1399 
1400     @Test
loading_hasLongTimeout_eventuallyTimesOutnull1401     fun loading_hasLongTimeout_eventuallyTimesOut() {
1402         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
1403             it.arguments[0]
1404         }
1405 
1406         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1407             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
1408             routeInfo,
1409             null,
1410         )
1411 
1412         // WHEN the default timeout has passed
1413         fakeClock.advanceTime(defaultTimeout + 1L)
1414 
1415         // THEN the view is still on-screen because it has a long timeout
1416         verify(windowManager, never()).removeView(any())
1417 
1418         // WHEN a very long amount of time has passed
1419         fakeClock.advanceTime(5L * defaultTimeout)
1420 
1421         // THEN the view does time out
1422         verify(windowManager).removeView(any())
1423     }
1424 
1425     @Test
succeeded_hasDefaultTimeoutnull1426     fun succeeded_hasDefaultTimeout() {
1427         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
1428             it.arguments[0]
1429         }
1430 
1431         displayReceiverTriggered()
1432         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1433             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
1434             routeInfo,
1435             null,
1436         )
1437 
1438         fakeClock.advanceTime(defaultTimeout + 1L)
1439 
1440         verify(windowManager).removeView(any())
1441     }
1442 
1443     @Test
failed_hasDefaultTimeoutnull1444     fun failed_hasDefaultTimeout() {
1445         whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
1446             it.arguments[0]
1447         }
1448 
1449         displayThisDeviceTriggered()
1450         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1451             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
1452             routeInfo,
1453             null,
1454         )
1455 
1456         fakeClock.advanceTime(defaultTimeout + 1L)
1457 
1458         verify(windowManager).removeView(any())
1459     }
1460 
getChipbarViewnull1461     private fun getChipbarView(): ViewGroup {
1462         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
1463         verify(windowManager).addView(viewCaptor.capture(), any())
1464         return viewCaptor.value as ViewGroup
1465     }
1466 
ViewGroupnull1467     private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.start_icon)
1468 
1469     private fun ViewGroup.getChipText(): String =
1470         (this.requireViewById<TextView>(R.id.text)).text as String
1471 
1472     private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
1473 
1474     private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error)
1475 
1476     private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
1477 
1478     private fun ChipStateSender.getExpectedStateText(
1479         otherDeviceName: String = OTHER_DEVICE_NAME
1480     ): String? {
1481         return this.getChipTextString(context, otherDeviceName).loadText(context)
1482     }
1483 
1484     // display receiver triggered state helper method to make sure we start from a valid state
1485     // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
displayReceiverTriggerednull1486     private fun displayReceiverTriggered() {
1487         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1488             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
1489             routeInfo,
1490             null,
1491         )
1492     }
1493 
1494     // display this device triggered state helper method to make sure we start from a valid state
1495     // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
displayThisDeviceTriggerednull1496     private fun displayThisDeviceTriggered() {
1497         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
1498             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
1499             routeInfo,
1500             null,
1501         )
1502     }
1503 
setCommandQueueCallbacknull1504     private fun setCommandQueueCallback() {
1505         val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
1506         verify(commandQueue).addCallback(capture(callbackCaptor))
1507         commandQueueCallback = callbackCaptor.value
1508         reset(commandQueue)
1509     }
1510 }
1511 
1512 private const val DEFAULT_ID = "defaultId"
1513 private const val APP_NAME = "Fake app name"
1514 private const val OTHER_DEVICE_NAME = "My Tablet"
1515 private const val BLANK_DEVICE_NAME = " "
1516 private const val PACKAGE_NAME = "com.android.systemui"
1517 private const val TIMEOUT = 10000
1518 
1519 private val routeInfo =
1520     MediaRoute2Info.Builder(DEFAULT_ID, OTHER_DEVICE_NAME)
1521         .addFeature("feature")
1522         .setClientPackageName(PACKAGE_NAME)
1523         .build()
1524 
1525 private val routeInfoWithBlankDeviceName =
1526     MediaRoute2Info.Builder(DEFAULT_ID, BLANK_DEVICE_NAME)
1527         .addFeature("feature")
1528         .setClientPackageName(PACKAGE_NAME)
1529         .build()
1530