• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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