/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.bluetooth; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresNoPermission; import android.os.Binder; import android.os.Parcel; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; /** @hide */ public final class BluetoothUtils { private static final String TAG = BluetoothUtils.class.getSimpleName(); /** This utility class cannot be instantiated */ private BluetoothUtils() {} /** Match with UserHandle.NULL but accessible inside bluetooth package */ public static final UserHandle USER_HANDLE_NULL = UserHandle.of(-10000); /** Class for Length-Value-Entry array parsing */ public static class TypeValueEntry { private final int mType; private final byte[] mValue; TypeValueEntry(int type, byte[] value) { mType = type; mValue = value; } @RequiresNoPermission public int getType() { return mType; } @RequiresNoPermission public byte[] getValue() { return mValue; } } /** Helper method to extract bytes from byte array. */ public static byte[] extractBytes(byte[] rawBytes, int start, int length) { int remainingLength = rawBytes.length - start; if (remainingLength < length) { Log.w( TAG, "extractBytes() remaining length " + remainingLength + " is less than copying length " + length + ", array length is " + rawBytes.length + " start is " + start); return null; } byte[] bytes = new byte[length]; System.arraycopy(rawBytes, start, bytes, 0, length); return bytes; } /** * Parse Length Value Entry from raw bytes * *
The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
*
* @param rawBytes raw bytes of Length-Value-Entry array
* @hide
*/
@SuppressWarnings("MixedMutabilityReturnType") // TODO(b/314811467)
public static List Below is a summary of the limited grammar supported by this method; if you need advanced
* features, please continue using {@link String#format}.
*
* (copied from framework/base/core/java/android/text/TextUtils.java)
*
* See {@code android.text.TextUtils.formatSimple}
*
* @throws IllegalArgumentException if the format string or arguments don't match the supported
* grammar described above.
* @hide
*/
public static @NonNull String formatSimple(@NonNull String format, Object... args) {
final StringBuilder sb = new StringBuilder(format);
int j = 0;
for (int i = 0; i < sb.length(); ) {
if (sb.charAt(i) == '%') {
char code = sb.charAt(i + 1);
// Decode any argument width request
char prefixChar = '\0';
int prefixLen = 0;
int consume = 2;
while ('0' <= code && code <= '9') {
if (prefixChar == '\0') {
prefixChar = (code == '0') ? '0' : ' ';
}
prefixLen *= 10;
prefixLen += Character.digit(code, 10);
consume += 1;
code = sb.charAt(i + consume - 1);
}
final String repl;
switch (code) {
case 'b' -> {
if (j == args.length) {
throw new IllegalArgumentException("Too few arguments");
}
final Object arg = args[j++];
if (arg instanceof Boolean) {
repl = Boolean.toString((boolean) arg);
} else {
repl = Boolean.toString(arg != null);
}
}
case 'c', 'd', 'f', 's' -> {
if (j == args.length) {
throw new IllegalArgumentException("Too few arguments");
}
final Object arg = args[j++];
repl = String.valueOf(arg);
}
case 'x' -> {
if (j == args.length) {
throw new IllegalArgumentException("Too few arguments");
}
final Object arg = args[j++];
if (arg instanceof Integer) {
repl = Integer.toHexString((int) arg);
} else if (arg instanceof Long) {
repl = Long.toHexString((long) arg);
} else if (arg instanceof Byte) {
repl = Integer.toHexString(Byte.toUnsignedInt((byte) arg));
} else {
throw new IllegalArgumentException(
"Unsupported hex type " + arg.getClass().getSimpleName());
}
}
case '%' -> {
repl = "%";
}
default -> {
throw new IllegalArgumentException("Unsupported format code " + code);
}
}
sb.replace(i, i + consume, repl);
// Apply any argument width request
final int prefixInsert = (prefixChar == '0' && repl.charAt(0) == '-') ? 1 : 0;
for (int k = repl.length(); k < prefixLen; k++) {
sb.insert(i + prefixInsert, prefixChar);
}
i += Math.max(repl.length(), prefixLen);
} else {
i++;
}
}
if (j != args.length) {
throw new IllegalArgumentException("Too many arguments");
}
return sb.toString();
}
/**
* Wrapper for Parcel.writeString that silence AndroidFrameworkEfficientParcelable
*
* ErrorProne wants us to use writeString8 but it is not exposed outside of fwk/base. The
* alternative to deactivate entirely AndroidFrameworkEfficientParcelable is not good because
* there are other error reported by it
*
* @hide
*/
public static void writeStringToParcel(@NonNull Parcel out, @Nullable String str) {
out.writeString(str);
}
/**
* Execute the callback without UID / PID information
*
* @hide
*/
public static void executeFromBinder(@NonNull Executor executor, @NonNull Runnable callback) {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> callback.run());
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/** A {@link Runnable} that automatically logs {@link RemoteException} @hide */
@FunctionalInterface
public interface RemoteExceptionIgnoringRunnable {
/** Called by {@code accept}. */
void runOrThrow() throws RemoteException;
@RequiresNoPermission
default void run() {
try {
runOrThrow();
} catch (RemoteException ex) {
logRemoteException(TAG, ex);
}
}
}
/** A {@link Consumer} that automatically logs {@link RemoteException} @hide */
@FunctionalInterface
public interface RemoteExceptionIgnoringConsumer
*
*
* R callService(
S service, RemoteExceptionIgnoringFunction function, R defaultValue) {
return function.apply(service, defaultValue);
}
public static R callServiceIfEnabled(
BluetoothAdapter adapter,
Supplier provider,
RemoteExceptionIgnoringFunction function,
R defaultValue) {
if (!adapter.isEnabled()) {
Log.d(TAG, "BluetoothAdapter is not enabled");
return defaultValue;
}
final S service = provider.get();
if (service == null) {
Log.d(TAG, "Proxy not attached to service");
return defaultValue;
}
return callService(service, function, defaultValue);
}
public static void callServiceIfEnabled(
BluetoothAdapter adapter,
Supplier provider,
RemoteExceptionIgnoringConsumer consumer) {
if (!adapter.isEnabled()) {
Log.d(TAG, "BluetoothAdapter is not enabled");
return;
}
final S service = provider.get();
if (service == null) {
Log.d(TAG, "Proxy not attached to service");
return;
}
consumer.accept(service);
}
/** return the current stack trace as a string without new line @hide */
public static String inlineStackTrace() {
StringBuilder sb = new StringBuilder();
Arrays.stream(new Throwable().getStackTrace())
.skip(1) // skip the inlineStackTrace method in the outputted stack trace
.forEach(trace -> sb.append(" [at ").append(trace).append("]"));
return sb.toString();
}
/** Gracefully print a RemoteException as a one line warning @hide */
public static void logRemoteException(String tag, RemoteException ex) {
Log.w(tag, ex.toString() + ": " + inlineStackTrace());
}
static boolean isValidDevice(BluetoothDevice device) {
return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
}
}