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.any;
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.ArgumentMatchers.anyInt;
24 import static org.mockito.Mockito.doNothing;
25 import static org.mockito.Mockito.doReturn;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.never;
28 import static org.mockito.Mockito.spy;
29 import static org.mockito.Mockito.times;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.when;
32 
33 import android.bluetooth.BluetoothAdapter;
34 import android.bluetooth.BluetoothDevice;
35 import android.bluetooth.BluetoothProfile;
36 import android.content.Context;
37 import android.content.res.Resources;
38 import android.graphics.drawable.Drawable;
39 import android.util.Pair;
40 
41 import androidx.preference.PreferenceGroup;
42 
43 import com.android.settings.R;
44 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
45 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
46 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
47 import com.android.settingslib.bluetooth.LocalBluetoothManager;
48 import com.android.settingslib.widget.FooterPreference;
49 
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.Answers;
54 import org.mockito.Mock;
55 import org.mockito.MockitoAnnotations;
56 import org.robolectric.RobolectricTestRunner;
57 import org.robolectric.RuntimeEnvironment;
58 import org.robolectric.annotation.Config;
59 import org.robolectric.shadow.api.Shadow;
60 
61 @RunWith(RobolectricTestRunner.class)
62 @Config(shadows = {ShadowBluetoothAdapter.class})
63 public class BluetoothPairingDetailTest {
64     private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
65     private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1";
66 
67     @Mock
68     private Resources mResource;
69     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
70     private LocalBluetoothManager mLocalManager;
71     @Mock
72     private PreferenceGroup mPreferenceGroup;
73     @Mock
74     private CachedBluetoothDevice mCachedBluetoothDevice;
75     @Mock
76     private Drawable mDrawable;
77 
78     private BluetoothPairingDetail mFragment;
79     private Context mContext;
80     private BluetoothProgressCategory mAvailableDevicesCategory;
81     private FooterPreference mFooterPreference;
82     private BluetoothAdapter mBluetoothAdapter;
83     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
84     private BluetoothDevice mBluetoothDevice;
85 
86     @Before
setUp()87     public void setUp() {
88         MockitoAnnotations.initMocks(this);
89 
90         Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
91         mContext = RuntimeEnvironment.application;
92         mFragment = spy(new BluetoothPairingDetail());
93         doReturn(mContext).when(mFragment).getContext();
94         doReturn(mResource).when(mFragment).getResources();
95         when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
96         when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
97 
98         mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
99         mFooterPreference = new FooterPreference(mContext);
100         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
101         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
102         mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
103 
104         mFragment.mBluetoothAdapter = mBluetoothAdapter;
105         mFragment.mLocalManager = mLocalManager;
106         mFragment.mDeviceListGroup = mPreferenceGroup;
107         mFragment.mAlwaysDiscoverable = new AlwaysDiscoverable(mContext);
108     }
109 
110     @Test
initPreferencesFromPreferenceScreen_findPreferences()111     public void initPreferencesFromPreferenceScreen_findPreferences() {
112         doReturn(mAvailableDevicesCategory).when(mFragment)
113             .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
114         doReturn(mFooterPreference).when(mFragment)
115             .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
116 
117         mFragment.initPreferencesFromPreferenceScreen();
118 
119         assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory);
120         assertThat(mFragment.mFooterPreference).isEqualTo(mFooterPreference);
121     }
122 
123     @Test
startScanning_startScanAndRemoveDevices()124     public void startScanning_startScanAndRemoveDevices() {
125         mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory;
126         mFragment.mDeviceListGroup = mAvailableDevicesCategory;
127 
128         mFragment.enableScanning();
129 
130         verify(mFragment).startScanning();
131         verify(mAvailableDevicesCategory).removeAll();
132     }
133 
134     @Test
updateContent_stateOn_addDevices()135     public void updateContent_stateOn_addDevices() {
136         mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory;
137         mFragment.mFooterPreference = mFooterPreference;
138         doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean());
139 
140         mFragment.updateContent(BluetoothAdapter.STATE_ON);
141 
142         verify(mFragment).addDeviceCategory(mAvailableDevicesCategory,
143                 R.string.bluetooth_preference_found_media_devices,
144                 BluetoothDeviceFilter.ALL_FILTER, false);
145         assertThat(mBluetoothAdapter.getScanMode())
146                 .isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
147     }
148 
149     @Test
updateContent_stateOff_finish()150     public void updateContent_stateOff_finish() {
151         mFragment.updateContent(BluetoothAdapter.STATE_OFF);
152 
153         verify(mFragment).finish();
154     }
155 
156     @Test
updateBluetooth_bluetoothOff_turnOnBluetooth()157     public void updateBluetooth_bluetoothOff_turnOnBluetooth() {
158         mShadowBluetoothAdapter.setEnabled(false);
159 
160         mFragment.updateBluetooth();
161 
162         assertThat(mBluetoothAdapter.isEnabled()).isTrue();
163     }
164 
165     @Test
updateBluetooth_bluetoothOn_updateState()166     public void updateBluetooth_bluetoothOn_updateState() {
167         mShadowBluetoothAdapter.setEnabled(true);
168         doNothing().when(mFragment).updateContent(anyInt());
169 
170         mFragment.updateBluetooth();
171 
172         verify(mFragment).updateContent(anyInt());
173     }
174 
175     @Test
onScanningStateChanged_restartScanAfterInitialScanning()176     public void onScanningStateChanged_restartScanAfterInitialScanning() {
177         mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory;
178         mFragment.mFooterPreference = mFooterPreference;
179         mFragment.mDeviceListGroup = mAvailableDevicesCategory;
180         doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean());
181 
182         // Initial Bluetooth ON will trigger scan enable, list clear and scan start
183         mFragment.updateContent(BluetoothAdapter.STATE_ON);
184         verify(mFragment).enableScanning();
185         assertThat(mAvailableDevicesCategory.getPreferenceCount()).isEqualTo(0);
186         verify(mFragment).startScanning();
187 
188         // Subsequent scan started event will not trigger start/stop nor list clear
189         mFragment.onScanningStateChanged(true);
190         verify(mFragment, times(1)).startScanning();
191         verify(mAvailableDevicesCategory, times(1)).setProgress(true);
192 
193         // Subsequent scan finished event will trigger scan start without list clean
194         mFragment.onScanningStateChanged(false);
195         verify(mFragment, times(2)).startScanning();
196         verify(mAvailableDevicesCategory, times(2)).setProgress(true);
197 
198         // Subsequent scan started event will not trigger any change
199         mFragment.onScanningStateChanged(true);
200         verify(mFragment, times(2)).startScanning();
201         verify(mAvailableDevicesCategory, times(3)).setProgress(true);
202         verify(mFragment, never()).stopScanning();
203 
204         // Disable scanning will trigger scan stop
205         mFragment.disableScanning();
206         verify(mFragment, times(1)).stopScanning();
207 
208         // Subsequent scan start event will not trigger any change besides progress circle
209         mFragment.onScanningStateChanged(true);
210         verify(mAvailableDevicesCategory, times(4)).setProgress(true);
211 
212         // However, subsequent scan finished event won't trigger new scan start and will stop
213         // progress circle from spinning
214         mFragment.onScanningStateChanged(false);
215         verify(mAvailableDevicesCategory, times(1)).setProgress(false);
216         verify(mFragment, times(2)).startScanning();
217         verify(mFragment, times(1)).stopScanning();
218 
219         // Verify that clean up only happen once at initialization
220         verify(mAvailableDevicesCategory, times(1)).removeAll();
221     }
222 
223     @Test
onBluetoothStateChanged_whenTurnedOnBTShowToast()224     public void onBluetoothStateChanged_whenTurnedOnBTShowToast() {
225         doNothing().when(mFragment).updateContent(anyInt());
226 
227         mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
228 
229         verify(mFragment).showBluetoothTurnedOnToast();
230     }
231 
232     @Test
onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish()233     public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
234         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
235         mFragment.mSelectedList.add(mBluetoothDevice);
236         mFragment.mSelectedList.add(device);
237 
238         when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
239         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
240 
241         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
242                 BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
243 
244         verify(mFragment).finish();
245     }
246 
247     @Test
onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing()248     public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
249         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
250         mFragment.mSelectedList.add(device);
251 
252         when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
253         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
254 
255         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
256                 BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
257 
258         // not crash
259     }
260 
261     @Test
onProfileConnectionStateChanged_deviceDisconnected_doNothing()262     public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
263         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
264         mFragment.mSelectedList.add(mBluetoothDevice);
265         mFragment.mSelectedList.add(device);
266 
267         when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
268         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
269 
270         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
271                 BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED);
272 
273         // not crash
274     }
275 
276     @Test
onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed()277     public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
278         final BluetoothDevicePreference preference =
279                 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
280                 true, BluetoothDevicePreference.SortType.TYPE_FIFO);
281         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
282         mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
283 
284         when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
285         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
286 
287         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
288                 BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
289 
290         assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
291     }
292 
293     @Test
onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing()294     public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
295         final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
296         final BluetoothDevicePreference preference =
297                 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
298                         true, BluetoothDevicePreference.SortType.TYPE_FIFO);
299         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
300         final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
301         mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
302 
303         when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
304         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
305         when(cachedDevice.isConnected()).thenReturn(true);
306         when(cachedDevice.getDevice()).thenReturn(device2);
307         when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
308 
309         mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP,
310                 BluetoothAdapter.STATE_CONNECTED);
311 
312         // not crash
313     }
314 }