• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 android.bluetooth;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresNoPermission;
22 import android.os.Binder;
23 import android.os.Parcel;
24 import android.os.RemoteException;
25 import android.os.UserHandle;
26 import android.util.Log;
27 
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.concurrent.Executor;
33 import java.util.function.Consumer;
34 import java.util.function.Function;
35 import java.util.function.Supplier;
36 
37 /** @hide */
38 public final class BluetoothUtils {
39     private static final String TAG = BluetoothUtils.class.getSimpleName();
40 
41     /** This utility class cannot be instantiated */
BluetoothUtils()42     private BluetoothUtils() {}
43 
44     /** Match with UserHandle.NULL but accessible inside bluetooth package */
45     public static final UserHandle USER_HANDLE_NULL = UserHandle.of(-10000);
46 
47     /** Class for Length-Value-Entry array parsing */
48     public static class TypeValueEntry {
49         private final int mType;
50         private final byte[] mValue;
51 
TypeValueEntry(int type, byte[] value)52         TypeValueEntry(int type, byte[] value) {
53             mType = type;
54             mValue = value;
55         }
56 
57         @RequiresNoPermission
getType()58         public int getType() {
59             return mType;
60         }
61 
62         @RequiresNoPermission
getValue()63         public byte[] getValue() {
64             return mValue;
65         }
66     }
67 
68     /** Helper method to extract bytes from byte array. */
extractBytes(byte[] rawBytes, int start, int length)69     public static byte[] extractBytes(byte[] rawBytes, int start, int length) {
70         int remainingLength = rawBytes.length - start;
71         if (remainingLength < length) {
72             Log.w(
73                     TAG,
74                     "extractBytes() remaining length "
75                             + remainingLength
76                             + " is less than copying length "
77                             + length
78                             + ", array length is "
79                             + rawBytes.length
80                             + " start is "
81                             + start);
82             return null;
83         }
84         byte[] bytes = new byte[length];
85         System.arraycopy(rawBytes, start, bytes, 0, length);
86         return bytes;
87     }
88 
89     /**
90      * Parse Length Value Entry from raw bytes
91      *
92      * <p>The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
93      *
94      * @param rawBytes raw bytes of Length-Value-Entry array
95      * @hide
96      */
97     @SuppressWarnings("MixedMutabilityReturnType") // TODO(b/314811467)
parseLengthTypeValueBytes(byte[] rawBytes)98     public static List<TypeValueEntry> parseLengthTypeValueBytes(byte[] rawBytes) {
99         if (rawBytes == null) {
100             return Collections.emptyList();
101         }
102         if (rawBytes.length == 0) {
103             return Collections.emptyList();
104         }
105 
106         int currentPos = 0;
107         List<TypeValueEntry> result = new ArrayList<>();
108         while (currentPos < rawBytes.length) {
109             // length is unsigned int.
110             int length = rawBytes[currentPos] & 0xFF;
111             if (length == 0) {
112                 break;
113             }
114             currentPos++;
115             if (currentPos >= rawBytes.length) {
116                 Log.w(
117                         TAG,
118                         "parseLtv() no type and value after length, rawBytes length = "
119                                 + rawBytes.length
120                                 + ", currentPost = "
121                                 + currentPos);
122                 break;
123             }
124             // Note the length includes the length of the field type itself.
125             int dataLength = length - 1;
126             // fieldType is unsigned int.
127             int type = rawBytes[currentPos] & 0xFF;
128             currentPos++;
129             if (currentPos >= rawBytes.length) {
130                 Log.w(
131                         TAG,
132                         "parseLtv() no value after length, rawBytes length = "
133                                 + rawBytes.length
134                                 + ", currentPost = "
135                                 + currentPos);
136                 break;
137             }
138             byte[] value = extractBytes(rawBytes, currentPos, dataLength);
139             if (value == null) {
140                 Log.w(TAG, "failed to extract bytes, currentPost = " + currentPos);
141                 break;
142             }
143             result.add(new TypeValueEntry(type, value));
144             currentPos += dataLength;
145         }
146         return result;
147     }
148 
149     /**
150      * Serialize type value entries to bytes
151      *
152      * @param typeValueEntries type value entries
153      * @return serialized type value entries on success, null on failure
154      */
serializeTypeValue(List<TypeValueEntry> typeValueEntries)155     public static byte[] serializeTypeValue(List<TypeValueEntry> typeValueEntries) {
156         // Calculate length
157         int length = 0;
158         for (TypeValueEntry entry : typeValueEntries) {
159             // 1 for length and 1 for type
160             length += 2;
161             if ((entry.getType() - (entry.getType() & 0xFF)) != 0) {
162                 Log.w(
163                         TAG,
164                         "serializeTypeValue() type "
165                                 + entry.getType()
166                                 + " is out of range of 0-0xFF");
167                 return null;
168             }
169             if (entry.getValue() == null) {
170                 Log.w(TAG, "serializeTypeValue() value is null");
171                 return null;
172             }
173             int lengthValue = entry.getValue().length + 1;
174             if ((lengthValue - (lengthValue & 0xFF)) != 0) {
175                 Log.w(
176                         TAG,
177                         "serializeTypeValue() entry length "
178                                 + entry.getValue().length
179                                 + " is not in range of 0 to 254");
180                 return null;
181             }
182             length += entry.getValue().length;
183         }
184         byte[] result = new byte[length];
185         int currentPos = 0;
186         for (TypeValueEntry entry : typeValueEntries) {
187             result[currentPos] = (byte) ((entry.getValue().length + 1) & 0xFF);
188             currentPos++;
189             result[currentPos] = (byte) (entry.getType() & 0xFF);
190             currentPos++;
191             System.arraycopy(entry.getValue(), 0, result, currentPos, entry.getValue().length);
192             currentPos += entry.getValue().length;
193         }
194         return result;
195     }
196 
197     /**
198      * Convert an address to an obfuscate one for logging purpose
199      *
200      * @param address Mac address to be log
201      * @return Loggable mac address
202      */
toAnonymizedAddress(String address)203     public static String toAnonymizedAddress(String address) {
204         if (address == null || address.length() != 17) {
205             return null;
206         }
207         return "XX:XX:XX:XX" + address.substring(11);
208     }
209 
210     /**
211      * Simple alternative to {@link String#format} which purposefully supports only a small handful
212      * of substitutions to improve execution speed. Benchmarking reveals this optimized alternative
213      * performs 6.5x faster for a typical format string.
214      *
215      * <p>Below is a summary of the limited grammar supported by this method; if you need advanced
216      * features, please continue using {@link String#format}.
217      *
218      * <ul>
219      *   <li>{@code %b} for {@code boolean}
220      *   <li>{@code %c} for {@code char}
221      *   <li>{@code %d} for {@code int} or {@code long}
222      *   <li>{@code %f} for {@code float} or {@code double}
223      *   <li>{@code %s} for {@code String}
224      *   <li>{@code %x} for hex representation of {@code int} or {@code long} or {@code byte}
225      *   <li>{@code %%} for literal {@code %}
226      *   <li>{@code %04d} style grammar to specify the argument width, such as {@code %04d} to
227      *       prefix an {@code int} with zeros or {@code %10b} to prefix a {@code boolean} with
228      *       spaces
229      * </ul>
230      *
231      * <p>(copied from framework/base/core/java/android/text/TextUtils.java)
232      *
233      * <p>See {@code android.text.TextUtils.formatSimple}
234      *
235      * @throws IllegalArgumentException if the format string or arguments don't match the supported
236      *     grammar described above.
237      * @hide
238      */
formatSimple(@onNull String format, Object... args)239     public static @NonNull String formatSimple(@NonNull String format, Object... args) {
240         final StringBuilder sb = new StringBuilder(format);
241         int j = 0;
242         for (int i = 0; i < sb.length(); ) {
243             if (sb.charAt(i) == '%') {
244                 char code = sb.charAt(i + 1);
245 
246                 // Decode any argument width request
247                 char prefixChar = '\0';
248                 int prefixLen = 0;
249                 int consume = 2;
250                 while ('0' <= code && code <= '9') {
251                     if (prefixChar == '\0') {
252                         prefixChar = (code == '0') ? '0' : ' ';
253                     }
254                     prefixLen *= 10;
255                     prefixLen += Character.digit(code, 10);
256                     consume += 1;
257                     code = sb.charAt(i + consume - 1);
258                 }
259 
260                 final String repl;
261                 switch (code) {
262                     case 'b' -> {
263                         if (j == args.length) {
264                             throw new IllegalArgumentException("Too few arguments");
265                         }
266                         final Object arg = args[j++];
267                         if (arg instanceof Boolean) {
268                             repl = Boolean.toString((boolean) arg);
269                         } else {
270                             repl = Boolean.toString(arg != null);
271                         }
272                     }
273                     case 'c', 'd', 'f', 's' -> {
274                         if (j == args.length) {
275                             throw new IllegalArgumentException("Too few arguments");
276                         }
277                         final Object arg = args[j++];
278                         repl = String.valueOf(arg);
279                     }
280                     case 'x' -> {
281                         if (j == args.length) {
282                             throw new IllegalArgumentException("Too few arguments");
283                         }
284                         final Object arg = args[j++];
285                         if (arg instanceof Integer) {
286                             repl = Integer.toHexString((int) arg);
287                         } else if (arg instanceof Long) {
288                             repl = Long.toHexString((long) arg);
289                         } else if (arg instanceof Byte) {
290                             repl = Integer.toHexString(Byte.toUnsignedInt((byte) arg));
291                         } else {
292                             throw new IllegalArgumentException(
293                                     "Unsupported hex type " + arg.getClass().getSimpleName());
294                         }
295                     }
296                     case '%' -> {
297                         repl = "%";
298                     }
299                     default -> {
300                         throw new IllegalArgumentException("Unsupported format code " + code);
301                     }
302                 }
303 
304                 sb.replace(i, i + consume, repl);
305 
306                 // Apply any argument width request
307                 final int prefixInsert = (prefixChar == '0' && repl.charAt(0) == '-') ? 1 : 0;
308                 for (int k = repl.length(); k < prefixLen; k++) {
309                     sb.insert(i + prefixInsert, prefixChar);
310                 }
311                 i += Math.max(repl.length(), prefixLen);
312             } else {
313                 i++;
314             }
315         }
316         if (j != args.length) {
317             throw new IllegalArgumentException("Too many arguments");
318         }
319         return sb.toString();
320     }
321 
322     /**
323      * Wrapper for Parcel.writeString that silence AndroidFrameworkEfficientParcelable
324      *
325      * <p>ErrorProne wants us to use writeString8 but it is not exposed outside of fwk/base. The
326      * alternative to deactivate entirely AndroidFrameworkEfficientParcelable is not good because
327      * there are other error reported by it
328      *
329      * @hide
330      */
writeStringToParcel(@onNull Parcel out, @Nullable String str)331     public static void writeStringToParcel(@NonNull Parcel out, @Nullable String str) {
332         out.writeString(str);
333     }
334 
335     /**
336      * Execute the callback without UID / PID information
337      *
338      * @hide
339      */
executeFromBinder(@onNull Executor executor, @NonNull Runnable callback)340     public static void executeFromBinder(@NonNull Executor executor, @NonNull Runnable callback) {
341         final long identity = Binder.clearCallingIdentity();
342         try {
343             executor.execute(() -> callback.run());
344         } finally {
345             Binder.restoreCallingIdentity(identity);
346         }
347     }
348 
349     /** A {@link Runnable} that automatically logs {@link RemoteException} @hide */
350     @FunctionalInterface
351     public interface RemoteExceptionIgnoringRunnable {
352         /** Called by {@code accept}. */
runOrThrow()353         void runOrThrow() throws RemoteException;
354 
355         @RequiresNoPermission
run()356         default void run() {
357             try {
358                 runOrThrow();
359             } catch (RemoteException ex) {
360                 logRemoteException(TAG, ex);
361             }
362         }
363     }
364 
365     /** A {@link Consumer} that automatically logs {@link RemoteException} @hide */
366     @FunctionalInterface
367     public interface RemoteExceptionIgnoringConsumer<T> {
368         /** Called by {@code accept}. */
acceptOrThrow(T t)369         void acceptOrThrow(T t) throws RemoteException;
370 
371         @RequiresNoPermission
accept(T t)372         default void accept(T t) {
373             try {
374                 acceptOrThrow(t);
375             } catch (RemoteException ex) {
376                 logRemoteException(TAG, ex);
377             }
378         }
379     }
380 
381     /** A {@link Function} that automatically logs {@link RemoteException} @hide */
382     @FunctionalInterface
383     public interface RemoteExceptionIgnoringFunction<T, R> {
applyOrThrow(T t)384         R applyOrThrow(T t) throws RemoteException;
385 
386         @RequiresNoPermission
apply(T t, R defaultValue)387         default R apply(T t, R defaultValue) {
388             try {
389                 return applyOrThrow(t);
390             } catch (RemoteException ex) {
391                 logRemoteException(TAG, ex);
392                 return defaultValue;
393             }
394         }
395     }
396 
callService( S service, RemoteExceptionIgnoringFunction<S, R> function, R defaultValue)397     public static <S, R> R callService(
398             S service, RemoteExceptionIgnoringFunction<S, R> function, R defaultValue) {
399         return function.apply(service, defaultValue);
400     }
401 
callServiceIfEnabled( BluetoothAdapter adapter, Supplier<S> provider, RemoteExceptionIgnoringFunction<S, R> function, R defaultValue)402     public static <S, R> R callServiceIfEnabled(
403             BluetoothAdapter adapter,
404             Supplier<S> provider,
405             RemoteExceptionIgnoringFunction<S, R> function,
406             R defaultValue) {
407         if (!adapter.isEnabled()) {
408             Log.d(TAG, "BluetoothAdapter is not enabled");
409             return defaultValue;
410         }
411         final S service = provider.get();
412         if (service == null) {
413             Log.d(TAG, "Proxy not attached to service");
414             return defaultValue;
415         }
416         return callService(service, function, defaultValue);
417     }
418 
callServiceIfEnabled( BluetoothAdapter adapter, Supplier<S> provider, RemoteExceptionIgnoringConsumer<S> consumer)419     public static <S> void callServiceIfEnabled(
420             BluetoothAdapter adapter,
421             Supplier<S> provider,
422             RemoteExceptionIgnoringConsumer<S> consumer) {
423         if (!adapter.isEnabled()) {
424             Log.d(TAG, "BluetoothAdapter is not enabled");
425             return;
426         }
427         final S service = provider.get();
428         if (service == null) {
429             Log.d(TAG, "Proxy not attached to service");
430             return;
431         }
432         consumer.accept(service);
433     }
434 
435     /** return the current stack trace as a string without new line @hide */
inlineStackTrace()436     public static String inlineStackTrace() {
437         StringBuilder sb = new StringBuilder();
438         Arrays.stream(new Throwable().getStackTrace())
439                 .skip(1) // skip the inlineStackTrace method in the outputted stack trace
440                 .forEach(trace -> sb.append(" [at ").append(trace).append("]"));
441         return sb.toString();
442     }
443 
444     /** Gracefully print a RemoteException as a one line warning @hide */
logRemoteException(String tag, RemoteException ex)445     public static void logRemoteException(String tag, RemoteException ex) {
446         Log.w(tag, ex.toString() + ": " + inlineStackTrace());
447     }
448 
isValidDevice(BluetoothDevice device)449     static boolean isValidDevice(BluetoothDevice device) {
450         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
451     }
452 }
453