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 package com.android.settings.connecteddevice;
17 
18 import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
19 import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.anyString;
24 import static org.mockito.Mockito.doReturn;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.spy;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.bluetooth.BluetoothDevice;
31 import android.content.Context;
32 import android.content.pm.PackageManager;
33 import android.hardware.input.InputManager;
34 import android.platform.test.annotations.EnableFlags;
35 import android.platform.test.flag.junit.SetFlagsRule;
36 import android.util.FeatureFlagUtils;
37 import android.view.InputDevice;
38 
39 import androidx.preference.Preference;
40 import androidx.preference.PreferenceGroup;
41 import androidx.preference.PreferenceManager;
42 import androidx.preference.PreferenceScreen;
43 
44 import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
45 import com.android.settings.bluetooth.Utils;
46 import com.android.settings.connecteddevice.dock.DockUpdater;
47 import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
48 import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
49 import com.android.settings.dashboard.DashboardFragment;
50 import com.android.settings.flags.Flags;
51 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
52 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
53 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
54 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
55 import com.android.settingslib.bluetooth.LocalBluetoothManager;
56 import com.android.settingslib.search.SearchIndexableRaw;
57 
58 import com.google.common.collect.ImmutableList;
59 
60 import org.junit.Before;
61 import org.junit.Rule;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 import org.mockito.Answers;
65 import org.mockito.Mock;
66 import org.mockito.MockitoAnnotations;
67 import org.robolectric.RobolectricTestRunner;
68 import org.robolectric.RuntimeEnvironment;
69 import org.robolectric.Shadows;
70 import org.robolectric.annotation.Config;
71 import org.robolectric.shadows.ShadowApplicationPackageManager;
72 
73 import java.util.ArrayList;
74 import java.util.List;
75 
76 @RunWith(RobolectricTestRunner.class)
77 @Config(shadows = {ShadowApplicationPackageManager.class, ShadowBluetoothUtils.class,
78         ShadowBluetoothAdapter.class})
79 public class ConnectedDeviceGroupControllerTest {
80 
81     private static final String PREFERENCE_KEY_1 = "pref_key_1";
82     private static final String DEVICE_NAME = "device";
83 
84     @Mock
85     private DashboardFragment mDashboardFragment;
86     @Mock
87     private ConnectedBluetoothDeviceUpdater mConnectedBluetoothDeviceUpdater;
88     @Mock
89     private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
90     @Mock
91     private DockUpdater mConnectedDockUpdater;
92     @Mock
93     private StylusDeviceUpdater mStylusDeviceUpdater;
94     @Mock
95     private PreferenceScreen mPreferenceScreen;
96     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
97     private PreferenceManager mPreferenceManager;
98     @Mock
99     private InputManager mInputManager;
100     @Mock
101     private CachedBluetoothDeviceManager mCachedDeviceManager;
102     @Mock
103     private LocalBluetoothManager mLocalBluetoothManager;
104     @Mock
105     private CachedBluetoothDevice mCachedDevice;
106     @Mock
107     private BluetoothDevice mDevice;
108 
109     private ShadowApplicationPackageManager mPackageManager;
110     private PreferenceGroup mPreferenceGroup;
111     private Context mContext;
112     private Preference mPreference;
113     private ConnectedDeviceGroupController mConnectedDeviceGroupController;
114 
115     @Rule
116     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
117 
118     @Before
setUp()119     public void setUp() {
120         MockitoAnnotations.initMocks(this);
121 
122         mContext = spy(RuntimeEnvironment.application);
123         mPreference = new Preference(mContext);
124         mPreference.setKey(PREFERENCE_KEY_1);
125         mPackageManager = (ShadowApplicationPackageManager) Shadows.shadowOf(
126                 mContext.getPackageManager());
127         mPreferenceGroup = spy(new PreferenceScreen(mContext, null));
128         when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager);
129         doReturn(mContext).when(mDashboardFragment).getContext();
130         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
131         when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
132         when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{});
133 
134         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
135         mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
136         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
137 
138         mConnectedDeviceGroupController = new ConnectedDeviceGroupController(mContext);
139         mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
140                 mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
141         mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
142 
143         when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
144         when(mCachedDevice.getDevice()).thenReturn(mDevice);
145         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
146                 ImmutableList.of(mCachedDevice));
147 
148         FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
149                 true);
150     }
151 
152     @Test
onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded()153     public void onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded() {
154         mConnectedDeviceGroupController.onDeviceAdded(mPreference);
155 
156         assertThat(mPreferenceGroup.isVisible()).isTrue();
157         assertThat((Preference) mPreferenceGroup.findPreference(PREFERENCE_KEY_1))
158                 .isEqualTo(mPreference);
159     }
160 
161     @Test
onDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved()162     public void onDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved() {
163         mPreferenceGroup.addPreference(mPreference);
164 
165         mConnectedDeviceGroupController.onDeviceRemoved(mPreference);
166 
167         assertThat(mPreferenceGroup.isVisible()).isFalse();
168         assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
169     }
170 
171     @Test
onDeviceRemoved_notLastRemove_stillVisible()172     public void onDeviceRemoved_notLastRemove_stillVisible() {
173         mPreferenceGroup.setVisible(true);
174         mPreferenceGroup.addPreference(mPreference);
175         mPreferenceGroup.addPreference(new Preference(mContext));
176 
177         mConnectedDeviceGroupController.onDeviceRemoved(mPreference);
178 
179         assertThat(mPreferenceGroup.isVisible()).isTrue();
180     }
181 
182     @Test
displayPreference_becomeInvisible()183     public void displayPreference_becomeInvisible() {
184         doReturn(mPreferenceGroup).when(mPreferenceScreen).findPreference(anyString());
185 
186         mConnectedDeviceGroupController.displayPreference(mPreferenceScreen);
187 
188         assertThat(mPreferenceGroup.isVisible()).isFalse();
189     }
190 
191     @Test
onStart_shouldRegisterUpdaters()192     public void onStart_shouldRegisterUpdaters() {
193         // register the callback in onStart()
194         mConnectedDeviceGroupController.onStart();
195 
196         verify(mConnectedBluetoothDeviceUpdater).registerCallback();
197         verify(mConnectedUsbDeviceUpdater).registerCallback();
198         verify(mConnectedDockUpdater).registerCallback();
199         verify(mConnectedBluetoothDeviceUpdater).refreshPreference();
200         verify(mStylusDeviceUpdater).registerCallback();
201     }
202 
203     @Test
onStop_shouldUnregisterUpdaters()204     public void onStop_shouldUnregisterUpdaters() {
205         // unregister the callback in onStop()
206         mConnectedDeviceGroupController.onStop();
207         verify(mConnectedBluetoothDeviceUpdater).unregisterCallback();
208         verify(mConnectedUsbDeviceUpdater).unregisterCallback();
209         verify(mConnectedDockUpdater).unregisterCallback();
210         verify(mStylusDeviceUpdater).unregisterCallback();
211     }
212 
213     @Test
getAvailabilityStatus_noBluetoothUsbDockFeature_returnUnSupported()214     public void getAvailabilityStatus_noBluetoothUsbDockFeature_returnUnSupported() {
215         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
216         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
217         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
218         mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
219                 mConnectedUsbDeviceUpdater, null, null);
220 
221         assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
222                 UNSUPPORTED_ON_DEVICE);
223     }
224 
225     @Test
getAvailabilityStatus_BluetoothFeature_returnSupported()226     public void getAvailabilityStatus_BluetoothFeature_returnSupported() {
227         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
228         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
229         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
230         mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
231                 mConnectedUsbDeviceUpdater, null, null);
232 
233         assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
234                 AVAILABLE_UNSEARCHABLE);
235     }
236 
237     @Test
getAvailabilityStatus_haveUsbFeature_returnSupported()238     public void getAvailabilityStatus_haveUsbFeature_returnSupported() {
239         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
240         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
241         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, true);
242         mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
243                 mConnectedUsbDeviceUpdater, null, null);
244 
245         assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
246                 AVAILABLE_UNSEARCHABLE);
247     }
248 
249     @Test
getAvailabilityStatus_haveDockFeature_returnSupported()250     public void getAvailabilityStatus_haveDockFeature_returnSupported() {
251         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
252         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
253         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
254         mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
255                 mConnectedUsbDeviceUpdater, mConnectedDockUpdater, null);
256 
257         assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
258                 AVAILABLE_UNSEARCHABLE);
259     }
260 
261 
262     @Test
getAvailabilityStatus_noUsiStylusFeature_returnUnSupported()263     public void getAvailabilityStatus_noUsiStylusFeature_returnUnSupported() {
264         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
265         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
266         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
267         when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{0});
268         when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
269                 InputDevice.SOURCE_DPAD).setExternal(false).build());
270 
271         mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
272                 mConnectedUsbDeviceUpdater, null, mStylusDeviceUpdater);
273 
274         assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
275                 UNSUPPORTED_ON_DEVICE);
276     }
277 
278     @Test
getAvailabilityStatus_haveUsiStylusFeature_returnSupported()279     public void getAvailabilityStatus_haveUsiStylusFeature_returnSupported() {
280         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
281         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
282         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
283         when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{0});
284         when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
285                 InputDevice.SOURCE_STYLUS).setExternal(false).build());
286 
287         mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
288                 mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
289 
290         assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
291                 AVAILABLE_UNSEARCHABLE);
292     }
293 
294     @Test
init_noBluetoothAndUsbFeature_doesNotCrash()295     public void init_noBluetoothAndUsbFeature_doesNotCrash() {
296         DashboardFragment fragment = mock(DashboardFragment.class);
297         when(fragment.getContext()).thenReturn(mContext);
298         when(mPreferenceScreen.findPreference(anyString())).thenReturn(mPreferenceGroup);
299         mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
300         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
301         mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
302 
303         mConnectedDeviceGroupController.init(fragment);
304         mConnectedDeviceGroupController.displayPreference(mPreferenceScreen);
305         mConnectedDeviceGroupController.onStart();
306         mConnectedDeviceGroupController.onStop();
307     }
308 
309     @Test
310     @EnableFlags(Flags.FLAG_ENABLE_BONDED_BLUETOOTH_DEVICE_SEARCHABLE)
updateDynamicRawDataToIndex_deviceNotBonded_deviceIsNotSearchable()311     public void updateDynamicRawDataToIndex_deviceNotBonded_deviceIsNotSearchable() {
312         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
313         List<SearchIndexableRaw> searchData = new ArrayList<>();
314 
315         mConnectedDeviceGroupController.updateDynamicRawDataToIndex(searchData);
316 
317         assertThat(searchData).isEmpty();
318     }
319 
320     @Test
321     @EnableFlags(Flags.FLAG_ENABLE_BONDED_BLUETOOTH_DEVICE_SEARCHABLE)
updateDynamicRawDataToIndex_deviceBonded_deviceIsSearchable()322     public void updateDynamicRawDataToIndex_deviceBonded_deviceIsSearchable() {
323         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
324         List<SearchIndexableRaw> searchData = new ArrayList<>();
325 
326         mConnectedDeviceGroupController.updateDynamicRawDataToIndex(searchData);
327 
328         assertThat(searchData).isNotEmpty();
329         assertThat(searchData.get(0).key).contains(DEVICE_NAME);
330     }
331 }
332