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