• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.car.settings.qc;
18 
19 import static android.os.UserManager.DISALLOW_BLUETOOTH;
20 
21 import static com.android.car.qc.QCItem.QC_ACTION_TOGGLE_STATE;
22 import static com.android.car.settings.qc.PairedBluetoothDevices.BLUETOOTH_BUTTON;
23 import static com.android.car.settings.qc.PairedBluetoothDevices.EXTRA_BUTTON_TYPE;
24 import static com.android.car.settings.qc.PairedBluetoothDevices.EXTRA_DEVICE_KEY;
25 import static com.android.car.settings.qc.PairedBluetoothDevices.MEDIA_BUTTON;
26 import static com.android.car.settings.qc.PairedBluetoothDevices.PHONE_BUTTON;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyBoolean;
32 import static org.mockito.ArgumentMatchers.eq;
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import android.bluetooth.BluetoothAdapter;
38 import android.bluetooth.BluetoothClass;
39 import android.bluetooth.BluetoothDevice;
40 import android.bluetooth.BluetoothProfile;
41 import android.content.Intent;
42 import android.os.UserManager;
43 
44 import androidx.test.ext.junit.runners.AndroidJUnit4;
45 
46 import com.android.car.qc.QCActionItem;
47 import com.android.car.qc.QCItem;
48 import com.android.car.qc.QCList;
49 import com.android.car.qc.QCRow;
50 import com.android.car.settings.R;
51 import com.android.dx.mockito.inline.extended.ExtendedMockito;
52 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
53 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
54 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
55 import com.android.settingslib.bluetooth.LocalBluetoothManager;
56 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
57 
58 import org.junit.After;
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.Mock;
63 import org.mockito.MockitoAnnotations;
64 import org.mockito.MockitoSession;
65 import org.mockito.quality.Strictness;
66 
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.HashSet;
70 import java.util.Set;
71 
72 @RunWith(AndroidJUnit4.class)
73 public class PairedBluetoothDevicesTest extends BaseSettingsQCItemTestCase {
74     private static final String DEFAULT_NAME = "DEFAULT_NAME";
75     private static final String DEFAULT_SUMMARY = "DEFAULT_SUMMARY";
76     private static final String DEFAULT_ADDRESS = "F6:8F:AC:E8:32:50";
77 
78     private PairedBluetoothDevices mPairedBluetoothDevices;
79     private MockitoSession mSession;
80     private ArrayList<CachedBluetoothDevice> mCachedDevices = new ArrayList<>();
81     private Set<BluetoothDevice> mBondedDevices = new HashSet<>();
82 
83     @Mock
84     private BluetoothAdapter mBluetoothAdapter;
85     @Mock
86     private LocalBluetoothAdapter mLocalBluetoothAdapter;
87     @Mock
88     private LocalBluetoothManager mBluetoothManager;
89 
90     @Before
setUp()91     public void setUp() {
92         MockitoAnnotations.initMocks(this);
93         mSession = ExtendedMockito.mockitoSession()
94                 .strictness(Strictness.LENIENT)
95                 .mockStatic(BluetoothAdapter.class)
96                 .mockStatic(LocalBluetoothManager.class)
97                 .startMocking();
98         when(BluetoothAdapter.getDefaultAdapter()).thenReturn(mBluetoothAdapter);
99         when(mBluetoothAdapter.isEnabled()).thenReturn(true);
100         when(LocalBluetoothManager.getInstance(any(), any())).thenReturn(mBluetoothManager);
101         when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter);
102         when(mLocalBluetoothAdapter.getBondedDevices()).thenReturn(mBondedDevices);
103 
104         CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class);
105         when(mBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager);
106         when(deviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
107 
108         mPairedBluetoothDevices = new PairedBluetoothDevices(mContext);
109     }
110 
111     @After
tearDown()112     public void tearDown() {
113         if (mSession != null) {
114             mSession.finishMocking();
115         }
116     }
117 
118     @Test
getQCItem_bluetoothDisallowed_returnsNull()119     public void getQCItem_bluetoothDisallowed_returnsNull() {
120         setUserRestriction(DISALLOW_BLUETOOTH, true);
121         QCItem item = mPairedBluetoothDevices.getQCItem();
122         assertThat(item).isNull();
123     }
124 
125     @Test
getQCItem_bluetoothDisabled_returnsBluetoothDisabledMessage()126     public void getQCItem_bluetoothDisabled_returnsBluetoothDisabledMessage() {
127         when(mBluetoothAdapter.isEnabled()).thenReturn(false);
128         QCItem item = mPairedBluetoothDevices.getQCItem();
129         assertThat(item).isNotNull();
130         QCList list = (QCList) item;
131         assertThat(list.getRows().size()).isEqualTo(1);
132         QCRow row = list.getRows().get(0);
133         assertThat(row.getTitle()).isEqualTo(
134                 mContext.getString(R.string.qc_bluetooth_off_devices_info));
135         assertThat(row.getStartIcon()).isNotNull();
136     }
137 
138     @Test
getQCItem_noDevices_returnsPairMessage()139     public void getQCItem_noDevices_returnsPairMessage() {
140         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
141         assertThat(list.getRows().size()).isEqualTo(1);
142         QCRow row = list.getRows().get(0);
143         assertThat(row.getTitle()).isEqualTo(
144                 mContext.getString(R.string.qc_bluetooth_on_no_devices_info));
145         assertThat(row.getStartIcon()).isNotNull();
146     }
147 
148     @Test
getQCItem_hasDevices_hasRows()149     public void getQCItem_hasDevices_hasRows() {
150         addBluetoothDevice("Device1", /* connected= */ true, /* busy= */ false,
151                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
152         addBluetoothDevice("Device2",  /* connected= */ true, /* busy= */ false,
153                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
154         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
155         assertThat(list.getRows().size()).isEqualTo(2);
156     }
157 
158     @Test
getQCItem_limitsDeviceCount()159     public void getQCItem_limitsDeviceCount() {
160         addBluetoothDevice("Device1", /* connected= */ true, /* busy= */ false,
161                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
162         addBluetoothDevice("Device2",  /* connected= */ false, /* busy= */ false,
163                 /* phoneEnabled= */ false, /* mediaEnabled= */ false);
164         addBluetoothDevice("Device3",  /* connected= */ false, /* busy= */ false,
165                 /* phoneEnabled= */ false, /* mediaEnabled= */ false);
166         addBluetoothDevice("Device4",  /* connected= */ false, /* busy= */ false,
167                 /* phoneEnabled= */ false, /* mediaEnabled= */ false);
168         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
169         assertThat(list.getRows().size()).isEqualTo(3);
170     }
171 
172     @Test
getQCItem_setsTitle()173     public void getQCItem_setsTitle() {
174         addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false,
175                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
176         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
177         QCRow row = list.getRows().get(0);
178         assertThat(row.getTitle()).isEqualTo(DEFAULT_NAME);
179     }
180 
181     @Test
getQCItem_bluetoothDisabled_togglesUpdated()182     public void getQCItem_bluetoothDisabled_togglesUpdated() {
183         addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ false,
184                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
185         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
186         QCRow row = list.getRows().get(0);
187         QCActionItem btToggle = row.getEndItems().get(0);
188         QCActionItem phoneToggle = row.getEndItems().get(1);
189         QCActionItem mediaToggle = row.getEndItems().get(2);
190         assertThat(btToggle.isChecked()).isFalse();
191         assertThat(btToggle.isEnabled()).isTrue();
192         assertThat(phoneToggle.isAvailable()).isFalse();
193         assertThat(mediaToggle.isAvailable()).isFalse();
194     }
195 
196     @Test
getQCItem_bluetoothEnabled_togglesUpdated()197     public void getQCItem_bluetoothEnabled_togglesUpdated() {
198         addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false,
199                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
200         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
201         QCRow row = list.getRows().get(0);
202         QCActionItem btToggle = row.getEndItems().get(0);
203         QCActionItem phoneToggle = row.getEndItems().get(1);
204         QCActionItem mediaToggle = row.getEndItems().get(2);
205         assertThat(btToggle.isChecked()).isTrue();
206         assertThat(btToggle.isEnabled()).isTrue();
207         assertThat(phoneToggle.isChecked()).isTrue();
208         assertThat(phoneToggle.isEnabled()).isTrue();
209         assertThat(phoneToggle.isAvailable()).isTrue();
210         assertThat(mediaToggle.isChecked()).isTrue();
211         assertThat(mediaToggle.isEnabled()).isTrue();
212         assertThat(mediaToggle.isAvailable()).isTrue();
213     }
214 
215     @Test
getQCItem_isBusy_togglesDisabled()216     public void getQCItem_isBusy_togglesDisabled() {
217         addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ true,
218                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
219         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
220         QCRow row = list.getRows().get(0);
221         QCActionItem btToggle = row.getEndItems().get(0);
222         QCActionItem phoneToggle = row.getEndItems().get(1);
223         QCActionItem mediaToggle = row.getEndItems().get(2);
224         assertThat(btToggle.isEnabled()).isFalse();
225         assertThat(phoneToggle.isEnabled()).isFalse();
226         assertThat(mediaToggle.isEnabled()).isFalse();
227     }
228 
229     @Test
getQCItem_hasBaseUmRestriction_togglesDisabled()230     public void getQCItem_hasBaseUmRestriction_togglesDisabled() {
231         addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false,
232                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
233         setBaseUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* restricted= */ true);
234         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
235         QCRow row = list.getRows().get(0);
236         QCActionItem btToggle = row.getEndItems().get(0);
237         QCActionItem phoneToggle = row.getEndItems().get(1);
238         QCActionItem mediaToggle = row.getEndItems().get(2);
239         assertThat(btToggle.isEnabled()).isTrue();
240         assertThat(phoneToggle.isEnabled()).isFalse();
241         assertThat(phoneToggle.isClickableWhileDisabled()).isFalse();
242         assertThat(mediaToggle.isEnabled()).isFalse();
243         assertThat(mediaToggle.isClickableWhileDisabled()).isFalse();
244     }
245 
246     @Test
getQCItem_hasUmRestriction_togglesClickableWhileDisabled()247     public void getQCItem_hasUmRestriction_togglesClickableWhileDisabled() {
248         addBluetoothDevice(DEFAULT_NAME, /* connected= */ true, /* busy= */ false,
249                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
250         setUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* restricted= */ true);
251         QCList list = (QCList) mPairedBluetoothDevices.getQCItem();
252         QCRow row = list.getRows().get(0);
253         QCActionItem btToggle = row.getEndItems().get(0);
254         QCActionItem phoneToggle = row.getEndItems().get(1);
255         QCActionItem mediaToggle = row.getEndItems().get(2);
256         assertThat(btToggle.isEnabled()).isTrue();
257         assertThat(phoneToggle.isEnabled()).isFalse();
258         assertThat(phoneToggle.isClickableWhileDisabled()).isTrue();
259         assertThat(mediaToggle.isEnabled()).isFalse();
260         assertThat(mediaToggle.isClickableWhileDisabled()).isTrue();
261     }
262 
263     @Test
onNotifyChange_toggleBluetooth()264     public void onNotifyChange_toggleBluetooth() {
265         addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true,
266                 /* phoneEnabled= */ true, /* mediaEnabled= */ true);
267         Intent intent = new Intent();
268         intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS);
269         intent.putExtra(EXTRA_BUTTON_TYPE, BLUETOOTH_BUTTON);
270         intent.putExtra(QC_ACTION_TOGGLE_STATE, true);
271         mPairedBluetoothDevices.onNotifyChange(intent);
272         CachedBluetoothDevice device = mCachedDevices.get(0);
273         verify(device).connect();
274     }
275 
276     @Test
onNotifyChange_togglePhone()277     public void onNotifyChange_togglePhone() {
278         addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true,
279                 /* phoneEnabled= */ false, /* mediaEnabled= */ true);
280         Intent intent = new Intent();
281         intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS);
282         intent.putExtra(EXTRA_BUTTON_TYPE, PHONE_BUTTON);
283         intent.putExtra(QC_ACTION_TOGGLE_STATE, true);
284         mPairedBluetoothDevices.onNotifyChange(intent);
285         CachedBluetoothDevice device = mCachedDevices.get(0);
286         LocalBluetoothProfile profile = getProfile(device, BluetoothProfile.HEADSET_CLIENT);
287         verify(profile).setEnabled(any(), eq(true));
288     }
289 
290     @Test
onNotifyChange_toggleMedia()291     public void onNotifyChange_toggleMedia() {
292         addBluetoothDevice(DEFAULT_NAME, /* connected= */ false, /* busy= */ true,
293                 /* phoneEnabled= */ true, /* mediaEnabled= */ false);
294         Intent intent = new Intent();
295         intent.putExtra(EXTRA_DEVICE_KEY, DEFAULT_ADDRESS);
296         intent.putExtra(EXTRA_BUTTON_TYPE, MEDIA_BUTTON);
297         intent.putExtra(QC_ACTION_TOGGLE_STATE, true);
298         mPairedBluetoothDevices.onNotifyChange(intent);
299         CachedBluetoothDevice device = mCachedDevices.get(0);
300         LocalBluetoothProfile profile = getProfile(device, BluetoothProfile.A2DP_SINK);
301         verify(profile).setEnabled(any(), eq(true));
302     }
303 
addBluetoothDevice(String name, boolean connected, boolean busy, boolean phoneEnabled, boolean mediaEnabled)304     private void addBluetoothDevice(String name, boolean connected, boolean busy,
305             boolean phoneEnabled, boolean mediaEnabled) {
306         CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
307         BluetoothDevice device = mock(BluetoothDevice.class);
308         when(cachedDevice.getDevice()).thenReturn(device);
309         when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
310         when(cachedDevice.getName()).thenReturn(name);
311         when(cachedDevice.getAddress()).thenReturn(DEFAULT_ADDRESS);
312         when(cachedDevice.isConnected()).thenReturn(connected);
313         when(cachedDevice.isBusy()).thenReturn(busy);
314         when(cachedDevice.getCarConnectionSummary(anyBoolean())).thenReturn(DEFAULT_SUMMARY);
315         BluetoothClass btClass = mock(BluetoothClass.class);
316         when(cachedDevice.getBtClass()).thenReturn(btClass);
317         when(btClass.getMajorDeviceClass()).thenReturn(BluetoothClass.Device.Major.PHONE);
318         LocalBluetoothProfile phoneProfile = mock(LocalBluetoothProfile.class);
319         LocalBluetoothProfile mediaProfile = mock(LocalBluetoothProfile.class);
320         when(phoneProfile.getProfileId()).thenReturn(BluetoothProfile.HEADSET_CLIENT);
321         when(mediaProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP_SINK);
322         when(phoneProfile.isEnabled(any())).thenReturn(phoneEnabled);
323         when(mediaProfile.isEnabled(any())).thenReturn(mediaEnabled);
324         when(cachedDevice.getProfiles()).thenReturn(Arrays.asList(phoneProfile, mediaProfile));
325         mCachedDevices.add(cachedDevice);
326         mBondedDevices.add(device);
327     }
328 
getProfile(CachedBluetoothDevice device, int profileId)329     private LocalBluetoothProfile getProfile(CachedBluetoothDevice device, int profileId) {
330         for (LocalBluetoothProfile profile : device.getProfiles()) {
331             if (profile.getProfileId() == profileId) {
332                 return profile;
333             }
334         }
335         return null;
336     }
337 }
338