Merge "Skeleton implementation of Bluetooth metadata APIs"

This commit is contained in:
Treehugger Robot
2019-01-23 07:38:06 +00:00
committed by Gerrit Code Review
3 changed files with 390 additions and 0 deletions

View File

@@ -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 {

View File

@@ -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) {
}
}
}

View File

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