1 /*
2  * Copyright (C) 2023 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.anyBoolean;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.times;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.app.KeyguardManager;
30 import android.bluetooth.BluetoothDevice;
31 import android.bluetooth.BluetoothLeBroadcastMetadata;
32 import android.bluetooth.BluetoothLeBroadcastReceiveState;
33 import android.media.AudioManager;
34 import android.media.session.MediaSessionManager;
35 import android.os.PowerExemptionManager;
36 import android.testing.TestableLooper;
37 import android.view.View;
38 import android.widget.EditText;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41 
42 import androidx.test.ext.junit.runners.AndroidJUnit4;
43 import androidx.test.filters.SmallTest;
44 
45 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
46 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
47 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
48 import com.android.settingslib.bluetooth.LocalBluetoothManager;
49 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
50 import com.android.settingslib.media.BluetoothMediaDevice;
51 import com.android.settingslib.media.LocalMediaManager;
52 import com.android.settingslib.media.MediaDevice;
53 import com.android.systemui.SysuiTestCase;
54 import com.android.systemui.animation.DialogTransitionAnimator;
55 import com.android.systemui.broadcast.BroadcastSender;
56 import com.android.systemui.flags.FeatureFlags;
57 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
58 import com.android.systemui.plugins.ActivityStarter;
59 import com.android.systemui.res.R;
60 import com.android.systemui.settings.UserTracker;
61 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
62 
63 import com.google.common.base.Strings;
64 
65 import org.junit.After;
66 import org.junit.Before;
67 import org.junit.Test;
68 import org.junit.runner.RunWith;
69 
70 import java.nio.charset.StandardCharsets;
71 import java.util.ArrayList;
72 import java.util.List;
73 
74 @SmallTest
75 @RunWith(AndroidJUnit4.class)
76 @TestableLooper.RunWithLooper
77 public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
78 
79     private static final String TEST_PACKAGE = "test_package";
80     private static final String BROADCAST_NAME_TEST = "Broadcast_name_test";
81     private static final String BROADCAST_CODE_TEST = "112233";
82     private static final String BROADCAST_CODE_UPDATE_TEST = "11223344";
83 
84     // Mock
85     private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
86     private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
87     private final LocalBluetoothProfileManager mLocalBluetoothProfileManager = mock(
88             LocalBluetoothProfileManager.class);
89     private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock(
90             LocalBluetoothLeBroadcast.class);
91     private final LocalBluetoothLeBroadcastAssistant mLocalBluetoothLeBroadcastAssistant = mock(
92             LocalBluetoothLeBroadcastAssistant.class);
93     private final BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata = mock(
94             BluetoothLeBroadcastMetadata.class);
95     private final BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState = mock(
96             BluetoothLeBroadcastReceiveState.class);
97     private final ActivityStarter mStarter = mock(ActivityStarter.class);
98     private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
99     private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
100     private final MediaDevice mBluetoothMediaDevice = mock(BluetoothMediaDevice.class);
101     private final BluetoothDevice mBluetoothFirstDevice = mock(BluetoothDevice.class);
102     private final BluetoothDevice mBluetoothSecondDevice = mock(BluetoothDevice.class);
103     private final CachedBluetoothDevice mCachedBluetoothDevice = mock(CachedBluetoothDevice.class);
104     private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
105     private final DialogTransitionAnimator mDialogTransitionAnimator = mock(
106             DialogTransitionAnimator.class);
107     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
108             NearbyMediaDevicesManager.class);
109     private final AudioManager mAudioManager = mock(AudioManager.class);
110     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
111     private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
112     private FeatureFlags mFlags = mock(FeatureFlags.class);
113     private UserTracker mUserTracker = mock(UserTracker.class);
114 
115     private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
116     private MediaOutputController mMediaOutputController;
117 
118     @Before
setUp()119     public void setUp() {
120         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
121         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
122         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
123         when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST);
124         when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn(
125                 BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
126 
127         mMediaOutputController =
128                 new MediaOutputController(
129                         mContext,
130                         TEST_PACKAGE,
131                         mContext.getUser(),
132                         /* token */ null,
133                         mMediaSessionManager,
134                         mLocalBluetoothManager,
135                         mStarter,
136                         mNotifCollection,
137                         mDialogTransitionAnimator,
138                         mNearbyMediaDevicesManager,
139                         mAudioManager,
140                         mPowerExemptionManager,
141                         mKeyguardManager,
142                         mFlags,
143                         mUserTracker);
144         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
145         mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
146                 mBroadcastSender, mMediaOutputController);
147         mMediaOutputBroadcastDialog.show();
148     }
149 
150     @After
tearDown()151     public void tearDown() {
152         if (mMediaOutputBroadcastDialog.mAlertDialog != null){
153             mMediaOutputBroadcastDialog.mAlertDialog.dismiss();
154         }
155         mMediaOutputBroadcastDialog.dismiss();
156     }
157 
158     @Test
startBroadcastWithConnectedDevices_noBroadcastMetadata_failToAddSource()159     public void startBroadcastWithConnectedDevices_noBroadcastMetadata_failToAddSource() {
160         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
161                 mLocalBluetoothLeBroadcast);
162         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(null);
163         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
164                 mLocalBluetoothLeBroadcastAssistant);
165 
166         mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
167 
168         verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
169     }
170 
171     @Test
startBroadcastWithConnectedDevices_noConnectedMediaDevice_failToAddSource()172     public void startBroadcastWithConnectedDevices_noConnectedMediaDevice_failToAddSource() {
173         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
174                 mLocalBluetoothLeBroadcast);
175         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
176                 mBluetoothLeBroadcastMetadata);
177         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
178                 mLocalBluetoothLeBroadcastAssistant);
179         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(null);
180 
181         mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
182 
183         verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
184     }
185 
186     @Test
startBroadcastWithConnectedDevices_hasBroadcastSource_failToAddSource()187     public void startBroadcastWithConnectedDevices_hasBroadcastSource_failToAddSource() {
188         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
189                 mLocalBluetoothLeBroadcast);
190         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
191                 mBluetoothLeBroadcastMetadata);
192         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
193                 mLocalBluetoothLeBroadcastAssistant);
194         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
195         when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
196                 .thenReturn(mCachedBluetoothDevice);
197         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
198         List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
199         sourceList.add(mBluetoothLeBroadcastReceiveState);
200         when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
201                 sourceList);
202 
203         mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
204 
205         verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
206     }
207 
208     @Test
startBroadcastWithConnectedDevices_noBroadcastSource_failToAddSource()209     public void startBroadcastWithConnectedDevices_noBroadcastSource_failToAddSource() {
210         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
211                 mLocalBluetoothLeBroadcast);
212         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
213                 mLocalBluetoothLeBroadcastAssistant);
214         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
215                 mBluetoothLeBroadcastMetadata);
216         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
217         when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
218         when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice()).thenReturn(
219                 mCachedBluetoothDevice);
220         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
221         List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
222         when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
223                 sourceList);
224         List<BluetoothDevice> connectedDevicesList = new ArrayList<>();
225         connectedDevicesList.add(mBluetoothFirstDevice);
226         when(mLocalBluetoothLeBroadcastAssistant.getConnectedDevices()).thenReturn(
227                 connectedDevicesList);
228 
229         mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
230 
231         verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
232     }
233 
234     @Test
handleLeBroadcastMetadataChanged_checkBroadcastName()235     public void handleLeBroadcastMetadataChanged_checkBroadcastName() {
236         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
237                 mLocalBluetoothLeBroadcast);
238         final TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView
239                 .requireViewById(R.id.broadcast_name_summary);
240 
241         mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged();
242 
243         assertThat(broadcastName.getText().toString()).isEqualTo(BROADCAST_NAME_TEST);
244     }
245 
246     @Test
handleLeBroadcastMetadataChanged_checkBroadcastCode()247     public void handleLeBroadcastMetadataChanged_checkBroadcastCode() {
248         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
249                 mLocalBluetoothLeBroadcast);
250 
251         final TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView
252                 .requireViewById(R.id.broadcast_code_summary);
253 
254         mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged();
255 
256         assertThat(broadcastCode.getText().toString()).isEqualTo(BROADCAST_CODE_TEST);
257     }
258 
259     @Test
updateBroadcastInfo_stopBroadcastFailed_handleFailedUi()260     public void updateBroadcastInfo_stopBroadcastFailed_handleFailedUi() {
261         ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
262                 .requireViewById(R.id.broadcast_code_edit);
263         TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
264                 R.id.broadcast_code_summary);
265         broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
266         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
267         broadcastCodeEdit.callOnClick();
268 
269         mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
270         assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(1);
271 
272         mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
273         assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(2);
274 
275         // It will be the MAX Retry Count = 3
276         mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
277         assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0);
278     }
279 
280     @Test
afterTextChanged_nameLengthMoreThanMax_showErrorMessage()281     public void afterTextChanged_nameLengthMoreThanMax_showErrorMessage() {
282         ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
283                 .requireViewById(R.id.broadcast_name_edit);
284         TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
285                 R.id.broadcast_name_summary);
286         broadcastName.setText(BROADCAST_NAME_TEST);
287         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
288                 mLocalBluetoothLeBroadcast);
289         broadcastNameEdit.callOnClick();
290         EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
291                 R.id.broadcast_edit_text);
292         TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
293                 R.id.broadcast_error_message);
294         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
295 
296         // input the invalid text
297         String moreThanMax = Strings.repeat("a",
298                 MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 3);
299         editText.setText(moreThanMax);
300 
301         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
302     }
303 
304     @Test
afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage()305     public void afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage() {
306         ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
307                 .requireViewById(R.id.broadcast_name_edit);
308         TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
309                 R.id.broadcast_name_summary);
310         broadcastName.setText(BROADCAST_NAME_TEST);
311         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
312                 mLocalBluetoothLeBroadcast);
313         broadcastNameEdit.callOnClick();
314         EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
315                 R.id.broadcast_edit_text);
316         TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
317                 R.id.broadcast_error_message);
318         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
319 
320         // input the invalid text
321         String testString = Strings.repeat("a",
322                 MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 2);
323         editText.setText(testString);
324         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
325 
326         // input the valid text
327         testString = Strings.repeat("b",
328                 MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH - 100);
329         editText.setText(testString);
330 
331         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
332     }
333 
334     @Test
afterTextChanged_codeLengthMoreThanMax_showErrorMessage()335     public void afterTextChanged_codeLengthMoreThanMax_showErrorMessage() {
336         ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
337                 .requireViewById(R.id.broadcast_code_edit);
338         TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
339                 R.id.broadcast_code_summary);
340         broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
341         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
342                 mLocalBluetoothLeBroadcast);
343         broadcastCodeEdit.callOnClick();
344         EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
345                 R.id.broadcast_edit_text);
346         TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
347                 R.id.broadcast_error_message);
348         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
349 
350         // input the invalid text
351         String moreThanMax = Strings.repeat("a",
352                 MediaOutputBroadcastDialog.BROADCAST_CODE_MAX_LENGTH + 1);
353         editText.setText(moreThanMax);
354 
355         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
356     }
357 
358     @Test
afterTextChanged_codeLengthLessThanMin_showErrorMessage()359     public void afterTextChanged_codeLengthLessThanMin_showErrorMessage() {
360         ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
361                 .requireViewById(R.id.broadcast_code_edit);
362         TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
363                 R.id.broadcast_code_summary);
364         broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
365         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
366                 mLocalBluetoothLeBroadcast);
367         broadcastCodeEdit.callOnClick();
368         EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
369                 R.id.broadcast_edit_text);
370         TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
371                 R.id.broadcast_error_message);
372         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
373 
374         // input the invalid text
375         String moreThanMax = Strings.repeat("a",
376                 MediaOutputBroadcastDialog.BROADCAST_CODE_MIN_LENGTH - 1);
377         editText.setText(moreThanMax);
378 
379         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
380     }
381 
382     @Test
addSourceToAllConnectedDevices()383     public void addSourceToAllConnectedDevices() {
384         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
385                 mLocalBluetoothLeBroadcast);
386         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
387                 mLocalBluetoothLeBroadcastAssistant);
388         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
389                 mBluetoothLeBroadcastMetadata);
390         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
391         when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
392         when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
393                 .thenReturn(mCachedBluetoothDevice);
394         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
395         List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
396         when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
397                 sourceList);
398         List<BluetoothDevice> connectedDevicesList = new ArrayList<>();
399         connectedDevicesList.add(mBluetoothFirstDevice);
400         connectedDevicesList.add(mBluetoothSecondDevice);
401         when(mLocalBluetoothLeBroadcastAssistant.getConnectedDevices()).thenReturn(
402                 connectedDevicesList);
403 
404         mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
405 
406         verify(mLocalBluetoothLeBroadcastAssistant, times(2)).addSource(any(), any(), anyBoolean());
407     }
408 
409 }
410