• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.dialog;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.ArgumentMatchers.anyInt;
24 import static org.mockito.ArgumentMatchers.anyLong;
25 import static org.mockito.ArgumentMatchers.anyString;
26 import static org.mockito.Mockito.atLeastOnce;
27 import static org.mockito.Mockito.doReturn;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.reset;
31 import static org.mockito.Mockito.spy;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 import static org.mockito.Mockito.when;
35 
36 import android.app.KeyguardManager;
37 import android.app.Notification;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.pm.PackageManager;
42 import android.graphics.drawable.Drawable;
43 import android.graphics.drawable.Icon;
44 import android.media.AudioDeviceAttributes;
45 import android.media.AudioDeviceInfo;
46 import android.media.AudioManager;
47 import android.media.MediaDescription;
48 import android.media.MediaMetadata;
49 import android.media.MediaRoute2Info;
50 import android.media.NearbyDevice;
51 import android.media.RoutingSessionInfo;
52 import android.media.session.ISessionController;
53 import android.media.session.MediaController;
54 import android.media.session.MediaSession;
55 import android.media.session.MediaSessionManager;
56 import android.media.session.PlaybackState;
57 import android.os.Bundle;
58 import android.os.PowerExemptionManager;
59 import android.os.RemoteException;
60 import android.os.UserHandle;
61 import android.platform.test.annotations.DisableFlags;
62 import android.platform.test.annotations.EnableFlags;
63 import android.platform.test.flag.junit.FlagsParameterization;
64 import android.service.notification.StatusBarNotification;
65 import android.testing.TestableLooper;
66 import android.text.TextUtils;
67 import android.view.View;
68 
69 import androidx.core.graphics.drawable.IconCompat;
70 import androidx.test.filters.SmallTest;
71 
72 import com.android.media.flags.Flags;
73 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
74 import com.android.settingslib.bluetooth.LocalBluetoothManager;
75 import com.android.settingslib.media.InputMediaDevice;
76 import com.android.settingslib.media.InputRouteManager;
77 import com.android.settingslib.media.LocalMediaManager;
78 import com.android.settingslib.media.MediaDevice;
79 import com.android.settingslib.utils.ThreadUtils;
80 import com.android.systemui.SysuiTestCase;
81 import com.android.systemui.SysuiTestCaseExtKt;
82 import com.android.systemui.animation.ActivityTransitionAnimator;
83 import com.android.systemui.animation.DialogTransitionAnimator;
84 import com.android.systemui.flags.FeatureFlags;
85 import com.android.systemui.kosmos.Kosmos;
86 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
87 import com.android.systemui.plugins.ActivityStarter;
88 import com.android.systemui.res.R;
89 import com.android.systemui.settings.UserTracker;
90 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
91 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
92 import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor;
93 import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt;
94 
95 import com.google.common.collect.ImmutableList;
96 
97 import org.junit.Before;
98 import org.junit.Test;
99 import org.junit.runner.RunWith;
100 import org.mockito.Mock;
101 import org.mockito.Mockito;
102 import org.mockito.MockitoAnnotations;
103 
104 import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
105 import platform.test.runner.parameterized.Parameters;
106 
107 import java.util.ArrayList;
108 import java.util.Collections;
109 import java.util.List;
110 import java.util.concurrent.CountDownLatch;
111 import java.util.concurrent.TimeUnit;
112 import java.util.stream.Collectors;
113 
114 @SmallTest
115 @RunWith(ParameterizedAndroidJunit4.class)
116 @TestableLooper.RunWithLooper(setAsMainLooper = true)
117 public class MediaSwitchingControllerTest extends SysuiTestCase {
118     private static final String TEST_DEVICE_1_ID = "test_device_1_id";
119     private static final String TEST_DEVICE_2_ID = "test_device_2_id";
120     private static final String TEST_DEVICE_3_ID = "test_device_3_id";
121     private static final String TEST_DEVICE_4_ID = "test_device_4_id";
122     private static final String TEST_DEVICE_5_ID = "test_device_5_id";
123     private static final String TEST_ARTIST = "test_artist";
124     private static final String TEST_SONG = "test_song";
125     private static final String TEST_SESSION_ID = "test_session_id";
126     private static final String TEST_SESSION_NAME = "test_session_name";
127     private static final int MAX_VOLUME = 1;
128     private static final int CURRENT_VOLUME = 0;
129     private static final boolean VOLUME_FIXED_TRUE = true;
130     private static final int LATCH_COUNT_DOWN_TIME_IN_SECOND = 5;
131     private static final int LATCH_TIME_OUT_TIME_IN_SECOND = 10;
132     private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic";
133     private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset";
134 
135     @Mock
136     private DialogTransitionAnimator mDialogTransitionAnimator;
137     @Mock
138     private ActivityTransitionAnimator.Controller mActivityTransitionAnimatorController;
139     @Mock
140     private NearbyMediaDevicesManager mNearbyMediaDevicesManager;
141     // Mock
142     @Mock
143     private MediaController mSessionMediaController;
144     @Mock
145     private MediaSessionManager mMediaSessionManager;
146     @Mock
147     private CachedBluetoothDeviceManager mCachedBluetoothDeviceManager;
148     @Mock
149     private LocalBluetoothManager mLocalBluetoothManager;
150     @Mock private MediaSwitchingController.Callback mCb;
151     @Mock
152     private MediaDevice mMediaDevice1;
153     @Mock
154     private MediaDevice mMediaDevice2;
155     @Mock private MediaDevice mMediaDevice3;
156     @Mock private MediaDevice mMediaDevice4;
157     @Mock private MediaDevice mMediaDevice5;
158     @Mock private NearbyDevice mNearbyDevice1;
159     @Mock
160     private NearbyDevice mNearbyDevice2;
161     @Mock
162     private MediaMetadata mMediaMetadata;
163     @Mock
164     private RoutingSessionInfo mRemoteSessionInfo;
165     @Mock
166     private ActivityStarter mStarter;
167     @Mock
168     private AudioManager mAudioManager;
169     @Mock
170     private KeyguardManager mKeyguardManager;
171     @Mock
172     private ActivityTransitionAnimator.Controller mController;
173     @Mock
174     private PowerExemptionManager mPowerExemptionManager;
175     @Mock
176     private CommonNotifCollection mNotifCollection;
177     @Mock
178     private PackageManager mPackageManager;
179     @Mock
180     private Drawable mDrawable;
181     @Mock
182     private PlaybackState mPlaybackState;
183 
184     @Mock
185     private UserTracker mUserTracker;
186 
187     private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
188 
189     private FeatureFlags mFlags = mock(FeatureFlags.class);
190     private View mDialogLaunchView = mock(View.class);
191     private MediaSwitchingController.Callback mCallback =
192             mock(MediaSwitchingController.Callback.class);
193 
194     final Notification mNotification = mock(Notification.class);
195     private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor =
196             VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor(
197                     mKosmos);
198 
199     private Context mSpyContext;
200     private String mPackageName = null;
201     private MediaSwitchingController mMediaSwitchingController;
202     private LocalMediaManager mLocalMediaManager;
203     private InputRouteManager mInputRouteManager;
204     private List<MediaController> mMediaControllers = new ArrayList<>();
205     private List<MediaDevice> mMediaDevices = new ArrayList<>();
206     private List<NearbyDevice> mNearbyDevices = new ArrayList<>();
207     private MediaDescription mMediaDescription;
208     private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
209 
210     @Parameters(name = "{0}")
getParams()211     public static List<FlagsParameterization> getParams() {
212         return FlagsParameterization.allCombinationsOf(
213                 Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION,
214                 Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING);
215     }
216 
MediaSwitchingControllerTest(FlagsParameterization flags)217     public MediaSwitchingControllerTest(FlagsParameterization flags) {
218         mSetFlagsRule.setFlagsParameterization(flags);
219     }
220 
221     @Before
setUp()222     public void setUp() {
223         mPackageName = mContext.getPackageName();
224 
225         MockitoAnnotations.initMocks(this);
226         mContext.setMockPackageManager(mPackageManager);
227         mSpyContext = spy(mContext);
228         final UserHandle userHandle = mock(UserHandle.class);
229         when(mUserTracker.getUserHandle()).thenReturn(userHandle);
230         when(mSessionMediaController.getPackageName()).thenReturn(mPackageName);
231         when(mSessionMediaController.getPlaybackState()).thenReturn(mPlaybackState);
232         mMediaControllers.add(mSessionMediaController);
233         when(mMediaSessionManager.getActiveSessionsForUser(any(),
234                 Mockito.eq(userHandle))).thenReturn(
235                 mMediaControllers);
236         doReturn(mMediaSessionManager).when(mSpyContext).getSystemService(
237                 MediaSessionManager.class);
238         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
239                 mCachedBluetoothDeviceManager);
240 
241         mMediaSwitchingController =
242                 new MediaSwitchingController(
243                         mSpyContext,
244                         mPackageName,
245                         mContext.getUser(),
246                         /* token */ null,
247                         mMediaSessionManager,
248                         mLocalBluetoothManager,
249                         mStarter,
250                         mNotifCollection,
251                         mDialogTransitionAnimator,
252                         mNearbyMediaDevicesManager,
253                         mAudioManager,
254                         mPowerExemptionManager,
255                         mKeyguardManager,
256                         mFlags,
257                         mVolumePanelGlobalStateInteractor,
258                         mUserTracker);
259         mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager);
260         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
261         mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
262 
263         mMediaSwitchingController.mInputRouteManager =
264                 new InputRouteManager(mContext, mAudioManager);
265         mInputRouteManager = spy(mMediaSwitchingController.mInputRouteManager);
266         mMediaSwitchingController.mInputRouteManager = mInputRouteManager;
267         when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
268                 .thenReturn(new AudioDeviceInfo[0]);
269 
270         MediaDescription.Builder builder = new MediaDescription.Builder();
271         builder.setTitle(TEST_SONG);
272         builder.setSubtitle(TEST_ARTIST);
273         mMediaDescription = builder.build();
274         when(mMediaMetadata.getDescription()).thenReturn(mMediaDescription);
275         when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID);
276         when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
277         mMediaDevices.add(mMediaDevice1);
278         mMediaDevices.add(mMediaDevice2);
279 
280         when(mNearbyDevice1.getMediaRoute2Id()).thenReturn(TEST_DEVICE_1_ID);
281         when(mNearbyDevice1.getRangeZone()).thenReturn(NearbyDevice.RANGE_FAR);
282         when(mNearbyDevice2.getMediaRoute2Id()).thenReturn(TEST_DEVICE_2_ID);
283         when(mNearbyDevice2.getRangeZone()).thenReturn(NearbyDevice.RANGE_CLOSE);
284         mNearbyDevices.add(mNearbyDevice1);
285         mNearbyDevices.add(mNearbyDevice2);
286 
287         final List<NotificationEntry> entryList = new ArrayList<>();
288         final NotificationEntry entry = mock(NotificationEntry.class);
289         final StatusBarNotification sbn = mock(StatusBarNotification.class);
290         final Bundle bundle = mock(Bundle.class);
291         final MediaSession.Token token = mock(MediaSession.Token.class);
292         final ISessionController binder = mock(ISessionController.class);
293         entryList.add(entry);
294 
295         when(mNotification.isMediaNotification()).thenReturn(false);
296         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
297         when(entry.getSbn()).thenReturn(sbn);
298         when(sbn.getNotification()).thenReturn(mNotification);
299         when(sbn.getPackageName()).thenReturn(mPackageName);
300         mNotification.extras = bundle;
301         when(bundle.getParcelable(Notification.EXTRA_MEDIA_SESSION,
302                 MediaSession.Token.class)).thenReturn(token);
303         when(token.getBinder()).thenReturn(binder);
304     }
305 
306     @Test
start_verifyLocalMediaManagerInit()307     public void start_verifyLocalMediaManagerInit() {
308         mMediaSwitchingController.start(mCb);
309 
310         verify(mLocalMediaManager).registerCallback(mMediaSwitchingController);
311         verify(mLocalMediaManager).startScan();
312     }
313 
314     @Test
stop_verifyLocalMediaManagerDeinit()315     public void stop_verifyLocalMediaManagerDeinit() {
316         mMediaSwitchingController.start(mCb);
317         reset(mLocalMediaManager);
318 
319         mMediaSwitchingController.stop();
320 
321         verify(mLocalMediaManager).unregisterCallback(mMediaSwitchingController);
322         verify(mLocalMediaManager).stopScan();
323     }
324 
325     @Test
start_notificationNotFound_mediaControllerInitFromSession()326     public void start_notificationNotFound_mediaControllerInitFromSession() {
327         mMediaSwitchingController.start(mCb);
328 
329         verify(mSessionMediaController).registerCallback(any());
330     }
331 
332     @Test
start_MediaNotificationFound_mediaControllerNotInitFromSession()333     public void start_MediaNotificationFound_mediaControllerNotInitFromSession() {
334         when(mNotification.isMediaNotification()).thenReturn(true);
335         mMediaSwitchingController.start(mCb);
336 
337         verify(mSessionMediaController, never()).registerCallback(any());
338         verifyNoMoreInteractions(mMediaSessionManager);
339     }
340 
341     @Test
start_withoutPackageName_verifyMediaControllerInit()342     public void start_withoutPackageName_verifyMediaControllerInit() {
343         mMediaSwitchingController =
344                 new MediaSwitchingController(
345                         mSpyContext,
346                         null,
347                         mContext.getUser(),
348                         /* token */ null,
349                         mMediaSessionManager,
350                         mLocalBluetoothManager,
351                         mStarter,
352                         mNotifCollection,
353                         mDialogTransitionAnimator,
354                         mNearbyMediaDevicesManager,
355                         mAudioManager,
356                         mPowerExemptionManager,
357                         mKeyguardManager,
358                         mFlags,
359                         mVolumePanelGlobalStateInteractor,
360                         mUserTracker);
361 
362         mMediaSwitchingController.start(mCb);
363 
364         verify(mSessionMediaController, never()).registerCallback(any());
365     }
366 
367     @Test
start_nearbyMediaDevicesManagerNotNull_registersNearbyDevicesCallback()368     public void start_nearbyMediaDevicesManagerNotNull_registersNearbyDevicesCallback() {
369         mMediaSwitchingController.start(mCb);
370 
371         verify(mNearbyMediaDevicesManager).registerNearbyDevicesCallback(any());
372     }
373 
374     @Test
stop_withPackageName_verifyMediaControllerDeinit()375     public void stop_withPackageName_verifyMediaControllerDeinit() {
376         mMediaSwitchingController.start(mCb);
377         reset(mSessionMediaController);
378 
379         mMediaSwitchingController.stop();
380 
381         verify(mSessionMediaController).unregisterCallback(any());
382     }
383 
384     @Test
stop_withoutPackageName_verifyMediaControllerDeinit()385     public void stop_withoutPackageName_verifyMediaControllerDeinit() {
386         mMediaSwitchingController =
387                 new MediaSwitchingController(
388                         mSpyContext,
389                         null,
390                         mSpyContext.getUser(),
391                         /* token */ null,
392                         mMediaSessionManager,
393                         mLocalBluetoothManager,
394                         mStarter,
395                         mNotifCollection,
396                         mDialogTransitionAnimator,
397                         mNearbyMediaDevicesManager,
398                         mAudioManager,
399                         mPowerExemptionManager,
400                         mKeyguardManager,
401                         mFlags,
402                         mVolumePanelGlobalStateInteractor,
403                         mUserTracker);
404 
405         mMediaSwitchingController.start(mCb);
406 
407         mMediaSwitchingController.stop();
408 
409         verify(mSessionMediaController, never()).unregisterCallback(any());
410     }
411 
412     @Test
stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback()413     public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() {
414         mMediaSwitchingController.start(mCb);
415         reset(mSessionMediaController);
416 
417         mMediaSwitchingController.stop();
418 
419         verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any());
420     }
421 
422     @Test
tryToLaunchMediaApplication_nullIntent_skip()423     public void tryToLaunchMediaApplication_nullIntent_skip() {
424         mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView);
425 
426         verify(mCb, never()).dismissDialog();
427     }
428 
429     @Test
tryToLaunchMediaApplication_intentNotNull_startActivity()430     public void tryToLaunchMediaApplication_intentNotNull_startActivity() {
431         when(mDialogTransitionAnimator.createActivityTransitionController(any(View.class)))
432                 .thenReturn(mController);
433         Intent intent = new Intent(mPackageName);
434         doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(mPackageName);
435         mMediaSwitchingController.start(mCallback);
436 
437         mMediaSwitchingController.tryToLaunchMediaApplication(mDialogLaunchView);
438 
439         verify(mStarter).startActivity(any(Intent.class), anyBoolean(),
440                 Mockito.eq(mController));
441     }
442 
443     @Test
tryToLaunchInAppRoutingIntent_componentNameNotNull_startActivity()444     public void tryToLaunchInAppRoutingIntent_componentNameNotNull_startActivity() {
445         when(mDialogTransitionAnimator.createActivityTransitionController(any(View.class)))
446                 .thenReturn(mController);
447         mMediaSwitchingController.start(mCallback);
448         when(mLocalMediaManager.getLinkedItemComponentName()).thenReturn(
449                 new ComponentName(mPackageName, ""));
450 
451         mMediaSwitchingController.tryToLaunchInAppRoutingIntent(
452                 TEST_DEVICE_1_ID, mDialogLaunchView);
453 
454         verify(mStarter).startActivity(any(Intent.class), anyBoolean(),
455                 Mockito.eq(mController));
456     }
457 
458     @Test
onDevicesUpdated_unregistersNearbyDevicesCallback()459     public void onDevicesUpdated_unregistersNearbyDevicesCallback() throws RemoteException {
460         mMediaSwitchingController.start(mCb);
461 
462         mMediaSwitchingController.onDevicesUpdated(ImmutableList.of());
463 
464         verify(mNearbyMediaDevicesManager).unregisterNearbyDevicesCallback(any());
465     }
466 
467     @Test
onDeviceListUpdate_withNearbyDevices_updatesRangeInformation()468     public void onDeviceListUpdate_withNearbyDevices_updatesRangeInformation()
469             throws RemoteException {
470         mMediaSwitchingController.start(mCb);
471         reset(mCb);
472 
473         mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
474         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
475 
476         verify(mMediaDevice1).setRangeZone(NearbyDevice.RANGE_FAR);
477         verify(mMediaDevice2).setRangeZone(NearbyDevice.RANGE_CLOSE);
478     }
479 
480     @Test
onDeviceListUpdate_withNearbyDevices_rankByRangeInformation()481     public void onDeviceListUpdate_withNearbyDevices_rankByRangeInformation()
482             throws RemoteException {
483         mMediaSwitchingController.start(mCb);
484         reset(mCb);
485 
486         mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
487         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
488 
489         assertThat(mMediaDevices.get(0).getId()).isEqualTo(TEST_DEVICE_1_ID);
490     }
491 
492     @Test
routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation()493     public void routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation()
494             throws RemoteException {
495         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
496         mMediaSwitchingController.start(mCb);
497         reset(mCb);
498 
499         mMediaSwitchingController.onDevicesUpdated(mNearbyDevices);
500         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
501 
502         verify(mMediaDevice1, never()).setRangeZone(anyInt());
503         verify(mMediaDevice2, never()).setRangeZone(anyInt());
504     }
505 
506     @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
507     @Test
onDeviceListUpdate_verifyDeviceListCallback()508     public void onDeviceListUpdate_verifyDeviceListCallback() {
509         // This test relies on mMediaSwitchingController.start being called while the selected
510         // device list has exactly one item, and that item's id is:
511         // - Different from both ids in mMediaDevices.
512         // - Different from the id of the route published by the device under test (usually the
513         //   built-in speakers).
514         // So mock the selected device to respect these two preconditions.
515         MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
516         when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
517         doReturn(List.of(mockSelectedMediaDevice))
518                 .when(mLocalMediaManager)
519                 .getSelectedMediaDevice();
520 
521         mMediaSwitchingController.start(mCb);
522         reset(mCb);
523 
524         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
525         final List<MediaDevice> devices = new ArrayList<>();
526         for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
527             if (item.getMediaDevice().isPresent()) {
528                 devices.add(item.getMediaDevice().get());
529             }
530         }
531 
532         assertThat(devices.containsAll(mMediaDevices)).isTrue();
533         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
534         // There should be 2 non-MediaDevice items: the "Speakers & Display" title, and the "Connect
535         // a device" button.
536         assertThat(mMediaSwitchingController.getMediaItemList().size())
537                 .isEqualTo(mMediaDevices.size() + 2);
538         verify(mCb).onDeviceListChanged();
539     }
540 
541     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
542     @Test
onDeviceListUpdate_verifyDeviceListCallback_inputRouting()543     public void onDeviceListUpdate_verifyDeviceListCallback_inputRouting() {
544         // This test relies on mMediaSwitchingController.start being called while the selected
545         // device list has exactly one item, and that item's id is:
546         // - Different from both ids in mMediaDevices.
547         // - Different from the id of the route published by the device under test (usually the
548         //   built-in speakers).
549         // So mock the selected device to respect these two preconditions.
550         MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
551         when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
552         doReturn(List.of(mockSelectedMediaDevice))
553                 .when(mLocalMediaManager)
554                 .getSelectedMediaDevice();
555 
556         mMediaSwitchingController.start(mCb);
557         reset(mCb);
558 
559         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
560         final List<MediaDevice> devices = new ArrayList<>();
561         for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
562             if (item.getMediaDevice().isPresent()) {
563                 devices.add(item.getMediaDevice().get());
564             }
565         }
566 
567         assertThat(devices.containsAll(mMediaDevices)).isTrue();
568         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
569         // When input routing is enabled, there should be 4 non-MediaDevice items: one for
570         // the "Output" title, one for the "Speakers & Displays" title, one for the "Connect a
571         // device" button, and one for the "Input" title.
572         assertThat(mMediaSwitchingController.getMediaItemList().size())
573                 .isEqualTo(mMediaDevices.size() + 4);
574         verify(mCb).onDeviceListChanged();
575     }
576 
577     @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
578     @Test
advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize()579     public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
580         // This test relies on mMediaSwitchingController.start being called while the selected
581         // device list has exactly one item, and that item's id is:
582         // - Different from both ids in mMediaDevices.
583         // - Different from the id of the route published by the device under test (usually the
584         //   built-in speakers).
585         // So mock the selected device to respect these two preconditions.
586         MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
587         when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
588         doReturn(List.of(mockSelectedMediaDevice))
589                 .when(mLocalMediaManager)
590                 .getSelectedMediaDevice();
591 
592         when(mMediaDevice1.getFeatures()).thenReturn(
593                 ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
594         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
595         mMediaSwitchingController.start(mCb);
596         reset(mCb);
597 
598         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
599         final List<MediaDevice> devices = new ArrayList<>();
600         for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
601             if (item.getMediaDevice().isPresent()) {
602                 devices.add(item.getMediaDevice().get());
603             }
604         }
605 
606         assertThat(devices.containsAll(mMediaDevices)).isTrue();
607         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
608         // There should be 1 non-MediaDevice item: the "Speakers & Display" title.
609         assertThat(mMediaSwitchingController.getMediaItemList().size())
610                 .isEqualTo(mMediaDevices.size() + 1);
611         verify(mCb).onDeviceListChanged();
612     }
613 
614     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
615     @Test
advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize_inputRouting()616     public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize_inputRouting() {
617         // This test relies on mMediaSwitchingController.start being called while the selected
618         // device list has exactly one item, and that item's id is:
619         // - Different from both ids in mMediaDevices.
620         // - Different from the id of the route published by the device under test (usually the
621         //   built-in speakers).
622         // So mock the selected device to respect these two preconditions.
623         MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
624         when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
625         doReturn(List.of(mockSelectedMediaDevice))
626                 .when(mLocalMediaManager)
627                 .getSelectedMediaDevice();
628 
629         when(mMediaDevice1.getFeatures())
630                 .thenReturn(ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
631         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
632         mMediaSwitchingController.start(mCb);
633         reset(mCb);
634 
635         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
636         final List<MediaDevice> devices = new ArrayList<>();
637         for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
638             if (item.getMediaDevice().isPresent()) {
639                 devices.add(item.getMediaDevice().get());
640             }
641         }
642 
643         assertThat(devices.containsAll(mMediaDevices)).isTrue();
644         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
645         // When input routing is enabled, there should be 3 non-MediaDevice items: one for
646         // the "Output" title, one for the "Speakers & Displays" title, and one for the "Input"
647         // title.
648         assertThat(mMediaSwitchingController.getMediaItemList().size())
649                 .isEqualTo(mMediaDevices.size() + 3);
650         verify(mCb).onDeviceListChanged();
651     }
652 
653     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
654     @Test
onInputDeviceListUpdate_verifyDeviceListCallback()655     public void onInputDeviceListUpdate_verifyDeviceListCallback() {
656         AudioDeviceInfo[] audioDeviceInfos = {};
657         when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
658                 .thenReturn(audioDeviceInfos);
659         mMediaSwitchingController.start(mCb);
660 
661         // Output devices have changed.
662         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
663 
664         final MediaDevice mediaDevice3 =
665                 InputMediaDevice.create(
666                         mContext,
667                         TEST_DEVICE_3_ID,
668                         AudioDeviceInfo.TYPE_BUILTIN_MIC,
669                         MAX_VOLUME,
670                         CURRENT_VOLUME,
671                         VOLUME_FIXED_TRUE,
672                         PRODUCT_NAME_BUILTIN_MIC);
673         final MediaDevice mediaDevice4 =
674                 InputMediaDevice.create(
675                         mContext,
676                         TEST_DEVICE_4_ID,
677                         AudioDeviceInfo.TYPE_WIRED_HEADSET,
678                         MAX_VOLUME,
679                         CURRENT_VOLUME,
680                         VOLUME_FIXED_TRUE,
681                         PRODUCT_NAME_WIRED_HEADSET);
682         final List<MediaDevice> inputDevices = new ArrayList<>();
683         inputDevices.add(mediaDevice3);
684         inputDevices.add(mediaDevice4);
685 
686         // Input devices have changed.
687         mMediaSwitchingController.mInputDeviceCallback.onInputDeviceListUpdated(inputDevices);
688 
689         final List<MediaDevice> devices = new ArrayList<>();
690         for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
691             if (item.getMediaDevice().isPresent()) {
692                 devices.add(item.getMediaDevice().get());
693             }
694         }
695 
696         assertThat(devices).containsAtLeastElementsIn(mMediaDevices);
697         assertThat(devices).hasSize(mMediaDevices.size() + inputDevices.size());
698         verify(mCb, atLeastOnce()).onDeviceListChanged();
699     }
700 
701     @Test
advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize()702     public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() {
703         when(mMediaDevice1.isSuggestedDevice()).thenReturn(true);
704         when(mMediaDevice2.isSuggestedDevice()).thenReturn(false);
705 
706         mMediaSwitchingController.start(mCb);
707         reset(mCb);
708         mMediaSwitchingController.clearMediaItemList();
709         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
710         final List<MediaDevice> devices = new ArrayList<>();
711         int dividerSize = 0;
712         for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
713             if (item.getMediaDevice().isPresent()) {
714                 devices.add(item.getMediaDevice().get());
715             }
716             if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
717                 dividerSize++;
718             }
719         }
720 
721         assertThat(devices.containsAll(mMediaDevices)).isTrue();
722         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
723         assertThat(dividerSize).isEqualTo(2);
724         verify(mCb).onDeviceListChanged();
725     }
726 
727     @Test
onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue()728     public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
729         mMediaSwitchingController.start(mCb);
730         reset(mCb);
731         mMediaSwitchingController.mIsRefreshing = true;
732 
733         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
734 
735         assertThat(mMediaSwitchingController.mNeedRefresh).isTrue();
736     }
737 
738     @Test
advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue()739     public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
740         mMediaSwitchingController.start(mCb);
741         reset(mCb);
742         mMediaSwitchingController.mIsRefreshing = true;
743 
744         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
745 
746         assertThat(mMediaSwitchingController.mNeedRefresh).isTrue();
747     }
748 
749     @Test
cancelMuteAwaitConnection_cancelsWithMediaManager()750     public void cancelMuteAwaitConnection_cancelsWithMediaManager() {
751         when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class));
752         mMediaSwitchingController.start(mCb);
753         reset(mCb);
754 
755         mMediaSwitchingController.cancelMuteAwaitConnection();
756 
757         verify(mAudioManager).cancelMuteAwaitConnection(any());
758     }
759 
760     @Test
cancelMuteAwaitConnection_audioManagerIsNull_noAction()761     public void cancelMuteAwaitConnection_audioManagerIsNull_noAction() {
762         when(mAudioManager.getMutingExpectedDevice()).thenReturn(null);
763         mMediaSwitchingController.start(mCb);
764         reset(mCb);
765         mMediaSwitchingController.cancelMuteAwaitConnection();
766 
767         verify(mAudioManager, never()).cancelMuteAwaitConnection(any());
768     }
769 
770     @Test
getAppSourceName_packageNameIsNull_returnsNull()771     public void getAppSourceName_packageNameIsNull_returnsNull() {
772         MediaSwitchingController testMediaSwitchingController =
773                 new MediaSwitchingController(
774                         mSpyContext,
775                         "",
776                         mSpyContext.getUser(),
777                         /* token */ null,
778                         mMediaSessionManager,
779                         mLocalBluetoothManager,
780                         mStarter,
781                         mNotifCollection,
782                         mDialogTransitionAnimator,
783                         mNearbyMediaDevicesManager,
784                         mAudioManager,
785                         mPowerExemptionManager,
786                         mKeyguardManager,
787                         mFlags,
788                         mVolumePanelGlobalStateInteractor,
789                         mUserTracker);
790         testMediaSwitchingController.start(mCb);
791         reset(mCb);
792 
793         testMediaSwitchingController.getAppSourceName();
794 
795         assertThat(testMediaSwitchingController.getAppSourceName()).isNull();
796     }
797 
798     @Test
isActiveItem_deviceNotConnected_returnsFalse()799     public void isActiveItem_deviceNotConnected_returnsFalse() {
800         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
801 
802         assertThat(mMediaSwitchingController.isActiveItem(mMediaDevice1)).isFalse();
803     }
804 
805     @Test
getNotificationSmallIcon_packageNameIsNull_returnsNull()806     public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
807         MediaSwitchingController testMediaSwitchingController =
808                 new MediaSwitchingController(
809                         mSpyContext,
810                         "",
811                         mSpyContext.getUser(),
812                         /* token */ null,
813                         mMediaSessionManager,
814                         mLocalBluetoothManager,
815                         mStarter,
816                         mNotifCollection,
817                         mDialogTransitionAnimator,
818                         mNearbyMediaDevicesManager,
819                         mAudioManager,
820                         mPowerExemptionManager,
821                         mKeyguardManager,
822                         mFlags,
823                         mVolumePanelGlobalStateInteractor,
824                         mUserTracker);
825         testMediaSwitchingController.start(mCb);
826         reset(mCb);
827 
828         testMediaSwitchingController.getAppSourceName();
829 
830         assertThat(testMediaSwitchingController.getNotificationSmallIcon()).isNull();
831     }
832 
833     @Test
refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse()834     public void refreshDataSetIfNeeded_needRefreshIsTrue_setsToFalse() {
835         mMediaSwitchingController.start(mCb);
836         reset(mCb);
837         mMediaSwitchingController.mNeedRefresh = true;
838 
839         mMediaSwitchingController.refreshDataSetIfNeeded();
840 
841         assertThat(mMediaSwitchingController.mNeedRefresh).isFalse();
842     }
843 
844     @Test
isCurrentConnectedDeviceRemote_containsFeatures_returnsTrue()845     public void isCurrentConnectedDeviceRemote_containsFeatures_returnsTrue() {
846         when(mMediaDevice1.getFeatures()).thenReturn(
847                 ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
848         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
849 
850         assertThat(mMediaSwitchingController.isCurrentConnectedDeviceRemote()).isTrue();
851     }
852 
853     @Test
addDeviceToPlayMedia_callsLocalMediaManager()854     public void addDeviceToPlayMedia_callsLocalMediaManager() {
855         MediaSwitchingController testMediaSwitchingController =
856                 new MediaSwitchingController(
857                         mSpyContext,
858                         null,
859                         mSpyContext.getUser(),
860                         /* token */ null,
861                         mMediaSessionManager,
862                         mLocalBluetoothManager,
863                         mStarter,
864                         mNotifCollection,
865                         mDialogTransitionAnimator,
866                         mNearbyMediaDevicesManager,
867                         mAudioManager,
868                         mPowerExemptionManager,
869                         mKeyguardManager,
870                         mFlags,
871                         mVolumePanelGlobalStateInteractor,
872                         mUserTracker);
873 
874         LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
875         testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager;
876 
877         testMediaSwitchingController.addDeviceToPlayMedia(mMediaDevice2);
878         verify(mockLocalMediaManager).addDeviceToPlayMedia(mMediaDevice2);
879     }
880 
881     @Test
removeDeviceFromPlayMedia_callsLocalMediaManager()882     public void removeDeviceFromPlayMedia_callsLocalMediaManager() {
883         MediaSwitchingController testMediaSwitchingController =
884                 new MediaSwitchingController(
885                         mSpyContext,
886                         null,
887                         mSpyContext.getUser(),
888                         /* token */ null,
889                         mMediaSessionManager,
890                         mLocalBluetoothManager,
891                         mStarter,
892                         mNotifCollection,
893                         mDialogTransitionAnimator,
894                         mNearbyMediaDevicesManager,
895                         mAudioManager,
896                         mPowerExemptionManager,
897                         mKeyguardManager,
898                         mFlags,
899                         mVolumePanelGlobalStateInteractor,
900                         mUserTracker);
901 
902         LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
903         testMediaSwitchingController.mLocalMediaManager = mockLocalMediaManager;
904 
905         testMediaSwitchingController.removeDeviceFromPlayMedia(mMediaDevice2);
906         verify(mockLocalMediaManager).removeDeviceFromPlayMedia(mMediaDevice2);
907     }
908 
909     @Test
getTransferableMediaDevice_triggersFromLocalMediaManager()910     public void getTransferableMediaDevice_triggersFromLocalMediaManager() {
911         mMediaSwitchingController.getTransferableMediaDevices();
912 
913         verify(mLocalMediaManager).getTransferableMediaDevices();
914     }
915 
916     @Test
getDeselectableMediaDevice_triggersFromLocalMediaManager()917     public void getDeselectableMediaDevice_triggersFromLocalMediaManager() {
918         mMediaSwitchingController.getDeselectableMediaDevice();
919 
920         verify(mLocalMediaManager).getDeselectableMediaDevice();
921     }
922 
923     @Test
adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager()924     public void adjustSessionVolume_adjustWithoutId_triggersFromLocalMediaManager() {
925         int testVolume = 10;
926         mMediaSwitchingController.adjustSessionVolume(testVolume);
927 
928         verify(mLocalMediaManager).adjustSessionVolume(testVolume);
929     }
930 
931     @Test
logInteractionAdjustVolume_triggersFromMetricLogger()932     public void logInteractionAdjustVolume_triggersFromMetricLogger() {
933         MediaOutputMetricLogger spyMediaOutputMetricLogger =
934                 spy(mMediaSwitchingController.mMetricLogger);
935         mMediaSwitchingController.mMetricLogger = spyMediaOutputMetricLogger;
936 
937         mMediaSwitchingController.logInteractionAdjustVolume(mMediaDevice1);
938 
939         verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1);
940     }
941 
942     @Test
getSessionVolumeMax_triggersFromLocalMediaManager()943     public void getSessionVolumeMax_triggersFromLocalMediaManager() {
944         mMediaSwitchingController.getSessionVolumeMax();
945 
946         verify(mLocalMediaManager).getSessionVolumeMax();
947     }
948 
949     @Test
getSessionVolume_triggersFromLocalMediaManager()950     public void getSessionVolume_triggersFromLocalMediaManager() {
951         mMediaSwitchingController.getSessionVolume();
952 
953         verify(mLocalMediaManager).getSessionVolume();
954     }
955 
956     @Test
getSessionName_triggersFromLocalMediaManager()957     public void getSessionName_triggersFromLocalMediaManager() {
958         mMediaSwitchingController.getSessionName();
959 
960         verify(mLocalMediaManager).getSessionName();
961     }
962 
963     @Test
releaseSession_triggersFromLocalMediaManager()964     public void releaseSession_triggersFromLocalMediaManager() {
965         mMediaSwitchingController.releaseSession();
966 
967         verify(mLocalMediaManager).releaseSession();
968     }
969 
970     @Test
isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse()971     public void isAnyDeviceTransferring_noDevicesStateIsConnecting_returnsFalse() {
972         mMediaSwitchingController.start(mCb);
973         reset(mCb);
974 
975         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
976 
977         assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isFalse();
978     }
979 
980     @Test
isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue()981     public void isAnyDeviceTransferring_deviceStateIsConnecting_returnsTrue() {
982         when(mMediaDevice1.getState()).thenReturn(
983                 LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
984         mMediaSwitchingController.start(mCb);
985         reset(mCb);
986 
987         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
988 
989         assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue();
990     }
991 
992     @Test
isAnyDeviceTransferring_advancedLayoutSupport()993     public void isAnyDeviceTransferring_advancedLayoutSupport() {
994         when(mMediaDevice1.getState()).thenReturn(
995                 LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
996         mMediaSwitchingController.start(mCb);
997         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
998 
999         assertThat(mMediaSwitchingController.isAnyDeviceTransferring()).isTrue();
1000     }
1001 
1002     @Test
isPlaying_stateIsNull()1003     public void isPlaying_stateIsNull() {
1004         when(mSessionMediaController.getPlaybackState()).thenReturn(null);
1005 
1006         assertThat(mMediaSwitchingController.isPlaying()).isFalse();
1007     }
1008 
1009     @Test
onSelectedDeviceStateChanged_verifyCallback()1010     public void onSelectedDeviceStateChanged_verifyCallback() {
1011         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
1012         mMediaSwitchingController.start(mCb);
1013         reset(mCb);
1014         mMediaSwitchingController.connectDevice(mMediaDevice1);
1015 
1016         mMediaSwitchingController.onSelectedDeviceStateChanged(
1017                 mMediaDevice1, LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
1018 
1019         verify(mCb).onRouteChanged();
1020     }
1021 
1022     @Test
onDeviceAttributesChanged_verifyCallback()1023     public void onDeviceAttributesChanged_verifyCallback() {
1024         mMediaSwitchingController.start(mCb);
1025         reset(mCb);
1026 
1027         mMediaSwitchingController.onDeviceAttributesChanged();
1028 
1029         verify(mCb).onRouteChanged();
1030     }
1031 
1032     @Test
onRequestFailed_verifyCallback()1033     public void onRequestFailed_verifyCallback() {
1034         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
1035         mMediaSwitchingController.start(mCb);
1036         reset(mCb);
1037         mMediaSwitchingController.connectDevice(mMediaDevice2);
1038 
1039         mMediaSwitchingController.onRequestFailed(0 /* reason */);
1040 
1041         verify(mCb, atLeastOnce()).onRouteChanged();
1042     }
1043 
1044     @Test
getHeaderTitle_withoutMetadata_returnDefaultString()1045     public void getHeaderTitle_withoutMetadata_returnDefaultString() {
1046         when(mSessionMediaController.getMetadata()).thenReturn(null);
1047 
1048         mMediaSwitchingController.start(mCb);
1049 
1050         assertThat(
1051                         mMediaSwitchingController
1052                                 .getHeaderTitle()
1053                                 .equals(mContext.getText(R.string.controls_media_title)))
1054                 .isTrue();
1055     }
1056 
1057     @Test
getHeaderTitle_withMetadata_returnSongName()1058     public void getHeaderTitle_withMetadata_returnSongName() {
1059         when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
1060 
1061         mMediaSwitchingController.start(mCb);
1062 
1063         assertThat(mMediaSwitchingController.getHeaderTitle().equals(TEST_SONG)).isTrue();
1064     }
1065 
1066     @Test
getHeaderSubTitle_withoutMetadata_returnNull()1067     public void getHeaderSubTitle_withoutMetadata_returnNull() {
1068         when(mSessionMediaController.getMetadata()).thenReturn(null);
1069 
1070         mMediaSwitchingController.start(mCb);
1071 
1072         assertThat(mMediaSwitchingController.getHeaderSubTitle()).isNull();
1073     }
1074 
1075     @Test
getHeaderSubTitle_withMetadata_returnArtistName()1076     public void getHeaderSubTitle_withMetadata_returnArtistName() {
1077         when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
1078 
1079         mMediaSwitchingController.start(mCb);
1080 
1081         assertThat(mMediaSwitchingController.getHeaderSubTitle().equals(TEST_ARTIST)).isTrue();
1082     }
1083 
1084     @Test
getActiveRemoteMediaDevices()1085     public void getActiveRemoteMediaDevices() {
1086         when(mRemoteSessionInfo.getId()).thenReturn(TEST_SESSION_ID);
1087         when(mRemoteSessionInfo.getName()).thenReturn(TEST_SESSION_NAME);
1088         when(mRemoteSessionInfo.getVolumeMax()).thenReturn(100);
1089         when(mRemoteSessionInfo.getVolume()).thenReturn(10);
1090         when(mRemoteSessionInfo.isSystemSession()).thenReturn(false);
1091         mRoutingSessionInfos.add(mRemoteSessionInfo);
1092         when(mLocalMediaManager.getRemoteRoutingSessions()).thenReturn(mRoutingSessionInfos);
1093 
1094         assertThat(mMediaSwitchingController.getActiveRemoteMediaDevices())
1095                 .containsExactly(mRemoteSessionInfo);
1096     }
1097 
1098     @Test
getGroupMediaDevices_differentDeviceOrder_showingSameOrder()1099     public void getGroupMediaDevices_differentDeviceOrder_showingSameOrder() {
1100         final MediaDevice selectedMediaDevice1 = mock(MediaDevice.class);
1101         final MediaDevice selectedMediaDevice2 = mock(MediaDevice.class);
1102         final MediaDevice selectableMediaDevice1 = mock(MediaDevice.class);
1103         final MediaDevice selectableMediaDevice2 = mock(MediaDevice.class);
1104         final List<MediaDevice> selectedMediaDevices = new ArrayList<>();
1105         final List<MediaDevice> selectableMediaDevices = new ArrayList<>();
1106         when(selectedMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID);
1107         when(selectedMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
1108         when(selectableMediaDevice1.getId()).thenReturn(TEST_DEVICE_3_ID);
1109         when(selectableMediaDevice2.getId()).thenReturn(TEST_DEVICE_4_ID);
1110         selectedMediaDevices.add(selectedMediaDevice1);
1111         selectedMediaDevices.add(selectedMediaDevice2);
1112         selectableMediaDevices.add(selectableMediaDevice1);
1113         selectableMediaDevices.add(selectableMediaDevice2);
1114         doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
1115         doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
1116         final List<MediaDevice> groupMediaDevices =
1117                 mMediaSwitchingController.getGroupMediaDevices();
1118         // Reset order
1119         selectedMediaDevices.clear();
1120         selectedMediaDevices.add(selectedMediaDevice2);
1121         selectedMediaDevices.add(selectedMediaDevice1);
1122         selectableMediaDevices.clear();
1123         selectableMediaDevices.add(selectableMediaDevice2);
1124         selectableMediaDevices.add(selectableMediaDevice1);
1125         final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices();
1126 
1127         assertThat(newDevices.size()).isEqualTo(groupMediaDevices.size());
1128         for (int i = 0; i < groupMediaDevices.size(); i++) {
1129             assertThat(TextUtils.equals(groupMediaDevices.get(i).getId(),
1130                     newDevices.get(i).getId())).isTrue();
1131         }
1132     }
1133 
1134     @Test
getGroupMediaDevices_newDevice_verifyDeviceOrder()1135     public void getGroupMediaDevices_newDevice_verifyDeviceOrder() {
1136         final MediaDevice selectedMediaDevice1 = mock(MediaDevice.class);
1137         final MediaDevice selectedMediaDevice2 = mock(MediaDevice.class);
1138         final MediaDevice selectableMediaDevice1 = mock(MediaDevice.class);
1139         final MediaDevice selectableMediaDevice2 = mock(MediaDevice.class);
1140         final MediaDevice selectableMediaDevice3 = mock(MediaDevice.class);
1141         final List<MediaDevice> selectedMediaDevices = new ArrayList<>();
1142         final List<MediaDevice> selectableMediaDevices = new ArrayList<>();
1143         when(selectedMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID);
1144         when(selectedMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
1145         when(selectableMediaDevice1.getId()).thenReturn(TEST_DEVICE_3_ID);
1146         when(selectableMediaDevice2.getId()).thenReturn(TEST_DEVICE_4_ID);
1147         when(selectableMediaDevice3.getId()).thenReturn(TEST_DEVICE_5_ID);
1148         selectedMediaDevices.add(selectedMediaDevice1);
1149         selectedMediaDevices.add(selectedMediaDevice2);
1150         selectableMediaDevices.add(selectableMediaDevice1);
1151         selectableMediaDevices.add(selectableMediaDevice2);
1152         doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
1153         doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
1154         final List<MediaDevice> groupMediaDevices =
1155                 mMediaSwitchingController.getGroupMediaDevices();
1156         // Reset order
1157         selectedMediaDevices.clear();
1158         selectedMediaDevices.add(selectedMediaDevice2);
1159         selectedMediaDevices.add(selectedMediaDevice1);
1160         selectableMediaDevices.clear();
1161         selectableMediaDevices.add(selectableMediaDevice3);
1162         selectableMediaDevices.add(selectableMediaDevice2);
1163         selectableMediaDevices.add(selectableMediaDevice1);
1164         final List<MediaDevice> newDevices = mMediaSwitchingController.getGroupMediaDevices();
1165 
1166         assertThat(newDevices.size()).isEqualTo(5);
1167         for (int i = 0; i < groupMediaDevices.size(); i++) {
1168             assertThat(TextUtils.equals(groupMediaDevices.get(i).getId(),
1169                     newDevices.get(i).getId())).isTrue();
1170         }
1171         assertThat(newDevices.get(4).getId()).isEqualTo(TEST_DEVICE_5_ID);
1172     }
1173 
1174     @Test
getNotificationLargeIcon_withoutPackageName_returnsNull()1175     public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
1176         mMediaSwitchingController =
1177                 new MediaSwitchingController(
1178                         mSpyContext,
1179                         null,
1180                         mSpyContext.getUser(),
1181                         /* token */ null,
1182                         mMediaSessionManager,
1183                         mLocalBluetoothManager,
1184                         mStarter,
1185                         mNotifCollection,
1186                         mDialogTransitionAnimator,
1187                         mNearbyMediaDevicesManager,
1188                         mAudioManager,
1189                         mPowerExemptionManager,
1190                         mKeyguardManager,
1191                         mFlags,
1192                         mVolumePanelGlobalStateInteractor,
1193                         mUserTracker);
1194 
1195         assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
1196     }
1197 
1198     @Test
getNotificationLargeIcon_withoutLargeIcon_returnsNull()1199     public void getNotificationLargeIcon_withoutLargeIcon_returnsNull() {
1200         final List<NotificationEntry> entryList = new ArrayList<>();
1201         final NotificationEntry entry = mock(NotificationEntry.class);
1202         final StatusBarNotification sbn = mock(StatusBarNotification.class);
1203         final Notification notification = mock(Notification.class);
1204         entryList.add(entry);
1205 
1206         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
1207         when(entry.getSbn()).thenReturn(sbn);
1208         when(sbn.getNotification()).thenReturn(notification);
1209         when(sbn.getPackageName()).thenReturn(mPackageName);
1210         when(notification.isMediaNotification()).thenReturn(true);
1211         when(notification.getLargeIcon()).thenReturn(null);
1212 
1213         assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
1214     }
1215 
1216     @Test
getNotificationLargeIcon_withPackageNameAndMediaSession_returnsIconCompat()1217     public void getNotificationLargeIcon_withPackageNameAndMediaSession_returnsIconCompat() {
1218         final List<NotificationEntry> entryList = new ArrayList<>();
1219         final NotificationEntry entry = mock(NotificationEntry.class);
1220         final StatusBarNotification sbn = mock(StatusBarNotification.class);
1221         final Notification notification = mock(Notification.class);
1222         final Icon icon = mock(Icon.class);
1223         entryList.add(entry);
1224 
1225         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
1226         when(entry.getSbn()).thenReturn(sbn);
1227         when(sbn.getNotification()).thenReturn(notification);
1228         when(sbn.getPackageName()).thenReturn(mPackageName);
1229         when(notification.isMediaNotification()).thenReturn(true);
1230         when(notification.getLargeIcon()).thenReturn(icon);
1231 
1232         assertThat(mMediaSwitchingController.getNotificationIcon()).isInstanceOf(IconCompat.class);
1233     }
1234 
1235     @Test
getNotificationLargeIcon_withPackageNameAndNoMediaSession_returnsNull()1236     public void getNotificationLargeIcon_withPackageNameAndNoMediaSession_returnsNull() {
1237         final List<NotificationEntry> entryList = new ArrayList<>();
1238         final NotificationEntry entry = mock(NotificationEntry.class);
1239         final StatusBarNotification sbn = mock(StatusBarNotification.class);
1240         final Notification notification = mock(Notification.class);
1241         final Icon icon = mock(Icon.class);
1242         entryList.add(entry);
1243 
1244         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
1245         when(entry.getSbn()).thenReturn(sbn);
1246         when(sbn.getNotification()).thenReturn(notification);
1247         when(sbn.getPackageName()).thenReturn(mPackageName);
1248         when(notification.isMediaNotification()).thenReturn(false);
1249         when(notification.getLargeIcon()).thenReturn(icon);
1250 
1251         assertThat(mMediaSwitchingController.getNotificationIcon()).isNull();
1252     }
1253 
1254     @Test
getNotificationSmallIcon_withoutSmallIcon_returnsNull()1255     public void getNotificationSmallIcon_withoutSmallIcon_returnsNull() {
1256         final List<NotificationEntry> entryList = new ArrayList<>();
1257         final NotificationEntry entry = mock(NotificationEntry.class);
1258         final StatusBarNotification sbn = mock(StatusBarNotification.class);
1259         final Notification notification = mock(Notification.class);
1260         entryList.add(entry);
1261 
1262         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
1263         when(entry.getSbn()).thenReturn(sbn);
1264         when(sbn.getNotification()).thenReturn(notification);
1265         when(sbn.getPackageName()).thenReturn(mPackageName);
1266         when(notification.isMediaNotification()).thenReturn(true);
1267         when(notification.getSmallIcon()).thenReturn(null);
1268 
1269         assertThat(mMediaSwitchingController.getNotificationSmallIcon()).isNull();
1270     }
1271 
1272     @Test
getNotificationSmallIcon_withPackageNameAndMediaSession_returnsIconCompat()1273     public void getNotificationSmallIcon_withPackageNameAndMediaSession_returnsIconCompat() {
1274         final List<NotificationEntry> entryList = new ArrayList<>();
1275         final NotificationEntry entry = mock(NotificationEntry.class);
1276         final StatusBarNotification sbn = mock(StatusBarNotification.class);
1277         final Notification notification = mock(Notification.class);
1278         final Icon icon = mock(Icon.class);
1279         entryList.add(entry);
1280 
1281         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
1282         when(entry.getSbn()).thenReturn(sbn);
1283         when(sbn.getNotification()).thenReturn(notification);
1284         when(sbn.getPackageName()).thenReturn(mPackageName);
1285         when(notification.isMediaNotification()).thenReturn(true);
1286         when(notification.getSmallIcon()).thenReturn(icon);
1287 
1288         assertThat(mMediaSwitchingController.getNotificationSmallIcon())
1289                 .isInstanceOf(IconCompat.class);
1290     }
1291 
1292     @Test
getDeviceIconCompat_deviceIconIsNotNull_returnsIcon()1293     public void getDeviceIconCompat_deviceIconIsNotNull_returnsIcon() {
1294         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
1295         when(mMediaDevice1.getIcon()).thenReturn(mDrawable);
1296 
1297         assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1))
1298                 .isInstanceOf(IconCompat.class);
1299     }
1300 
1301     @Test
getDeviceIconCompat_deviceIconIsNull_returnsIcon()1302     public void getDeviceIconCompat_deviceIconIsNull_returnsIcon() {
1303         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice2);
1304         when(mMediaDevice1.getIcon()).thenReturn(null);
1305 
1306         assertThat(mMediaSwitchingController.getDeviceIconCompat(mMediaDevice1))
1307                 .isInstanceOf(IconCompat.class);
1308     }
1309 
1310     @Test
resetGroupMediaDevices_clearGroupDevices()1311     public void resetGroupMediaDevices_clearGroupDevices() {
1312         final MediaDevice selectedMediaDevice1 = mock(MediaDevice.class);
1313         final MediaDevice selectedMediaDevice2 = mock(MediaDevice.class);
1314         final MediaDevice selectableMediaDevice1 = mock(MediaDevice.class);
1315         final MediaDevice selectableMediaDevice2 = mock(MediaDevice.class);
1316         final List<MediaDevice> selectedMediaDevices = new ArrayList<>();
1317         final List<MediaDevice> selectableMediaDevices = new ArrayList<>();
1318         when(selectedMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID);
1319         when(selectedMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
1320         when(selectableMediaDevice1.getId()).thenReturn(TEST_DEVICE_3_ID);
1321         when(selectableMediaDevice2.getId()).thenReturn(TEST_DEVICE_4_ID);
1322         selectedMediaDevices.add(selectedMediaDevice1);
1323         selectedMediaDevices.add(selectedMediaDevice2);
1324         selectableMediaDevices.add(selectableMediaDevice1);
1325         selectableMediaDevices.add(selectableMediaDevice2);
1326         doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
1327         doReturn(selectableMediaDevices).when(mLocalMediaManager).getSelectableMediaDevice();
1328         assertThat(mMediaSwitchingController.getGroupMediaDevices().isEmpty()).isFalse();
1329 
1330         mMediaSwitchingController.resetGroupMediaDevices();
1331 
1332         assertThat(mMediaSwitchingController.mGroupMediaDevices.isEmpty()).isTrue();
1333     }
1334 
1335     @Test
isVolumeControlEnabled_isCastWithVolumeFixed_returnsFalse()1336     public void isVolumeControlEnabled_isCastWithVolumeFixed_returnsFalse() {
1337         when(mMediaDevice1.getDeviceType()).thenReturn(
1338                 MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
1339 
1340         when(mMediaDevice1.isVolumeFixed()).thenReturn(true);
1341 
1342         assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isFalse();
1343     }
1344 
1345     @Test
isVolumeControlEnabled_isCastWithVolumeNotFixed_returnsTrue()1346     public void isVolumeControlEnabled_isCastWithVolumeNotFixed_returnsTrue() {
1347         when(mMediaDevice1.getDeviceType()).thenReturn(
1348                 MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
1349 
1350         when(mMediaDevice1.isVolumeFixed()).thenReturn(false);
1351 
1352         assertThat(mMediaSwitchingController.isVolumeControlEnabled(mMediaDevice1)).isTrue();
1353     }
1354 
1355     @Test
setTemporaryAllowListExceptionIfNeeded_fromRemoteToBluetooth_addsAllowList()1356     public void setTemporaryAllowListExceptionIfNeeded_fromRemoteToBluetooth_addsAllowList() {
1357         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
1358         when(mMediaDevice1.getDeviceType()).thenReturn(
1359                 MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
1360         when(mMediaDevice1.getFeatures()).thenReturn(
1361                 ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK));
1362         when(mMediaDevice2.getDeviceType()).thenReturn(
1363                 MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
1364 
1365         mMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
1366 
1367         verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
1368                 anyLong());
1369     }
1370 
1371     @Test
setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction()1372     public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() {
1373         MediaSwitchingController testMediaSwitchingController =
1374                 new MediaSwitchingController(
1375                         mSpyContext,
1376                         null,
1377                         mSpyContext.getUser(),
1378                         /* token */ null,
1379                         mMediaSessionManager,
1380                         mLocalBluetoothManager,
1381                         mStarter,
1382                         mNotifCollection,
1383                         mDialogTransitionAnimator,
1384                         mNearbyMediaDevicesManager,
1385                         mAudioManager,
1386                         mPowerExemptionManager,
1387                         mKeyguardManager,
1388                         mFlags,
1389                         mVolumePanelGlobalStateInteractor,
1390                         mUserTracker);
1391 
1392         testMediaSwitchingController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
1393 
1394         verify(mPowerExemptionManager, never()).addToTemporaryAllowList(anyString(), anyInt(),
1395                 anyString(),
1396                 anyLong());
1397     }
1398 
1399     @Test
onMetadataChanged_triggersOnMetadataChanged()1400     public void onMetadataChanged_triggersOnMetadataChanged() {
1401         mMediaSwitchingController.mCallback = this.mCallback;
1402 
1403         mMediaSwitchingController.mCb.onMetadataChanged(mMediaMetadata);
1404 
1405         verify(mMediaSwitchingController.mCallback).onMediaChanged();
1406     }
1407 
1408     @Test
onPlaybackStateChanged_updateWithNullState_onMediaStoppedOrPaused()1409     public void onPlaybackStateChanged_updateWithNullState_onMediaStoppedOrPaused() {
1410         when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
1411         mMediaSwitchingController.mCallback = this.mCallback;
1412         mMediaSwitchingController.start(mCb);
1413 
1414         mMediaSwitchingController.mCb.onPlaybackStateChanged(null);
1415 
1416         verify(mMediaSwitchingController.mCallback).onMediaStoppedOrPaused();
1417     }
1418 
1419     @Test
launchBluetoothPairing_isKeyguardLocked_dismissDialog()1420     public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() {
1421         when(mDialogTransitionAnimator.createActivityTransitionController(mDialogLaunchView))
1422                 .thenReturn(mActivityTransitionAnimatorController);
1423         when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
1424         mMediaSwitchingController.mCallback = this.mCallback;
1425 
1426         mMediaSwitchingController.launchBluetoothPairing(mDialogLaunchView);
1427 
1428         verify(mCallback).dismissDialog();
1429     }
1430 
1431     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
1432     @Test
getSelectedMediaDevice()1433     public void getSelectedMediaDevice() {
1434         // Mock MediaDevice since none of the output media device constructor is publicly available
1435         // outside of SettingsLib package.
1436         final MediaDevice selectedOutputMediaDevice = mock(MediaDevice.class);
1437         doReturn(Collections.singletonList(selectedOutputMediaDevice))
1438                 .when(mLocalMediaManager)
1439                 .getSelectedMediaDevice();
1440 
1441         // Mock selected input media device.
1442         final MediaDevice selectedInputMediaDevice = mock(MediaDevice.class);
1443         doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice();
1444 
1445         List<MediaDevice> selectedMediaDevices = mMediaSwitchingController.getSelectedMediaDevice();
1446         assertThat(selectedMediaDevices)
1447                 .containsExactly(selectedOutputMediaDevice, selectedInputMediaDevice);
1448     }
1449 
1450     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
1451     @Test
selectInputDevice()1452     public void selectInputDevice() throws InterruptedException {
1453         final MediaDevice inputMediaDevice =
1454                 InputMediaDevice.create(
1455                         mContext,
1456                         TEST_DEVICE_1_ID,
1457                         AudioDeviceInfo.TYPE_BUILTIN_MIC,
1458                         MAX_VOLUME,
1459                         CURRENT_VOLUME,
1460                         VOLUME_FIXED_TRUE,
1461                         PRODUCT_NAME_BUILTIN_MIC);
1462         mMediaSwitchingController.connectDevice(inputMediaDevice);
1463 
1464         CountDownLatch latch = new CountDownLatch(LATCH_COUNT_DOWN_TIME_IN_SECOND);
1465         var unused = ThreadUtils.postOnBackgroundThread(latch::countDown);
1466         latch.await(LATCH_TIME_OUT_TIME_IN_SECOND, TimeUnit.SECONDS);
1467 
1468         verify(mInputRouteManager, atLeastOnce()).selectDevice(inputMediaDevice);
1469         verify(mLocalMediaManager, never()).connectDevice(inputMediaDevice);
1470     }
1471 
1472     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
1473     @Test
selectOutputDevice()1474     public void selectOutputDevice() throws InterruptedException {
1475         final MediaDevice outputMediaDevice = mock(MediaDevice.class);
1476         mMediaSwitchingController.connectDevice(outputMediaDevice);
1477 
1478         CountDownLatch latch = new CountDownLatch(LATCH_COUNT_DOWN_TIME_IN_SECOND);
1479         var unused = ThreadUtils.postOnBackgroundThread(latch::countDown);
1480         latch.await(LATCH_TIME_OUT_TIME_IN_SECOND, TimeUnit.SECONDS);
1481 
1482         verify(mInputRouteManager, never()).selectDevice(outputMediaDevice);
1483         verify(mLocalMediaManager, atLeastOnce()).connectDevice(outputMediaDevice);
1484     }
1485 
1486     @Test
connectDeviceButton_remoteDevice_noButton()1487     public void connectDeviceButton_remoteDevice_noButton() {
1488         when(mMediaDevice1.getFeatures()).thenReturn(
1489                 ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
1490         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
1491         mMediaSwitchingController.start(mCb);
1492         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1493 
1494         List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();
1495 
1496         assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(0);
1497     }
1498 
1499     @Test
connectDeviceButton_localDevice_hasButton()1500     public void connectDeviceButton_localDevice_hasButton() {
1501         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
1502         mMediaSwitchingController.start(mCb);
1503         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1504 
1505         List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();
1506 
1507         assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(1);
1508         assertThat(resultList.get(resultList.size() - 1).getMediaItemType()).isEqualTo(
1509                 MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE);
1510     }
1511 
1512     @Test
connectDeviceButton_localDeviceButtonDisabledByParam_noButton()1513     public void connectDeviceButton_localDeviceButtonDisabledByParam_noButton() {
1514         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
1515         mMediaSwitchingController.start(mCb);
1516         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1517 
1518         List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList(
1519                 false /* addConnectDeviceButton */);
1520 
1521         assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(0);
1522     }
1523 
1524     @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
1525     @Test
connectDeviceButton_presentAtAllTimesForNonGroupOutputs()1526     public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs() {
1527         mMediaSwitchingController.start(mCb);
1528         reset(mCb);
1529 
1530         // Mock the selected output device.
1531         doReturn(Collections.singletonList(mMediaDevice1))
1532                 .when(mLocalMediaManager)
1533                 .getSelectedMediaDevice();
1534 
1535         // Verify that there is initially one "Connect a device" button present.
1536         assertThat(getNumberOfConnectDeviceButtons(
1537                 mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
1538 
1539         // Change the selected device, and verify that there is still one "Connect a device" button
1540         // present.
1541         doReturn(Collections.singletonList(mMediaDevice2))
1542                 .when(mLocalMediaManager)
1543                 .getSelectedMediaDevice();
1544         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1545 
1546         assertThat(getNumberOfConnectDeviceButtons(
1547                 mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
1548     }
1549 
1550     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
1551     @Test
connectDeviceButton_presentAtAllTimesForNonGroupOutputs_inputRoutingEnabled()1552     public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs_inputRoutingEnabled() {
1553         mMediaSwitchingController.start(mCb);
1554         reset(mCb);
1555 
1556         // Mock the selected output device.
1557         doReturn(Collections.singletonList(mMediaDevice1))
1558                 .when(mLocalMediaManager)
1559                 .getSelectedMediaDevice();
1560 
1561         // Mock the selected input media device.
1562         final MediaDevice selectedInputMediaDevice = mock(MediaDevice.class);
1563         doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice();
1564 
1565         // Verify that there is initially one "Connect a device" button present.
1566         assertThat(getNumberOfConnectDeviceButtons(
1567                 mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
1568 
1569         // Change the selected device, and verify that there is still one "Connect a device" button
1570         // present.
1571         doReturn(Collections.singletonList(mMediaDevice2))
1572                 .when(mLocalMediaManager)
1573                 .getSelectedMediaDevice();
1574         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1575 
1576         assertThat(getNumberOfConnectDeviceButtons(
1577                 mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
1578     }
1579 
1580     @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
1581     @Test
selectedDevicesAddedInSameOrder()1582     public void selectedDevicesAddedInSameOrder() {
1583         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
1584         doReturn(mMediaDevices)
1585                 .when(mLocalMediaManager)
1586                 .getSelectedMediaDevice();
1587         mMediaSwitchingController.start(mCb);
1588         reset(mCb);
1589         mMediaSwitchingController.clearMediaItemList();
1590 
1591         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1592 
1593         List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
1594         assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice1);
1595         assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice2);
1596     }
1597 
1598     @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
1599     @Test
selectedDevicesAddedInSameOrderWhenRlpDoesNotExist()1600     public void selectedDevicesAddedInSameOrderWhenRlpDoesNotExist() {
1601         setUpSelectedDevicesAndOrdering();
1602 
1603         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1604 
1605         List<MediaDevice> devices =
1606                 mMediaSwitchingController.getMediaItemList().stream()
1607                         .filter(item -> item.getMediaDevice().isPresent())
1608                         .map(item -> item.getMediaDevice().orElse(null))
1609                         .collect(Collectors.toList());
1610         assertThat(devices)
1611                 .containsExactly(
1612                         mMediaDevice4,
1613                         mMediaDevice3,
1614                         mMediaDevice5,
1615                         mMediaDevice1,
1616                         mMediaDevice2)
1617                 .inOrder();
1618     }
1619 
1620     @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
1621     @Test
selectedDevicesAddedInSortedOrderWhenRlpDoesNotExist()1622     public void selectedDevicesAddedInSortedOrderWhenRlpDoesNotExist() {
1623         setUpSelectedDevicesAndOrdering();
1624 
1625         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1626 
1627         List<MediaDevice> devices =
1628                 mMediaSwitchingController.getMediaItemList().stream()
1629                         .filter(item -> item.getMediaDevice().isPresent())
1630                         .map(item -> item.getMediaDevice().orElse(null))
1631                         .collect(Collectors.toList());
1632 
1633         assertThat(devices)
1634                 .containsExactly(
1635                         mMediaDevice5,
1636                         mMediaDevice4,
1637                         mMediaDevice3,
1638                         mMediaDevice1,
1639                         mMediaDevice2)
1640                 .inOrder();
1641     }
1642 
setUpSelectedDevicesAndOrdering()1643     private void setUpSelectedDevicesAndOrdering() {
1644         when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID);
1645         when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
1646         when(mMediaDevice3.getId()).thenReturn(TEST_DEVICE_3_ID);
1647         when(mMediaDevice4.getId()).thenReturn(TEST_DEVICE_4_ID);
1648         when(mMediaDevice5.getId()).thenReturn(TEST_DEVICE_5_ID);
1649         mMediaDevices.clear();
1650         Collections.addAll(
1651                 mMediaDevices,
1652                 mMediaDevice2,
1653                 mMediaDevice1,
1654                 mMediaDevice4,
1655                 mMediaDevice3,
1656                 mMediaDevice5);
1657         List<MediaDevice> selectedMediaDevices = new ArrayList<>();
1658         Collections.addAll(selectedMediaDevices, mMediaDevice3, mMediaDevice4, mMediaDevice5);
1659         doReturn(selectedMediaDevices).when(mLocalMediaManager).getSelectedMediaDevice();
1660         // Sort the media devices in the order they appear in the deviceOrder list
1661         List<MediaDevice> deviceOrder = new ArrayList<>();
1662         Collections.addAll(
1663                 deviceOrder,
1664                 mMediaDevice1,
1665                 mMediaDevice2,
1666                 mMediaDevice3,
1667                 mMediaDevice4,
1668                 mMediaDevice5);
1669         for (int i = 0; i < deviceOrder.size(); i++) {
1670             for (int j = i + 1; j < deviceOrder.size(); j++) {
1671                 when(deviceOrder.get(i).compareTo(deviceOrder.get(j))).thenReturn(-1);
1672                 when(deviceOrder.get(j).compareTo(deviceOrder.get(i))).thenReturn(1);
1673             }
1674         }
1675         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
1676         mMediaSwitchingController.start(mCb);
1677         reset(mCb);
1678         mMediaSwitchingController.clearMediaItemList();
1679     }
1680 
1681     @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
1682     @Test
selectedDevicesAddedInReverseOrder()1683     public void selectedDevicesAddedInReverseOrder() {
1684         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
1685         doReturn(mMediaDevices)
1686                 .when(mLocalMediaManager)
1687                 .getSelectedMediaDevice();
1688         mMediaSwitchingController.start(mCb);
1689         reset(mCb);
1690         mMediaSwitchingController.clearMediaItemList();
1691 
1692         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1693 
1694         List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
1695         assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice2);
1696         assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice1);
1697     }
1698 
1699     @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
1700     @Test
firstSelectedDeviceIsFirstDeviceInGroupIsTrue()1701     public void firstSelectedDeviceIsFirstDeviceInGroupIsTrue() {
1702         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
1703         doReturn(mMediaDevices)
1704                 .when(mLocalMediaManager)
1705                 .getSelectedMediaDevice();
1706         mMediaSwitchingController.start(mCb);
1707         reset(mCb);
1708         mMediaSwitchingController.clearMediaItemList();
1709 
1710         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1711 
1712         List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
1713         assertThat(items.get(0).isFirstDeviceInGroup()).isTrue();
1714         assertThat(items.get(1).isFirstDeviceInGroup()).isFalse();
1715     }
1716 
1717     @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
1718     @Test
deviceListUpdateWithDifferentDevices_firstSelectedDeviceIsFirstDeviceInGroup()1719     public void deviceListUpdateWithDifferentDevices_firstSelectedDeviceIsFirstDeviceInGroup() {
1720         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
1721         doReturn(mMediaDevices)
1722                 .when(mLocalMediaManager)
1723                 .getSelectedMediaDevice();
1724         mMediaSwitchingController.start(mCb);
1725         reset(mCb);
1726         mMediaSwitchingController.clearMediaItemList();
1727         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1728         mMediaDevices.clear();
1729         mMediaDevices.add(mMediaDevice2);
1730         mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
1731 
1732         List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
1733         assertThat(items.get(0).isFirstDeviceInGroup()).isTrue();
1734     }
1735 
getNumberOfConnectDeviceButtons(List<MediaItem> itemList)1736     private int getNumberOfConnectDeviceButtons(List<MediaItem> itemList) {
1737         int numberOfConnectDeviceButtons = 0;
1738         for (MediaItem item : itemList) {
1739             if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE) {
1740                 numberOfConnectDeviceButtons++;
1741             }
1742         }
1743         return numberOfConnectDeviceButtons;
1744     }
1745 }
1746