1 /* 2 * Copyright (C) 2012 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.bluetooth; 18 19 import static android.Manifest.permission.ACCESS_COARSE_LOCATION; 20 import static android.Manifest.permission.ACCESS_FINE_LOCATION; 21 import static android.Manifest.permission.BLUETOOTH_ADVERTISE; 22 import static android.Manifest.permission.BLUETOOTH_CONNECT; 23 import static android.Manifest.permission.BLUETOOTH_SCAN; 24 import static android.Manifest.permission.RENOUNCE_PERMISSIONS; 25 import static android.bluetooth.BluetoothUtils.USER_HANDLE_NULL; 26 import static android.content.pm.PackageManager.GET_PERMISSIONS; 27 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; 28 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 29 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; 30 import static android.permission.PermissionManager.PERMISSION_HARD_DENIED; 31 32 import android.Manifest; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.annotation.RequiresPermission; 36 import android.annotation.SuppressLint; 37 import android.app.BroadcastOptions; 38 import android.bluetooth.BluetoothAdapter; 39 import android.bluetooth.BluetoothDevice; 40 import android.companion.AssociationInfo; 41 import android.companion.CompanionDeviceManager; 42 import android.content.AttributionSource; 43 import android.content.BroadcastReceiver; 44 import android.content.ContentValues; 45 import android.content.Context; 46 import android.content.Intent; 47 import android.content.pm.PackageInfo; 48 import android.content.pm.PackageManager; 49 import android.location.LocationManager; 50 import android.net.Uri; 51 import android.os.Binder; 52 import android.os.Build; 53 import android.os.Bundle; 54 import android.os.Handler; 55 import android.os.ParcelUuid; 56 import android.os.PowerExemptionManager; 57 import android.os.Process; 58 import android.os.SystemProperties; 59 import android.os.UserHandle; 60 import android.os.UserManager; 61 import android.permission.PermissionManager; 62 import android.provider.DeviceConfig; 63 import android.provider.Telephony; 64 import android.util.Log; 65 66 import androidx.annotation.RequiresApi; 67 68 import com.android.bluetooth.btservice.AdapterService; 69 import com.android.bluetooth.btservice.ProfileService; 70 71 import org.xmlpull.v1.XmlPullParser; 72 import org.xmlpull.v1.XmlPullParserException; 73 74 import java.io.IOException; 75 import java.io.InputStream; 76 import java.io.OutputStream; 77 import java.math.BigInteger; 78 import java.nio.ByteBuffer; 79 import java.nio.ByteOrder; 80 import java.nio.charset.Charset; 81 import java.nio.charset.CharsetDecoder; 82 import java.time.Instant; 83 import java.time.ZoneId; 84 import java.time.format.DateTimeFormatter; 85 import java.util.List; 86 import java.util.Objects; 87 import java.util.UUID; 88 import java.util.concurrent.TimeUnit; 89 90 /** 91 * @hide 92 */ 93 public final class Utils { 94 private static final String TAG = "BluetoothUtils"; 95 private static final int MICROS_PER_UNIT = 625; 96 private static final String PTS_TEST_MODE_PROPERTY = "persist.bluetooth.pts"; 97 98 private static final String ENABLE_DUAL_MODE_AUDIO = 99 "persist.bluetooth.enable_dual_mode_audio"; 100 private static boolean sDualModeEnabled = 101 SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false); 102 103 private static final String KEY_TEMP_ALLOW_LIST_DURATION_MS = "temp_allow_list_duration_ms"; 104 private static final long DEFAULT_TEMP_ALLOW_LIST_DURATION_MS = 20_000; 105 106 static final int BD_ADDR_LEN = 6; // bytes 107 static final int BD_UUID_LEN = 16; // bytes 108 109 /* 110 * Special character 111 * 112 * (See "What is a phone number?" doc) 113 * 'p' --- GSM pause character, same as comma 114 * 'n' --- GSM wild character 115 * 'w' --- GSM wait character 116 */ 117 public static final char PAUSE = ','; 118 public static final char WAIT = ';'; 119 public static final String PAIRING_UI_PROPERTY = "bluetooth.pairing_ui_package.name"; 120 isPause(char c)121 private static boolean isPause(char c) { 122 return c == 'p' || c == 'P'; 123 } 124 isToneWait(char c)125 private static boolean isToneWait(char c) { 126 return c == 'w' || c == 'W'; 127 } 128 129 /** 130 * Check if dual mode audio is enabled. This is set via the system property 131 * persist.bluetooth.enable_dual_mode_audio. 132 * <p> 133 * When set to {@code false}, we will not connect A2DP and HFP on a dual mode (BR/EDR + BLE) 134 * device. We will only attempt to use BLE Audio in this scenario. 135 * <p> 136 * When set to {@code true}, we will connect all the supported audio profiles 137 * (A2DP, HFP, and LE Audio) at the same time. In this state, we will respect calls to 138 * profile-specific APIs (e.g. if a SCO API is invoked, we will route audio over HFP). If no 139 * profile-specific API is invoked to route audio (e.g. Telecom routed phone calls, media, 140 * game audio, etc.), then audio will be routed in accordance with the preferred audio profiles 141 * for the remote device. You can get the preferred audio profiles for a remote device by 142 * calling {@link BluetoothAdapter#getPreferredAudioProfiles(BluetoothDevice)}. 143 * 144 * @return true if dual mode audio is enabled, false otherwise 145 */ isDualModeAudioEnabled()146 public static boolean isDualModeAudioEnabled() { 147 Log.i(TAG, "Dual mode enable state is: " + sDualModeEnabled); 148 return sDualModeEnabled; 149 } 150 151 /** 152 * Only exposed for testing, do not invoke this method outside of tests. 153 * @param enabled true if the dual mode state is enabled, false otherwise 154 */ setDualModeAudioStateForTesting(boolean enabled)155 public static void setDualModeAudioStateForTesting(boolean enabled) { 156 Log.i(TAG, "Updating dual mode audio state for testing to: " + enabled); 157 sDualModeEnabled = enabled; 158 } 159 getName(@ullable BluetoothDevice device)160 public static @Nullable String getName(@Nullable BluetoothDevice device) { 161 final AdapterService service = AdapterService.getAdapterService(); 162 if (service != null && device != null) { 163 return service.getRemoteName(device); 164 } else { 165 return null; 166 } 167 } 168 getLoggableAddress(@ullable BluetoothDevice device)169 public static String getLoggableAddress(@Nullable BluetoothDevice device) { 170 if (device == null) { 171 return "00:00:00:00:00:00"; 172 } else { 173 return "xx:xx:xx:xx:" + device.toString().substring(12); 174 } 175 } 176 getAddressStringFromByte(byte[] address)177 public static String getAddressStringFromByte(byte[] address) { 178 if (address == null || address.length != BD_ADDR_LEN) { 179 return null; 180 } 181 182 return String.format("%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], address[2], 183 address[3], address[4], address[5]); 184 } 185 getRedactedAddressStringFromByte(byte[] address)186 public static String getRedactedAddressStringFromByte(byte[] address) { 187 if (address == null || address.length != BD_ADDR_LEN) { 188 return null; 189 } 190 191 return String.format("XX:XX:XX:XX:%02X:%02X", address[4], address[5]); 192 } 193 getByteAddress(BluetoothDevice device)194 public static byte[] getByteAddress(BluetoothDevice device) { 195 return getBytesFromAddress(device.getAddress()); 196 } 197 getBytesFromAddress(String address)198 public static byte[] getBytesFromAddress(String address) { 199 int i, j = 0; 200 byte[] output = new byte[BD_ADDR_LEN]; 201 202 for (i = 0; i < address.length(); i++) { 203 if (address.charAt(i) != ':') { 204 output[j] = (byte) Integer.parseInt(address.substring(i, i + 2), BD_UUID_LEN); 205 j++; 206 i++; 207 } 208 } 209 210 return output; 211 } 212 byteArrayToInt(byte[] valueBuf)213 public static int byteArrayToInt(byte[] valueBuf) { 214 return byteArrayToInt(valueBuf, 0); 215 } 216 byteArrayToShort(byte[] valueBuf)217 public static short byteArrayToShort(byte[] valueBuf) { 218 ByteBuffer converter = ByteBuffer.wrap(valueBuf); 219 converter.order(ByteOrder.nativeOrder()); 220 return converter.getShort(); 221 } 222 byteArrayToInt(byte[] valueBuf, int offset)223 public static int byteArrayToInt(byte[] valueBuf, int offset) { 224 ByteBuffer converter = ByteBuffer.wrap(valueBuf); 225 converter.order(ByteOrder.nativeOrder()); 226 return converter.getInt(offset); 227 } 228 byteArrayToString(byte[] valueBuf)229 public static String byteArrayToString(byte[] valueBuf) { 230 StringBuilder sb = new StringBuilder(); 231 for (int idx = 0; idx < valueBuf.length; idx++) { 232 if (idx != 0) { 233 sb.append(" "); 234 } 235 sb.append(String.format("%02x", valueBuf[idx])); 236 } 237 return sb.toString(); 238 } 239 240 /** 241 * A parser to transfer a byte array to a UTF8 string 242 * 243 * @param valueBuf the byte array to transfer 244 * @return the transferred UTF8 string 245 */ byteArrayToUtf8String(byte[] valueBuf)246 public static String byteArrayToUtf8String(byte[] valueBuf) { 247 CharsetDecoder decoder = Charset.forName("UTF8").newDecoder(); 248 ByteBuffer byteBuffer = ByteBuffer.wrap(valueBuf); 249 String valueStr = ""; 250 try { 251 valueStr = decoder.decode(byteBuffer).toString(); 252 } catch (Exception ex) { 253 Log.e(TAG, "Error when parsing byte array to UTF8 String. " + ex); 254 } 255 return valueStr; 256 } 257 intToByteArray(int value)258 public static byte[] intToByteArray(int value) { 259 ByteBuffer converter = ByteBuffer.allocate(4); 260 converter.order(ByteOrder.nativeOrder()); 261 converter.putInt(value); 262 return converter.array(); 263 } 264 uuidToByteArray(ParcelUuid pUuid)265 public static byte[] uuidToByteArray(ParcelUuid pUuid) { 266 int length = BD_UUID_LEN; 267 ByteBuffer converter = ByteBuffer.allocate(length); 268 converter.order(ByteOrder.BIG_ENDIAN); 269 long msb, lsb; 270 UUID uuid = pUuid.getUuid(); 271 msb = uuid.getMostSignificantBits(); 272 lsb = uuid.getLeastSignificantBits(); 273 converter.putLong(msb); 274 converter.putLong(8, lsb); 275 return converter.array(); 276 } 277 uuidsToByteArray(ParcelUuid[] uuids)278 public static byte[] uuidsToByteArray(ParcelUuid[] uuids) { 279 int length = uuids.length * BD_UUID_LEN; 280 ByteBuffer converter = ByteBuffer.allocate(length); 281 converter.order(ByteOrder.BIG_ENDIAN); 282 UUID uuid; 283 long msb, lsb; 284 for (int i = 0; i < uuids.length; i++) { 285 uuid = uuids[i].getUuid(); 286 msb = uuid.getMostSignificantBits(); 287 lsb = uuid.getLeastSignificantBits(); 288 converter.putLong(i * BD_UUID_LEN, msb); 289 converter.putLong(i * BD_UUID_LEN + 8, lsb); 290 } 291 return converter.array(); 292 } 293 byteArrayToUuid(byte[] val)294 public static ParcelUuid[] byteArrayToUuid(byte[] val) { 295 int numUuids = val.length / BD_UUID_LEN; 296 ParcelUuid[] puuids = new ParcelUuid[numUuids]; 297 int offset = 0; 298 299 ByteBuffer converter = ByteBuffer.wrap(val); 300 converter.order(ByteOrder.BIG_ENDIAN); 301 302 for (int i = 0; i < numUuids; i++) { 303 puuids[i] = new ParcelUuid( 304 new UUID(converter.getLong(offset), converter.getLong(offset + 8))); 305 offset += BD_UUID_LEN; 306 } 307 return puuids; 308 } 309 debugGetAdapterStateString(int state)310 public static String debugGetAdapterStateString(int state) { 311 switch (state) { 312 case BluetoothAdapter.STATE_OFF: 313 return "STATE_OFF"; 314 case BluetoothAdapter.STATE_ON: 315 return "STATE_ON"; 316 case BluetoothAdapter.STATE_TURNING_ON: 317 return "STATE_TURNING_ON"; 318 case BluetoothAdapter.STATE_TURNING_OFF: 319 return "STATE_TURNING_OFF"; 320 default: 321 return "UNKNOWN"; 322 } 323 } 324 ellipsize(String s)325 public static String ellipsize(String s) { 326 // Only ellipsize release builds 327 if (!Build.TYPE.equals("user")) { 328 return s; 329 } 330 if (s == null) { 331 return null; 332 } 333 if (s.length() < 3) { 334 return s; 335 } 336 return s.charAt(0) + "⋯" + s.charAt(s.length() - 1); 337 } 338 copyStream(InputStream is, OutputStream os, int bufferSize)339 public static void copyStream(InputStream is, OutputStream os, int bufferSize) 340 throws IOException { 341 if (is != null && os != null) { 342 byte[] buffer = new byte[bufferSize]; 343 int bytesRead = 0; 344 while ((bytesRead = is.read(buffer)) >= 0) { 345 os.write(buffer, 0, bytesRead); 346 } 347 } 348 } 349 safeCloseStream(InputStream is)350 public static void safeCloseStream(InputStream is) { 351 if (is != null) { 352 try { 353 is.close(); 354 } catch (Throwable t) { 355 Log.d(TAG, "Error closing stream", t); 356 } 357 } 358 } 359 safeCloseStream(OutputStream os)360 public static void safeCloseStream(OutputStream os) { 361 if (os != null) { 362 try { 363 os.close(); 364 } catch (Throwable t) { 365 Log.d(TAG, "Error closing stream", t); 366 } 367 } 368 } 369 370 static int sSystemUiUid = USER_HANDLE_NULL.getIdentifier(); setSystemUiUid(int uid)371 public static void setSystemUiUid(int uid) { 372 Utils.sSystemUiUid = uid; 373 } 374 375 static int sForegroundUserId = USER_HANDLE_NULL.getIdentifier(); 376 getForegroundUserId()377 public static int getForegroundUserId() { 378 return Utils.sForegroundUserId; 379 } 380 setForegroundUserId(int userId)381 public static void setForegroundUserId(int userId) { 382 Utils.sForegroundUserId = userId; 383 } 384 385 /** 386 * Enforces that a Companion Device Manager (CDM) association exists between the calling 387 * application and the Bluetooth Device. 388 * 389 * @param cdm the CompanionDeviceManager object 390 * @param context the Bluetooth AdapterService context 391 * @param callingPackage the calling package 392 * @param callingUid the calling app uid 393 * @param device the remote BluetoothDevice 394 * @return {@code true} if there is a CDM association 395 * @throws SecurityException if the package name does not match the uid or the association 396 * doesn't exist 397 */ 398 @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") enforceCdmAssociation(CompanionDeviceManager cdm, Context context, String callingPackage, int callingUid, BluetoothDevice device)399 public static boolean enforceCdmAssociation(CompanionDeviceManager cdm, Context context, 400 String callingPackage, int callingUid, BluetoothDevice device) { 401 if (!isPackageNameAccurate(context, callingPackage, callingUid)) { 402 throw new SecurityException("hasCdmAssociation: Package name " + callingPackage 403 + " is inaccurate for calling uid " + callingUid); 404 } 405 406 for (AssociationInfo association : getCdmAssociations(cdm)) { 407 if (association.getPackageName().equals(callingPackage) 408 && !association.isSelfManaged() && device.getAddress() != null 409 && association.getDeviceMacAddress() != null 410 && device.getAddress().equalsIgnoreCase( 411 association.getDeviceMacAddress().toString())) { 412 return true; 413 } 414 } 415 throw new SecurityException("The application with package name " + callingPackage 416 + " does not have a CDM association with the Bluetooth Device"); 417 } 418 419 /** 420 * Obtains the complete list of registered CDM associations. 421 * 422 * @param cdm the CompanionDeviceManager object 423 * @return the list of AssociationInfo objects 424 */ 425 @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") 426 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 427 // TODO(b/193460475): Android Lint handles change from SystemApi to public incorrectly. 428 // CompanionDeviceManager#getAllAssociations() is public in U, 429 // but existed in T as an identical SystemApi. 430 @SuppressLint("NewApi") getCdmAssociations(CompanionDeviceManager cdm)431 public static List<AssociationInfo> getCdmAssociations(CompanionDeviceManager cdm) { 432 return cdm.getAllAssociations(); 433 } 434 435 /** 436 * Verifies whether the calling package name matches the calling app uid 437 * @param context the Bluetooth AdapterService context 438 * @param callingPackage the calling application package name 439 * @param callingUid the calling application uid 440 * @return {@code true} if the package name matches the calling app uid, {@code false} otherwise 441 */ isPackageNameAccurate(Context context, String callingPackage, int callingUid)442 public static boolean isPackageNameAccurate(Context context, String callingPackage, 443 int callingUid) { 444 UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 445 446 // Verifies the integrity of the calling package name 447 try { 448 int packageUid = context.createContextAsUser(callingUser, 0) 449 .getPackageManager().getPackageUid(callingPackage, 0); 450 if (packageUid != callingUid) { 451 Log.e(TAG, "isPackageNameAccurate: App with package name " + callingPackage 452 + " is UID " + packageUid + " but caller is " + callingUid); 453 return false; 454 } 455 } catch (PackageManager.NameNotFoundException e) { 456 Log.e(TAG, "isPackageNameAccurate: App with package name " + callingPackage 457 + " does not exist"); 458 return false; 459 } 460 return true; 461 } 462 463 /** 464 * Checks whether the caller has the BLUETOOTH_PRIVILEGED permission 465 * 466 * @param context the Bluetooth AdapterService context 467 * @return {@code true} if the caller has the BLUETOOTH_PRIVILEGED permission, {@code false} 468 * otherwise 469 */ 470 // Suppressed since we're not actually enforcing here 471 @SuppressLint("AndroidFrameworkRequiresPermission") hasBluetoothPrivilegedPermission(Context context)472 public static boolean hasBluetoothPrivilegedPermission(Context context) { 473 return context.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) 474 == PackageManager.PERMISSION_GRANTED; 475 } 476 477 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) enforceBluetoothPrivilegedPermission(Context context)478 public static void enforceBluetoothPrivilegedPermission(Context context) { 479 context.enforceCallingOrSelfPermission( 480 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 481 "Need BLUETOOTH PRIVILEGED permission"); 482 } 483 484 @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) enforceLocalMacAddressPermission(Context context)485 public static void enforceLocalMacAddressPermission(Context context) { 486 context.enforceCallingOrSelfPermission( 487 android.Manifest.permission.LOCAL_MAC_ADDRESS, 488 "Need LOCAL_MAC_ADDRESS permission"); 489 } 490 491 @RequiresPermission(android.Manifest.permission.DUMP) enforceDumpPermission(Context context)492 public static void enforceDumpPermission(Context context) { 493 context.enforceCallingOrSelfPermission( 494 android.Manifest.permission.DUMP, 495 "Need DUMP permission"); 496 } 497 getCallingAttributionSource(Context context)498 public static AttributionSource getCallingAttributionSource(Context context) { 499 int callingUid = Binder.getCallingUid(); 500 if (callingUid == android.os.Process.ROOT_UID) { 501 callingUid = android.os.Process.SYSTEM_UID; 502 } 503 return new AttributionSource.Builder(callingUid) 504 .setPackageName(context.getPackageManager().getPackagesForUid(callingUid)[0]) 505 .build(); 506 } 507 508 @SuppressLint("AndroidFrameworkRequiresPermission") checkPermissionForPreflight(Context context, String permission)509 private static boolean checkPermissionForPreflight(Context context, String permission) { 510 PermissionManager pm = context.getSystemService(PermissionManager.class); 511 if (pm == null) { 512 return false; 513 } 514 final int result = pm.checkPermissionForPreflight(permission, 515 context.getAttributionSource()); 516 if (result == PERMISSION_GRANTED) { 517 return true; 518 } 519 520 final String msg = "Need " + permission + " permission"; 521 if (result == PERMISSION_HARD_DENIED) { 522 throw new SecurityException(msg); 523 } else { 524 Log.w(TAG, msg); 525 return false; 526 } 527 } 528 529 @SuppressLint("AndroidFrameworkRequiresPermission") checkPermissionForDataDelivery(Context context, String permission, AttributionSource attributionSource, String message)530 private static boolean checkPermissionForDataDelivery(Context context, String permission, 531 AttributionSource attributionSource, String message) { 532 if (isInstrumentationTestMode()) { 533 return true; 534 } 535 // STOPSHIP(b/188391719): enable this security enforcement 536 // attributionSource.enforceCallingUid(); 537 AttributionSource currentAttribution = new AttributionSource 538 .Builder(context.getAttributionSource()) 539 .setNext(attributionSource) 540 .build(); 541 PermissionManager pm = context.getSystemService(PermissionManager.class); 542 if (pm == null) { 543 return false; 544 } 545 final int result = pm.checkPermissionForDataDeliveryFromDataSource(permission, 546 currentAttribution, message); 547 if (result == PERMISSION_GRANTED) { 548 return true; 549 } 550 551 final String msg = "Need " + permission + " permission for " + attributionSource + ": " 552 + message; 553 if (result == PERMISSION_HARD_DENIED) { 554 throw new SecurityException(msg); 555 } else { 556 Log.w(TAG, msg); 557 return false; 558 } 559 } 560 561 /** 562 * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns 563 * false if the result is a soft denial. Throws SecurityException if the result is a hard 564 * denial. 565 * 566 * <p>Should be used in situations where the app op should not be noted. 567 */ 568 @SuppressLint("AndroidFrameworkRequiresPermission") 569 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) checkConnectPermissionForPreflight(Context context)570 public static boolean checkConnectPermissionForPreflight(Context context) { 571 return checkPermissionForPreflight(context, BLUETOOTH_CONNECT); 572 } 573 574 /** 575 * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns 576 * false if the result is a soft denial. Throws SecurityException if the result is a hard 577 * denial. 578 * 579 * <p>Should be used in situations where data will be delivered and hence the app op should 580 * be noted. 581 */ 582 @SuppressLint("AndroidFrameworkRequiresPermission") 583 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) checkConnectPermissionForDataDelivery( Context context, AttributionSource attributionSource, String message)584 public static boolean checkConnectPermissionForDataDelivery( 585 Context context, AttributionSource attributionSource, String message) { 586 return checkPermissionForDataDelivery(context, BLUETOOTH_CONNECT, 587 attributionSource, message); 588 } 589 590 /** 591 * Returns true if the BLUETOOTH_SCAN permission is granted for the calling app. Returns false 592 * if the result is a soft denial. Throws SecurityException if the result is a hard denial. 593 * 594 * <p>Should be used in situations where the app op should not be noted. 595 */ 596 @SuppressLint("AndroidFrameworkRequiresPermission") 597 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) checkScanPermissionForPreflight(Context context)598 public static boolean checkScanPermissionForPreflight(Context context) { 599 return checkPermissionForPreflight(context, BLUETOOTH_SCAN); 600 } 601 602 /** 603 * Returns true if the BLUETOOTH_SCAN permission is granted for the calling app. Returns false 604 * if the result is a soft denial. Throws SecurityException if the result is a hard denial. 605 * 606 * <p>Should be used in situations where data will be delivered and hence the app op should 607 * be noted. 608 */ 609 @SuppressLint("AndroidFrameworkRequiresPermission") 610 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) checkScanPermissionForDataDelivery( Context context, AttributionSource attributionSource, String message)611 public static boolean checkScanPermissionForDataDelivery( 612 Context context, AttributionSource attributionSource, String message) { 613 return checkPermissionForDataDelivery(context, BLUETOOTH_SCAN, 614 attributionSource, message); 615 } 616 617 /** 618 * Returns true if the BLUETOOTH_ADVERTISE permission is granted for the 619 * calling app. Returns false if the result is a soft denial. Throws 620 * SecurityException if the result is a hard denial. 621 * <p> 622 * Should be used in situations where the app op should not be noted. 623 */ 624 @SuppressLint("AndroidFrameworkRequiresPermission") 625 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) checkAdvertisePermissionForPreflight(Context context)626 public static boolean checkAdvertisePermissionForPreflight(Context context) { 627 return checkPermissionForPreflight(context, BLUETOOTH_ADVERTISE); 628 } 629 630 /** 631 * Returns true if the BLUETOOTH_ADVERTISE permission is granted for the 632 * calling app. Returns false if the result is a soft denial. Throws 633 * SecurityException if the result is a hard denial. 634 * <p> 635 * Should be used in situations where data will be delivered and hence the 636 * app op should be noted. 637 */ 638 @SuppressLint("AndroidFrameworkRequiresPermission") 639 @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) checkAdvertisePermissionForDataDelivery( Context context, AttributionSource attributionSource, String message)640 public static boolean checkAdvertisePermissionForDataDelivery( 641 Context context, AttributionSource attributionSource, String message) { 642 return checkPermissionForDataDelivery(context, BLUETOOTH_ADVERTISE, 643 attributionSource, message); 644 } 645 646 /** 647 * Returns true if the specified package has disavowed the use of bluetooth scans for location, 648 * that is, if they have specified the {@code neverForLocation} flag on the BLUETOOTH_SCAN 649 * permission. 650 */ 651 // Suppressed since we're not actually enforcing here 652 @SuppressLint("AndroidFrameworkRequiresPermission") hasDisavowedLocationForScan( Context context, AttributionSource attributionSource, boolean inTestMode)653 public static boolean hasDisavowedLocationForScan( 654 Context context, AttributionSource attributionSource, boolean inTestMode) { 655 656 // Check every step along the attribution chain for a renouncement. 657 // If location has been renounced anywhere in the chain we treat it as a disavowal. 658 AttributionSource currentAttrib = attributionSource; 659 while (true) { 660 if (currentAttrib.getRenouncedPermissions().contains(ACCESS_FINE_LOCATION) 661 && (inTestMode || context.checkPermission(RENOUNCE_PERMISSIONS, -1, 662 currentAttrib.getUid()) 663 == PackageManager.PERMISSION_GRANTED)) { 664 return true; 665 } 666 AttributionSource nextAttrib = currentAttrib.getNext(); 667 if (nextAttrib == null) { 668 break; 669 } 670 currentAttrib = nextAttrib; 671 } 672 673 // Check the last attribution in the chain for a neverForLocation disavowal. 674 String packageName = currentAttrib.getPackageName(); 675 PackageManager pm = context.getPackageManager(); 676 try { 677 // TODO(b/183478032): Cache PackageInfo for use here. 678 PackageInfo pkgInfo = 679 pm.getPackageInfo(packageName, GET_PERMISSIONS | MATCH_UNINSTALLED_PACKAGES); 680 for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) { 681 if (pkgInfo.requestedPermissions[i].equals(BLUETOOTH_SCAN)) { 682 return (pkgInfo.requestedPermissionsFlags[i] 683 & PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION) != 0; 684 } 685 } 686 } catch (PackageManager.NameNotFoundException e) { 687 Log.w(TAG, "Could not find package for disavowal check: " + packageName); 688 } 689 return false; 690 } 691 checkCallerIsSystem()692 private static boolean checkCallerIsSystem() { 693 int callingUid = Binder.getCallingUid(); 694 return UserHandle.getAppId(Process.SYSTEM_UID) == UserHandle.getAppId(callingUid); 695 } 696 checkCallerIsSystemOrActiveUser()697 private static boolean checkCallerIsSystemOrActiveUser() { 698 int callingUid = Binder.getCallingUid(); 699 UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 700 701 return (sForegroundUserId == callingUser.getIdentifier()) 702 || (UserHandle.getAppId(sSystemUiUid) == UserHandle.getAppId(callingUid)) 703 || (UserHandle.getAppId(Process.SYSTEM_UID) == UserHandle.getAppId(callingUid)); 704 } 705 checkCallerIsSystemOrActiveUser(String tag)706 public static boolean checkCallerIsSystemOrActiveUser(String tag) { 707 final boolean res = checkCallerIsSystemOrActiveUser(); 708 if (!res) { 709 Log.w(TAG, tag + " - Not allowed for non-active user and non-system user"); 710 } 711 return res; 712 } 713 callerIsSystemOrActiveUser(String tag, String method)714 public static boolean callerIsSystemOrActiveUser(String tag, String method) { 715 return checkCallerIsSystemOrActiveUser(tag + "." + method + "()"); 716 } 717 718 /** 719 * Checks if the caller to the method is system server. 720 * 721 * @param tag the log tag to use in case the caller is not system server 722 * @param method the API method name 723 * @return {@code true} if the caller is system server, {@code false} otherwise 724 */ callerIsSystem(String tag, String method)725 public static boolean callerIsSystem(String tag, String method) { 726 if (isInstrumentationTestMode()) { 727 return true; 728 } 729 final boolean res = checkCallerIsSystem(); 730 if (!res) { 731 Log.w(TAG, tag + "." + method + "()" + " - Not allowed outside system server"); 732 } 733 return res; 734 } 735 checkCallerIsSystemOrActiveOrManagedUser(Context context)736 private static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context) { 737 if (context == null) { 738 return checkCallerIsSystemOrActiveUser(); 739 } 740 int callingUid = Binder.getCallingUid(); 741 UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 742 743 // Use the Bluetooth process identity when making call to get parent user 744 final long ident = Binder.clearCallingIdentity(); 745 try { 746 UserManager um = context.getSystemService(UserManager.class); 747 UserHandle uh = um.getProfileParent(callingUser); 748 int parentUser = (uh != null) ? uh.getIdentifier() : USER_HANDLE_NULL.getIdentifier(); 749 750 // Always allow SystemUI/System access. 751 return (sForegroundUserId == callingUser.getIdentifier()) 752 || (sForegroundUserId == parentUser) 753 || (UserHandle.getAppId(sSystemUiUid) == UserHandle.getAppId(callingUid)) 754 || (UserHandle.getAppId(Process.SYSTEM_UID) == UserHandle.getAppId(callingUid)); 755 } catch (Exception ex) { 756 Log.e(TAG, "checkCallerAllowManagedProfiles: Exception ex=" + ex); 757 return false; 758 } finally { 759 Binder.restoreCallingIdentity(ident); 760 } 761 } 762 checkCallerIsSystemOrActiveOrManagedUser(Context context, String tag)763 public static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context, String tag) { 764 if (isInstrumentationTestMode()) { 765 return true; 766 } 767 final boolean res = checkCallerIsSystemOrActiveOrManagedUser(context); 768 if (!res) { 769 Log.w(TAG, tag + " - Not allowed for" 770 + " non-active user and non-system and non-managed user"); 771 } 772 return res; 773 } 774 callerIsSystemOrActiveOrManagedUser(Context context, String tag, String method)775 public static boolean callerIsSystemOrActiveOrManagedUser(Context context, String tag, 776 String method) { 777 return checkCallerIsSystemOrActiveOrManagedUser(context, tag + "." + method + "()"); 778 } 779 checkServiceAvailable(ProfileService service, String tag)780 public static boolean checkServiceAvailable(ProfileService service, String tag) { 781 if (service == null) { 782 Log.w(TAG, tag + " - Not present"); 783 return false; 784 } 785 if (!service.isAvailable()) { 786 Log.w(TAG, tag + " - Not available"); 787 return false; 788 } 789 return true; 790 } 791 792 /** 793 * Checks whether location is off and must be on for us to perform some operation 794 */ blockedByLocationOff(Context context, UserHandle userHandle)795 public static boolean blockedByLocationOff(Context context, UserHandle userHandle) { 796 return !context.getSystemService(LocationManager.class) 797 .isLocationEnabledForUser(userHandle); 798 } 799 800 /** 801 * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION and 802 * OP_COARSE_LOCATION is allowed 803 */ 804 // Suppressed since we're not actually enforcing here 805 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasCoarseLocation( Context context, AttributionSource attributionSource, UserHandle userHandle)806 public static boolean checkCallerHasCoarseLocation( 807 Context context, AttributionSource attributionSource, UserHandle userHandle) { 808 if (blockedByLocationOff(context, userHandle)) { 809 Log.e(TAG, "Permission denial: Location is off."); 810 return false; 811 } 812 AttributionSource currentAttribution = new AttributionSource 813 .Builder(context.getAttributionSource()) 814 .setNext(attributionSource) 815 .build(); 816 // STOPSHIP(b/188391719): enable this security enforcement 817 // attributionSource.enforceCallingUid(); 818 PermissionManager pm = context.getSystemService(PermissionManager.class); 819 if (pm == null) { 820 return false; 821 } 822 if (pm.checkPermissionForDataDeliveryFromDataSource(ACCESS_COARSE_LOCATION, 823 currentAttribution, "Bluetooth location check") == PERMISSION_GRANTED) { 824 return true; 825 } 826 827 Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION " 828 + "permission to get scan results"); 829 return false; 830 } 831 832 /** 833 * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION and 834 * OP_COARSE_LOCATION is allowed or android.Manifest.permission.ACCESS_FINE_LOCATION and 835 * OP_FINE_LOCATION is allowed 836 */ 837 // Suppressed since we're not actually enforcing here 838 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasCoarseOrFineLocation( Context context, AttributionSource attributionSource, UserHandle userHandle)839 public static boolean checkCallerHasCoarseOrFineLocation( 840 Context context, AttributionSource attributionSource, UserHandle userHandle) { 841 if (blockedByLocationOff(context, userHandle)) { 842 Log.e(TAG, "Permission denial: Location is off."); 843 return false; 844 } 845 846 final AttributionSource currentAttribution = new AttributionSource 847 .Builder(context.getAttributionSource()) 848 .setNext(attributionSource) 849 .build(); 850 // STOPSHIP(b/188391719): enable this security enforcement 851 // attributionSource.enforceCallingUid(); 852 PermissionManager pm = context.getSystemService(PermissionManager.class); 853 if (pm == null) { 854 return false; 855 } 856 if (pm.checkPermissionForDataDeliveryFromDataSource(ACCESS_FINE_LOCATION, 857 currentAttribution, "Bluetooth location check") == PERMISSION_GRANTED) { 858 return true; 859 } 860 861 if (pm.checkPermissionForDataDeliveryFromDataSource(ACCESS_COARSE_LOCATION, 862 currentAttribution, "Bluetooth location check") == PERMISSION_GRANTED) { 863 return true; 864 } 865 866 Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION" 867 + "permission to get scan results"); 868 return false; 869 } 870 871 /** 872 * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION and 873 * OP_FINE_LOCATION is allowed 874 */ 875 // Suppressed since we're not actually enforcing here 876 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasFineLocation( Context context, AttributionSource attributionSource, UserHandle userHandle)877 public static boolean checkCallerHasFineLocation( 878 Context context, AttributionSource attributionSource, UserHandle userHandle) { 879 if (blockedByLocationOff(context, userHandle)) { 880 Log.e(TAG, "Permission denial: Location is off."); 881 return false; 882 } 883 884 AttributionSource currentAttribution = new AttributionSource 885 .Builder(context.getAttributionSource()) 886 .setNext(attributionSource) 887 .build(); 888 // STOPSHIP(b/188391719): enable this security enforcement 889 // attributionSource.enforceCallingUid(); 890 PermissionManager pm = context.getSystemService(PermissionManager.class); 891 if (pm == null) { 892 return false; 893 } 894 if (pm.checkPermissionForDataDeliveryFromDataSource(ACCESS_FINE_LOCATION, 895 currentAttribution, "Bluetooth location check") == PERMISSION_GRANTED) { 896 return true; 897 } 898 899 Log.e(TAG, "Permission denial: Need ACCESS_FINE_LOCATION " 900 + "permission to get scan results"); 901 return false; 902 } 903 904 /** 905 * Returns true if the caller holds NETWORK_SETTINGS 906 */ 907 // Suppressed since we're not actually enforcing here 908 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasNetworkSettingsPermission(Context context)909 public static boolean checkCallerHasNetworkSettingsPermission(Context context) { 910 return context.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS) 911 == PackageManager.PERMISSION_GRANTED; 912 } 913 914 /** 915 * Returns true if the caller holds NETWORK_SETUP_WIZARD 916 */ 917 // Suppressed since we're not actually enforcing here 918 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasNetworkSetupWizardPermission(Context context)919 public static boolean checkCallerHasNetworkSetupWizardPermission(Context context) { 920 return context.checkCallingOrSelfPermission( 921 android.Manifest.permission.NETWORK_SETUP_WIZARD) 922 == PackageManager.PERMISSION_GRANTED; 923 } 924 925 /** 926 * Returns true if the caller holds RADIO_SCAN_WITHOUT_LOCATION 927 */ 928 // Suppressed since we're not actually enforcing here 929 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasScanWithoutLocationPermission(Context context)930 public static boolean checkCallerHasScanWithoutLocationPermission(Context context) { 931 return context.checkCallingOrSelfPermission( 932 android.Manifest.permission.RADIO_SCAN_WITHOUT_LOCATION) 933 == PackageManager.PERMISSION_GRANTED; 934 } 935 936 // Suppressed since we're not actually enforcing here 937 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasPrivilegedPermission(Context context)938 public static boolean checkCallerHasPrivilegedPermission(Context context) { 939 return context.checkCallingOrSelfPermission( 940 android.Manifest.permission.BLUETOOTH_PRIVILEGED) 941 == PackageManager.PERMISSION_GRANTED; 942 } 943 944 // Suppressed since we're not actually enforcing here 945 @SuppressLint("AndroidFrameworkRequiresPermission") checkCallerHasWriteSmsPermission(Context context)946 public static boolean checkCallerHasWriteSmsPermission(Context context) { 947 return context.checkCallingOrSelfPermission( 948 android.Manifest.permission.WRITE_SMS) == PackageManager.PERMISSION_GRANTED; 949 } 950 951 /** 952 * Checks that the target sdk of the app corresponding to the provided package name is greater 953 * than or equal to the passed in target sdk. 954 * <p> 955 * For example, if the calling app has target SDK {@link Build.VERSION_CODES#S} and we pass in 956 * the targetSdk {@link Build.VERSION_CODES#R}, the API will return true because S >= R. 957 * 958 * @param context Bluetooth service context 959 * @param pkgName caller's package name 960 * @param expectedMinimumTargetSdk one of the values from {@link Build.VERSION_CODES} 961 * @return {@code true} if the caller's target sdk is greater than or equal to 962 * expectedMinimumTargetSdk, {@code false} otherwise 963 */ checkCallerTargetSdk(Context context, String pkgName, int expectedMinimumTargetSdk)964 public static boolean checkCallerTargetSdk(Context context, String pkgName, 965 int expectedMinimumTargetSdk) { 966 try { 967 return context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion 968 >= expectedMinimumTargetSdk; 969 } catch (PackageManager.NameNotFoundException e) { 970 // In case of exception, assume true 971 } 972 return true; 973 } 974 975 /** 976 * Converts {@code milliseconds} to unit. Each unit is 0.625 millisecond. 977 */ millsToUnit(int milliseconds)978 public static int millsToUnit(int milliseconds) { 979 return (int) (TimeUnit.MILLISECONDS.toMicros(milliseconds) / MICROS_PER_UNIT); 980 } 981 982 /** 983 * Check if we are running in BluetoothInstrumentationTest context by trying to load 984 * com.android.bluetooth.FileSystemWriteTest. If we are not in Instrumentation test mode, this 985 * class should not be found. Thus, the assumption is that FileSystemWriteTest must exist. 986 * If FileSystemWriteTest is removed in the future, another test class in 987 * BluetoothInstrumentationTest should be used instead 988 * 989 * @return true if in BluetoothInstrumentationTest, false otherwise 990 */ isInstrumentationTestMode()991 public static boolean isInstrumentationTestMode() { 992 try { 993 return Class.forName("com.android.bluetooth.FileSystemWriteTest") != null; 994 } catch (ClassNotFoundException exception) { 995 return false; 996 } 997 } 998 999 /** 1000 * Throws {@link IllegalStateException} if we are not in BluetoothInstrumentationTest. Useful 1001 * for ensuring certain methods only get called in BluetoothInstrumentationTest 1002 */ enforceInstrumentationTestMode()1003 public static void enforceInstrumentationTestMode() { 1004 if (!isInstrumentationTestMode()) { 1005 throw new IllegalStateException("Not in BluetoothInstrumentationTest"); 1006 } 1007 } 1008 1009 /** 1010 * Check if we are running in PTS test mode. To enable/disable PTS test mode, invoke 1011 * {@code adb shell setprop persist.bluetooth.pts true/false} 1012 * 1013 * @return true if in PTS Test mode, false otherwise 1014 */ isPtsTestMode()1015 public static boolean isPtsTestMode() { 1016 return SystemProperties.getBoolean(PTS_TEST_MODE_PROPERTY, false); 1017 } 1018 1019 /** 1020 * Get uid/pid string in a binder call 1021 * 1022 * @return "uid/pid=xxxx/yyyy" 1023 */ getUidPidString()1024 public static String getUidPidString() { 1025 return "uid/pid=" + Binder.getCallingUid() + "/" + Binder.getCallingPid(); 1026 } 1027 1028 /** 1029 * Get system local time 1030 * 1031 * @return "MM-dd HH:mm:ss.SSS" 1032 */ getLocalTimeString()1033 public static String getLocalTimeString() { 1034 return DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS") 1035 .withZone(ZoneId.systemDefault()).format(Instant.now()); 1036 } 1037 skipCurrentTag(XmlPullParser parser)1038 public static void skipCurrentTag(XmlPullParser parser) 1039 throws XmlPullParserException, IOException { 1040 int outerDepth = parser.getDepth(); 1041 int type; 1042 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 1043 && (type != XmlPullParser.END_TAG 1044 || parser.getDepth() > outerDepth)) { 1045 } 1046 } 1047 1048 /** 1049 * Converts pause and tonewait pause characters 1050 * to Android representation. 1051 * RFC 3601 says pause is 'p' and tonewait is 'w'. 1052 */ convertPreDial(String phoneNumber)1053 public static String convertPreDial(String phoneNumber) { 1054 if (phoneNumber == null) { 1055 return null; 1056 } 1057 int len = phoneNumber.length(); 1058 StringBuilder ret = new StringBuilder(len); 1059 1060 for (int i = 0; i < len; i++) { 1061 char c = phoneNumber.charAt(i); 1062 1063 if (isPause(c)) { 1064 c = PAUSE; 1065 } else if (isToneWait(c)) { 1066 c = WAIT; 1067 } 1068 ret.append(c); 1069 } 1070 return ret.toString(); 1071 } 1072 1073 /** 1074 * Move a message to the given folder. 1075 * 1076 * @param context the context to use 1077 * @param uri the message to move 1078 * @param messageSent if the message is SENT or FAILED 1079 * @return true if the operation succeeded 1080 */ moveMessageToFolder(Context context, Uri uri, boolean messageSent)1081 public static boolean moveMessageToFolder(Context context, Uri uri, boolean messageSent) { 1082 if (uri == null) { 1083 return false; 1084 } 1085 1086 ContentValues values = new ContentValues(3); 1087 if (messageSent) { 1088 values.put(Telephony.Sms.READ, 1); 1089 values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_SENT); 1090 } else { 1091 values.put(Telephony.Sms.READ, 0); 1092 values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_FAILED); 1093 } 1094 values.put(Telephony.Sms.ERROR_CODE, 0); 1095 1096 return 1 == BluetoothMethodProxy.getInstance().contentResolverUpdate( 1097 context.getContentResolver(), uri, values, null, null); 1098 } 1099 1100 /** 1101 * Returns bundled broadcast options. 1102 */ 1103 // TODO(b/193460475): Remove when tooling supports SystemApi to public API. 1104 @SuppressLint("NewApi") getTempAllowlistBroadcastOptions()1105 public static @NonNull Bundle getTempAllowlistBroadcastOptions() { 1106 return getTempBroadcastOptions().toBundle(); 1107 } 1108 1109 /** 1110 * Returns broadcast options. 1111 */ 1112 // TODO(b/193460475): Remove when tooling supports SystemApi to public API. 1113 @SuppressLint("NewApi") getTempBroadcastOptions()1114 public static @NonNull BroadcastOptions getTempBroadcastOptions() { 1115 final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); 1116 // Use the Bluetooth process identity to pass permission check when reading DeviceConfig 1117 final long ident = Binder.clearCallingIdentity(); 1118 try { 1119 final long durationMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_BLUETOOTH, 1120 KEY_TEMP_ALLOW_LIST_DURATION_MS, DEFAULT_TEMP_ALLOW_LIST_DURATION_MS); 1121 bOptions.setTemporaryAppAllowlist(durationMs, 1122 TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, 1123 PowerExemptionManager.REASON_BLUETOOTH_BROADCAST, ""); 1124 } finally { 1125 Binder.restoreCallingIdentity(ident); 1126 } 1127 return bOptions; 1128 } 1129 1130 /** 1131 * Sends the {@code intent} as a broadcast in the provided {@code context} to receivers that 1132 * have been granted the specified {@code receiverPermission} with the {@link BroadcastOptions} 1133 * {@code options}. 1134 * 1135 * @see Context#sendBroadcast(Intent, String, Bundle) 1136 */ 1137 // TODO(b/193460475): Remove when tooling supports SystemApi to public API. 1138 @SuppressLint("NewApi") sendBroadcast(@onNull Context context, @NonNull Intent intent, @Nullable String receiverPermission, @Nullable Bundle options)1139 public static void sendBroadcast(@NonNull Context context, @NonNull Intent intent, 1140 @Nullable String receiverPermission, @Nullable Bundle options) { 1141 context.sendBroadcast(intent, receiverPermission, options); 1142 } 1143 1144 /** 1145 * @see Context#sendOrderedBroadcast(Intent, String, Bundle, BroadcastReceiver, Handler, 1146 * int, String, Bundle) 1147 */ 1148 // TODO(b/193460475): Remove when tooling supports SystemApi to public API. 1149 @SuppressLint("NewApi") sendOrderedBroadcast(@onNull Context context, @NonNull Intent intent, @Nullable String receiverPermission, @Nullable Bundle options, @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras)1150 public static void sendOrderedBroadcast(@NonNull Context context, @NonNull Intent intent, 1151 @Nullable String receiverPermission, @Nullable Bundle options, 1152 @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler, 1153 int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { 1154 context.sendOrderedBroadcast(intent, receiverPermission, options, resultReceiver, scheduler, 1155 initialCode, initialData, initialExtras); 1156 } 1157 1158 /** 1159 * Checks that value is present as at least one of the elements of the array. 1160 * @param array the array to check in 1161 * @param value the value to check for 1162 * @return true if the value is present in the array 1163 */ arrayContains(@ullable T[] array, T value)1164 public static <T> boolean arrayContains(@Nullable T[] array, T value) { 1165 if (array == null) return false; 1166 for (T element : array) { 1167 if (Objects.equals(element, value)) return true; 1168 } 1169 return false; 1170 } 1171 1172 /** 1173 * CCC descriptor short integer value to string. 1174 * @param cccValue the short value of CCC descriptor 1175 * @return String value representing CCC state 1176 */ cccIntToStr(Short cccValue)1177 public static String cccIntToStr(Short cccValue) { 1178 String string = ""; 1179 1180 if (cccValue == 0) { 1181 return string += "NO SUBSCRIPTION"; 1182 } 1183 1184 if (BigInteger.valueOf(cccValue).testBit(0)) { 1185 string += "NOTIFICATION"; 1186 } 1187 if (BigInteger.valueOf(cccValue).testBit(1)) { 1188 string += string.isEmpty() ? "INDICATION" : "|INDICATION"; 1189 } 1190 1191 return string; 1192 } 1193 1194 /** 1195 * Check if BLE is supported by this platform 1196 * @param context current device context 1197 * @return true if BLE is supported, false otherwise 1198 */ isBleSupported(Context context)1199 public static boolean isBleSupported(Context context) { 1200 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); 1201 } 1202 1203 /** 1204 * Check if this is an automotive device 1205 * @param context current device context 1206 * @return true if this Android device is an automotive device, false otherwise 1207 */ isAutomotive(Context context)1208 public static boolean isAutomotive(Context context) { 1209 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 1210 } 1211 1212 /** 1213 * Check if this is a watch device 1214 * @param context current device context 1215 * @return true if this Android device is a watch device, false otherwise 1216 */ isWatch(Context context)1217 public static boolean isWatch(Context context) { 1218 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); 1219 } 1220 1221 /** 1222 * Check if this is a TV device 1223 * @param context current device context 1224 * @return true if this Android device is a TV device, false otherwise 1225 */ isTv(Context context)1226 public static boolean isTv(Context context) { 1227 PackageManager pm = context.getPackageManager(); 1228 return pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION) 1229 || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); 1230 } 1231 } 1232