• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.settings.bluetooth;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.eq;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.spy;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.when;
26 
27 import android.bluetooth.BluetoothClass;
28 import android.bluetooth.BluetoothDevice;
29 import android.bluetooth.BluetoothProfile;
30 import android.content.Context;
31 
32 import androidx.preference.PreferenceCategory;
33 import androidx.preference.SwitchPreference;
34 
35 import com.android.settings.R;
36 import com.android.settings.testutils.shadow.ShadowBluetoothDevice;
37 import com.android.settingslib.bluetooth.A2dpProfile;
38 import com.android.settingslib.bluetooth.LocalBluetoothManager;
39 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
40 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
41 import com.android.settingslib.bluetooth.MapProfile;
42 import com.android.settingslib.bluetooth.PbapServerProfile;
43 
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 import org.mockito.Mock;
47 import org.robolectric.RobolectricTestRunner;
48 import org.robolectric.annotation.Config;
49 
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 
57 @RunWith(RobolectricTestRunner.class)
58 @Config(shadows = ShadowBluetoothDevice.class)
59 public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsControllerTestBase {
60 
61     private BluetoothDetailsProfilesController mController;
62     private List<LocalBluetoothProfile> mConnectableProfiles;
63     private PreferenceCategory mProfiles;
64 
65     @Mock
66     private LocalBluetoothManager mLocalManager;
67     @Mock
68     private LocalBluetoothProfileManager mProfileManager;
69 
70     @Override
setUp()71     public void setUp() {
72         super.setUp();
73 
74         mProfiles = spy(new PreferenceCategory(mContext));
75         when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager);
76 
77         mConnectableProfiles = new ArrayList<>();
78         when(mLocalManager.getProfileManager()).thenReturn(mProfileManager);
79         when(mCachedDevice.getConnectableProfiles()).thenAnswer(invocation ->
80             new ArrayList<>(mConnectableProfiles)
81         );
82 
83         setupDevice(mDeviceConfig);
84         mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
85                 mCachedDevice, mLifecycle);
86         mProfiles.setKey(mController.getPreferenceKey());
87         mScreen.addPreference(mProfiles);
88     }
89 
90     static class FakeBluetoothProfile implements LocalBluetoothProfile {
91 
92         private Set<BluetoothDevice> mConnectedDevices = new HashSet<>();
93         private Map<BluetoothDevice, Boolean> mPreferred = new HashMap<>();
94         private Context mContext;
95         private int mNameResourceId;
96 
FakeBluetoothProfile(Context context, int nameResourceId)97         private FakeBluetoothProfile(Context context, int nameResourceId) {
98             mContext = context;
99             mNameResourceId = nameResourceId;
100         }
101 
102         @Override
toString()103         public String toString() {
104             return mContext.getString(mNameResourceId);
105         }
106 
107         @Override
accessProfileEnabled()108         public boolean accessProfileEnabled() {
109             return true;
110         }
111 
112         @Override
isAutoConnectable()113         public boolean isAutoConnectable() {
114             return true;
115         }
116 
117         @Override
connect(BluetoothDevice device)118         public boolean connect(BluetoothDevice device) {
119             mConnectedDevices.add(device);
120             return true;
121         }
122 
123         @Override
disconnect(BluetoothDevice device)124         public boolean disconnect(BluetoothDevice device) {
125             mConnectedDevices.remove(device);
126             return false;
127         }
128 
129         @Override
getConnectionStatus(BluetoothDevice device)130         public int getConnectionStatus(BluetoothDevice device) {
131             if (mConnectedDevices.contains(device)) {
132                 return BluetoothProfile.STATE_CONNECTED;
133             } else {
134                 return BluetoothProfile.STATE_DISCONNECTED;
135             }
136         }
137 
138         @Override
isPreferred(BluetoothDevice device)139         public boolean isPreferred(BluetoothDevice device) {
140             return mPreferred.getOrDefault(device, false);
141         }
142 
143         @Override
getPreferred(BluetoothDevice device)144         public int getPreferred(BluetoothDevice device) {
145             return isPreferred(device) ?
146                     BluetoothProfile.PRIORITY_ON : BluetoothProfile.PRIORITY_OFF;
147         }
148 
149         @Override
setPreferred(BluetoothDevice device, boolean preferred)150         public void setPreferred(BluetoothDevice device, boolean preferred) {
151             mPreferred.put(device, preferred);
152         }
153 
154         @Override
isProfileReady()155         public boolean isProfileReady() {
156             return true;
157         }
158 
159         @Override
getProfileId()160         public int getProfileId() {
161             return 0;
162         }
163 
164         @Override
getOrdinal()165         public int getOrdinal() {
166             return 0;
167         }
168 
169         @Override
getNameResource(BluetoothDevice device)170         public int getNameResource(BluetoothDevice device) {
171             return mNameResourceId;
172         }
173 
174         @Override
getSummaryResourceForDevice(BluetoothDevice device)175         public int getSummaryResourceForDevice(BluetoothDevice device) {
176             return Utils.getConnectionStateSummary(getConnectionStatus(device));
177         }
178 
179         @Override
getDrawableResource(BluetoothClass btClass)180         public int getDrawableResource(BluetoothClass btClass) {
181             return 0;
182         }
183     }
184 
185     /**
186      * Creates and adds a mock LocalBluetoothProfile to the list of connectable profiles for the
187      * device.
188      * @param profileNameResId  the resource id for the name used by this profile
189      * @param deviceIsPreferred  whether this profile should start out as enabled for the device
190      */
addFakeProfile(int profileNameResId, boolean deviceIsPreferred)191     private LocalBluetoothProfile addFakeProfile(int profileNameResId,
192             boolean deviceIsPreferred) {
193         LocalBluetoothProfile profile = new FakeBluetoothProfile(mContext, profileNameResId);
194         profile.setPreferred(mDevice, deviceIsPreferred);
195         mConnectableProfiles.add(profile);
196         when(mProfileManager.getProfileByName(eq(profile.toString()))).thenReturn(profile);
197         return profile;
198     }
199 
200     /** Returns the list of SwitchPreference objects added to the screen - there should be one per
201      *  Bluetooth profile.
202      */
getProfileSwitches(boolean expectOnlyMConnectable)203     private List<SwitchPreference> getProfileSwitches(boolean expectOnlyMConnectable) {
204         if (expectOnlyMConnectable) {
205             assertThat(mConnectableProfiles).isNotEmpty();
206             assertThat(mProfiles.getPreferenceCount()).isEqualTo(mConnectableProfiles.size());
207         }
208         List<SwitchPreference> result = new ArrayList<>();
209         for (int i = 0; i < mProfiles.getPreferenceCount(); i++) {
210             result.add((SwitchPreference)mProfiles.getPreference(i));
211         }
212         return result;
213     }
214 
verifyProfileSwitchTitles(List<SwitchPreference> switches)215      private void verifyProfileSwitchTitles(List<SwitchPreference> switches) {
216         for (int i = 0; i < switches.size(); i++) {
217             String expectedTitle =
218                 mContext.getString(mConnectableProfiles.get(i).getNameResource(mDevice));
219             assertThat(switches.get(i).getTitle()).isEqualTo(expectedTitle);
220         }
221     }
222 
223     @Test
oneProfile()224     public void oneProfile() {
225         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
226         showScreen(mController);
227         verifyProfileSwitchTitles(getProfileSwitches(true));
228     }
229 
230     @Test
multipleProfiles()231     public void multipleProfiles() {
232         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
233         addFakeProfile(R.string.bluetooth_profile_headset, false);
234         showScreen(mController);
235         List<SwitchPreference> switches = getProfileSwitches(true);
236         verifyProfileSwitchTitles(switches);
237         assertThat(switches.get(0).isChecked()).isTrue();
238         assertThat(switches.get(1).isChecked()).isFalse();
239 
240         // Both switches should be enabled.
241         assertThat(switches.get(0).isEnabled()).isTrue();
242         assertThat(switches.get(1).isEnabled()).isTrue();
243 
244         // Make device busy.
245         when(mCachedDevice.isBusy()).thenReturn(true);
246         mController.onDeviceAttributesChanged();
247 
248         // There should have been no new switches added.
249         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
250 
251         // Make sure both switches got disabled.
252         assertThat(switches.get(0).isEnabled()).isFalse();
253         assertThat(switches.get(1).isEnabled()).isFalse();
254     }
255 
256     @Test
disableThenReenableOneProfile()257     public void disableThenReenableOneProfile() {
258         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
259         addFakeProfile(R.string.bluetooth_profile_headset, true);
260         showScreen(mController);
261         List<SwitchPreference> switches = getProfileSwitches(true);
262         SwitchPreference pref = switches.get(0);
263 
264         // Clicking the pref should cause the profile to become not-preferred.
265         assertThat(pref.isChecked()).isTrue();
266         pref.performClick();
267         assertThat(pref.isChecked()).isFalse();
268         assertThat(mConnectableProfiles.get(0).isPreferred(mDevice)).isFalse();
269 
270         // Make sure no new preferences were added.
271         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
272 
273         // Clicking the pref again should make the profile once again preferred.
274         pref.performClick();
275         assertThat(pref.isChecked()).isTrue();
276         assertThat(mConnectableProfiles.get(0).isPreferred(mDevice)).isTrue();
277 
278         // Make sure we still haven't gotten any new preferences added.
279         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
280     }
281 
282     @Test
disconnectedDeviceOneProfile()283     public void disconnectedDeviceOneProfile() {
284         setupDevice(makeDefaultDeviceConfig().setConnected(false).setConnectionSummary(null));
285         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
286         showScreen(mController);
287         verifyProfileSwitchTitles(getProfileSwitches(true));
288     }
289 
290     @Test
pbapProfileStartsEnabled()291     public void pbapProfileStartsEnabled() {
292         setupDevice(makeDefaultDeviceConfig());
293         mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
294         PbapServerProfile psp = mock(PbapServerProfile.class);
295         when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap);
296         when(psp.toString()).thenReturn(PbapServerProfile.NAME);
297         when(psp.isProfileReady()).thenReturn(true);
298         when(mProfileManager.getPbapProfile()).thenReturn(psp);
299 
300         showScreen(mController);
301         List<SwitchPreference> switches = getProfileSwitches(false);
302         assertThat(switches.size()).isEqualTo(1);
303         SwitchPreference pref = switches.get(0);
304         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_pbap));
305         assertThat(pref.isChecked()).isTrue();
306 
307         pref.performClick();
308         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
309         assertThat(mDevice.getPhonebookAccessPermission())
310                 .isEqualTo(BluetoothDevice.ACCESS_REJECTED);
311     }
312 
313     @Test
pbapProfileStartsDisabled()314     public void pbapProfileStartsDisabled() {
315         setupDevice(makeDefaultDeviceConfig());
316         mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
317         PbapServerProfile psp = mock(PbapServerProfile.class);
318         when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap);
319         when(psp.toString()).thenReturn(PbapServerProfile.NAME);
320         when(psp.isProfileReady()).thenReturn(true);
321         when(mProfileManager.getPbapProfile()).thenReturn(psp);
322 
323         showScreen(mController);
324         List<SwitchPreference> switches = getProfileSwitches(false);
325         assertThat(switches.size()).isEqualTo(1);
326         SwitchPreference pref = switches.get(0);
327         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_pbap));
328         assertThat(pref.isChecked()).isFalse();
329 
330         pref.performClick();
331         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
332         assertThat(mDevice.getPhonebookAccessPermission())
333                 .isEqualTo(BluetoothDevice.ACCESS_ALLOWED);
334     }
335 
336     @Test
mapProfile()337     public void mapProfile() {
338         setupDevice(makeDefaultDeviceConfig());
339         MapProfile mapProfile = mock(MapProfile.class);
340         when(mapProfile.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_map);
341         when(mapProfile.isProfileReady()).thenReturn(true);
342         when(mProfileManager.getMapProfile()).thenReturn(mapProfile);
343         when(mProfileManager.getProfileByName(eq(mapProfile.toString()))).thenReturn(mapProfile);
344         mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
345         showScreen(mController);
346         List<SwitchPreference> switches = getProfileSwitches(false);
347         assertThat(switches.size()).isEqualTo(1);
348         SwitchPreference pref = switches.get(0);
349         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_map));
350         assertThat(pref.isChecked()).isFalse();
351 
352         pref.performClick();
353         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
354         assertThat(mDevice.getMessageAccessPermission()).isEqualTo(BluetoothDevice.ACCESS_ALLOWED);
355     }
356 
addMockA2dpProfile(boolean preferred, boolean supportsHighQualityAudio, boolean highQualityAudioEnabled)357     private A2dpProfile addMockA2dpProfile(boolean preferred, boolean supportsHighQualityAudio,
358             boolean highQualityAudioEnabled) {
359         A2dpProfile profile = mock(A2dpProfile.class);
360         when(mProfileManager.getProfileByName(eq(profile.toString()))).thenReturn(profile);
361         when(profile.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_a2dp);
362         when(profile.getHighQualityAudioOptionLabel(mDevice)).thenReturn(
363             mContext.getString(R.string.bluetooth_profile_a2dp_high_quality_unknown_codec));
364         when(profile.supportsHighQualityAudio(mDevice)).thenReturn(supportsHighQualityAudio);
365         when(profile.isHighQualityAudioEnabled(mDevice)).thenReturn(highQualityAudioEnabled);
366         when(profile.isPreferred(mDevice)).thenReturn(preferred);
367         when(profile.isProfileReady()).thenReturn(true);
368         mConnectableProfiles.add(profile);
369         return profile;
370     }
371 
getHighQualityAudioPref()372     private SwitchPreference getHighQualityAudioPref() {
373         return (SwitchPreference) mScreen.findPreference(
374                 BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
375     }
376 
377     @Test
highQualityAudio_prefIsPresentWhenSupported()378     public void highQualityAudio_prefIsPresentWhenSupported() {
379         setupDevice(makeDefaultDeviceConfig());
380         addMockA2dpProfile(true, true, true);
381         showScreen(mController);
382         SwitchPreference pref = getHighQualityAudioPref();
383         assertThat(pref.getKey()).isEqualTo(
384                 BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
385 
386         // Make sure the preference works when clicked on.
387         pref.performClick();
388         A2dpProfile profile = (A2dpProfile) mConnectableProfiles.get(0);
389         verify(profile).setHighQualityAudioEnabled(mDevice, false);
390         pref.performClick();
391         verify(profile).setHighQualityAudioEnabled(mDevice, true);
392     }
393 
394     @Test
highQualityAudio_prefIsAbsentWhenNotSupported()395     public void highQualityAudio_prefIsAbsentWhenNotSupported() {
396         setupDevice(makeDefaultDeviceConfig());
397         addMockA2dpProfile(true, false, false);
398         showScreen(mController);
399         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
400         SwitchPreference pref = (SwitchPreference) mProfiles.getPreference(0);
401         assertThat(pref.getKey())
402             .isNotEqualTo(BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
403         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_a2dp));
404     }
405 
406     @Test
highQualityAudio_busyDeviceDisablesSwitch()407     public void highQualityAudio_busyDeviceDisablesSwitch() {
408         setupDevice(makeDefaultDeviceConfig());
409         addMockA2dpProfile(true, true, true);
410         when(mCachedDevice.isBusy()).thenReturn(true);
411         showScreen(mController);
412         SwitchPreference pref = getHighQualityAudioPref();
413         assertThat(pref.isEnabled()).isFalse();
414     }
415 
416     @Test
highQualityAudio_mediaAudioDisabledAndReEnabled()417     public void highQualityAudio_mediaAudioDisabledAndReEnabled() {
418         setupDevice(makeDefaultDeviceConfig());
419         A2dpProfile audioProfile = addMockA2dpProfile(true, true, true);
420         showScreen(mController);
421         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
422 
423         // Disabling media audio should cause the high quality audio switch to disappear, but not
424         // the regular audio one.
425         SwitchPreference audioPref =
426             (SwitchPreference) mScreen.findPreference(audioProfile.toString());
427         audioPref.performClick();
428         verify(audioProfile).setPreferred(mDevice, false);
429         when(audioProfile.isPreferred(mDevice)).thenReturn(false);
430         mController.onDeviceAttributesChanged();
431         assertThat(audioPref.isVisible()).isTrue();
432         SwitchPreference highQualityAudioPref = getHighQualityAudioPref();
433         assertThat(highQualityAudioPref.isVisible()).isFalse();
434 
435         // And re-enabling media audio should make high quality switch to reappear.
436         audioPref.performClick();
437         verify(audioProfile).setPreferred(mDevice, true);
438         when(audioProfile.isPreferred(mDevice)).thenReturn(true);
439         mController.onDeviceAttributesChanged();
440         highQualityAudioPref = getHighQualityAudioPref();
441         assertThat(highQualityAudioPref.isVisible()).isTrue();
442     }
443 
444     @Test
highQualityAudio_mediaAudioStartsDisabled()445     public void highQualityAudio_mediaAudioStartsDisabled() {
446         setupDevice(makeDefaultDeviceConfig());
447         A2dpProfile audioProfile = addMockA2dpProfile(false, true, true);
448         showScreen(mController);
449         SwitchPreference audioPref = mScreen.findPreference(audioProfile.toString());
450         SwitchPreference highQualityAudioPref = getHighQualityAudioPref();
451         assertThat(audioPref).isNotNull();
452         assertThat(audioPref.isChecked()).isFalse();
453         assertThat(highQualityAudioPref).isNotNull();
454         assertThat(highQualityAudioPref.isVisible()).isFalse();
455     }
456 
457     @Test
onResume_addServiceListener()458     public void onResume_addServiceListener() {
459         mController.onResume();
460 
461         verify(mProfileManager).addServiceListener(mController);
462     }
463 
464     @Test
onPause_removeServiceListener()465     public void onPause_removeServiceListener() {
466         mController.onPause();
467 
468         verify(mProfileManager).removeServiceListener(mController);
469     }
470 }
471