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
Merged-In: I79460071c7693f648e92cf849738c24f8bc269d9
diff --git a/api/system-current.txt b/api/system-current.txt
index f0de579..ec0caee 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -726,30 +726,29 @@
 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
@@ -759,21 +758,21 @@
     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 b8a741a..31bbd16 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -20,6 +20,7 @@
 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.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.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 @@
 
     private final Object mLock = new Object();
     private final Map<LeScanCallback, ScanCallback> mLeScanClients;
-    private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>>
+    private static final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>>
                 sMetadataListeners = new HashMap<>();
 
     /**
@@ -660,14 +661,15 @@
     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<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device);
-                    for (Pair<MetadataListener, Handler> pair : list) {
-                        MetadataListener listener = pair.first;
-                        Handler handler = pair.second;
-                        handler.post(() -> {
+                    List<Pair<OnMetadataChangedListener, Executor>> list =
+                            sMetadataListeners.get(device);
+                    for (Pair<OnMetadataChangedListener, Executor> pair : list) {
+                        OnMetadataChangedListener listener = pair.first;
+                        Executor executor = pair.second;
+                        executor.execute(() -> {
                             listener.onMetadataChanged(device, key, value);
                         });
                     }
@@ -3153,30 +3155,30 @@
     }
 
     /**
-     * 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 @@
         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<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device);
+            List<Pair<OnMetadataChangedListener, Executor>> 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 @@
                 }
             }
 
-            Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler);
+            Pair<OnMetadataChangedListener, Executor> listenerPair = new Pair(listener, executor);
             listenerList.add(listenerPair);
 
             boolean ret = false;
@@ -3230,63 +3233,74 @@
     }
 
     /**
-     * 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 6af1096..1e12801 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -18,6 +18,7 @@
 
 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 @@
 
     /**
      * Manufacturer name of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
      * @hide
      */
     @SystemApi
@@ -358,6 +360,7 @@
 
     /**
      * Model name of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
      * @hide
      */
     @SystemApi
@@ -365,6 +368,7 @@
 
     /**
      * Software version of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
      * @hide
      */
     @SystemApi
@@ -372,6 +376,7 @@
 
     /**
      * Hardware version of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
      * @hide
      */
     @SystemApi
@@ -379,6 +384,7 @@
 
     /**
      * Package name of the companion app, if any
+     * Data type should be {@String} as {@link Byte} array.
      * @hide
      */
     @SystemApi
@@ -386,6 +392,7 @@
 
     /**
      * URI to the main icon shown on the settings UI
+     * Data type should be {@link Byte} array.
      * @hide
      */
     @SystemApi
@@ -393,80 +400,91 @@
 
     /**
      * 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 @@
      * {@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 @@
      * 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");