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