Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d22fb8f0 authored by Ugo Yu's avatar Ugo Yu
Browse files

Skeleton implementation of Bluetooth metadata APIs

Bug: 121051445
Test: Build pass
Change-Id: I5e80210205b37294b1eb8356502ebf242e627ce4
parent 2d4e9092
Loading
Loading
Loading
Loading
+175 −0
Original line number Original line Diff line number Diff line
@@ -36,6 +36,7 @@ import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Context;
import android.os.BatteryStats;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.RemoteException;
@@ -648,6 +649,32 @@ public final class BluetoothAdapter {


    private final Object mLock = new Object();
    private final Object mLock = new Object();
    private final Map<LeScanCallback, ScanCallback> mLeScanClients;
    private final Map<LeScanCallback, ScanCallback> mLeScanClients;
    private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>>
                sMetadataListeners = new HashMap<>();

    /**
     * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener
     * implementation.
     */
    private static final IBluetoothMetadataListener sBluetoothMetadataListener =
            new IBluetoothMetadataListener.Stub() {
        @Override
        public void onMetadataChanged(BluetoothDevice device, int key, String 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(() -> {
                            listener.onMetadataChanged(device, key, value);
                        });
                    }
                }
            }
            return;
        }
    };


    /**
    /**
     * Get a handle to the default local Bluetooth adapter.
     * Get a handle to the default local Bluetooth adapter.
@@ -2607,6 +2634,16 @@ public final class BluetoothAdapter {
                            }
                            }
                        }
                        }
                    }
                    }
                    synchronized (sMetadataListeners) {
                        sMetadataListeners.forEach((device, pair) -> {
                            try {
                                mService.registerMetadataListener(sBluetoothMetadataListener,
                                        device);
                            } catch (RemoteException e) {
                                Log.e(TAG, "Failed to register metadata listener", e);
                            }
                        });
                    }
                }
                }


                public void onBluetoothServiceDown() {
                public void onBluetoothServiceDown() {
@@ -3090,4 +3127,142 @@ public final class BluetoothAdapter {
                    + "listenUsingInsecureL2capChannel");
                    + "listenUsingInsecureL2capChannel");
        return listenUsingInsecureL2capChannel();
        return listenUsingInsecureL2capChannel();
    }
    }

    /**
     * Register a {@link #MetadataListener} 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
     * 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}.
     *
     * @param device {@link BluetoothDevice} that will be registered
     * @param listener {@link #MetadataListener} that will receive asynchronous callbacks
     * @param handler the handler for listener callback
     * @return true on success, false on error
     * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler}
     * is null.
     * @throws IllegalArgumentException The same {@link #MetadataListener} 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()");

        final IBluetooth service = mService;
        if (service == null) {
            Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener");
            return false;
        }
        if (listener == null) {
            throw new NullPointerException("listener is null");
        }
        if (device == null) {
            throw new NullPointerException("device is null");
        }
        if (handler == null) {
            throw new NullPointerException("handler is null");
        }

        synchronized (sMetadataListeners) {
            List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device);
            if (listenerList == null) {
                // Create new listener/handler list for registeration
                listenerList = new ArrayList<>();
                sMetadataListeners.put(device, listenerList);
            } else {
                // Check whether this device was already registed by the lisenter
                if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) {
                    throw new IllegalArgumentException("listener was already regestered"
                            + " for the device");
                }
            }

            Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler);
            listenerList.add(listenerPair);

            boolean ret = false;
            try {
                ret = service.registerMetadataListener(sBluetoothMetadataListener, device);
            } catch (RemoteException e) {
                Log.e(TAG, "registerMetadataListener fail", e);
            } finally {
                if (!ret) {
                    // Remove listener registered earlier when fail.
                    listenerList.remove(listenerPair);
                    if (listenerList.isEmpty()) {
                        // Remove the device if its listener list is empty
                        sMetadataListeners.remove(device);
                    }
                }
            }
            return ret;
        }
    }

    /**
     * Unregister all {@link MetadataListener} from this {@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}.
     *
     * @param device {@link BluetoothDevice} 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 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()");
        if (device == null) {
            throw new NullPointerException("device is null");
        }

        synchronized (sMetadataListeners) {
            if (sMetadataListeners.containsKey(device)) {
                sMetadataListeners.remove(device);
            } else {
                throw new IllegalArgumentException("device was not registered");
            }

            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;
            }
        }
    }

    /**
     * This abstract class is used to implement {@link BluetoothAdapter} metadata listener.
     * @hide
     */
    @SystemApi
    public abstract class MetadataListener {
        /**
         * Callback triggered if the metadata of {@link BluetoothDevice} registered in
         * {@link #registerMetadataListener}.
         *
         * @param device changed {@link BluetoothDevice}.
         * @param key changed metadata key, one of BluetoothDevice.METADATA_*.
         * @param value the new value of metadata.
         */
        public void onMetadataChanged(BluetoothDevice device, int key, String value) {
        }
    }
}
}
+188 −0
Original line number Original line Diff line number Diff line
@@ -340,6 +340,137 @@ public final class BluetoothDevice implements Parcelable {
    public static final String ACTION_SDP_RECORD =
    public static final String ACTION_SDP_RECORD =
            "android.bluetooth.device.action.SDP_RECORD";
            "android.bluetooth.device.action.SDP_RECORD";


    /**
     * Maximum length of a metadata entry, this is to avoid exploding Bluetooth
     * disk usage
     * @hide
     */
    @SystemApi
    public static final int METADATA_MAX_LENGTH = 2048;

    /**
     * Manufacturer name of this Bluetooth device
     * @hide
     */
    @SystemApi
    public static final int METADATA_MANUFACTURER_NAME = 0;

    /**
     * Model name of this Bluetooth device
     * @hide
     */
    @SystemApi
    public static final int METADATA_MODEL_NAME = 1;

    /**
     * Software version of this Bluetooth device
     * @hide
     */
    @SystemApi
    public static final int METADATA_SOFTWARE_VERSION = 2;

    /**
     * Hardware version of this Bluetooth device
     * @hide
     */
    @SystemApi
    public static final int METADATA_HARDWARE_VERSION = 3;

    /**
     * Package name of the companion app, if any
     * @hide
     */
    @SystemApi
    public static final int METADATA_COMPANION_APP = 4;

    /**
     * URI to the main icon shown on the settings UI
     * @hide
     */
    @SystemApi
    public static final int METADATA_MAIN_ICON = 5;

    /**
     * Whether this device is an untethered headset with left, right and case
     * @hide
     */
    @SystemApi
    public static final int METADATA_IS_UNTHETHERED_HEADSET = 6;

    /**
     * URI to icon of the left headset
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_LEFT_ICON = 7;

    /**
     * URI to icon of the right headset
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8;

    /**
     * URI to icon of the headset charging case
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_CASE_ICON = 9;

    /**
     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
     * is invalid, of the left headset
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10;

    /**
     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
     * is invalid, of the right headset
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11;

    /**
     * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
     * is invalid, of the headset charging case
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12;

    /**
     * Whether the left headset is charging
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13;

    /**
     * Whether the right headset is charging
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14;

    /**
     * Whether the headset charging case is charging
     * @hide
     */
    @SystemApi
    public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15;

    /**
     * URI to the enhanced settings UI slice, null or empty String means
     * the UI does not exist
     * @hide
     */
    @SystemApi
    public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;

    /**
    /**
     * Broadcast Action: This intent is used to broadcast the {@link UUID}
     * Broadcast Action: This intent is used to broadcast the {@link UUID}
     * wrapped as a {@link android.os.ParcelUuid} of the remote device after it
     * wrapped as a {@link android.os.ParcelUuid} of the remote device after it
@@ -2028,4 +2159,61 @@ public final class BluetoothDevice implements Parcelable {
        Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel");
        Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel");
        return createInsecureL2capChannel(psm);
        return createInsecureL2capChannel(psm);
    }
    }

    /**
     * Set a keyed metadata of this {@link BluetoothDevice} to a
     * {@link String} value.
     * Only bonded devices's metadata will be persisted across Bluetooth
     * restart.
     * Metadata will be removed when the device's bond state is moved to
     * {@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
     * {@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) {
        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()
                    + ", should not over " + METADATA_MAX_LENGTH);
        }
        try {
            return service.setMetadata(this, key, value);
        } catch (RemoteException e) {
            Log.e(TAG, "setMetadata fail", e);
            return false;
        }
    }

    /**
     * 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
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
    public String getMetadata(int key) {
        final IBluetooth service = sService;
        if (service == null) {
            Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
            return null;
        }
        try {
            return service.getMetadata(this, key);
        } catch (RemoteException e) {
            Log.e(TAG, "getMetadata fail", e);
            return null;
        }
    }
}
}