• 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.Matchers.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 import android.support.v14.preference.SwitchPreference;
32 import android.support.v7.preference.PreferenceCategory;
33 
34 import com.android.settings.R;
35 import com.android.settings.testutils.SettingsRobolectricTestRunner;
36 import com.android.settings.TestConfig;
37 import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice;
38 import com.android.settingslib.bluetooth.A2dpProfile;
39 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
40 import com.android.settingslib.bluetooth.LocalBluetoothManager;
41 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
42 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
43 import com.android.settingslib.bluetooth.MapProfile;
44 import com.android.settingslib.bluetooth.PbapServerProfile;
45 
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.mockito.Mock;
49 import org.robolectric.annotation.Config;
50 
51 import java.util.ArrayList;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.List;
55 
56 @RunWith(SettingsRobolectricTestRunner.class)
57 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
58         shadows=SettingsShadowBluetoothDevice.class)
59 public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsControllerTestBase {
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         protected HashSet<BluetoothDevice> mConnectedDevices;
91         protected HashMap<BluetoothDevice, Boolean> mPreferred;
92         protected Context mContext;
93         protected int mNameResourceId;
94 
FakeBluetoothProfile(Context context, int nameResourceId)95         public FakeBluetoothProfile(Context context, int nameResourceId) {
96             mConnectedDevices = new HashSet<>();
97             mPreferred = new HashMap<>();
98             mContext = context;
99             mNameResourceId = nameResourceId;
100         }
101 
102         @Override
toString()103         public String toString() {
104             return mContext.getString(mNameResourceId);
105         }
106 
107         @Override
isConnectable()108         public boolean isConnectable() {
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
getOrdinal()160         public int getOrdinal() {
161             return 0;
162         }
163 
164         @Override
getNameResource(BluetoothDevice device)165         public int getNameResource(BluetoothDevice device) {
166             return mNameResourceId;
167         }
168 
169         @Override
getSummaryResourceForDevice(BluetoothDevice device)170         public int getSummaryResourceForDevice(BluetoothDevice device) {
171             return Utils.getConnectionStateSummary(getConnectionStatus(device));
172         }
173 
174         @Override
getDrawableResource(BluetoothClass btClass)175         public int getDrawableResource(BluetoothClass btClass) {
176             return 0;
177         }
178     }
179 
180     /**
181      * Creates and adds a mock LocalBluetoothProfile to the list of connectable profiles for the
182      * device.
183      @param profileNameResId  the resource id for the name used by this profile
184      @param deviceIsPreferred  whether this profile should start out as enabled for the device
185      */
addFakeProfile(int profileNameResId, boolean deviceIsPreferred)186     private LocalBluetoothProfile addFakeProfile(int profileNameResId,
187             boolean deviceIsPreferred) {
188         LocalBluetoothProfile profile = new FakeBluetoothProfile(mContext, profileNameResId);
189         profile.setPreferred(mDevice, deviceIsPreferred);
190         mConnectableProfiles.add(profile);
191         when(mProfileManager.getProfileByName(eq(profile.toString()))).thenReturn(profile);
192         return profile;
193     }
194 
195     /** Returns the list of SwitchPreference objects added to the screen - there should be one per
196      *  Bluetooth profile.
197      */
getProfileSwitches(boolean expectOnlyMConnectable)198     private List<SwitchPreference> getProfileSwitches(boolean expectOnlyMConnectable) {
199         if (expectOnlyMConnectable) {
200             assertThat(mConnectableProfiles).isNotEmpty();
201             assertThat(mProfiles.getPreferenceCount()).isEqualTo(mConnectableProfiles.size());
202         }
203         ArrayList<SwitchPreference> result = new ArrayList<>();
204         for (int i = 0; i < mProfiles.getPreferenceCount(); i++) {
205             result.add((SwitchPreference)mProfiles.getPreference(i));
206         }
207         return result;
208     }
209 
verifyProfileSwitchTitles(List<SwitchPreference> switches)210      private void verifyProfileSwitchTitles(List<SwitchPreference> switches) {
211         for (int i = 0; i < switches.size(); i++) {
212             String expectedTitle = mContext.getString(
213                     mConnectableProfiles.get(i).getNameResource(mDevice));
214             assertThat(switches.get(i).getTitle()).isEqualTo(expectedTitle);
215         }
216     }
217 
218     @Test
oneProfile()219     public void oneProfile() {
220         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
221         showScreen(mController);
222         verifyProfileSwitchTitles(getProfileSwitches(true));
223     }
224 
225     @Test
multipleProfiles()226     public void multipleProfiles() {
227         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
228         addFakeProfile(R.string.bluetooth_profile_headset, false);
229         showScreen(mController);
230         List<SwitchPreference> switches = getProfileSwitches(true);
231         verifyProfileSwitchTitles(switches);
232         assertThat(switches.get(0).isChecked()).isTrue();
233         assertThat(switches.get(1).isChecked()).isFalse();
234 
235         // Both switches should be enabled.
236         assertThat(switches.get(0).isEnabled()).isTrue();
237         assertThat(switches.get(1).isEnabled()).isTrue();
238 
239         // Make device busy.
240         when(mCachedDevice.isBusy()).thenReturn(true);
241         mController.onDeviceAttributesChanged();
242 
243         // There should have been no new switches added.
244         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
245 
246         // Make sure both switches got disabled.
247         assertThat(switches.get(0).isEnabled()).isFalse();
248         assertThat(switches.get(1).isEnabled()).isFalse();
249     }
250 
251     @Test
disableThenReenableOneProfile()252     public void disableThenReenableOneProfile() {
253         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
254         addFakeProfile(R.string.bluetooth_profile_headset, true);
255         showScreen(mController);
256         List<SwitchPreference> switches = getProfileSwitches(true);
257         SwitchPreference pref = switches.get(0);
258 
259         // Clicking the pref should cause the profile to become not-preferred.
260         assertThat(pref.isChecked()).isTrue();
261         pref.performClick();
262         assertThat(pref.isChecked()).isFalse();
263         assertThat(mConnectableProfiles.get(0).isPreferred(mDevice)).isFalse();
264 
265         // Make sure no new preferences were added.
266         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
267 
268         // Clicking the pref again should make the profile once again preferred.
269         pref.performClick();
270         assertThat(pref.isChecked()).isTrue();
271         assertThat(mConnectableProfiles.get(0).isPreferred(mDevice)).isTrue();
272 
273         // Make sure we still haven't gotten any new preferences added.
274         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
275     }
276 
277     @Test
disconnectedDeviceOneProfile()278     public void disconnectedDeviceOneProfile() {
279         setupDevice(makeDefaultDeviceConfig().setConnected(false).setConnectionSummary(null));
280         addFakeProfile(R.string.bluetooth_profile_a2dp, true);
281         showScreen(mController);
282         verifyProfileSwitchTitles(getProfileSwitches(true));
283     }
284 
285     @Test
pbapProfileStartsEnabled()286     public void pbapProfileStartsEnabled() {
287         setupDevice(makeDefaultDeviceConfig());
288         when(mCachedDevice.getPhonebookPermissionChoice()).thenReturn(
289                 CachedBluetoothDevice.ACCESS_ALLOWED);
290         PbapServerProfile psp = mock(PbapServerProfile.class);
291         when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap);
292         when(psp.toString()).thenReturn(PbapServerProfile.NAME);
293         when(mProfileManager.getPbapProfile()).thenReturn(psp);
294 
295         showScreen(mController);
296         List<SwitchPreference> switches = getProfileSwitches(false);
297         assertThat(switches.size()).isEqualTo(1);
298         SwitchPreference pref = switches.get(0);
299         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_pbap));
300         assertThat(pref.isChecked()).isTrue();
301 
302         pref.performClick();
303         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
304         verify(mCachedDevice).setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
305     }
306 
307     @Test
pbapProfileStartsDisabled()308     public void pbapProfileStartsDisabled() {
309         setupDevice(makeDefaultDeviceConfig());
310         when(mCachedDevice.getPhonebookPermissionChoice()).thenReturn(
311                 CachedBluetoothDevice.ACCESS_REJECTED);
312         PbapServerProfile psp = mock(PbapServerProfile.class);
313         when(psp.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_pbap);
314         when(psp.toString()).thenReturn(PbapServerProfile.NAME);
315         when(mProfileManager.getPbapProfile()).thenReturn(psp);
316 
317         showScreen(mController);
318         List<SwitchPreference> switches = getProfileSwitches(false);
319         assertThat(switches.size()).isEqualTo(1);
320         SwitchPreference pref = switches.get(0);
321         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_pbap));
322         assertThat(pref.isChecked()).isFalse();
323 
324         pref.performClick();
325         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
326         verify(mCachedDevice).setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
327     }
328 
329     @Test
mapProfile()330     public void mapProfile() {
331         setupDevice(makeDefaultDeviceConfig());
332         MapProfile mapProfile = mock(MapProfile.class);
333         when(mapProfile.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_map);
334         when(mProfileManager.getMapProfile()).thenReturn(mapProfile);
335         when(mProfileManager.getProfileByName(eq(mapProfile.toString()))).thenReturn(mapProfile);
336         when(mCachedDevice.getMessagePermissionChoice()).thenReturn(
337                 CachedBluetoothDevice.ACCESS_REJECTED);
338         showScreen(mController);
339         List<SwitchPreference> switches = getProfileSwitches(false);
340         assertThat(switches.size()).isEqualTo(1);
341         SwitchPreference pref = switches.get(0);
342         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_map));
343         assertThat(pref.isChecked()).isFalse();
344 
345         pref.performClick();
346         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
347         verify(mCachedDevice).setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
348     }
349 
addMockA2dpProfile(boolean preferred, boolean supportsHighQualityAudio, boolean highQualityAudioEnabled)350     private A2dpProfile addMockA2dpProfile(boolean preferred, boolean supportsHighQualityAudio,
351             boolean highQualityAudioEnabled) {
352         A2dpProfile profile = mock(A2dpProfile.class);
353         when(mProfileManager.getProfileByName(eq(profile.toString()))).thenReturn(profile);
354         when(profile.getNameResource(mDevice)).thenReturn(R.string.bluetooth_profile_a2dp);
355         when(profile.getHighQualityAudioOptionLabel(mDevice)).thenReturn(mContext.getString(
356                 R.string.bluetooth_profile_a2dp_high_quality_unknown_codec));
357         when(profile.supportsHighQualityAudio(mDevice)).thenReturn(supportsHighQualityAudio);
358         when(profile.isHighQualityAudioEnabled(mDevice)).thenReturn(highQualityAudioEnabled);
359         when(profile.isPreferred(mDevice)).thenReturn(preferred);
360         mConnectableProfiles.add(profile);
361         return profile;
362     }
363 
getHighQualityAudioPref()364     private SwitchPreference getHighQualityAudioPref() {
365         return (SwitchPreference) mScreen.findPreference(
366                 BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
367     }
368 
369     @Test
highQualityAudio_prefIsPresentWhenSupported()370     public void highQualityAudio_prefIsPresentWhenSupported() {
371         setupDevice(makeDefaultDeviceConfig());
372         addMockA2dpProfile(true, true, true);
373         showScreen(mController);
374         SwitchPreference pref = getHighQualityAudioPref();
375         assertThat(pref.getKey()).isEqualTo(
376                 BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
377 
378         // Make sure the preference works when clicked on.
379         pref.performClick();
380         A2dpProfile profile = (A2dpProfile) mConnectableProfiles.get(0);
381         verify(profile).setHighQualityAudioEnabled(mDevice, false);
382         pref.performClick();
383         verify(profile).setHighQualityAudioEnabled(mDevice, true);
384     }
385 
386     @Test
highQualityAudio_prefIsAbsentWhenNotSupported()387     public void highQualityAudio_prefIsAbsentWhenNotSupported() {
388         setupDevice(makeDefaultDeviceConfig());
389         addMockA2dpProfile(true, false, false);
390         showScreen(mController);
391         assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
392         SwitchPreference pref = (SwitchPreference) mProfiles.getPreference(0);
393         assertThat(pref.getKey()).isNotEqualTo(
394                 BluetoothDetailsProfilesController.HIGH_QUALITY_AUDIO_PREF_TAG);
395         assertThat(pref.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_profile_a2dp));
396     }
397 
398     @Test
highQualityAudio_busyDeviceDisablesSwitch()399     public void highQualityAudio_busyDeviceDisablesSwitch() {
400         setupDevice(makeDefaultDeviceConfig());
401         addMockA2dpProfile(true, true, true);
402         when(mCachedDevice.isBusy()).thenReturn(true);
403         showScreen(mController);
404         SwitchPreference pref = getHighQualityAudioPref();
405         assertThat(pref.isEnabled()).isFalse();
406     }
407 
408     @Test
highQualityAudio_mediaAudioDisabledAndReEnabled()409     public void highQualityAudio_mediaAudioDisabledAndReEnabled() {
410         setupDevice(makeDefaultDeviceConfig());
411         A2dpProfile audioProfile = addMockA2dpProfile(true, true, true);
412         showScreen(mController);
413         assertThat(mProfiles.getPreferenceCount()).isEqualTo(2);
414 
415         // Disabling media audio should cause the high quality audio switch to disappear, but not
416         // the regular audio one.
417         SwitchPreference audioPref = (SwitchPreference) mScreen.findPreference(
418                 audioProfile.toString());
419         audioPref.performClick();
420         verify(audioProfile).setPreferred(mDevice, false);
421         when(audioProfile.isPreferred(mDevice)).thenReturn(false);
422         mController.onDeviceAttributesChanged();
423         assertThat(audioPref.isVisible()).isTrue();
424         SwitchPreference highQualityAudioPref = getHighQualityAudioPref();
425         assertThat(highQualityAudioPref.isVisible()).isFalse();
426 
427         // And re-enabling media audio should make high quality switch to reappear.
428         audioPref.performClick();
429         verify(audioProfile).setPreferred(mDevice, true);
430         when(audioProfile.isPreferred(mDevice)).thenReturn(true);
431         mController.onDeviceAttributesChanged();
432         highQualityAudioPref = getHighQualityAudioPref();
433         assertThat(highQualityAudioPref.isVisible()).isTrue();
434     }
435 
436     @Test
highQualityAudio_mediaAudioStartsDisabled()437     public void highQualityAudio_mediaAudioStartsDisabled() {
438         setupDevice(makeDefaultDeviceConfig());
439         A2dpProfile audioProfile = addMockA2dpProfile(false, true, true);
440         showScreen(mController);
441         SwitchPreference audioPref = (SwitchPreference) mScreen.findPreference(
442                 audioProfile.toString());
443         SwitchPreference highQualityAudioPref = getHighQualityAudioPref();
444         assertThat(audioPref).isNotNull();
445         assertThat(audioPref.isChecked()).isFalse();
446         assertThat(highQualityAudioPref).isNotNull();
447         assertThat(highQualityAudioPref.isVisible()).isFalse();
448     }
449 }
450