1 /* 2 * Copyright (C) 2020 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 android.bluetooth.cts; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.fail; 21 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothManager; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.le.ScanRecord; 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.provider.Settings; 29 import android.sysprop.BluetoothProperties; 30 import android.util.Log; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.test.platform.app.InstrumentationRegistry; 35 36 import java.lang.reflect.InvocationTargetException; 37 import java.lang.reflect.Method; 38 import java.util.Arrays; 39 import java.util.Set; 40 import java.util.concurrent.TimeUnit; 41 import java.util.concurrent.locks.Condition; 42 import java.util.concurrent.locks.ReentrantLock; 43 44 /** 45 * Utility class for Bluetooth CTS test. 46 */ 47 public class TestUtils { 48 /** 49 * Checks whether this device has Bluetooth feature 50 * @return true if this device has Bluetooth feature 51 */ hasBluetooth()52 public static boolean hasBluetooth() { 53 Context context = InstrumentationRegistry.getInstrumentation().getContext(); 54 return context.getPackageManager().hasSystemFeature( 55 PackageManager.FEATURE_BLUETOOTH); 56 } 57 58 /** 59 * Get the current enabled status of a given profile 60 */ isProfileEnabled(int profile)61 public static boolean isProfileEnabled(int profile) { 62 switch (profile) { 63 case BluetoothProfile.A2DP: 64 return BluetoothProperties.isProfileA2dpSourceEnabled().orElse(false); 65 case BluetoothProfile.A2DP_SINK: 66 return BluetoothProperties.isProfileA2dpSinkEnabled().orElse(false); 67 // Hidden profile 68 // case BluetoothProfile.AVRCP: 69 // return BluetoothProperties.isProfileAvrcpTargetEnabled().orElse(false); 70 case BluetoothProfile.AVRCP_CONTROLLER: 71 return BluetoothProperties.isProfileAvrcpControllerEnabled().orElse(false); 72 case BluetoothProfile.CSIP_SET_COORDINATOR: 73 return BluetoothProperties.isProfileCsipSetCoordinatorEnabled().orElse(false); 74 case BluetoothProfile.GATT: 75 return BluetoothProperties.isProfileGattEnabled().orElse(true); 76 case BluetoothProfile.HAP_CLIENT: 77 return BluetoothProperties.isProfileHapClientEnabled().orElse(false); 78 case BluetoothProfile.HEADSET: 79 return BluetoothProperties.isProfileHfpAgEnabled().orElse(false); 80 case BluetoothProfile.HEADSET_CLIENT: 81 return BluetoothProperties.isProfileHfpHfEnabled().orElse(false); 82 case BluetoothProfile.HEARING_AID: 83 Context context = InstrumentationRegistry.getInstrumentation().getContext(); 84 if (!isBleSupported(context)) { 85 return false; 86 } 87 boolean default_value = true; 88 if (isAutomotive(context) || isWatch(context) || isTv(context)) { 89 default_value = false; 90 } 91 return BluetoothProperties.isProfileAshaCentralEnabled().orElse(default_value); 92 case BluetoothProfile.HID_DEVICE: 93 return BluetoothProperties.isProfileHidDeviceEnabled().orElse(false); 94 case BluetoothProfile.HID_HOST: 95 return BluetoothProperties.isProfileHidHostEnabled().orElse(false); 96 case BluetoothProfile.LE_AUDIO: 97 return BluetoothProperties.isProfileBapUnicastClientEnabled().orElse(false); 98 case BluetoothProfile.LE_AUDIO_BROADCAST: 99 return BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false); 100 case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT: 101 return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false); 102 // Hidden profile 103 // case BluetoothProfile.LE_CALL_CONTROL: 104 // return BluetoothProperties.isProfileCcpServerEnabled().orElse(false); 105 case BluetoothProfile.MAP: 106 return BluetoothProperties.isProfileMapServerEnabled().orElse(false); 107 case BluetoothProfile.MAP_CLIENT: 108 return BluetoothProperties.isProfileMapClientEnabled().orElse(false); 109 // Hidden profile 110 // case BluetoothProfile.MCP_SERVER: 111 // return BluetoothProperties.isProfileMcpServerEnabled().orElse(false); 112 case BluetoothProfile.OPP: 113 return BluetoothProperties.isProfileOppEnabled().orElse(false); 114 case BluetoothProfile.PAN: 115 return BluetoothProperties.isProfilePanNapEnabled().orElse(false) 116 || BluetoothProperties.isProfilePanPanuEnabled().orElse(false); 117 case BluetoothProfile.PBAP: 118 return BluetoothProperties.isProfilePbapServerEnabled().orElse(false); 119 case BluetoothProfile.PBAP_CLIENT: 120 return BluetoothProperties.isProfilePbapClientEnabled().orElse(false); 121 case BluetoothProfile.SAP: 122 return BluetoothProperties.isProfileSapServerEnabled().orElse(false); 123 case BluetoothProfile.VOLUME_CONTROL: 124 return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false); 125 default: 126 return false; 127 } 128 } 129 130 /** 131 * Adopt shell UID's permission via {@link android.app.UiAutomation} 132 * @param permission permission to adopt 133 */ adoptPermissionAsShellUid(@ullable String... permission)134 public static void adoptPermissionAsShellUid(@Nullable String... permission) { 135 InstrumentationRegistry.getInstrumentation().getUiAutomation() 136 .adoptShellPermissionIdentity(permission); 137 } 138 139 /** 140 * Drop all permissions adopted as shell UID 141 */ dropPermissionAsShellUid()142 public static void dropPermissionAsShellUid() { 143 InstrumentationRegistry.getInstrumentation().getUiAutomation() 144 .dropShellPermissionIdentity(); 145 } 146 147 /** 148 * @return permissions adopted from Shell 149 */ getAdoptedShellPermissions()150 public static Set<String> getAdoptedShellPermissions() { 151 return InstrumentationRegistry.getInstrumentation().getUiAutomation() 152 .getAdoptedShellPermissions(); 153 } 154 155 /** 156 * Get {@link BluetoothAdapter} via {@link android.bluetooth.BluetoothManager} 157 * Fail the test if {@link BluetoothAdapter} is null 158 * @return instance of {@link BluetoothAdapter} 159 */ getBluetoothAdapterOrDie()160 @NonNull public static BluetoothAdapter getBluetoothAdapterOrDie() { 161 Context context = InstrumentationRegistry.getInstrumentation().getContext(); 162 BluetoothManager manager = context.getSystemService(BluetoothManager.class); 163 assertNotNull(manager); 164 BluetoothAdapter adapter = manager.getAdapter(); 165 assertNotNull(adapter); 166 return adapter; 167 } 168 169 /** 170 * Utility method to call hidden ScanRecord.parseFromBytes method. 171 */ parseScanRecord(byte[] bytes)172 public static ScanRecord parseScanRecord(byte[] bytes) { 173 Class<?> scanRecordClass = ScanRecord.class; 174 try { 175 Method method = scanRecordClass.getDeclaredMethod("parseFromBytes", byte[].class); 176 return (ScanRecord) method.invoke(null, bytes); 177 } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException 178 | InvocationTargetException e) { 179 return null; 180 } 181 } 182 183 /** 184 * Assert two byte arrays are equal. 185 */ assertArrayEquals(byte[] expected, byte[] actual)186 public static void assertArrayEquals(byte[] expected, byte[] actual) { 187 if (!Arrays.equals(expected, actual)) { 188 fail("expected:<" + Arrays.toString(expected) 189 + "> but was:<" + Arrays.toString(actual) + ">"); 190 } 191 } 192 193 /** 194 * Get current location mode settings. 195 */ getLocationMode(Context context)196 public static int getLocationMode(Context context) { 197 return Settings.Secure.getInt(context.getContentResolver(), 198 Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); 199 } 200 201 /** 202 * Set location settings mode. 203 */ setLocationMode(Context context, int mode)204 public static void setLocationMode(Context context, int mode) { 205 Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE, 206 mode); 207 } 208 209 /** 210 * Return true if location is on. 211 */ isLocationOn(Context context)212 public static boolean isLocationOn(Context context) { 213 return getLocationMode(context) != Settings.Secure.LOCATION_MODE_OFF; 214 } 215 216 /** 217 * Enable location and set the mode to GPS only. 218 */ enableLocation(Context context)219 public static void enableLocation(Context context) { 220 setLocationMode(context, Settings.Secure.LOCATION_MODE_SENSORS_ONLY); 221 } 222 223 /** 224 * Disable location. 225 */ disableLocation(Context context)226 public static void disableLocation(Context context) { 227 setLocationMode(context, Settings.Secure.LOCATION_MODE_OFF); 228 } 229 230 /** 231 * Check if BLE is supported by this platform 232 * @param context current device context 233 * @return true if BLE is supported, false otherwise 234 */ isBleSupported(Context context)235 public static boolean isBleSupported(Context context) { 236 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); 237 } 238 239 /** 240 * Check if this is an automotive device 241 * @param context current device context 242 * @return true if this Android device is an automotive device, false otherwise 243 */ isAutomotive(Context context)244 public static boolean isAutomotive(Context context) { 245 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 246 } 247 248 /** 249 * Check if this is a watch device 250 * @param context current device context 251 * @return true if this Android device is a watch device, false otherwise 252 */ isWatch(Context context)253 public static boolean isWatch(Context context) { 254 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); 255 } 256 257 /** 258 * Check if this is a TV device 259 * @param context current device context 260 * @return true if this Android device is a TV device, false otherwise 261 */ isTv(Context context)262 public static boolean isTv(Context context) { 263 PackageManager pm = context.getPackageManager(); 264 return pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION) 265 || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); 266 } 267 268 /** 269 * Put the current thread to sleep. 270 * @param sleepMillis number of milliseconds to sleep for 271 */ sleep(int sleepMillis)272 public static void sleep(int sleepMillis) { 273 try { 274 Thread.sleep(sleepMillis); 275 } catch (InterruptedException e) { 276 Log.e(TestUtils.class.getSimpleName(), "interrupted", e); 277 } 278 } 279 280 /** 281 * Boilerplate class for profile listener 282 */ 283 public static class BluetoothCtsServiceConnector { 284 private static final int PROXY_CONNECTION_TIMEOUT_MS = 500; // ms timeout for Proxy Connect 285 private BluetoothProfile mProfileProxy = null; 286 private boolean mIsProfileReady = false; 287 private boolean mIsProfileConnecting = false; 288 private final Condition mConditionProfileConnection; 289 private final ReentrantLock mProfileConnectionLock; 290 private final String mLogTag; 291 private final int mProfileId; 292 private final BluetoothAdapter mAdapter; 293 private final Context mContext; BluetoothCtsServiceConnector(String logTag, int profileId, BluetoothAdapter adapter, Context context)294 public BluetoothCtsServiceConnector(String logTag, int profileId, BluetoothAdapter adapter, 295 Context context) { 296 mLogTag = logTag; 297 mProfileId = profileId; 298 mAdapter = adapter; 299 mContext = context; 300 mProfileConnectionLock = new ReentrantLock(); 301 mConditionProfileConnection = mProfileConnectionLock.newCondition(); 302 assertNotNull(mLogTag); 303 assertNotNull(mAdapter); 304 assertNotNull(mContext); 305 } 306 getProfileProxy()307 public BluetoothProfile getProfileProxy() { 308 return mProfileProxy; 309 } 310 closeProfileProxy()311 public void closeProfileProxy() { 312 if (mProfileProxy != null) { 313 mAdapter.closeProfileProxy(mProfileId, mProfileProxy); 314 mProfileProxy = null; 315 mIsProfileReady = false; 316 } 317 } 318 openProfileProxyAsync()319 public boolean openProfileProxyAsync() { 320 mIsProfileConnecting = mAdapter.getProfileProxy(mContext, mServiceListener, mProfileId); 321 return mIsProfileConnecting; 322 } 323 waitForProfileConnect()324 public boolean waitForProfileConnect() { 325 return waitForProfileConnect(PROXY_CONNECTION_TIMEOUT_MS); 326 } 327 waitForProfileConnect(int timeoutMs)328 public boolean waitForProfileConnect(int timeoutMs) { 329 if (!mIsProfileConnecting) { 330 mIsProfileConnecting = 331 mAdapter.getProfileProxy(mContext, mServiceListener, mProfileId); 332 } 333 if (!mIsProfileConnecting) { 334 return false; 335 } 336 mProfileConnectionLock.lock(); 337 try { 338 // Wait for the Adapter to be disabled 339 while (!mIsProfileReady) { 340 if (!mConditionProfileConnection.await(timeoutMs, TimeUnit.MILLISECONDS)) { 341 // Timeout 342 Log.e(mLogTag, "Timeout while waiting for Profile Connect"); 343 break; 344 } // else spurious wake-ups 345 } 346 } catch (InterruptedException e) { 347 Log.e(mLogTag, "waitForProfileConnect: interrupted"); 348 } finally { 349 mProfileConnectionLock.unlock(); 350 } 351 mIsProfileConnecting = false; 352 return mIsProfileReady; 353 } 354 355 private final BluetoothProfile.ServiceListener mServiceListener = 356 new BluetoothProfile.ServiceListener() { 357 @Override 358 public void onServiceConnected(int profile, BluetoothProfile proxy) { 359 mProfileConnectionLock.lock(); 360 mProfileProxy = proxy; 361 mIsProfileReady = true; 362 try { 363 mConditionProfileConnection.signal(); 364 } finally { 365 mProfileConnectionLock.unlock(); 366 } 367 } 368 369 @Override 370 public void onServiceDisconnected(int profile) { 371 mProfileConnectionLock.lock(); 372 mIsProfileReady = false; 373 try { 374 mConditionProfileConnection.signal(); 375 } finally { 376 mProfileConnectionLock.unlock(); 377 } 378 } 379 }; 380 } 381 } 382