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