Merge "Skeleton implementation of Bluetooth metadata APIs" am: 625a1f4461
am: 2f1cba20a5
Change-Id: If532d6e87a7d863f0802084f945b12406152d666
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -2026,4 +2157,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user