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

Commit 625a1f44 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Skeleton implementation of Bluetooth metadata APIs"

parents 0a905463 4a33b887
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -729,19 +729,46 @@ package android.bluetooth {
    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);
    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 class BluetoothAdapter.MetadataListener {
    ctor public BluetoothAdapter.MetadataListener();
    method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
  }
  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 @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
    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 setPhonebookAccessPermission(int);
    field public static final int ACCESS_ALLOWED = 1; // 0x1
    field public static final int ACCESS_REJECTED = 2; // 0x2
    field public static final int ACCESS_UNKNOWN = 0; // 0x0
    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_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
  }
  public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
+175 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ 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;
@@ -648,6 +649,32 @@ public final class BluetoothAdapter {

    private final Object mLock = new Object();
    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.
@@ -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() {
@@ -3090,4 +3127,142 @@ public final class BluetoothAdapter {
                    + "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 Diff line number Diff line
@@ -340,6 +340,137 @@ public final class BluetoothDevice implements Parcelable {
    public static final String 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}
     * 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");
        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;
        }
    }
}