blob: 253629c8d1282eac51e94fa30f89ed340a512064 [file] [log] [blame]
package com.android.settingslib.bluetooth;
import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.IconCompat;
import com.android.settingslib.R;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
import java.io.IOException;
import java.util.List;
public class BluetoothUtils {
private static final String TAG = "BluetoothUtils";
public static final boolean V = false; // verbose logging
public static final boolean D = true; // regular logging
public static final int META_INT_ERROR = -1;
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static ErrorListener sErrorListener;
public static int getConnectionStateSummary(int connectionState) {
switch (connectionState) {
case BluetoothProfile.STATE_CONNECTED:
return R.string.bluetooth_connected;
case BluetoothProfile.STATE_CONNECTING:
return R.string.bluetooth_connecting;
case BluetoothProfile.STATE_DISCONNECTED:
return R.string.bluetooth_disconnected;
case BluetoothProfile.STATE_DISCONNECTING:
return R.string.bluetooth_disconnecting;
default:
return 0;
}
}
static void showError(Context context, String name, int messageResId) {
if (sErrorListener != null) {
sErrorListener.onShowError(context, name, messageResId);
}
}
public static void setErrorListener(ErrorListener listener) {
sErrorListener = listener;
}
public interface ErrorListener {
void onShowError(Context context, String name, int messageResId);
}
public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
CachedBluetoothDevice cachedDevice) {
BluetoothClass btClass = cachedDevice.getBtClass();
if (btClass != null) {
switch (btClass.getMajorDeviceClass()) {
case BluetoothClass.Device.Major.COMPUTER:
return new Pair<>(getBluetoothDrawable(context,
com.android.internal.R.drawable.ic_bt_laptop),
context.getString(R.string.bluetooth_talkback_computer));
case BluetoothClass.Device.Major.PHONE:
return new Pair<>(
getBluetoothDrawable(context,
com.android.internal.R.drawable.ic_phone),
context.getString(R.string.bluetooth_talkback_phone));
case BluetoothClass.Device.Major.PERIPHERAL:
return new Pair<>(
getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass)),
context.getString(R.string.bluetooth_talkback_input_peripheral));
case BluetoothClass.Device.Major.IMAGING:
return new Pair<>(
getBluetoothDrawable(context,
com.android.internal.R.drawable.ic_settings_print),
context.getString(R.string.bluetooth_talkback_imaging));
default:
// unrecognized device class; continue
}
}
List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
for (LocalBluetoothProfile profile : profiles) {
int resId = profile.getDrawableResource(btClass);
if (resId != 0) {
return new Pair<>(getBluetoothDrawable(context, resId), null);
}
}
if (btClass != null) {
if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
return new Pair<>(
getBluetoothDrawable(context,
com.android.internal.R.drawable.ic_bt_headset_hfp),
context.getString(R.string.bluetooth_talkback_headset));
}
if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
return new Pair<>(
getBluetoothDrawable(context,
com.android.internal.R.drawable.ic_bt_headphones_a2dp),
context.getString(R.string.bluetooth_talkback_headphone));
}
}
return new Pair<>(
getBluetoothDrawable(context,
com.android.internal.R.drawable.ic_settings_bluetooth).mutate(),
context.getString(R.string.bluetooth_talkback_bluetooth));
}
/**
* Get bluetooth drawable by {@code resId}
*/
public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) {
return context.getDrawable(resId);
}
/**
* Get colorful bluetooth icon with description
*/
public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context,
CachedBluetoothDevice cachedDevice) {
final Resources resources = context.getResources();
final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context,
cachedDevice);
if (pair.first instanceof BitmapDrawable) {
return new Pair<>(new AdaptiveOutlineDrawable(
resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
}
return new Pair<>(buildBtRainbowDrawable(context,
pair.first, cachedDevice.getAddress().hashCode()), pair.second);
}
/**
* Build Bluetooth device icon with rainbow
*/
public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
int hashCode) {
final Resources resources = context.getResources();
// Deal with normal headset
final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors);
final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors);
// get color index based on mac address
final int index = Math.abs(hashCode % iconBgColors.length);
drawable.setTint(iconFgColors[index]);
final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable);
((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]);
return adaptiveIcon;
}
/**
* Get bluetooth icon with description
*/
public static Pair<Drawable, String> getBtDrawableWithDescription(Context context,
CachedBluetoothDevice cachedDevice) {
final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
context, cachedDevice);
final BluetoothDevice bluetoothDevice = cachedDevice.getDevice();
final int iconSize = context.getResources().getDimensionPixelSize(
R.dimen.bt_nearby_icon_size);
final Resources resources = context.getResources();
// Deal with advanced device icon
if (isAdvancedDetailsHeader(bluetoothDevice)) {
final Uri iconUri = getUriMetaData(bluetoothDevice,
BluetoothDevice.METADATA_MAIN_ICON);
if (iconUri != null) {
try {
context.getContentResolver().takePersistableUriPermission(iconUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e);
}
try {
final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
context.getContentResolver(), iconUri);
if (bitmap != null) {
final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
iconSize, false);
bitmap.recycle();
return new Pair<>(new BitmapDrawable(resources,
resizedBitmap), pair.second);
}
} catch (IOException e) {
Log.e(TAG, "Failed to get drawable for: " + iconUri, e);
} catch (SecurityException e) {
Log.e(TAG, "Failed to get permission for: " + iconUri, e);
}
}
}
return new Pair<>(pair.first, pair.second);
}
/**
* Check if the Bluetooth device supports advanced metadata
*
* @param bluetoothDevice the BluetoothDevice to get metadata
* @return true if it supports advanced metadata, false otherwise.
*/
public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) {
if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
true)) {
Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
return false;
}
// The metadata is for Android R
if (getBooleanMetaData(bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true");
return true;
}
// The metadata is for Android S
String deviceType = getStringMetaData(bluetoothDevice,
BluetoothDevice.METADATA_DEVICE_TYPE);
if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)) {
Log.d(TAG, "isAdvancedDetailsHeader: deviceType is " + deviceType);
return true;
}
return false;
}
/**
* Create an Icon pointing to a drawable.
*/
public static IconCompat createIconWithDrawable(Drawable drawable) {
Bitmap bitmap;
if (drawable instanceof BitmapDrawable) {
bitmap = ((BitmapDrawable) drawable).getBitmap();
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
bitmap = createBitmap(drawable,
width > 0 ? width : 1,
height > 0 ? height : 1);
}
return IconCompat.createWithBitmap(bitmap);
}
/**
* Build device icon with advanced outline
*/
public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) {
final int iconSize = context.getResources().getDimensionPixelSize(
R.dimen.advanced_icon_size);
final Resources resources = context.getResources();
Bitmap bitmap = null;
if (drawable instanceof BitmapDrawable) {
bitmap = ((BitmapDrawable) drawable).getBitmap();
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
bitmap = createBitmap(drawable,
width > 0 ? width : 1,
height > 0 ? height : 1);
}
if (bitmap != null) {
final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
iconSize, false);
bitmap.recycle();
return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED);
}
return drawable;
}
/**
* Creates a drawable with specified width and height.
*/
public static Bitmap createBitmap(Drawable drawable, int width, int height) {
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
/**
* Get boolean Bluetooth metadata
*
* @param bluetoothDevice the BluetoothDevice to get metadata
* @param key key value within the list of BluetoothDevice.METADATA_*
* @return the boolean metdata
*/
public static boolean getBooleanMetaData(BluetoothDevice bluetoothDevice, int key) {
if (bluetoothDevice == null) {
return false;
}
final byte[] data = bluetoothDevice.getMetadata(key);
if (data == null) {
return false;
}
return Boolean.parseBoolean(new String(data));
}
/**
* Get String Bluetooth metadata
*
* @param bluetoothDevice the BluetoothDevice to get metadata
* @param key key value within the list of BluetoothDevice.METADATA_*
* @return the String metdata
*/
public static String getStringMetaData(BluetoothDevice bluetoothDevice, int key) {
if (bluetoothDevice == null) {
return null;
}
final byte[] data = bluetoothDevice.getMetadata(key);
if (data == null) {
return null;
}
return new String(data);
}
/**
* Get integer Bluetooth metadata
*
* @param bluetoothDevice the BluetoothDevice to get metadata
* @param key key value within the list of BluetoothDevice.METADATA_*
* @return the int metdata
*/
public static int getIntMetaData(BluetoothDevice bluetoothDevice, int key) {
if (bluetoothDevice == null) {
return META_INT_ERROR;
}
final byte[] data = bluetoothDevice.getMetadata(key);
if (data == null) {
return META_INT_ERROR;
}
try {
return Integer.parseInt(new String(data));
} catch (NumberFormatException e) {
return META_INT_ERROR;
}
}
/**
* Get URI Bluetooth metadata
*
* @param bluetoothDevice the BluetoothDevice to get metadata
* @param key key value within the list of BluetoothDevice.METADATA_*
* @return the URI metdata
*/
public static Uri getUriMetaData(BluetoothDevice bluetoothDevice, int key) {
String data = getStringMetaData(bluetoothDevice, key);
if (data == null) {
return null;
}
return Uri.parse(data);
}
}