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