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