From 4741a8bd53ebf7c33ef854aeede526e627e0d0df Mon Sep 17 00:00:00 2001 From: Ugo Yu Date: Tue, 5 Mar 2019 16:20:27 +0800 Subject: [PATCH 1/2] Refine Bluetooth Metadata API - Modify MetadataListener to as an interface and rename it to OnMetadataChangedListener - Fix typo UNTHETHERED -> UNTETHERED - Add NonNull annotation for metadata API parameters - Re-design metadata unregister API - Change metadata type to byte array Bug: 124448651 Bug: 126701203 Bug: 126699213 Test: build pass Change-Id: I79460071c7693f648e92cf849738c24f8bc269d9 --- api/system-current.txt | 33 +++-- .../android/bluetooth/BluetoothAdapter.java | 116 ++++++++++-------- .../android/bluetooth/BluetoothDevice.java | 67 ++++++---- 3 files changed, 124 insertions(+), 92 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 64589093654eb..47027202adfb0 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1241,30 +1241,29 @@ package android.app.usage { package android.bluetooth { public final class BluetoothAdapter { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); method public boolean disableBLE(); method public boolean enableBLE(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect(); method public boolean isBleScanAlwaysAvailable(); method public boolean isLeEnabled(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; } - public abstract static class BluetoothAdapter.MetadataListener { - ctor public BluetoothAdapter.MetadataListener(); - method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String); + public static interface BluetoothAdapter.OnMetadataChangedListener { + method public void onMetadataChanged(@NonNull android.bluetooth.BluetoothDevice, int, @Nullable byte[]); } public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); + method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, @NonNull byte[]); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean); field public static final int ACCESS_ALLOWED = 1; // 0x1 @@ -1274,21 +1273,21 @@ package android.bluetooth { field public static final int METADATA_COMPANION_APP = 4; // 0x4 field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 - field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6 + field public static final int METADATA_IS_UNTETHERED_HEADSET = 6; // 0x6 field public static final int METADATA_MAIN_ICON = 5; // 0x5 field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0 field public static final int METADATA_MAX_LENGTH = 2048; // 0x800 field public static final int METADATA_MODEL_NAME = 1; // 0x1 field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2 - field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc - field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf - field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9 - field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa - field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd - field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7 - field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb - field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe - field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8 + field public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; // 0xc + field public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; // 0xf + field public static final int METADATA_UNTETHERED_CASE_ICON = 9; // 0x9 + field public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; // 0xa + field public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; // 0xd + field public static final int METADATA_UNTETHERED_LEFT_ICON = 7; // 0x7 + field public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; // 0xb + field public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; // 0xe + field public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; // 0x8 } public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index b8a741ab2b72c..31bbd16497cb2 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -20,6 +20,7 @@ package android.bluetooth; import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -37,7 +38,6 @@ import android.bluetooth.le.ScanSettings; import android.content.Context; import android.os.BatteryStats; import android.os.Binder; -import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -61,6 +61,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -650,7 +651,7 @@ public final class BluetoothAdapter { private final Object mLock = new Object(); private final Map mLeScanClients; - private static final Map>> + private static final Map>> sMetadataListeners = new HashMap<>(); /** @@ -660,14 +661,15 @@ public final class BluetoothAdapter { private static final IBluetoothMetadataListener sBluetoothMetadataListener = new IBluetoothMetadataListener.Stub() { @Override - public void onMetadataChanged(BluetoothDevice device, int key, String value) { + public void onMetadataChanged(BluetoothDevice device, int key, byte[] value) { synchronized (sMetadataListeners) { if (sMetadataListeners.containsKey(device)) { - List> list = sMetadataListeners.get(device); - for (Pair pair : list) { - MetadataListener listener = pair.first; - Handler handler = pair.second; - handler.post(() -> { + List> list = + sMetadataListeners.get(device); + for (Pair pair : list) { + OnMetadataChangedListener listener = pair.first; + Executor executor = pair.second; + executor.execute(() -> { listener.onMetadataChanged(device, key, value); }); } @@ -3153,30 +3155,30 @@ public final class BluetoothAdapter { } /** - * Register a {@link #MetadataListener} to receive update about metadata + * Register a {@link #OnMetadataChangedListener} to receive update about metadata * changes for this {@link BluetoothDevice}. * Registration must be done when Bluetooth is ON and will last until - * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth + * {@link #removeOnMetadataChangedListener(BluetoothDevice)} is called, even when Bluetooth * restarted in the middle. * All input parameters should not be null or {@link NullPointerException} will be triggered. - * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered - * once, double registration would cause {@link IllegalArgumentException}. + * The same {@link BluetoothDevice} and {@link #OnMetadataChangedListener} pair can only be + * registered once, double registration would cause {@link IllegalArgumentException}. * * @param device {@link BluetoothDevice} that will be registered - * @param listener {@link #MetadataListener} that will receive asynchronous callbacks - * @param handler the handler for listener callback + * @param executor the executor for listener callback + * @param listener {@link #OnMetadataChangedListener} that will receive asynchronous callbacks * @return true on success, false on error - * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler} + * @throws NullPointerException If one of {@code listener}, {@code device} or {@code executor} * is null. - * @throws IllegalArgumentException The same {@link #MetadataListener} and + * @throws IllegalArgumentException The same {@link #OnMetadataChangedListener} and * {@link BluetoothDevice} are registered twice. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener, - Handler handler) { - if (DBG) Log.d(TAG, "registerMetdataListener()"); + public boolean addOnMetadataChangedListener(@NonNull BluetoothDevice device, + @NonNull Executor executor, @NonNull OnMetadataChangedListener listener) { + if (DBG) Log.d(TAG, "addOnMetadataChangedListener()"); final IBluetooth service = mService; if (service == null) { @@ -3189,14 +3191,15 @@ public final class BluetoothAdapter { if (device == null) { throw new NullPointerException("device is null"); } - if (handler == null) { - throw new NullPointerException("handler is null"); + if (executor == null) { + throw new NullPointerException("executor is null"); } synchronized (sMetadataListeners) { - List> listenerList = sMetadataListeners.get(device); + List> listenerList = + sMetadataListeners.get(device); if (listenerList == null) { - // Create new listener/handler list for registeration + // Create new listener/executor list for registeration listenerList = new ArrayList<>(); sMetadataListeners.put(device, listenerList); } else { @@ -3207,7 +3210,7 @@ public final class BluetoothAdapter { } } - Pair listenerPair = new Pair(listener, handler); + Pair listenerPair = new Pair(listener, executor); listenerList.add(listenerPair); boolean ret = false; @@ -3230,63 +3233,74 @@ public final class BluetoothAdapter { } /** - * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}. + * Unregister a {@link #OnMetadataChangedListener} from a registered {@link BluetoothDevice}. * Unregistration can be done when Bluetooth is either ON or OFF. - * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must - * be called before unregisteration. - * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}. + * {@link #addOnMetadataChangedListener(OnMetadataChangedListener, BluetoothDevice, Executor)} + * must be called before unregisteration. * - * @param device {@link BluetoothDevice} that will be unregistered. it + * @param device {@link BluetoothDevice} that will be unregistered. It + * should not be null or {@link NullPointerException} will be triggered. + * @param listener {@link OnMetadataChangedListener} that will be unregistered. It * should not be null or {@link NullPointerException} will be triggered. * @return true on success, false on error - * @throws NullPointerException If {@code device} is null. + * @throws NullPointerException If {@code listener} or {@code device} is null. * @throws IllegalArgumentException If {@code device} has not been registered before. * @hide */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean unregisterMetadataListener(BluetoothDevice device) { - if (DBG) Log.d(TAG, "unregisterMetdataListener()"); + public boolean removeOnMetadataChangedListener(@NonNull BluetoothDevice device, + @NonNull OnMetadataChangedListener listener) { + if (DBG) Log.d(TAG, "removeOnMetadataChangedListener()"); if (device == null) { throw new NullPointerException("device is null"); } + if (listener == null) { + throw new NullPointerException("listener is null"); + } synchronized (sMetadataListeners) { - if (sMetadataListeners.containsKey(device)) { - sMetadataListeners.remove(device); - } else { + if (!sMetadataListeners.containsKey(device)) { throw new IllegalArgumentException("device was not registered"); } + // Remove issued listener from the registered device + sMetadataListeners.get(device).removeIf((pair) -> (pair.first.equals(listener))); - final IBluetooth service = mService; - if (service == null) { - // Bluetooth is OFF, do nothing to Bluetooth service. - return true; - } - try { - return service.unregisterMetadataListener(device); - } catch (RemoteException e) { - Log.e(TAG, "unregisterMetadataListener fail", e); - return false; + if (sMetadataListeners.get(device).isEmpty()) { + // Unregister to Bluetooth service if all listeners are removed from + // the registered device + sMetadataListeners.remove(device); + final IBluetooth service = mService; + if (service == null) { + // Bluetooth is OFF, do nothing to Bluetooth service. + return true; + } + try { + return service.unregisterMetadataListener(device); + } catch (RemoteException e) { + Log.e(TAG, "unregisterMetadataListener fail", e); + return false; + } } } + return true; } /** - * This abstract class is used to implement {@link BluetoothAdapter} metadata listener. + * This interface is used to implement {@link BluetoothAdapter} metadata listener. * @hide */ @SystemApi - public abstract static class MetadataListener { + public interface OnMetadataChangedListener { /** * Callback triggered if the metadata of {@link BluetoothDevice} registered in - * {@link #registerMetadataListener}. + * {@link #addOnMetadataChangedListener}. * * @param device changed {@link BluetoothDevice}. * @param key changed metadata key, one of BluetoothDevice.METADATA_*. - * @param value the new value of metadata. + * @param value the new value of metadata as byte array. */ - public void onMetadataChanged(BluetoothDevice device, int key, String value) { - } + void onMetadataChanged(@NonNull BluetoothDevice device, int key, + @Nullable byte[] value); } } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 204d7e3ceca68..74ceeb92f7513 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -351,6 +352,7 @@ public final class BluetoothDevice implements Parcelable { /** * Manufacturer name of this Bluetooth device + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi @@ -358,6 +360,7 @@ public final class BluetoothDevice implements Parcelable { /** * Model name of this Bluetooth device + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi @@ -365,6 +368,7 @@ public final class BluetoothDevice implements Parcelable { /** * Software version of this Bluetooth device + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi @@ -372,6 +376,7 @@ public final class BluetoothDevice implements Parcelable { /** * Hardware version of this Bluetooth device + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi @@ -379,6 +384,7 @@ public final class BluetoothDevice implements Parcelable { /** * Package name of the companion app, if any + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi @@ -386,6 +392,7 @@ public final class BluetoothDevice implements Parcelable { /** * URI to the main icon shown on the settings UI + * Data type should be {@link Byte} array. * @hide */ @SystemApi @@ -393,80 +400,91 @@ public final class BluetoothDevice implements Parcelable { /** * Whether this device is an untethered headset with left, right and case + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi - public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; + public static final int METADATA_IS_UNTETHERED_HEADSET = 6; /** * URI to icon of the left headset + * Data type should be {@link Byte} array. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; + public static final int METADATA_UNTETHERED_LEFT_ICON = 7; /** * URI to icon of the right headset + * Data type should be {@link Byte} array. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; + public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; /** * URI to icon of the headset charging case + * Data type should be {@link Byte} array. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_CASE_ICON = 9; + public static final int METADATA_UNTETHERED_CASE_ICON = 9; /** - * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} - * is invalid, of the left headset + * Battery level of left headset + * Data type should be {@String} 0-100 as {@link Byte} array, otherwise + * as invalid. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; + public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; /** - * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} - * is invalid, of the right headset + * Battery level of rigth headset + * Data type should be {@String} 0-100 as {@link Byte} array, otherwise + * as invalid. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; + public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; /** - * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} - * is invalid, of the headset charging case + * Battery level of the headset charging case + * Data type should be {@String} 0-100 as {@link Byte} array, otherwise + * as invalid. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; + public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; /** * Whether the left headset is charging + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; + public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; /** * Whether the right headset is charging + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; + public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; /** * Whether the headset charging case is charging + * Data type should be {@String} as {@link Byte} array. * @hide */ @SystemApi - public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; + public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; /** - * URI to the enhanced settings UI slice, null or empty String means - * the UI does not exist + * URI to the enhanced settings UI slice + * Data type should be {@String} as {@link Byte} array, null means + * the UI does not exist. * @hide */ @SystemApi @@ -2243,21 +2261,21 @@ public final class BluetoothDevice implements Parcelable { * {@link #BOND_NONE}. * * @param key must be within the list of BluetoothDevice.METADATA_* - * @param value the string data to set for key. Must be less than + * @param value a byte array data to set for key. Must be less than * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length * @return true on success, false on error * @hide */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean setMetadata(int key, String value) { + public boolean setMetadata(int key, @NonNull byte[] value) { final IBluetooth service = sService; if (service == null) { Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); return false; } - if (value.length() > METADATA_MAX_LENGTH) { - throw new IllegalArgumentException("value length is " + value.length() + if (value.length > METADATA_MAX_LENGTH) { + throw new IllegalArgumentException("value length is " + value.length + ", should not over " + METADATA_MAX_LENGTH); } try { @@ -2272,12 +2290,13 @@ public final class BluetoothDevice implements Parcelable { * Get a keyed metadata for this {@link BluetoothDevice} as {@link String} * * @param key must be within the list of BluetoothDevice.METADATA_* - * @return Metadata of the key as string, null on error or not found + * @return Metadata of the key as byte array, null on error or not found * @hide */ @SystemApi + @Nullable @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public String getMetadata(int key) { + public byte[] getMetadata(int key) { final IBluetooth service = sService; if (service == null) { Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); From 73c387d9502f202230ab530a89090033651da0da Mon Sep 17 00:00:00 2001 From: Ugo Yu Date: Wed, 20 Mar 2019 19:50:58 +0800 Subject: [PATCH 2/2] Bluetooth metadata API changes in SettingsLib * Align with the changes of Bluetooth metadata API. * Move metadata utils from Settings to SettingsLib. Bug: 124448651 Test: make -j56 RunSettingsLibRoboTests Change-Id: I431e7bcbd8123eacf0ed3b5ba87af6cc54670481 --- .../settingslib/bluetooth/BluetoothUtils.java | 87 +++++++++++++++++-- .../bluetooth/CachedBluetoothDevice.java | 16 ++-- .../bluetooth/BluetoothUtilsTest.java | 68 ++++++++++++++- .../bluetooth/CachedBluetoothDeviceTest.java | 24 ++--- 4 files changed, 163 insertions(+), 32 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 46e9129d9cf8e..0d972c5e2e0e6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -28,6 +28,8 @@ public class 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; + private static ErrorListener sErrorListener; public static int getConnectionStateSummary(int connectionState) { @@ -133,20 +135,16 @@ public class BluetoothUtils { final Pair pair = BluetoothUtils.getBtClassDrawableWithDescription( context, cachedDevice); final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); - final boolean untetheredHeadset = bluetoothDevice != null - ? Boolean.parseBoolean(bluetoothDevice.getMetadata( - BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)) - : false; + final boolean untetheredHeadset = getBooleanMetaData( + bluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET); final int iconSize = context.getResources().getDimensionPixelSize( R.dimen.bt_nearby_icon_size); final Resources resources = context.getResources(); // Deal with untethered headset if (untetheredHeadset) { - final String uriString = bluetoothDevice != null - ? bluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON) - : null; - final Uri iconUri = uriString != null ? Uri.parse(uriString) : null; + final Uri iconUri = getUriMetaData(bluetoothDevice, + BluetoothDevice.METADATA_MAIN_ICON); if (iconUri != null) { try { context.getContentResolver().takePersistableUriPermission(iconUri, @@ -194,4 +192,77 @@ public class BluetoothUtils { return adaptiveIcon; } + + /** + * 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); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 2405666af5008..ff34578d0fea5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -881,16 +881,12 @@ public class CachedBluetoothDevice implements Comparable //when profile is connected, information would be available if (profileConnected) { // Update Meta data for connected device - if (Boolean.parseBoolean( - mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET))) { - try { - leftBattery = Integer.parseInt( - mDevice.getMetadata(BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY)); - rightBattery = Integer.parseInt(mDevice.getMetadata( - BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY)); - } catch (NumberFormatException e) { - Log.d(TAG, "Parse error for unthethered battery level."); - } + if (BluetoothUtils.getBooleanMetaData( + mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + leftBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY); + rightBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY); } // Set default string with battery level in device connected situation. diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index b228cf7c10c6d..3a95852c0ba9c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -25,6 +25,7 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.util.Pair; import com.android.settingslib.widget.AdaptiveIcon; @@ -47,6 +48,9 @@ public class BluetoothUtilsTest { private BluetoothDevice mBluetoothDevice; private Context mContext; + private static final String STRING_METADATA = "string_metadata"; + private static final String BOOL_METADATA = "true"; + private static final String INT_METADATA = "25"; @Before public void setUp() { @@ -78,7 +82,7 @@ public class BluetoothUtilsTest { @Test public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() { when(mBluetoothDevice.getMetadata( - BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn("false"); + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes()); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); when(mCachedBluetoothDevice.getAddress()).thenReturn("1f:aa:bb"); @@ -86,4 +90,64 @@ public class BluetoothUtilsTest { RuntimeEnvironment.application, mCachedBluetoothDevice).first).isInstanceOf(AdaptiveIcon.class); } -} \ No newline at end of file + + @Test + public void getStringMetaData_hasMetaData_getCorrectMetaData() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).thenReturn( + STRING_METADATA.getBytes()); + + assertThat(BluetoothUtils.getStringMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).isEqualTo(STRING_METADATA); + } + + @Test + public void getIntMetaData_hasMetaData_getCorrectMetaData() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( + INT_METADATA.getBytes()); + + assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)) + .isEqualTo(Integer.parseInt(INT_METADATA)); + } + + @Test + public void getIntMetaData_invalidMetaData_getErrorCode() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(null); + + assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)) + .isEqualTo(BluetoothUtils.META_INT_ERROR); + } + + @Test + public void getBooleanMetaData_hasMetaData_getCorrectMetaData() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( + BOOL_METADATA.getBytes()); + + assertThat(BluetoothUtils.getBooleanMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).isEqualTo(true); + } + + @Test + public void getUriMetaData_hasMetaData_getCorrectMetaData() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_MAIN_ICON)).thenReturn( + STRING_METADATA.getBytes()); + + assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_MAIN_ICON)).isEqualTo(Uri.parse(STRING_METADATA)); + } + + @Test + public void getUriMetaData_nullMetaData_getNullUri() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null); + + assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice, + BluetoothDevice.METADATA_MAIN_ICON)).isNull(); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 79b84b9e58294..c0a1f11a0a876 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -455,12 +455,12 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); - when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn( - "true"); - when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY)).thenReturn( - TWS_BATTERY_LEFT); - when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY)).thenReturn( - TWS_BATTERY_RIGHT); + when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( + "true".getBytes()); + when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( + TWS_BATTERY_LEFT.getBytes()); + when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn( + TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo( "Active, L: 15% battery, R: 25% battery"); @@ -472,12 +472,12 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn( - "true"); - when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY)).thenReturn( - TWS_BATTERY_LEFT); - when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY)).thenReturn( - TWS_BATTERY_RIGHT); + when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( + "true".getBytes()); + when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( + TWS_BATTERY_LEFT.getBytes()); + when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn( + TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo( "L: 15% battery, R: 25% battery");