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