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.Mockito.any;
22 import static org.mockito.Mockito.doNothing;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
27 
28 import android.app.KeyguardManager;
29 import android.content.Context;
30 import android.graphics.Bitmap;
31 import android.media.AudioManager;
32 import android.media.session.MediaController;
33 import android.media.session.MediaSessionManager;
34 import android.media.session.PlaybackState;
35 import android.os.Bundle;
36 import android.os.PowerExemptionManager;
37 import android.testing.TestableLooper;
38 import android.view.View;
39 import android.widget.Button;
40 import android.widget.ImageView;
41 import android.widget.TextView;
42 
43 import androidx.core.graphics.drawable.IconCompat;
44 import androidx.test.ext.junit.runners.AndroidJUnit4;
45 import androidx.test.filters.SmallTest;
46 
47 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
48 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
49 import com.android.settingslib.bluetooth.LocalBluetoothManager;
50 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
51 import com.android.settingslib.media.LocalMediaManager;
52 import com.android.systemui.SysuiTestCase;
53 import com.android.systemui.animation.DialogTransitionAnimator;
54 import com.android.systemui.broadcast.BroadcastSender;
55 import com.android.systemui.flags.FeatureFlags;
56 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
57 import com.android.systemui.plugins.ActivityStarter;
58 import com.android.systemui.res.R;
59 import com.android.systemui.settings.UserTracker;
60 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
61 
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 
66 import java.util.ArrayList;
67 import java.util.List;
68 
69 @SmallTest
70 @RunWith(AndroidJUnit4.class)
71 @TestableLooper.RunWithLooper(setAsMainLooper = true)
72 public class MediaOutputBaseDialogTest extends SysuiTestCase {
73 
74     private static final String TEST_PACKAGE = "test_package";
75 
76     // Mock
77     private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class);
78     private MediaController mMediaController = mock(MediaController.class);
79     private PlaybackState mPlaybackState = mock(PlaybackState.class);
80     private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
81     private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
82     private final LocalBluetoothProfileManager mLocalBluetoothProfileManager = mock(
83             LocalBluetoothProfileManager.class);
84     private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock(
85             LocalBluetoothLeBroadcast.class);
86     private ActivityStarter mStarter = mock(ActivityStarter.class);
87     private BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
88     private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
89     private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
90             NearbyMediaDevicesManager.class);
91     private final DialogTransitionAnimator mDialogTransitionAnimator = mock(
92             DialogTransitionAnimator.class);
93     private final AudioManager mAudioManager = mock(AudioManager.class);
94     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
95     private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
96     private FeatureFlags mFlags = mock(FeatureFlags.class);
97     private UserTracker mUserTracker = mock(UserTracker.class);
98 
99     private List<MediaController> mMediaControllers = new ArrayList<>();
100     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
101     private MediaOutputController mMediaOutputController;
102     private int mHeaderIconRes;
103     private IconCompat mIconCompat;
104     private CharSequence mHeaderTitle;
105     private CharSequence mHeaderSubtitle;
106     private String mStopText;
107     private boolean mIsBroadcasting;
108     private boolean mIsBroadcastIconVisibility;
109 
110 
111     @Before
setUp()112     public void setUp() {
113         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
114         final CachedBluetoothDeviceManager cachedBluetoothDeviceManager = mock(
115                 CachedBluetoothDeviceManager.class);
116         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
117                 cachedBluetoothDeviceManager);
118         when(cachedBluetoothDeviceManager.findDevice(any())).thenReturn(null);
119         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
120         when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
121         when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_NONE);
122         when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE);
123         mMediaControllers.add(mMediaController);
124         when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
125 
126         mMediaOutputController =
127                 new MediaOutputController(
128                         mContext,
129                         TEST_PACKAGE,
130                         mContext.getUser(),
131                         /* token */ null,
132                         mMediaSessionManager,
133                         mLocalBluetoothManager,
134                         mStarter,
135                         mNotifCollection,
136                         mDialogTransitionAnimator,
137                         mNearbyMediaDevicesManager,
138                         mAudioManager,
139                         mPowerExemptionManager,
140                         mKeyguardManager,
141                         mFlags,
142                         mUserTracker);
143 
144         // Using a fake package will cause routing operations to fail, so we intercept
145         // scanning-related operations.
146         mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
147         doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
148         doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
149 
150         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
151                 mMediaOutputController);
152         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
153     }
154 
155     @Test
refresh_withIconRes_iconIsVisible()156     public void refresh_withIconRes_iconIsVisible() {
157         mHeaderIconRes = 1;
158         mMediaOutputBaseDialogImpl.refresh();
159         final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
160                 R.id.header_icon);
161 
162         assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
163     }
164 
165     @Test
refresh_withIconCompat_iconIsVisible()166     public void refresh_withIconCompat_iconIsVisible() {
167         mIconCompat = IconCompat.createWithBitmap(
168                 Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888));
169         when(mMediaOutputBaseAdapter.getController()).thenReturn(mMediaOutputController);
170 
171         mMediaOutputBaseDialogImpl.refresh();
172         final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
173                 R.id.header_icon);
174 
175         assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
176     }
177 
178     @Test
refresh_noIcon_iconLayoutNotVisible()179     public void refresh_noIcon_iconLayoutNotVisible() {
180         mHeaderIconRes = 0;
181         mIconCompat = null;
182         mMediaOutputBaseDialogImpl.refresh();
183         final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
184                 R.id.header_icon);
185 
186         assertThat(view.getVisibility()).isEqualTo(View.GONE);
187     }
188 
189     @Test
refresh_broadcastIconVisibilityOff_broadcastIconLayoutNotVisible()190     public void refresh_broadcastIconVisibilityOff_broadcastIconLayoutNotVisible() {
191         mIsBroadcastIconVisibility = false;
192 
193         mMediaOutputBaseDialogImpl.refresh();
194         final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
195                 R.id.broadcast_icon);
196 
197         assertThat(view.getVisibility()).isEqualTo(View.GONE);
198     }
199     @Test
refresh_broadcastIconVisibilityOn_broadcastIconLayoutVisible()200     public void refresh_broadcastIconVisibilityOn_broadcastIconLayoutVisible() {
201         mIsBroadcastIconVisibility = true;
202 
203         mMediaOutputBaseDialogImpl.refresh();
204         final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
205                 R.id.broadcast_icon);
206 
207         assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
208     }
209 
210     @Test
refresh_checkTitle()211     public void refresh_checkTitle() {
212         mHeaderTitle = "test_string";
213 
214         mMediaOutputBaseDialogImpl.refresh();
215         final TextView titleView = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
216                 R.id.header_title);
217 
218         assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE);
219         assertThat(titleView.getText()).isEqualTo(mHeaderTitle);
220     }
221 
222     @Test
refresh_withSubtitle_checkSubtitle()223     public void refresh_withSubtitle_checkSubtitle() {
224         mHeaderSubtitle = "test_string";
225 
226         mMediaOutputBaseDialogImpl.refresh();
227         final TextView subtitleView = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
228                 R.id.header_subtitle);
229 
230         assertThat(subtitleView.getVisibility()).isEqualTo(View.VISIBLE);
231         assertThat(subtitleView.getText()).isEqualTo(mHeaderSubtitle);
232     }
233 
234     @Test
refresh_noSubtitle_checkSubtitle()235     public void refresh_noSubtitle_checkSubtitle() {
236         mMediaOutputBaseDialogImpl.refresh();
237         final TextView subtitleView = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
238                 R.id.header_subtitle);
239 
240         assertThat(subtitleView.getVisibility()).isEqualTo(View.GONE);
241     }
242 
243     @Test
refresh_inDragging_notUpdateAdapter()244     public void refresh_inDragging_notUpdateAdapter() {
245         when(mMediaOutputBaseAdapter.isDragging()).thenReturn(true);
246         mMediaOutputBaseDialogImpl.refresh();
247 
248         verify(mMediaOutputBaseAdapter, never()).notifyDataSetChanged();
249     }
250 
251     @Test
refresh_inDragging_directSetRefreshingToFalse()252     public void refresh_inDragging_directSetRefreshingToFalse() {
253         when(mMediaOutputBaseAdapter.isDragging()).thenReturn(true);
254         mMediaOutputBaseDialogImpl.refresh();
255 
256         assertThat(mMediaOutputController.isRefreshing()).isFalse();
257     }
258 
259     @Test
refresh_notInDragging_verifyUpdateAdapter()260     public void refresh_notInDragging_verifyUpdateAdapter() {
261         when(mMediaOutputBaseAdapter.getCurrentActivePosition()).thenReturn(-1);
262         when(mMediaOutputBaseAdapter.isDragging()).thenReturn(false);
263         mMediaOutputBaseDialogImpl.refresh();
264 
265         verify(mMediaOutputBaseAdapter).updateItems();
266     }
267 
268     @Test
dismissDialog_closesDialogByBroadcastSender()269     public void dismissDialog_closesDialogByBroadcastSender() {
270         mMediaOutputBaseDialogImpl.dismissDialog();
271 
272         verify(mBroadcastSender).closeSystemDialogs();
273     }
274 
275     @Test
whenBroadcasting_verifyLeBroadcastServiceCallBackIsRegisteredAndUnregistered()276     public void whenBroadcasting_verifyLeBroadcastServiceCallBackIsRegisteredAndUnregistered() {
277         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
278                 mLocalBluetoothLeBroadcast);
279         mIsBroadcasting = true;
280 
281         mMediaOutputBaseDialogImpl.start();
282         verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
283 
284         mMediaOutputBaseDialogImpl.stop();
285         verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
286     }
287 
288     @Test
289     public void
whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsNotRegisteredOrUnregistered()290             whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsNotRegisteredOrUnregistered() {
291         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
292                 mLocalBluetoothLeBroadcast);
293         mIsBroadcasting = false;
294 
295         mMediaOutputBaseDialogImpl.start();
296         mMediaOutputBaseDialogImpl.stop();
297 
298         verify(mLocalBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any());
299         verify(mLocalBluetoothLeBroadcast, never()).unregisterServiceCallBack(any());
300     }
301 
302     @Test
303     public void
whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsUnregisteredIfProfileEnabled()304             whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsUnregisteredIfProfileEnabled() {
305         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
306                 mLocalBluetoothLeBroadcast);
307         mIsBroadcasting = true;
308 
309         mMediaOutputBaseDialogImpl.start();
310         verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
311 
312         mIsBroadcasting = false;
313         mMediaOutputBaseDialogImpl.stop();
314         verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
315     }
316 
317     @Test
refresh_checkStopText()318     public void refresh_checkStopText() {
319         mStopText = "test_string";
320         mMediaOutputBaseDialogImpl.refresh();
321         final Button stop = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(R.id.stop);
322 
323         assertThat(stop.getText().toString()).isEqualTo(mStopText);
324     }
325 
326     class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
327 
MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender, MediaOutputController mediaOutputController)328         MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender,
329                 MediaOutputController mediaOutputController) {
330             super(
331                     context,
332                     broadcastSender,
333                     mediaOutputController, /* includePlaybackAndAppMetadata */
334                     true);
335 
336             mAdapter = mMediaOutputBaseAdapter;
337         }
338 
339         @Override
getAppSourceIcon()340         IconCompat getAppSourceIcon() {
341             return null;
342         }
343 
344         @Override
getHeaderIconRes()345         int getHeaderIconRes() {
346             return mHeaderIconRes;
347         }
348 
349         @Override
getHeaderIcon()350         IconCompat getHeaderIcon() {
351             return mIconCompat;
352         }
353 
354         @Override
getHeaderIconSize()355         int getHeaderIconSize() {
356             return 10;
357         }
358 
359         @Override
getHeaderText()360         CharSequence getHeaderText() {
361             return mHeaderTitle;
362         }
363 
364         @Override
getHeaderSubtitle()365         CharSequence getHeaderSubtitle() {
366             return mHeaderSubtitle;
367         }
368 
369         @Override
getStopButtonVisibility()370         int getStopButtonVisibility() {
371             return 0;
372         }
373 
374         @Override
isBroadcastSupported()375         public boolean isBroadcastSupported() {
376             return mIsBroadcasting;
377         }
378 
379         @Override
getStopButtonText()380         public CharSequence getStopButtonText() {
381             return mStopText;
382         }
383 
384         @Override
getBroadcastIconVisibility()385         public int getBroadcastIconVisibility() {
386             return mIsBroadcastIconVisibility ? View.VISIBLE : View.GONE;
387         }
388     }
389 }
390