1 package com.android.settingslib.bluetooth; 2 3 import android.bluetooth.BluetoothClass; 4 import android.bluetooth.BluetoothDevice; 5 import android.bluetooth.BluetoothProfile; 6 import android.content.Context; 7 import android.content.Intent; 8 import android.content.res.Resources; 9 import android.graphics.Bitmap; 10 import android.graphics.drawable.Drawable; 11 import android.net.Uri; 12 import android.provider.MediaStore; 13 import android.util.Log; 14 import android.util.Pair; 15 16 import androidx.annotation.DrawableRes; 17 18 import com.android.settingslib.R; 19 import com.android.settingslib.widget.AdaptiveIcon; 20 import com.android.settingslib.widget.AdaptiveOutlineDrawable; 21 22 import java.io.IOException; 23 import java.util.List; 24 25 public class BluetoothUtils { 26 private static final String TAG = "BluetoothUtils"; 27 28 public static final boolean V = false; // verbose logging 29 public static final boolean D = true; // regular logging 30 31 public static final int META_INT_ERROR = -1; 32 33 private static ErrorListener sErrorListener; 34 getConnectionStateSummary(int connectionState)35 public static int getConnectionStateSummary(int connectionState) { 36 switch (connectionState) { 37 case BluetoothProfile.STATE_CONNECTED: 38 return R.string.bluetooth_connected; 39 case BluetoothProfile.STATE_CONNECTING: 40 return R.string.bluetooth_connecting; 41 case BluetoothProfile.STATE_DISCONNECTED: 42 return R.string.bluetooth_disconnected; 43 case BluetoothProfile.STATE_DISCONNECTING: 44 return R.string.bluetooth_disconnecting; 45 default: 46 return 0; 47 } 48 } 49 showError(Context context, String name, int messageResId)50 static void showError(Context context, String name, int messageResId) { 51 if (sErrorListener != null) { 52 sErrorListener.onShowError(context, name, messageResId); 53 } 54 } 55 setErrorListener(ErrorListener listener)56 public static void setErrorListener(ErrorListener listener) { 57 sErrorListener = listener; 58 } 59 60 public interface ErrorListener { onShowError(Context context, String name, int messageResId)61 void onShowError(Context context, String name, int messageResId); 62 } 63 getBtClassDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice)64 public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context, 65 CachedBluetoothDevice cachedDevice) { 66 BluetoothClass btClass = cachedDevice.getBtClass(); 67 if (btClass != null) { 68 switch (btClass.getMajorDeviceClass()) { 69 case BluetoothClass.Device.Major.COMPUTER: 70 return new Pair<>(getBluetoothDrawable(context, 71 com.android.internal.R.drawable.ic_bt_laptop), 72 context.getString(R.string.bluetooth_talkback_computer)); 73 74 case BluetoothClass.Device.Major.PHONE: 75 return new Pair<>( 76 getBluetoothDrawable(context, 77 com.android.internal.R.drawable.ic_phone), 78 context.getString(R.string.bluetooth_talkback_phone)); 79 80 case BluetoothClass.Device.Major.PERIPHERAL: 81 return new Pair<>( 82 getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass)), 83 context.getString(R.string.bluetooth_talkback_input_peripheral)); 84 85 case BluetoothClass.Device.Major.IMAGING: 86 return new Pair<>( 87 getBluetoothDrawable(context, 88 com.android.internal.R.drawable.ic_settings_print), 89 context.getString(R.string.bluetooth_talkback_imaging)); 90 91 default: 92 // unrecognized device class; continue 93 } 94 } 95 96 List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles(); 97 for (LocalBluetoothProfile profile : profiles) { 98 int resId = profile.getDrawableResource(btClass); 99 if (resId != 0) { 100 return new Pair<>(getBluetoothDrawable(context, resId), null); 101 } 102 } 103 if (btClass != null) { 104 if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { 105 return new Pair<>( 106 getBluetoothDrawable(context, 107 com.android.internal.R.drawable.ic_bt_headset_hfp), 108 context.getString(R.string.bluetooth_talkback_headset)); 109 } 110 if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { 111 return new Pair<>( 112 getBluetoothDrawable(context, 113 com.android.internal.R.drawable.ic_bt_headphones_a2dp), 114 context.getString(R.string.bluetooth_talkback_headphone)); 115 } 116 } 117 return new Pair<>( 118 getBluetoothDrawable(context, 119 com.android.internal.R.drawable.ic_settings_bluetooth).mutate(), 120 context.getString(R.string.bluetooth_talkback_bluetooth)); 121 } 122 123 /** 124 * Get bluetooth drawable by {@code resId} 125 */ getBluetoothDrawable(Context context, @DrawableRes int resId)126 public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) { 127 return context.getDrawable(resId); 128 } 129 130 /** 131 * Get colorful bluetooth icon with description 132 */ getBtRainbowDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice)133 public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context, 134 CachedBluetoothDevice cachedDevice) { 135 final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription( 136 context, cachedDevice); 137 final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); 138 final boolean untetheredHeadset = getBooleanMetaData( 139 bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET); 140 final int iconSize = context.getResources().getDimensionPixelSize( 141 R.dimen.bt_nearby_icon_size); 142 final Resources resources = context.getResources(); 143 144 // Deal with untethered headset 145 if (untetheredHeadset) { 146 final Uri iconUri = getUriMetaData(bluetoothDevice, 147 BluetoothDevice.METADATA_MAIN_ICON); 148 if (iconUri != null) { 149 try { 150 context.getContentResolver().takePersistableUriPermission(iconUri, 151 Intent.FLAG_GRANT_READ_URI_PERMISSION); 152 } catch (SecurityException e) { 153 Log.e(TAG, "Failed to take persistable permission for: " + iconUri); 154 } 155 try { 156 final Bitmap bitmap = MediaStore.Images.Media.getBitmap( 157 context.getContentResolver(), iconUri); 158 if (bitmap != null) { 159 final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, 160 iconSize, false); 161 bitmap.recycle(); 162 final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable( 163 resources, resizedBitmap); 164 return new Pair<>(drawable, pair.second); 165 } 166 } catch (IOException e) { 167 Log.e(TAG, "Failed to get drawable for: " + iconUri, e); 168 } 169 } 170 } 171 172 return new Pair<>(buildBtRainbowDrawable(context, 173 pair.first, cachedDevice.getAddress().hashCode()), pair.second); 174 } 175 176 /** 177 * Build Bluetooth device icon with rainbow 178 */ buildBtRainbowDrawable(Context context, Drawable drawable, int hashCode)179 public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, 180 int hashCode) { 181 final Resources resources = context.getResources(); 182 183 // Deal with normal headset 184 final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors); 185 final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors); 186 187 // get color index based on mac address 188 final int index = Math.abs(hashCode % iconBgColors.length); 189 drawable.setTint(iconFgColors[index]); 190 final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable); 191 ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]); 192 193 return adaptiveIcon; 194 } 195 196 /** 197 * Get boolean Bluetooth metadata 198 * 199 * @param bluetoothDevice the BluetoothDevice to get metadata 200 * @param key key value within the list of BluetoothDevice.METADATA_* 201 * @return the boolean metdata 202 */ getBooleanMetaData(BluetoothDevice bluetoothDevice, int key)203 public static boolean getBooleanMetaData(BluetoothDevice bluetoothDevice, int key) { 204 if (bluetoothDevice == null) { 205 return false; 206 } 207 final byte[] data = bluetoothDevice.getMetadata(key); 208 if (data == null) { 209 return false; 210 } 211 return Boolean.parseBoolean(new String(data)); 212 } 213 214 /** 215 * Get String Bluetooth metadata 216 * 217 * @param bluetoothDevice the BluetoothDevice to get metadata 218 * @param key key value within the list of BluetoothDevice.METADATA_* 219 * @return the String metdata 220 */ getStringMetaData(BluetoothDevice bluetoothDevice, int key)221 public static String getStringMetaData(BluetoothDevice bluetoothDevice, int key) { 222 if (bluetoothDevice == null) { 223 return null; 224 } 225 final byte[] data = bluetoothDevice.getMetadata(key); 226 if (data == null) { 227 return null; 228 } 229 return new String(data); 230 } 231 232 /** 233 * Get integer Bluetooth metadata 234 * 235 * @param bluetoothDevice the BluetoothDevice to get metadata 236 * @param key key value within the list of BluetoothDevice.METADATA_* 237 * @return the int metdata 238 */ getIntMetaData(BluetoothDevice bluetoothDevice, int key)239 public static int getIntMetaData(BluetoothDevice bluetoothDevice, int key) { 240 if (bluetoothDevice == null) { 241 return META_INT_ERROR; 242 } 243 final byte[] data = bluetoothDevice.getMetadata(key); 244 if (data == null) { 245 return META_INT_ERROR; 246 } 247 try { 248 return Integer.parseInt(new String(data)); 249 } catch (NumberFormatException e) { 250 return META_INT_ERROR; 251 } 252 } 253 254 /** 255 * Get URI Bluetooth metadata 256 * 257 * @param bluetoothDevice the BluetoothDevice to get metadata 258 * @param key key value within the list of BluetoothDevice.METADATA_* 259 * @return the URI metdata 260 */ getUriMetaData(BluetoothDevice bluetoothDevice, int key)261 public static Uri getUriMetaData(BluetoothDevice bluetoothDevice, int key) { 262 String data = getStringMetaData(bluetoothDevice, key); 263 if (data == null) { 264 return null; 265 } 266 return Uri.parse(data); 267 } 268 } 269