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