summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack He <siyuanh@google.com>2022-02-23 16:58:03 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-02-23 16:58:03 +0000
commit3a88d77b08c87cb1517fa63cbdf7f99ca10100db (patch)
treec2c8d3e52f453cfd79776457c35d2935a698b93c
parent89b965d721c1b41755dc4935e576158d554714e5 (diff)
parent2e6d4b42917664491cd42bc0e13a2961ef058db4 (diff)
Merge changes I343ff48c,I4b1ecf81 am: 2e6d4b4291
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/1985329 Change-Id: Id92c7e06e2d4a4ecbbf63942a922613ce6484020
-rw-r--r--android/app/src/com/android/bluetooth/btservice/PhonePolicy.java10
-rw-r--r--android/app/src/com/android/bluetooth/btservice/ServiceFactory.java5
-rw-r--r--android/app/src/com/android/bluetooth/gatt/GattService.java3
-rw-r--r--android/app/src/com/android/bluetooth/hap/HapClientService.java765
-rw-r--r--android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java43
-rw-r--r--android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java8
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java513
-rw-r--r--framework/java/android/bluetooth/BluetoothHapClient.java677
-rw-r--r--framework/java/android/bluetooth/BluetoothUuid.java3
-rw-r--r--system/binder/Android.bp1
-rw-r--r--system/binder/android/bluetooth/IBluetoothHapClient.aidl44
-rw-r--r--system/binder/android/bluetooth/IBluetoothHapClientCallback.aidl39
-rw-r--r--system/bta/has/has_client.cc12
-rw-r--r--system/bta/has/has_types.h2
-rw-r--r--system/btif/src/btif_dm.cc3
-rw-r--r--system/include/hardware/bt_has.h7
16 files changed, 1221 insertions, 914 deletions
diff --git a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
index 50aff4ae61..f1cef7af66 100644
--- a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -43,6 +43,7 @@ import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.hap.HapClientService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hid.HidHostService;
@@ -296,6 +297,7 @@ class PhonePolicy {
mFactory.getCsipSetCoordinatorService();
VolumeControlService volumeControlService =
mFactory.getVolumeControlService();
+ HapClientService hapClientService = mFactory.getHapClientService();
// Set profile priorities only for the profiles discovered on the remote device.
// This avoids needless auto-connect attempts to profiles non-existent on the remote device
@@ -364,6 +366,14 @@ class PhonePolicy {
mAdapterService.getDatabase().setProfileConnectionPolicy(device,
BluetoothProfile.VOLUME_CONTROL, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
+
+ if ((hapClientService != null) && Utils.arrayContains(uuids,
+ BluetoothUuid.HAS) && (hapClientService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
+ debugLog("setting hearing access profile priority for device " + device);
+ mAdapterService.getDatabase().setProfileConnectionPolicy(device,
+ BluetoothProfile.HAP_CLIENT, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ }
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
diff --git a/android/app/src/com/android/bluetooth/btservice/ServiceFactory.java b/android/app/src/com/android/bluetooth/btservice/ServiceFactory.java
index 6842dc0de5..f9256ad2a1 100644
--- a/android/app/src/com/android/bluetooth/btservice/ServiceFactory.java
+++ b/android/app/src/com/android/bluetooth/btservice/ServiceFactory.java
@@ -19,6 +19,7 @@ package com.android.bluetooth.btservice;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.avrcp.AvrcpTargetService;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.hap.HapClientService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hid.HidDeviceService;
@@ -73,4 +74,8 @@ public class ServiceFactory {
public VolumeControlService getVolumeControlService() {
return VolumeControlService.getVolumeControlService();
}
+
+ public HapClientService getHapClientService() {
+ return HapClientService.getHapClientService();
+ }
}
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index 1c9c6d44f4..2f32315d27 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -168,8 +168,7 @@ public class GattService extends ProfileService {
UUID.fromString("00001850-0000-1000-8000-00805F9B34FB"), // PACS
UUID.fromString("0000184E-0000-1000-8000-00805F9B34FB"), // ASCS
UUID.fromString("0000184F-0000-1000-8000-00805F9B34FB"), // BASS
- /* FIXME: Not known yet, using a placeholder instead. */
- UUID.fromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE"), // HAP
+ UUID.fromString("00001854-0000-1000-8000-00805F9B34FB"), // HAP
};
/**
diff --git a/android/app/src/com/android/bluetooth/hap/HapClientService.java b/android/app/src/com/android/bluetooth/hap/HapClientService.java
index dca14bfdc7..941ec29095 100644
--- a/android/app/src/com/android/bluetooth/hap/HapClientService.java
+++ b/android/app/src/com/android/bluetooth/hap/HapClientService.java
@@ -20,14 +20,18 @@ package com.android.bluetooth.hap;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
+
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHapPresetInfo;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothHapClient;
+import android.bluetooth.IBluetoothHapClientCallback;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -35,6 +39,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.HandlerThread;
import android.os.ParcelUuid;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.util.Log;
import com.android.bluetooth.Utils;
@@ -48,6 +54,7 @@ import com.android.modules.utils.SynchronousResultReceiver;
import java.math.BigInteger;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
@@ -64,6 +71,7 @@ public class HapClientService extends ProfileService {
// Upper limit of all HearingAccess devices: Bonded or Connected
private static final int MAX_HEARING_ACCESS_STATE_MACHINES = 10;
+ private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000;
private static HapClientService sHapClient;
private final Map<BluetoothDevice, HapClientStateMachine> mStateMachines =
new HashMap<>();
@@ -76,10 +84,13 @@ public class HapClientService extends ProfileService {
private final Map<BluetoothDevice, Integer> mDeviceCurrentPresetMap = new HashMap<>();
private final Map<BluetoothDevice, Integer> mDeviceFeaturesMap = new HashMap<>();
- private final Map<BluetoothDevice, ArrayList<BluetoothHapPresetInfo>> mPresetsMap =
+ private final Map<BluetoothDevice, List<BluetoothHapPresetInfo>> mPresetsMap =
new HashMap<>();
@VisibleForTesting
+ RemoteCallbackList<IBluetoothHapClientCallback> mCallbacks;
+
+ @VisibleForTesting
ServiceFactory mFactory = new ServiceFactory();
private static synchronized void setHapClient(HapClientService instance) {
@@ -148,10 +159,6 @@ public class HapClientService extends ProfileService {
mStateMachinesThread = new HandlerThread("HapClientService.StateMachines");
mStateMachinesThread.start();
- mDeviceCurrentPresetMap.clear();
- mDeviceFeaturesMap.clear();
- mPresetsMap.clear();
-
// Setup broadcast receivers
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
@@ -162,12 +169,14 @@ public class HapClientService extends ProfileService {
mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
registerReceiver(mConnectionStateChangedReceiver, filter);
- // Mark service as started
- setHapClient(this);
+ mCallbacks = new RemoteCallbackList<IBluetoothHapClientCallback>();
// Initialize native interface
mHapClientNativeInterface.init();
+ // Mark service as started
+ setHapClient(this);
+
return true;
}
@@ -181,10 +190,6 @@ public class HapClientService extends ProfileService {
return true;
}
- // Cleanup GATT interface
- mHapClientNativeInterface.cleanup();
- mHapClientNativeInterface = null;
-
// Marks service as stopped
setHapClient(null);
@@ -203,20 +208,29 @@ public class HapClientService extends ProfileService {
mStateMachines.clear();
}
- mDeviceCurrentPresetMap.clear();
- mDeviceFeaturesMap.clear();
- mPresetsMap.clear();
-
if (mStateMachinesThread != null) {
try {
mStateMachinesThread.quitSafely();
- mStateMachinesThread.join();
+ mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS);
mStateMachinesThread = null;
} catch (InterruptedException e) {
// Do not rethrow as we are shutting down anyway
}
}
+ // Cleanup GATT interface
+ mHapClientNativeInterface.cleanup();
+ mHapClientNativeInterface = null;
+
+ // Cleanup the internals
+ mDeviceCurrentPresetMap.clear();
+ mDeviceFeaturesMap.clear();
+ mPresetsMap.clear();
+
+ if (mCallbacks != null) {
+ mCallbacks.kill();
+ }
+
// Clear AdapterService
mAdapterService = null;
@@ -345,8 +359,7 @@ public class HapClientService extends ProfileService {
* @return true on success, otherwise false
*/
public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH_PRIVILEGED permission");
+ enforceBluetoothPrivilegedPermission(this);
if (DBG) {
Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
@@ -437,8 +450,7 @@ public class HapClientService extends ProfileService {
* @return true if hearing access service client successfully connected, false otherwise
*/
public boolean connect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH_PRIVILEGED permission");
+ enforceBluetoothPrivilegedPermission(this);
if (DBG) {
Log.d(TAG, "connect(): " + device);
}
@@ -474,8 +486,7 @@ public class HapClientService extends ProfileService {
* @return true if hearing access service client successfully disconnected, false otherwise
*/
public boolean disconnect(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH_PRIVILEGED permission");
+ enforceBluetoothPrivilegedPermission(this);
if (DBG) {
Log.d(TAG, "disconnect(): " + device);
}
@@ -542,12 +553,33 @@ public class HapClientService extends ProfileService {
* Gets the currently active preset index for a HA device
*
* @param device is the device for which we want to get the currently active preset
- * @return true if valid request was sent, false otherwise
+ * @return active preset index
*/
- public boolean getActivePresetIndex(BluetoothDevice device) {
- notifyActivePresetIndex(device, mDeviceCurrentPresetMap.getOrDefault(device,
- BluetoothHapClient.PRESET_INDEX_UNAVAILABLE));
- return true;
+ public int getActivePresetIndex(BluetoothDevice device) {
+ return mDeviceCurrentPresetMap.getOrDefault(device,
+ BluetoothHapClient.PRESET_INDEX_UNAVAILABLE);
+ }
+
+ /**
+ * Gets the currently active preset info for a HA device
+ *
+ * @param device is the device for which we want to get the currently active preset info
+ * @return active preset info
+ */
+ public BluetoothHapPresetInfo getActivePresetInfo(BluetoothDevice device) {
+ int index = getActivePresetIndex(device);
+ if (index == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return null;
+
+ List<BluetoothHapPresetInfo> current_presets = mPresetsMap.get(device);
+ if (current_presets != null) {
+ for (BluetoothHapPresetInfo preset : current_presets) {
+ if (preset.getIndex() == index) {
+ return preset;
+ }
+ }
+ }
+
+ return null;
}
/**
@@ -555,12 +587,25 @@ public class HapClientService extends ProfileService {
*
* @param device is the device for which we want to set the active preset
* @param presetIndex is an index of one of the available presets
- * @return true if valid request was sent, false otherwise
*/
- public boolean selectActivePreset(BluetoothDevice device, int presetIndex) {
- if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return false;
+ public void selectPreset(BluetoothDevice device, int presetIndex) {
+ if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSelectActivePresetFailed(device,
+ BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ return;
+ }
+
mHapClientNativeInterface.selectActivePreset(device, presetIndex);
- return true;
}
/**
@@ -568,64 +613,70 @@ public class HapClientService extends ProfileService {
*
* @param groupId is the device group identifier for which want to set the active preset
* @param presetIndex is an index of one of the available presets
- * @return true if valid group request was sent, false otherwise
*/
- public boolean groupSelectActivePreset(int groupId, int presetIndex) {
- if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE
- || groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- return false;
+ public void selectPresetForGroup(int groupId, int presetIndex) {
+ int status = BluetoothStatusCodes.ERROR_UNKNOWN;
+
+ if ((presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE)
+ || (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID)) {
+ if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
+ status = BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX;
+ } else {
+ status = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID;
+ }
+
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSelectActivePresetForGroupFailed(groupId,
+ BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ return;
}
mHapClientNativeInterface.groupSelectActivePreset(groupId, presetIndex);
- return true;
}
/**
* Sets the next preset as a currently active preset for a HA device
*
* @param device is the device for which we want to set the active preset
- * @return true if valid request was sent, false otherwise
*/
- public boolean nextActivePreset(BluetoothDevice device) {
+ public void switchToNextPreset(BluetoothDevice device) {
mHapClientNativeInterface.nextActivePreset(device);
- return true;
}
/**
* Sets the next preset as a currently active preset for a HA device group
*
* @param groupId is the device group identifier for which want to set the active preset
- * @return true if valid group request was sent, false otherwise
*/
- public boolean groupNextActivePreset(int groupId) {
- if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return false;
-
+ public void switchToNextPresetForGroup(int groupId) {
mHapClientNativeInterface.groupNextActivePreset(groupId);
- return true;
}
/**
* Sets the previous preset as a currently active preset for a HA device
*
* @param device is the device for which we want to set the active preset
- * @return true if valid request was sent, false otherwise
*/
- public boolean previousActivePreset(BluetoothDevice device) {
+ public void switchToPreviousPreset(BluetoothDevice device) {
mHapClientNativeInterface.previousActivePreset(device);
- return true;
}
/**
* Sets the previous preset as a currently active preset for a HA device group
*
* @param groupId is the device group identifier for which want to set the active preset
- * @return true if valid group request was sent, false otherwise
*/
- public boolean groupPreviousActivePreset(int groupId) {
- if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return false;
-
+ public void switchToPreviousPresetForGroup(int groupId) {
mHapClientNativeInterface.groupPreviousActivePreset(groupId);
- return true;
}
/**
@@ -633,92 +684,270 @@ public class HapClientService extends ProfileService {
*
* @param device is the device for which we want to get the preset name
* @param presetIndex is an index of one of the available presets
- * @return true if valid request was sent, false otherwise
+ * @return a preset Info corresponding to the requested preset index
*/
- public boolean getPresetInfo(BluetoothDevice device, int presetIndex) {
- if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return false;
- mHapClientNativeInterface.getPresetInfo(device, presetIndex);
- return true;
+ public BluetoothHapPresetInfo getPresetInfo(BluetoothDevice device, int presetIndex) {
+ BluetoothHapPresetInfo defaultValue = new BluetoothHapPresetInfo.Builder().build();
+
+ if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return defaultValue;
+
+ List<BluetoothHapPresetInfo> current_presets = mPresetsMap.get(device);
+ if (current_presets != null) {
+ for (BluetoothHapPresetInfo preset : current_presets) {
+ if (preset.getIndex() == presetIndex) {
+ return preset;
+ }
+ }
+ }
+
+ return defaultValue;
}
/**
* Requests all presets info
*
* @param device is the device for which we want to get all presets info
- * @return true if request was processed, false otherwise
+ * @return a list of all presets Info
*/
- public boolean getAllPresetsInfo(BluetoothDevice device) {
+ public List<BluetoothHapPresetInfo> getAllPresetInfo(BluetoothDevice device) {
if (mPresetsMap.containsKey(device)) {
- notifyPresets(device, BluetoothHapClient.PRESET_INFO_REASON_ALL_PRESET_INFO,
- mPresetsMap.get(device));
- return true;
+ return mPresetsMap.get(device);
}
- return false;
+ return Collections.emptyList();
}
/**
* Requests features
*
* @param device is the device for which we want to get features
- * @return true if request was processed, false otherwise
+ * @return integer with feature bits set
*/
- public boolean getFeatures(BluetoothDevice device) {
+ public int getFeatures(BluetoothDevice device) {
if (mDeviceFeaturesMap.containsKey(device)) {
- notifyFeatures(device, mDeviceFeaturesMap.get(device));
- return true;
+ return mDeviceFeaturesMap.get(device);
}
- return false;
+ return 0x00;
}
- private void notifyPresets(BluetoothDevice device, int infoReason,
- ArrayList<BluetoothHapPresetInfo> presets) {
- Intent intent = null;
+ private int stackEventPresetInfoReasonToProfileStatus(int statusCode) {
+ switch (statusCode) {
+ case HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO:
+ return BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST;
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE:
+ return BluetoothStatusCodes.REASON_REMOTE_REQUEST;
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_DELETED:
+ return BluetoothStatusCodes.REASON_REMOTE_REQUEST;
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED:
+ return BluetoothStatusCodes.REASON_REMOTE_REQUEST;
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE:
+ return BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST;
+ default:
+ return BluetoothStatusCodes.ERROR_UNKNOWN;
+ }
+ }
- intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_PRESET_INFO);
- if (intent != null) {
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INFO_REASON, infoReason);
- intent.putParcelableArrayListExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INFO, presets);
- sendBroadcast(intent, BLUETOOTH_PRIVILEGED);
+ private void notifyPresetInfoChanged(BluetoothDevice device, int infoReason) {
+ List current_presets = mPresetsMap.get(device);
+ if (current_presets == null) return;
+
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onPresetInfoChanged(device, current_presets,
+ stackEventPresetInfoReasonToProfileStatus(infoReason));
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
}
}
- private void notifyPresets(int groupId, int infoReason,
- ArrayList<BluetoothHapPresetInfo> presets) {
- Intent intent = null;
+ private void notifyPresetInfoForGroupChanged(int groupId, int infoReason) {
+ List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
+ for (BluetoothDevice dev : all_group_devices) {
+ notifyPresetInfoChanged(dev, infoReason);
+ }
+ }
- intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_PRESET_INFO);
- if (intent != null) {
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_GROUP_ID, groupId);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INFO_REASON, infoReason);
- intent.putParcelableArrayListExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INFO, presets);
- sendBroadcast(intent, BLUETOOTH_PRIVILEGED);
+ private void notifyFeaturesAvailable(BluetoothDevice device, int features) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onHapFeaturesAvailable(device, features);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
}
}
- private void notifyFeatures(BluetoothDevice device, int features) {
- Intent intent = null;
+ private void notifyActivePresetChanged(BluetoothDevice device, int presetIndex) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onActivePresetChanged(device, presetIndex);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ }
- intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_DEVICE_FEATURES);
- if (intent != null) {
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, features);
- sendBroadcast(intent, BLUETOOTH_PRIVILEGED);
+ private void notifyActivePresetChangedForGroup(int groupId, int presetIndex) {
+ List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
+ for (BluetoothDevice dev : all_group_devices) {
+ notifyActivePresetChanged(dev, presetIndex);
+ }
+ }
+
+ private int stackEventStatusToProfileStatus(int statusCode) {
+ switch (statusCode) {
+ case HapClientStackEvent.STATUS_SET_NAME_NOT_ALLOWED:
+ return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED;
+ case HapClientStackEvent.STATUS_OPERATION_NOT_SUPPORTED:
+ return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED;
+ case HapClientStackEvent.STATUS_OPERATION_NOT_POSSIBLE:
+ return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED;
+ case HapClientStackEvent.STATUS_INVALID_PRESET_NAME_LENGTH:
+ return BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG;
+ case HapClientStackEvent.STATUS_INVALID_PRESET_INDEX:
+ return BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX;
+ case HapClientStackEvent.STATUS_GROUP_OPERATION_NOT_SUPPORTED:
+ return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED;
+ case HapClientStackEvent.STATUS_PROCEDURE_ALREADY_IN_PROGRESS:
+ return BluetoothStatusCodes.ERROR_UNKNOWN;
+ default:
+ return BluetoothStatusCodes.ERROR_UNKNOWN;
+ }
+ }
+
+ private void notifySelectActivePresetFailed(BluetoothDevice device, int statusCode) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSelectActivePresetFailed(device,
+ stackEventStatusToProfileStatus(statusCode));
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifySelectActivePresetForGroupFailed(int groupId, int statusCode) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSelectActivePresetForGroupFailed(groupId,
+ stackEventStatusToProfileStatus(statusCode));
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
}
}
+ private void notifySetPresetNameFailed(BluetoothDevice device, int statusCode) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSetPresetNameFailed(device,
+ stackEventStatusToProfileStatus(statusCode));
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifySetPresetNameForGroupFailed(int groupId, int statusCode) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSetPresetNameForGroupFailed(groupId,
+ stackEventStatusToProfileStatus(statusCode));
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ }
+
+ private boolean isPresetIndexValid(BluetoothDevice device, int presetIndex) {
+ if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return false;
+
+ List<BluetoothHapPresetInfo> device_presets = mPresetsMap.get(device);
+ if (device_presets != null) {
+ for (BluetoothHapPresetInfo preset : device_presets) {
+ if (preset.getIndex() == presetIndex) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isPresetIndexValid(int groupId, int presetIndex) {
+ List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
+ for (BluetoothDevice device : all_group_devices) {
+ if (!isPresetIndexValid(device, presetIndex)) return false;
+ }
+ return true;
+ }
+
+
+ private boolean isGroupIdValid(int groupId) {
+ if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return false;
+
+ CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
+ if (csipClient != null) {
+ List<Integer> groups = csipClient.getAllGroupIds(BluetoothUuid.CAP);
+ return groups.contains(groupId);
+ }
+ return true;
+ }
+
/**
* Sets the preset name
*
* @param device is the device for which we want to get the preset name
* @param presetIndex is an index of one of the available presets
* @param name is a new name for a preset
- * @return true if valid request was sent, false otherwise
*/
- public boolean setPresetName(BluetoothDevice device, int presetIndex, String name) {
- if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return false;
+ public void setPresetName(BluetoothDevice device, int presetIndex, String name) {
+ if (!isPresetIndexValid(device, presetIndex)) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSetPresetNameFailed(device,
+ BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ return;
+ }
+ // WARNING: We should check cache if preset exists and is writable, but then we would still
+ // need a way to trigger this action with an invalid index or on a non-writable
+ // preset for tests purpose.
mHapClientNativeInterface.setPresetName(device, presetIndex, name);
- return true;
}
/**
@@ -727,13 +956,33 @@ public class HapClientService extends ProfileService {
* @param groupId is the device group identifier
* @param presetIndex is an index of one of the available presets
* @param name is a new name for a preset
- * @return true if valid request was sent, false otherwise
*/
- public boolean groupSetPresetName(int groupId, int presetIndex, String name) {
- if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return false;
- if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return false;
+ public void setPresetNameForGroup(int groupId, int presetIndex, String name) {
+ int status = BluetoothStatusCodes.SUCCESS;
+
+ if (!isGroupIdValid(groupId)) {
+ status = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID;
+ }
+ if (!isPresetIndexValid(groupId, presetIndex)) {
+ status = BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX;
+ }
+ if (status != BluetoothStatusCodes.SUCCESS) {
+ if (mCallbacks != null) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onSetPresetNameForGroupFailed(groupId,
+ status);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ return;
+ }
+
mHapClientNativeInterface.groupSetPresetName(groupId, presetIndex, name);
- return true;
}
@Override
@@ -750,30 +999,16 @@ public class HapClientService extends ProfileService {
HapClientStackEvent.FEATURE_BIT_NUM_SYNCHRONIZATED_PRESETS);
}
- void notifyActivePresetIndex(BluetoothDevice device, int presetIndex) {
- Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, presetIndex);
- sendBroadcast(intent, BLUETOOTH_PRIVILEGED);
- }
-
- void notifyActivePresetIndex(int groupId, int presetIndex) {
- Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_GROUP_ID, groupId);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, presetIndex);
- sendBroadcast(intent, BLUETOOTH_PRIVILEGED);
- }
-
void updateDevicePresetsCache(BluetoothDevice device, int infoReason,
- ArrayList<BluetoothHapPresetInfo> presets) {
+ List<BluetoothHapPresetInfo> presets) {
switch (infoReason) {
- case BluetoothHapClient.PRESET_INFO_REASON_ALL_PRESET_INFO:
+ case HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO:
mPresetsMap.put(device, presets);
break;
- case BluetoothHapClient.PRESET_INFO_REASON_PRESET_INFO_UPDATE:
- case BluetoothHapClient.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED:
- case BluetoothHapClient.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE: {
- ArrayList current_presets = mPresetsMap.get(device);
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE:
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED:
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE: {
+ List current_presets = mPresetsMap.get(device);
if (current_presets != null) {
ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator();
for (BluetoothHapPresetInfo new_preset : presets) {
@@ -791,8 +1026,8 @@ public class HapClientService extends ProfileService {
}
break;
- case BluetoothHapClient.PRESET_INFO_REASON_PRESET_DELETED: {
- ArrayList current_presets = mPresetsMap.get(device);
+ case HapClientStackEvent.PRESET_INFO_REASON_PRESET_DELETED: {
+ List current_presets = mPresetsMap.get(device);
if (current_presets != null) {
ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator();
for (BluetoothHapPresetInfo new_preset : presets) {
@@ -812,6 +1047,19 @@ public class HapClientService extends ProfileService {
}
}
+ private List<BluetoothDevice> getGroupDevices(int groupId) {
+ List<BluetoothDevice> devices = new ArrayList<>();
+
+ // TODO: Fix missing CSIS service API to decouple from LeAudioService
+ LeAudioService le_audio_service = mFactory.getLeAudioService();
+ if (le_audio_service != null) {
+ if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
+ devices = le_audio_service.getGroupDevices(groupId);
+ }
+ }
+ return devices;
+ }
+
/**
* Handle messages from native (JNI) to Java
*
@@ -845,7 +1093,7 @@ public class HapClientService extends ProfileService {
if (device != null) {
mDeviceFeaturesMap.put(device, features);
- notifyFeatures(device, features);
+ notifyFeaturesAvailable(device, features);
}
} return;
@@ -855,36 +1103,25 @@ public class HapClientService extends ProfileService {
if (device != null) {
mDeviceCurrentPresetMap.put(device, currentPresetIndex);
- notifyActivePresetIndex(device, currentPresetIndex);
+ notifyActivePresetChanged(device, currentPresetIndex);
} else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- // TODO: Fix missing CSIS service API to decouple from LeAudioService
- LeAudioService le_audio_service = mFactory.getLeAudioService();
- if (le_audio_service != null) {
- int group_id = le_audio_service.getGroupId(device);
- if (group_id != BluetoothLeAudio.GROUP_ID_INVALID) {
- List<BluetoothDevice> all_group_devices =
- le_audio_service.getGroupDevices(group_id);
- for (BluetoothDevice dev : all_group_devices) {
- mDeviceCurrentPresetMap.put(dev, currentPresetIndex);
- }
- }
+ List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
+ for (BluetoothDevice dev : all_group_devices) {
+ mDeviceCurrentPresetMap.put(dev, currentPresetIndex);
}
- notifyActivePresetIndex(groupId, currentPresetIndex);
+ notifyActivePresetChangedForGroup(groupId, currentPresetIndex);
}
} return;
case (HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECT_ERROR): {
- int statusCode = stackEvent.valueInt1;
int groupId = stackEvent.valueInt2;
-
- intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET_SELECT_ERROR);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_STATUS_CODE, statusCode);
+ int statusCode = stackEvent.valueInt1;
if (device != null) {
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ notifySelectActivePresetFailed(device, statusCode);
} else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_GROUP_ID, groupId);
+ notifySelectActivePresetForGroupFailed(groupId, statusCode);
}
} break;
@@ -896,22 +1133,14 @@ public class HapClientService extends ProfileService {
if (device != null) {
updateDevicePresetsCache(device, infoReason, presets);
- notifyPresets(device, infoReason, presets);
+ notifyPresetInfoChanged(device, infoReason);
} else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- // TODO: Fix missing CSIS service API to decouple from LeAudioService
- LeAudioService le_audio_service = mFactory.getLeAudioService();
- if (le_audio_service != null) {
- int group_id = le_audio_service.getGroupId(device);
- if (group_id != BluetoothLeAudio.GROUP_ID_INVALID) {
- List<BluetoothDevice> all_group_devices =
- le_audio_service.getGroupDevices(group_id);
- for (BluetoothDevice dev : all_group_devices) {
- updateDevicePresetsCache(dev, infoReason, presets);
- }
- }
+ List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
+ for (BluetoothDevice dev : all_group_devices) {
+ updateDevicePresetsCache(dev, infoReason, presets);
}
- notifyPresets(groupId, infoReason, presets);
+ notifyPresetInfoForGroupChanged(groupId, infoReason);
}
} return;
@@ -919,33 +1148,18 @@ public class HapClientService extends ProfileService {
case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_NAME_SET_ERROR): {
int statusCode = stackEvent.valueInt1;
int presetIndex = stackEvent.valueInt2;
-
- intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_PRESET_NAME_SET_ERROR);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, presetIndex);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_STATUS_CODE, statusCode);
+ int groupId = stackEvent.valueInt3;
if (device != null) {
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- } else {
- int groupId = stackEvent.valueInt3;
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_GROUP_ID, groupId);
+ notifySetPresetNameFailed(device, statusCode);
+ } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ notifySetPresetNameForGroupFailed(groupId, statusCode);
}
} break;
case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO_ERROR): {
- int statusCode = stackEvent.valueInt1;
- int presetIndex = stackEvent.valueInt2;
-
- intent = new Intent(BluetoothHapClient.ACTION_HAP_ON_PRESET_INFO_GET_ERROR);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, presetIndex);
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_STATUS_CODE, statusCode);
-
- if (device != null) {
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- } else {
- int groupId = stackEvent.valueInt3;
- intent.putExtra(BluetoothHapClient.EXTRA_HAP_GROUP_ID, groupId);
- }
+ // Used only to report back on hidden API calls used for testing.
+ Log.d(TAG, stackEvent.toString());
} break;
default:
@@ -1014,36 +1228,6 @@ public class HapClientService extends ProfileService {
}
@Override
- public void connect(BluetoothDevice device, AttributionSource source,
- SynchronousResultReceiver receiver) {
- try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.connect(device);
- }
- receiver.send(defaultValue);
- } catch (RuntimeException e) {
- receiver.propagateException(e);
- }
- }
-
- @Override
- public void disconnect(BluetoothDevice device, AttributionSource source,
- SynchronousResultReceiver receiver) {
- try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.disconnect(device);
- }
- receiver.send(defaultValue);
- } catch (RuntimeException e) {
- receiver.propagateException(e);
- }
- }
-
- @Override
public void getConnectedDevices(AttributionSource source,
SynchronousResultReceiver receiver) {
try {
@@ -1122,7 +1306,7 @@ public class HapClientService extends ProfileService {
public void getActivePresetIndex(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
- boolean defaultValue = false;
+ int defaultValue = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
HapClientService service = getService(source);
if (service != null) {
defaultValue = service.getActivePresetIndex(device);
@@ -1134,13 +1318,13 @@ public class HapClientService extends ProfileService {
}
@Override
- public void getHapGroup(BluetoothDevice device, AttributionSource source,
- SynchronousResultReceiver receiver) {
+ public void getActivePresetInfo(BluetoothDevice device,
+ AttributionSource source, SynchronousResultReceiver receiver) {
try {
- int defaultValue = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ BluetoothHapPresetInfo defaultValue = null;
HapClientService service = getService(source);
if (service != null) {
- defaultValue = service.getHapGroup(device);
+ defaultValue = service.getActivePresetInfo(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
@@ -1149,13 +1333,13 @@ public class HapClientService extends ProfileService {
}
@Override
- public void selectActivePreset(BluetoothDevice device, int presetIndex,
- AttributionSource source, SynchronousResultReceiver receiver) {
+ public void getHapGroup(BluetoothDevice device, AttributionSource source,
+ SynchronousResultReceiver receiver) {
try {
- boolean defaultValue = false;
+ int defaultValue = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
HapClientService service = getService(source);
if (service != null) {
- defaultValue = service.selectActivePreset(device, presetIndex);
+ defaultValue = service.getHapGroup(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
@@ -1164,76 +1348,51 @@ public class HapClientService extends ProfileService {
}
@Override
- public void groupSelectActivePreset(int groupId, int presetIndex,
- AttributionSource source, SynchronousResultReceiver receiver) {
- try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.groupSelectActivePreset(groupId, presetIndex);
- }
- receiver.send(defaultValue);
- } catch (RuntimeException e) {
- receiver.propagateException(e);
+ public void selectPreset(BluetoothDevice device, int presetIndex,
+ AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.selectPreset(device, presetIndex);
}
}
@Override
- public void nextActivePreset(BluetoothDevice device, AttributionSource source,
- SynchronousResultReceiver receiver) {
- try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.nextActivePreset(device);
- }
- receiver.send(defaultValue);
- } catch (RuntimeException e) {
- receiver.propagateException(e);
+ public void selectPresetForGroup(int groupId, int presetIndex, AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.selectPresetForGroup(groupId, presetIndex);
}
}
@Override
- public void groupNextActivePreset(int groupId, AttributionSource source,
- SynchronousResultReceiver receiver) {
- try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.groupNextActivePreset(groupId);
- }
- receiver.send(defaultValue);
- } catch (RuntimeException e) {
- receiver.propagateException(e);
+ public void switchToNextPreset(BluetoothDevice device, AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.switchToNextPreset(device);
}
}
@Override
- public void previousActivePreset(BluetoothDevice device, AttributionSource source,
- SynchronousResultReceiver receiver) {
- try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.previousActivePreset(device);
- }
- receiver.send(defaultValue);
- } catch (RuntimeException e) {
- receiver.propagateException(e);
+ public void switchToNextPresetForGroup(int groupId, AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.switchToNextPresetForGroup(groupId);
}
}
@Override
- public void groupPreviousActivePreset(int groupId, AttributionSource source, SynchronousResultReceiver receiver) {
- try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.groupPreviousActivePreset(groupId);
- }
- receiver.send(defaultValue);
- } catch (RuntimeException e) {
- receiver.propagateException(e);
+ public void switchToPreviousPreset(BluetoothDevice device, AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.switchToPreviousPreset(device);
+ }
+ }
+
+ @Override
+ public void switchToPreviousPresetForGroup(int groupId, AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.switchToPreviousPresetForGroup(groupId);
}
}
@@ -1241,7 +1400,7 @@ public class HapClientService extends ProfileService {
public void getPresetInfo(BluetoothDevice device, int presetIndex,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
- boolean defaultValue = false;
+ BluetoothHapPresetInfo defaultValue = new BluetoothHapPresetInfo.Builder().build();
HapClientService service = getService(source);
if (service != null) {
defaultValue = service.getPresetInfo(device, presetIndex);
@@ -1253,12 +1412,13 @@ public class HapClientService extends ProfileService {
}
@Override
- public void getAllPresetsInfo(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) {
+ public void getAllPresetInfo(BluetoothDevice device, AttributionSource source,
+ SynchronousResultReceiver receiver) {
try {
- boolean defaultValue = false;
+ List<BluetoothHapPresetInfo> defaultValue = new ArrayList<>();
HapClientService service = getService(source);
if (service != null) {
- defaultValue = service.getAllPresetsInfo(device);
+ defaultValue = service.getAllPresetInfo(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
@@ -1267,9 +1427,10 @@ public class HapClientService extends ProfileService {
}
@Override
- public void getFeatures(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) {
+ public void getFeatures(BluetoothDevice device, AttributionSource source,
+ SynchronousResultReceiver receiver) {
try {
- boolean defaultValue = false;
+ int defaultValue = 0x00;
HapClientService service = getService(source);
if (service != null) {
defaultValue = service.getFeatures(device);
@@ -1282,29 +1443,51 @@ public class HapClientService extends ProfileService {
@Override
public void setPresetName(BluetoothDevice device, int presetIndex, String name,
+ AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.setPresetName(device, presetIndex, name);
+ }
+ }
+
+ @Override
+ public void setPresetNameForGroup(int groupId, int presetIndex, String name,
+ AttributionSource source) {
+ HapClientService service = getService(source);
+ if (service != null) {
+ service.setPresetNameForGroup(groupId, presetIndex, name);
+ }
+ }
+
+ @Override
+ public void registerCallback(IBluetoothHapClientCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
+ HapClientService service = getService(source);
+ if (service == null) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.setPresetName(device, presetIndex, name);
- }
- receiver.send(defaultValue);
+ service.mCallbacks.register(callback);
+ receiver.send(null);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
- public void groupSetPresetName(int groupId, int presetIndex, String name,
+ public void unregisterCallback(IBluetoothHapClientCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
+ HapClientService service = getService(source);
+ if (service == null) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
try {
- boolean defaultValue = false;
- HapClientService service = getService(source);
- if (service != null) {
- defaultValue = service.groupSetPresetName(groupId, presetIndex, name);
- }
- receiver.send(defaultValue);
+ service.mCallbacks.unregister(callback);
+ receiver.send(null);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
diff --git a/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java b/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java
index 278bb1a06f..e322f505f0 100644
--- a/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java
+++ b/android/app/src/com/android/bluetooth/hap/HapClientStackEvent.java
@@ -47,20 +47,15 @@ public class HapClientStackEvent {
static final int CONNECTION_STATE_DISCONNECTING = 3;
// Possible operation results
- static final int STATUS_SET_NAME_NOT_ALLOWED =
- IBluetoothHapClient.STATUS_SET_NAME_NOT_ALLOWED;
- static final int STATUS_OPERATION_NOT_SUPPORTED =
- IBluetoothHapClient.STATUS_OPERATION_NOT_SUPPORTED;
- static final int STATUS_OPERATION_NOT_POSSIBLE =
- IBluetoothHapClient.STATUS_OPERATION_NOT_POSSIBLE;
- static final int STATUS_INVALID_PRESET_NAME_LENGTH =
- IBluetoothHapClient.STATUS_INVALID_PRESET_NAME_LENGTH;
- static final int STATUS_INVALID_PRESET_INDEX =
- IBluetoothHapClient.STATUS_INVALID_PRESET_INDEX;
- static final int STATUS_GROUP_OPERATION_NOT_SUPPORTED =
- IBluetoothHapClient.STATUS_GROUP_OPERATION_NOT_SUPPORTED;
- static final int STATUS_PROCEDURE_ALREADY_IN_PROGRESS =
- IBluetoothHapClient.STATUS_PROCEDURE_ALREADY_IN_PROGRESS;
+ /* WARNING: Matches status codes defined in bta_has.h */
+ static final int STATUS_NO_ERROR = 0;
+ static final int STATUS_SET_NAME_NOT_ALLOWED = 1;
+ static final int STATUS_OPERATION_NOT_SUPPORTED = 2;
+ static final int STATUS_OPERATION_NOT_POSSIBLE = 3;
+ static final int STATUS_INVALID_PRESET_NAME_LENGTH = 4;
+ static final int STATUS_INVALID_PRESET_INDEX = 5;
+ static final int STATUS_GROUP_OPERATION_NOT_SUPPORTED = 6;
+ static final int STATUS_PROCEDURE_ALREADY_IN_PROGRESS = 7;
// Supported features
public static final int FEATURE_BIT_NUM_TYPE_MONAURAL =
@@ -77,16 +72,12 @@ public class HapClientStackEvent {
IBluetoothHapClient.FEATURE_BIT_NUM_WRITABLE_PRESETS;
// Preset Info notification reason
- public static final int PRESET_INFO_REASON_ALL_PRESET_INFO =
- IBluetoothHapClient.PRESET_INFO_REASON_ALL_PRESET_INFO;
- public static final int PRESET_INFO_REASON_PRESET_INFO_UPDATE =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_INFO_UPDATE;
- public static final int PRESET_INFO_REASON_PRESET_DELETED =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_DELETED;
- public static final int PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED;
- public static final int PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE;
+ /* WARNING: Matches status codes defined in bta_has.h */
+ public static final int PRESET_INFO_REASON_ALL_PRESET_INFO = 0;
+ public static final int PRESET_INFO_REASON_PRESET_INFO_UPDATE = 1;
+ public static final int PRESET_INFO_REASON_PRESET_DELETED = 2;
+ public static final int PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED = 3;
+ public static final int PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE = 4;
public int type;
public BluetoothDevice device;
@@ -207,6 +198,8 @@ public class HapClientStackEvent {
private String statusCodeValueToString(int value) {
switch (value) {
+ case STATUS_NO_ERROR:
+ return "STATUS_NO_ERROR";
case STATUS_SET_NAME_NOT_ALLOWED:
return "STATUS_SET_NAME_NOT_ALLOWED";
case STATUS_OPERATION_NOT_SUPPORTED:
@@ -222,7 +215,7 @@ public class HapClientStackEvent {
case STATUS_PROCEDURE_ALREADY_IN_PROGRESS:
return "STATUS_PROCEDURE_ALREADY_IN_PROGRESS";
default:
- return "UNKNOWN_STATUS_CODE";
+ return "ERROR_UNKNOWN";
}
}
diff --git a/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java b/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java
index 7f10decb19..62c9dd2d14 100644
--- a/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hap/HapClientStateMachine.java
@@ -238,7 +238,7 @@ final class HapClientStateMachine extends StateMachine {
@Override
public boolean processMessage(Message message) {
- log("Disconnected process message(" + mDevice + "): " + messageWhatToString(
+ log("Disconnected: process message(" + mDevice + "): " + messageWhatToString(
message.what));
switch (message.what) {
@@ -343,7 +343,7 @@ final class HapClientStateMachine extends StateMachine {
@Override
public boolean processMessage(Message message) {
- log("Connecting process message(" + mDevice + "): "
+ log("Connecting: process message(" + mDevice + "): "
+ messageWhatToString(message.what));
switch (message.what) {
@@ -430,7 +430,7 @@ final class HapClientStateMachine extends StateMachine {
@Override
public boolean processMessage(Message message) {
- log("Disconnecting process message(" + mDevice + "): "
+ log("Disconnecting: process message(" + mDevice + "): "
+ messageWhatToString(message.what));
switch (message.what) {
@@ -530,7 +530,7 @@ final class HapClientStateMachine extends StateMachine {
@Override
public boolean processMessage(Message message) {
- log("Connected process message(" + mDevice + "): "
+ log("Connected: process message(" + mDevice + "): "
+ messageWhatToString(message.what));
switch (message.what) {
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
index 7589ff24ae..6d2f61b240 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
@@ -24,8 +24,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Binder;
import android.os.Looper;
import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
@@ -39,6 +42,7 @@ import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.le_audio.LeAudioService;
import org.junit.After;
import org.junit.Assert;
@@ -47,13 +51,16 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
@@ -69,9 +76,9 @@ public class HapClientTest {
private BluetoothDevice mDevice3;
private Context mTargetContext;
private HapClientService mService;
- private IBluetoothHapClient.Stub mServiceBinder;
private HasIntentReceiver mHasIntentReceiver;
private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mIntentQueue;
+
@Mock
private AdapterService mAdapterService;
@Mock
@@ -82,6 +89,12 @@ public class HapClientTest {
private ServiceFactory mServiceFactory;
@Mock
private CsipSetCoordinatorService mCsipService;
+ @Mock
+ private LeAudioService mLeAudioService;
+ @Mock
+ private IBluetoothHapClientCallback mCallback;
+ @Mock
+ private Binder mBinder;
@Before
public void setUp() throws Exception {
@@ -96,6 +109,8 @@ public class HapClientTest {
Looper.prepare();
}
+ HapClientStateMachine.sConnectTimeoutMs = TIMEOUT_MS;
+
TestUtils.setAdapterService(mAdapterService);
doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
@@ -106,18 +121,15 @@ public class HapClientTest {
mService.mHapClientNativeInterface = mNativeInterface;
mService.mFactory = mServiceFactory;
doReturn(mCsipService).when(mServiceFactory).getCsipSetCoordinatorService();
- mServiceBinder = (IBluetoothHapClient.Stub) mService.initBinder();
- Assert.assertNotNull(mServiceBinder);
+ doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService();
// Set up the State Changed receiver
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE);
- filter.addAction(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET);
- filter.addAction(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET_SELECT_ERROR);
- filter.addAction(BluetoothHapClient.ACTION_HAP_ON_PRESET_INFO);
- filter.addAction(BluetoothHapClient.ACTION_HAP_ON_PRESET_NAME_SET_ERROR);
- filter.addAction(BluetoothHapClient.ACTION_HAP_ON_PRESET_INFO_GET_ERROR);
+
+ when(mCallback.asBinder()).thenReturn(mBinder);
+ mService.mCallbacks.register(mCallback);
mHasIntentReceiver = new HasIntentReceiver();
mTargetContext.registerReceiver(mHasIntentReceiver, filter);
@@ -129,6 +141,27 @@ public class HapClientTest {
mDevice3 = TestUtils.getTestDevice(mAdapter, 2);
when(mNativeInterface.getDevice(getByteAddress(mDevice3))).thenReturn(mDevice3);
+ /* Prepare CAS groups */
+ doReturn(Arrays.asList(0x02, 0x03)).when(mCsipService).getAllGroupIds(BluetoothUuid.CAP);
+
+ int groupId2 = 0x02;
+ Map groups2 = new HashMap<Integer, ParcelUuid>();
+ groups2.put(groupId2, ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"));
+
+ int groupId3 = 0x03;
+ Map groups3 = new HashMap<Integer, ParcelUuid>();
+ groups3.put(groupId3,
+ ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"));
+
+ doReturn(Arrays.asList(mDevice, mDevice2)).when(mLeAudioService).getGroupDevices(groupId2);
+ doReturn(groups2).when(mCsipService).getGroupUuidMapByDevice(mDevice);
+ doReturn(groups2).when(mCsipService).getGroupUuidMapByDevice(mDevice2);
+
+ doReturn(Arrays.asList(mDevice3)).when(mLeAudioService).getGroupDevices(0x03);
+ doReturn(groups3).when(mCsipService).getGroupUuidMapByDevice(mDevice3);
+
+ doReturn(Arrays.asList(mDevice)).when(mLeAudioService).getGroupDevices(0x01);
+
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
@@ -147,8 +180,12 @@ public class HapClientTest {
R.bool.profile_supported_hap_client)) {
return;
}
+ mService.mCallbacks.unregister(mCallback);
+
stopService();
mTargetContext.unregisterReceiver(mHasIntentReceiver);
+
+ mAdapter = null;
TestUtils.clearAdapterService(mAdapterService);
mIntentQueue.clear();
}
@@ -169,7 +206,7 @@ public class HapClientTest {
* Test getting HA Service Client
*/
@Test
- public void testGetHearingAidService() {
+ public void testGetHapService() {
Assert.assertEquals(mService, HapClientService.getHapClientService());
}
@@ -177,7 +214,7 @@ public class HapClientTest {
* Test stop HA Service Client
*/
@Test
- public void testStopHearingAidService() {
+ public void testStopHapService() {
Assert.assertEquals(mService, HapClientService.getHapClientService());
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@@ -371,14 +408,8 @@ public class HapClientTest {
*/
@Test
public void testCallsForNotConnectedDevice() {
- Assert.assertEquals(true, mService.getActivePresetIndex(mDevice));
-
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET, intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(BluetoothHapClient.PRESET_INDEX_UNAVAILABLE,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
+ mService.getActivePresetIndex(mDevice));
}
/**
@@ -395,51 +426,45 @@ public class HapClientTest {
int flags = 0x04;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
- int flags2 = 0x04;
- mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice2), flags);
+ int flags3 = 0x04;
+ mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice3), flags);
/* This one has no coordinated operation support but is part of a coordinated set with
* mDevice, which supports it, thus mDevice will forward the operation to mDevice2.
* This device should also be rocognised as grouped one.
*/
- int flags3 = 0;
- mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice3), flags3);
+ int flags2 = 0;
+ mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice2), flags2);
- /* Prepare CAS groups */
- int base_group_id = 0x03;
- Map groups = new HashMap<Integer, ParcelUuid>();
- groups.put(base_group_id, ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"));
-
- Map groups2 = new HashMap<Integer, ParcelUuid>();
- groups2.put(base_group_id + 1,
- ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"));
-
- doReturn(groups).when(mCsipService).getGroupUuidMapByDevice(mDevice);
- doReturn(groups).when(mCsipService).getGroupUuidMapByDevice(mDevice3);
- doReturn(groups2).when(mCsipService).getGroupUuidMapByDevice(mDevice2);
-
- /* Two devices support coordinated operations thus shell report valid group ID */
- Assert.assertEquals(base_group_id, mService.getHapGroup(mDevice));
- Assert.assertEquals(base_group_id + 1, mService.getHapGroup(mDevice2));
+ /* Two devices support coordinated operations thus shall report valid group ID */
+ Assert.assertEquals(2, mService.getHapGroup(mDevice));
+ Assert.assertEquals(3, mService.getHapGroup(mDevice3));
/* Third one has no coordinated operations support but is part of the group */
- Assert.assertEquals(base_group_id, mService.getHapGroup(mDevice3));
+ Assert.assertEquals(2, mService.getHapGroup(mDevice2));
}
/**
- * Test that selectActivePreset properly calls the native method.
+ * Test that selectPreset properly calls the native method.
*/
@Test
- public void testSelectActivePresetNative() {
+ public void testSelectPresetNative() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
// Verify Native Interface call
- Assert.assertFalse(mService.selectActivePreset(mDevice, 0x00));
+ mService.selectPreset(mDevice, 0x00);
verify(mNativeInterface, times(0))
.selectActivePreset(eq(mDevice), eq(0x00));
- Assert.assertTrue(mService.selectActivePreset(mDevice, 0x01));
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSelectActivePresetFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ mService.selectPreset(mDevice, 0x01);
verify(mNativeInterface, times(1))
.selectActivePreset(eq(mDevice), eq(0x01));
}
@@ -455,8 +480,15 @@ public class HapClientTest {
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
// Verify Native Interface call
- Assert.assertFalse(mService.groupSelectActivePreset(0x03, 0x00));
- Assert.assertTrue(mService.groupSelectActivePreset(0x03, 0x01));
+ mService.selectPresetForGroup(0x03, 0x00);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSelectActivePresetForGroupFailed(
+ eq(0x03), eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ mService.selectPresetForGroup(0x03, 0x01);
verify(mNativeInterface, times(1))
.groupSelectActivePreset(eq(0x03), eq(0x01));
}
@@ -465,13 +497,13 @@ public class HapClientTest {
* Test that nextActivePreset properly calls the native method.
*/
@Test
- public void testNextActivePresetNative() {
+ public void testSwitchToNextPreset() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
// Verify Native Interface call
- Assert.assertTrue(mService.nextActivePreset(mDevice));
+ mService.switchToNextPreset(mDevice);
verify(mNativeInterface, times(1))
.nextActivePreset(eq(mDevice));
}
@@ -480,14 +512,14 @@ public class HapClientTest {
* Test that groupNextActivePreset properly calls the native method.
*/
@Test
- public void testGroupNextActivePresetNative() {
+ public void testSwitchToNextPresetForGroup() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
int flags = 0x01;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
// Verify Native Interface call
- Assert.assertTrue(mService.groupNextActivePreset(0x03));
+ mService.switchToNextPresetForGroup(0x03);
verify(mNativeInterface, times(1)).groupNextActivePreset(eq(0x03));
}
@@ -495,13 +527,13 @@ public class HapClientTest {
* Test that previousActivePreset properly calls the native method.
*/
@Test
- public void testPreviousActivePresetNative() {
+ public void testSwitchToPreviousPreset() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
// Verify Native Interface call
- Assert.assertTrue(mService.previousActivePreset(mDevice));
+ mService.switchToPreviousPreset(mDevice);
verify(mNativeInterface, times(1))
.previousActivePreset(eq(mDevice));
}
@@ -510,7 +542,7 @@ public class HapClientTest {
* Test that groupPreviousActivePreset properly calls the native method.
*/
@Test
- public void testGroupPreviousActivePresetNative() {
+ public void testSwitchToPreviousPresetForGroup() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
@@ -520,7 +552,7 @@ public class HapClientTest {
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
// Verify Native Interface call
- Assert.assertTrue(mService.groupPreviousActivePreset(0x03));
+ mService.switchToPreviousPresetForGroup(0x03);
verify(mNativeInterface, times(1)).groupPreviousActivePreset(eq(0x03));
}
@@ -532,39 +564,38 @@ public class HapClientTest {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
- testOnActivePresetSelected(mDevice, 0x01);
+ testOnActivePresetChanged(mDevice, 0x01);
// Verify cached value
- Assert.assertEquals(true, mService.getActivePresetIndex(mDevice));
-
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET, intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(0x01,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
+ Assert.assertEquals(0x01, mService.getActivePresetIndex(mDevice));
}
/**
- * Test that getPresetInfo properly calls the native method.
+ * Test that getActivePresetInfo returns cached value for valid parameters.
*/
@Test
- public void testGetPresetInfoNative() {
+ public void testGetActivePresetInfo() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
- testConnectingDevice(mDevice);
+ testConnectingDevice(mDevice2);
- // Verify Native Interface call
- Assert.assertFalse(mService.getPresetInfo(mDevice, 0x00));
- verify(mNativeInterface, times(0))
- .getPresetInfo(eq(mDevice), eq(0x00));
- Assert.assertTrue(mService.getPresetInfo(mDevice, 0x01));
- verify(mNativeInterface, times(1))
- .getPresetInfo(eq(mDevice), eq(0x01));
+ // Check when active preset is not known yet
+ Assert.assertEquals(BluetoothHapClient.PRESET_INDEX_UNAVAILABLE,
+ mService.getActivePresetIndex(mDevice2));
+ Assert.assertEquals(null, mService.getActivePresetInfo(mDevice2));
+
+ // Inject active preset change event
+ testOnActivePresetChanged(mDevice2, 0x01);
+
+ // Check when active preset is known
+ Assert.assertEquals(0x01, mService.getActivePresetIndex(mDevice2));
+ BluetoothHapPresetInfo info = mService.getActivePresetInfo(mDevice2);
+ Assert.assertNotNull(info);
+ Assert.assertEquals(0x01, info.getIndex());
}
/**
- * Test that setPresetName properly calls the native method.
+ * Test that setPresetName properly calls the native method for the valid parameters.
*/
@Test
public void testSetPresetNameNative() {
@@ -572,31 +603,57 @@ public class HapClientTest {
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
- // Verify Native Interface call
- Assert.assertFalse(mService.setPresetName(mDevice, 0x00, "ExamplePresetName"));
+ mService.setPresetName(mDevice, 0x00, "ExamplePresetName");
verify(mNativeInterface, times(0))
.setPresetName(eq(mDevice), eq(0x00), eq("ExamplePresetName"));
- Assert.assertTrue(mService.setPresetName(mDevice, 0x01, "ExamplePresetName"));
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ // Verify Native Interface call
+ mService.setPresetName(mDevice, 0x01, "ExamplePresetName");
verify(mNativeInterface, times(1))
.setPresetName(eq(mDevice), eq(0x01), eq("ExamplePresetName"));
}
/**
- * Test that groupSetPresetName properly calls the native method.
+ * Test that setPresetNameForGroup properly calls the native method for the valid parameters.
*/
@Test
- public void testGroupSetPresetName() {
+ public void testSetPresetNameForGroup() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
+ int test_group = 0x02;
+ for (BluetoothDevice device : mLeAudioService.getGroupDevices(test_group)) {
+ testConnectingDevice(device);
+ }
+
int flags = 0x21;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
+ mService.setPresetNameForGroup(test_group, 0x00, "ExamplePresetName");
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(eq(test_group),
+ eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ mService.setPresetNameForGroup(-1, 0x01, "ExamplePresetName");
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(eq(-1),
+ eq(BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
// Verify Native Interface call
- Assert.assertFalse(mService.groupSetPresetName(0x03, 0x00, "ExamplePresetName"));
- Assert.assertFalse(mService.groupSetPresetName(-1, 0x01, "ExamplePresetName"));
- Assert.assertTrue(mService.groupSetPresetName(0x03, 0x01, "ExamplePresetName"));
+ mService.setPresetNameForGroup(test_group, 0x01, "ExamplePresetName");
verify(mNativeInterface, times(1))
- .groupSetPresetName(eq(0x03), eq(0x01), eq("ExamplePresetName"));
+ .groupSetPresetName(eq(test_group), eq(0x01), eq("ExamplePresetName"));
}
/**
@@ -607,153 +664,233 @@ public class HapClientTest {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
- // Verify getting current preset return an invalid value when there is no such device
- // available
- Assert.assertEquals(true, mService.getActivePresetIndex(mDevice));
-
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET, intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(BluetoothHapClient.PRESET_INDEX_UNAVAILABLE,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
-
mNativeInterface.onDeviceAvailable(getByteAddress(mDevice), 0x03);
- intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
+ Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
Assert.assertNotNull(intent);
Assert.assertEquals(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE, intent.getAction());
Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(0x03,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, 0x03));
+ intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, 0x00));
}
/**
- * Test that native callback generates proper intent.
+ * Test that native callback generates proper callback call.
*/
@Test
- public void testStackEventOnActivePresetSelected() {
+ public void testStackEventOnFeaturesUpdate() {
+ doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
+ .getRemoteUuids(any(BluetoothDevice.class));
+
+ mNativeInterface.onDeviceAvailable(getByteAddress(mDevice), 0x00);
+ mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), 0x03);
+
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onHapFeaturesAvailable(eq(mDevice),
+ eq(0x03));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Test that native callback generates proper callback call.
+ */
+ @Test
+ public void testStackEventOnActivePresetChanged() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
mNativeInterface.onActivePresetSelected(getByteAddress(mDevice), 0x01);
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET, intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(0x01,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onActivePresetChanged(eq(mDevice),
+ eq(0x01));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
// Verify that getting current preset returns a proper value now
- Assert.assertEquals(true, mService.getActivePresetIndex(mDevice));
-
- intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET, intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(0x01,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
+ Assert.assertEquals(0x01, mService.getActivePresetIndex(mDevice));
}
/**
- * Test that native callback generates proper intent.
+ * Test that native callback generates proper callback call.
*/
@Test
- public void testStackEventOnCurrentPresetSelectError() {
+ public void testStackEventOnActivePresetSelectError() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
- mNativeInterface.onActivePresetSelectError(getByteAddress(mDevice),
- BluetoothHapClient.STATUS_INVALID_PRESET_INDEX);
+ /* Send INVALID_PRESET_INDEX error */
+ mNativeInterface.onActivePresetSelectError(getByteAddress(mDevice), 0x05);
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET_SELECT_ERROR,
- intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(BluetoothHapClient.STATUS_INVALID_PRESET_INDEX,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_STATUS_CODE, -1));
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSelectActivePresetFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
- * Test that native callback generates proper intent.
+ * Test that native callback generates proper callback call.
*/
@Test
public void testStackEventOnPresetInfo() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
- int info_reason = BluetoothHapClient.PRESET_INFO_REASON_PRESET_INFO_UPDATE;
+ // Connect and inject initial presets
+ testConnectingDevice(mDevice);
+
+ int info_reason = HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE;
BluetoothHapPresetInfo[] info =
{new BluetoothHapPresetInfo.Builder()
.setIndex(0x01)
- .setName("PresetName")
+ .setName("OneChangedToUnavailable")
.setWritable(true)
- .setAvailable(true)
+ .setAvailable(false)
.build()};
mNativeInterface.onPresetInfo(getByteAddress(mDevice), info_reason, info);
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_PRESET_INFO, intent.getAction());
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(info_reason,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INFO_REASON, -1));
+ ArgumentCaptor<List<BluetoothHapPresetInfo>> presetsCaptor =
+ ArgumentCaptor.forClass(List.class);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onPresetInfoChanged(eq(mDevice),
+ presetsCaptor.capture(), eq(BluetoothStatusCodes.REASON_REMOTE_REQUEST));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
- ArrayList presets =
- intent.getParcelableArrayListExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INFO);
- Assert.assertNotNull(presets);
+ List<BluetoothHapPresetInfo> presets = presetsCaptor.getValue();
+ Assert.assertEquals(3, presets.size());
- BluetoothHapPresetInfo preset = (BluetoothHapPresetInfo) presets.get(0);
- Assert.assertEquals(preset.getIndex(), info[0].getIndex());
- Assert.assertEquals(preset.getName(), info[0].getName());
- Assert.assertEquals(preset.isWritable(), info[0].isWritable());
- Assert.assertEquals(preset.isAvailable(), info[0].isAvailable());
+ Optional<BluetoothHapPresetInfo> preset = presetsCaptor.getValue()
+ .stream()
+ .filter(p -> 0x01 == p.getIndex())
+ .findFirst();
+ Assert.assertEquals("OneChangedToUnavailable", preset.get().getName());
+ Assert.assertFalse(preset.get().isAvailable());
+ Assert.assertTrue(preset.get().isWritable());
}
/**
- * Test that native callback generates proper intent.
+ * Test that native callback generates proper callback call.
*/
@Test
public void testStackEventOnPresetNameSetError() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
+ /* Not a valid name length */
mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
- BluetoothHapClient.STATUS_SET_NAME_NOT_ALLOWED);
+ HapClientStackEvent.STATUS_INVALID_PRESET_NAME_LENGTH);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_PRESET_NAME_SET_ERROR,
- intent.getAction());
- Assert.assertEquals(0x01,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(BluetoothHapClient.STATUS_SET_NAME_NOT_ALLOWED,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_STATUS_CODE, -1));
+ /* Invalid preset index provided */
+ mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
+ HapClientStackEvent.STATUS_INVALID_PRESET_INDEX);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ /* Not allowed on this particular preset */
+ mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
+ HapClientStackEvent.STATUS_SET_NAME_NOT_ALLOWED);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ /* Not allowed on this particular preset at this time, might be possible later on */
+ mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
+ HapClientStackEvent.STATUS_OPERATION_NOT_POSSIBLE);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(2)).onSetPresetNameFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ /* Not allowed on all presets - for example missing characteristic */
+ mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
+ HapClientStackEvent.STATUS_OPERATION_NOT_SUPPORTED);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
+ eq(BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
- * Test that native callback generates proper intent.
+ * Test that native callback generates proper callback call.
*/
@Test
- public void testStackEventOnPresetInfoError() {
+ public void testStackEventOnGroupPresetNameSetError() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
- mNativeInterface.onPresetInfoError(getByteAddress(mDevice), 0x01,
- BluetoothHapClient.STATUS_INVALID_PRESET_INDEX);
+ /* Not a valid name length */
+ mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
+ HapClientStackEvent.STATUS_INVALID_PRESET_NAME_LENGTH);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
+ BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_PRESET_INFO_GET_ERROR,
- intent.getAction());
- Assert.assertEquals(0x01,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
- Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(BluetoothHapClient.STATUS_INVALID_PRESET_INDEX,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_STATUS_CODE, -1));
+ /* Invalid preset index provided */
+ mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
+ HapClientStackEvent.STATUS_INVALID_PRESET_INDEX);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
+ BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ /* Not allowed on this particular preset */
+ mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
+ HapClientStackEvent.STATUS_SET_NAME_NOT_ALLOWED);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
+ BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ /* Not allowed on this particular preset at this time, might be possible later on */
+ mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
+ HapClientStackEvent.STATUS_OPERATION_NOT_POSSIBLE);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(2)).onSetPresetNameForGroupFailed(0x01,
+ BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ /* Not allowed on all presets - for example if peer is missing optional CP characteristic */
+ mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
+ HapClientStackEvent.STATUS_OPERATION_NOT_SUPPORTED);
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
+ BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -812,21 +949,57 @@ public class HapClientTest {
Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(evt.valueInt1,
intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, -1));
+
+ evt = new HapClientStackEvent(HapClientStackEvent.EVENT_TYPE_DEVICE_FEATURES);
+ evt.device = device;
+ evt.valueInt1 = 0x01; // features
+ mService.messageFromNative(evt);
+
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onHapFeaturesAvailable(eq(device),
+ eq(evt.valueInt1));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ // Inject some initial presets
+ List<BluetoothHapPresetInfo> presets =
+ new ArrayList<BluetoothHapPresetInfo>(Arrays.asList(
+ new BluetoothHapPresetInfo.Builder()
+ .setIndex(0x01)
+ .setName("One")
+ .setAvailable(true)
+ .setWritable(false)
+ .build(),
+ new BluetoothHapPresetInfo.Builder()
+ .setIndex(0x02)
+ .setName("Two")
+ .setAvailable(true)
+ .setWritable(true)
+ .build(),
+ new BluetoothHapPresetInfo.Builder()
+ .setIndex(0x03)
+ .setName("Three")
+ .setAvailable(false)
+ .setWritable(false)
+ .build()));
+ mService.updateDevicePresetsCache(device,
+ HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO, presets);
}
- private void testOnActivePresetSelected(BluetoothDevice device, int index) {
+ private void testOnActivePresetChanged(BluetoothDevice device, int index) {
HapClientStackEvent evt =
new HapClientStackEvent(HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECTED);
evt.device = device;
evt.valueInt1 = index;
mService.messageFromNative(evt);
- Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(device));
- Assert.assertNotNull(intent);
- Assert.assertEquals(BluetoothHapClient.ACTION_HAP_ON_ACTIVE_PRESET, intent.getAction());
- Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- Assert.assertEquals(index,
- intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_PRESET_INDEX, -1));
+ try {
+ verify(mCallback, after(TIMEOUT_MS).times(1)).onActivePresetChanged(eq(device),
+ eq(evt.valueInt1));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/framework/java/android/bluetooth/BluetoothHapClient.java b/framework/java/android/bluetooth/BluetoothHapClient.java
index 2b245bcb41..fd769d412b 100644
--- a/framework/java/android/bluetooth/BluetoothHapClient.java
+++ b/framework/java/android/bluetooth/BluetoothHapClient.java
@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.content.AttributionSource;
@@ -39,7 +40,9 @@ import com.android.modules.utils.SynchronousResultReceiver;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
@@ -58,6 +61,8 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
private static final boolean DBG = false;
private static final boolean VDBG = false;
+ private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>();
+
private CloseGuard mCloseGuard;
/**
@@ -83,17 +88,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
@interface Status {}
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {
- PRESET_INFO_REASON_ALL_PRESET_INFO,
- PRESET_INFO_REASON_PRESET_INFO_UPDATE,
- PRESET_INFO_REASON_PRESET_DELETED,
- PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED,
- PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE,
- })
- @interface PresetInfoReason {}
-
/**
* Invoked to inform about HA device's currently active preset.
*
@@ -180,6 +174,87 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
void onSetPresetNameForGroupFailed(int hapGroupId, @Status int status);
}
+ @SuppressLint("AndroidFrameworkBluetoothPermission")
+ private final IBluetoothHapClientCallback mCallback = new IBluetoothHapClientCallback.Stub() {
+ @Override
+ public void onActivePresetChanged(@NonNull BluetoothDevice device, int presetIndex) {
+ Attributable.setAttributionSource(device, mAttributionSource);
+ for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onActivePresetChanged(device, presetIndex));
+ }
+ }
+
+ @Override
+ public void onSelectActivePresetFailed(@NonNull BluetoothDevice device, int status) {
+ Attributable.setAttributionSource(device, mAttributionSource);
+ for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onSelectActivePresetFailed(device, status));
+ }
+ }
+
+ @Override
+ public void onSelectActivePresetForGroupFailed(int hapGroupId, int statusCode) {
+ for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(
+ () -> callback.onSelectActivePresetForGroupFailed(hapGroupId, statusCode));
+ }
+ }
+
+ @Override
+ public void onPresetInfoChanged(@NonNull BluetoothDevice device,
+ @NonNull List<BluetoothHapPresetInfo> presetInfoList, int statusCode) {
+ Attributable.setAttributionSource(device, mAttributionSource);
+ for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(
+ () -> callback.onPresetInfoChanged(device, presetInfoList, statusCode));
+ }
+ }
+
+ @Override
+ public void onHapFeaturesAvailable(@NonNull BluetoothDevice device, int hapFeatures) {
+ Attributable.setAttributionSource(device, mAttributionSource);
+ for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onHapFeaturesAvailable(device, hapFeatures));
+ }
+ }
+
+ @Override
+ public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int status) {
+ Attributable.setAttributionSource(device, mAttributionSource);
+ for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onSetPresetNameFailed(device, status));
+ }
+ }
+
+ @Override
+ public void onSetPresetNameForGroupFailed(int hapGroupId, int status) {
+ for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onSetPresetNameForGroupFailed(hapGroupId, status));
+ }
+ }
+ };
+
/**
* Intent used to broadcast the change in connection state of the Hearing Access Profile Client
* service. Please note that in the binaural case, there will be two different LE devices for
@@ -229,235 +304,12 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
"android.bluetooth.action.HAP_DEVICE_AVAILABLE";
/**
- * Intent used to broadcast HA device's feature set.
- *
- * <p>This intent will have 2 extras:
- * <ul>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * <li> {@link #EXTRA_HAP_FEATURES}- The feature set integer with these possible bit numbers
- * set: {@link #FEATURE_BIT_NUM_TYPE_MONAURAL}, {@link #FEATURE_BIT_NUM_TYPE_BANDED},
- * {@link #FEATURE_BIT_NUM_SYNCHRONIZATED_PRESETS},
- * {@link #FEATURE_BIT_NUM_INDEPENDENT_PRESETS}, {@link #FEATURE_BIT_NUM_DYNAMIC_PRESETS},
- * {@link #FEATURE_BIT_NUM_WRITABLE_PRESETS}.</li>
- * </ul>
- *
- * @hide
- */
- @RequiresPermission(allOf = {
- android.Manifest.permission.BLUETOOTH_CONNECT,
- android.Manifest.permission.BLUETOOTH_PRIVILEGED,
- })
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_HAP_ON_DEVICE_FEATURES =
- "android.bluetooth.action.HAP_ON_DEVICE_FEATURES";
-
- /**
- * Intent used to broadcast the change of a HA device's active preset.
- *
- * <p>This intent will have 2 extras:
- * <ul>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * <li> {@link #EXTRA_HAP_PRESET_INDEX}- The currently active preset.</li>
- * </ul>
- *
- * @hide
- */
- @RequiresPermission(allOf = {
- android.Manifest.permission.BLUETOOTH_CONNECT,
- android.Manifest.permission.BLUETOOTH_PRIVILEGED,
- })
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_HAP_ON_ACTIVE_PRESET =
- "android.bluetooth.action.HAP_ON_ACTIVE_PRESET";
-
- /**
- * Intent used to broadcast the result of a failed preset change attempt.
- *
- * <p>This intent will have 2 extras:
- * <ul>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * <li> {@link #EXTRA_HAP_STATUS_CODE}- Failure reason.</li>
- * </ul>
- *
- * <p>{@link #EXTRA_HAP_STATUS_CODE} can be any of {@link #STATUS_INVALID_PRESET_INDEX},
- * {@link #STATUS_OPERATION_NOT_POSSIBLE},{@link #STATUS_OPERATION_NOT_SUPPORTED}.
- *
- * @hide
- */
- @RequiresPermission(allOf = {
- android.Manifest.permission.BLUETOOTH_CONNECT,
- android.Manifest.permission.BLUETOOTH_PRIVILEGED,
- })
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_HAP_ON_ACTIVE_PRESET_SELECT_ERROR =
- "android.bluetooth.action.HAP_ON_ACTIVE_PRESET_SELECT_ERROR";
-
- /**
- * Intent used to broadcast preset name change.
- *
- * <p>This intent will have 4 extras:
- * <ul>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * <li> {@link #EXTRA_HAP_PRESET_INFO}- List of preset informations </li>
- * <li> {@link #EXTRA_HAP_PRESET_INFO_REASON}- Why this preset info notification was sent </li>
- * notifications or the user should expect more to come. </li>
- * </ul>
- *
- * @hide
- */
- @RequiresPermission(allOf = {
- android.Manifest.permission.BLUETOOTH_CONNECT,
- android.Manifest.permission.BLUETOOTH_PRIVILEGED,
- })
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_HAP_ON_PRESET_INFO =
- "android.bluetooth.action.HAP_ON_PRESET_INFO";
-
- /**
- * Intent used to broadcast result of a failed rename attempt.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * <li> {@link #EXTRA_HAP_PRESET_INDEX}- The currently active preset.</li>
- * <li> {@link #EXTRA_HAP_STATUS_CODE}- Failure reason code.</li>
- * </ul>
- *
- * <p>{@link #EXTRA_HAP_STATUS_CODE} can be any of {@link #STATUS_SET_NAME_NOT_ALLOWED},
- * {@link #STATUS_INVALID_PRESET_INDEX}, {@link #STATUS_INVALID_PRESET_NAME_LENGTH}.
- *
- * @hide
- */
- @RequiresPermission(allOf = {
- android.Manifest.permission.BLUETOOTH_CONNECT,
- android.Manifest.permission.BLUETOOTH_PRIVILEGED,
- })
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_HAP_ON_PRESET_NAME_SET_ERROR =
- "android.bluetooth.action.HAP_ON_PRESET_NAME_SET_ERROR";
-
- /**
- * Intent used to broadcast the result of a failed name get attempt.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * <li> {@link #EXTRA_HAP_PRESET_INDEX}- The currently active preset.</li>
- * <li> {@link #EXTRA_HAP_STATUS_CODE}- Failure reason code.</li>
- * </ul>
- *
- * <p>{@link #EXTRA_HAP_STATUS_CODE} can be any of {@link #STATUS_INVALID_PRESET_INDEX},
- * {@link #STATUS_OPERATION_NOT_POSSIBLE}.
- *
- * @hide
- */
- @RequiresPermission(allOf = {
- android.Manifest.permission.BLUETOOTH_CONNECT,
- android.Manifest.permission.BLUETOOTH_PRIVILEGED,
- })
- @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_HAP_ON_PRESET_INFO_GET_ERROR =
- "android.bluetooth.action.HAP_ON_PRESET_INFO_GET_ERROR";
-
- /**
* Contains a list of all available presets
* @hide
*/
public static final String EXTRA_HAP_FEATURES = "android.bluetooth.extra.HAP_FEATURES";
/**
- * Contains a preset identifier
- * @hide
- */
- public static final String EXTRA_HAP_PRESET_INDEX = "android.bluetooth.extra.HAP_PRESET_INDEX";
-
- /**
- * Used to report failure reasons.
- * @hide
- */
- public static final String EXTRA_HAP_STATUS_CODE = "android.bluetooth.extra.HAP_STATUS_CODE";
-
- /**
- * Used by group events.
- * @hide
- */
- public static final String EXTRA_HAP_GROUP_ID = "android.bluetooth.extra.HAP_GROUP_ID";
-
- /**
- * Preset Info reason.
- * Possible values:
- * {@link #PRESET_INFO_REASON_ALL_PRESET_INFO} or
- * {@link #PRESET_INFO_REASON_PRESET_INFO_UPDATE} or
- * {@link #PRESET_INFO_REASON_PRESET_DELETED} or
- * {@link #PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED} or
- * {@link #PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE}
- * @hide
- */
- public static final String EXTRA_HAP_PRESET_INFO_REASON =
- "android.bluetooth.extra.HAP_PRESET_INFO_REASON";
-
- /**
- * Preset Info.
- * @hide
- */
- public static final String EXTRA_HAP_PRESET_INFO = "android.bluetooth.extra.HAP_PRESET_INFO";
-
- /**
- * Preset name change failure due to preset being read-only.
- * @hide
- */
- public static final int STATUS_SET_NAME_NOT_ALLOWED =
- IBluetoothHapClient.STATUS_SET_NAME_NOT_ALLOWED;
-
- /**
- * Means that the requested operation is not supported by the HA device.
- *
- * <p> It could mean that the requested name change is not supported on
- * a given preset or the device does not support presets at all.
- * @hide
- */
- public static final int STATUS_OPERATION_NOT_SUPPORTED =
- IBluetoothHapClient.STATUS_OPERATION_NOT_SUPPORTED;
-
- /**
- * Usually means a temporary denial of certain operation. Peer device may report this
- * status due to various implementation specific reasons. It's different than
- * the {@link #STATUS_OPERATION_NOT_SUPPORTED} which represents more of a
- * permanent inability to perform some of the operations.
- * @hide
- */
- public static final int STATUS_OPERATION_NOT_POSSIBLE =
- IBluetoothHapClient.STATUS_OPERATION_NOT_POSSIBLE;
-
- /**
- * Used when preset name change failed due to the passed name parameter being to long.
- * @hide
- */
- public static final int STATUS_INVALID_PRESET_NAME_LENGTH =
- IBluetoothHapClient.STATUS_INVALID_PRESET_NAME_LENGTH;
-
- /**
- * Group operations are not supported.
- * @hide
- */
- public static final int STATUS_GROUP_OPERATION_NOT_SUPPORTED =
- IBluetoothHapClient.STATUS_GROUP_OPERATION_NOT_SUPPORTED;
-
- /**
- * Procedure is already in progress.
- * @hide
- */
- public static final int STATUS_PROCEDURE_ALREADY_IN_PROGRESS =
- IBluetoothHapClient.STATUS_PROCEDURE_ALREADY_IN_PROGRESS;
-
- /**
- * Invalid preset index input parameter used in one of the API calls.
- * @hide
- */
- public static final int STATUS_INVALID_PRESET_INDEX =
- IBluetoothHapClient.STATUS_INVALID_PRESET_INDEX;
-
- /**
* Represets an invalid index value. This is usually value returned in a currently
* active preset request for a device which is not connected. This value shouldn't be used
* in the API calls.
@@ -507,48 +359,6 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
public static final int FEATURE_BIT_NUM_WRITABLE_PRESETS =
IBluetoothHapClient.FEATURE_BIT_NUM_WRITABLE_PRESETS;
- /**
- * Preset Info notification reason.
- * @hide
- */
- public static final int PRESET_INFO_REASON_ALL_PRESET_INFO =
- IBluetoothHapClient.PRESET_INFO_REASON_ALL_PRESET_INFO;
-
- /**
- * Preset Info notification reason.
- * @hide
- */
- public static final int PRESET_INFO_REASON_PRESET_INFO_UPDATE =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_INFO_UPDATE;
-
- /**
- * Preset Info notification reason.
- * @hide
- */
- public static final int PRESET_INFO_REASON_PRESET_DELETED =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_DELETED;
-
- /**
- * Preset Info notification reason.
- * @hide
- */
- public static final int PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED;
-
- /**
- * Preset Info notification reason.
- * @hide
- */
- public static final int PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE =
- IBluetoothHapClient.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE;
-
- /**
- * Represents invalid group identifier. It's returned when user requests a group identifier
- * for a device which is not part of any group. This value shouldn't be used in the API calls.
- * @hide
- */
- public static final int HAP_GROUP_UNAVAILABLE = IBluetoothHapClient.GROUP_ID_UNAVAILABLE;
-
private final BluetoothAdapter mAdapter;
private final AttributionSource mAttributionSource;
private final BluetoothProfileConnector<IBluetoothHapClient> mProfileConnector =
@@ -560,6 +370,35 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
}
};
+ @SuppressLint("AndroidFrameworkBluetoothPermission")
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (up) {
+ // re-register the service-to-app callback
+ synchronized (mCallbackExecutorMap) {
+ if (mCallbackExecutorMap.isEmpty()) return;
+
+ try {
+ final IBluetoothHapClient service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv =
+ new SynchronousResultReceiver();
+ service.registerCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n"
+ + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ };
+
/**
* Create a BluetoothHapClient proxy object for interacting with the local
* Bluetooth Hearing Access Profile (HAP) client.
@@ -568,6 +407,16 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAttributionSource = mAdapter.getAttributionSource();
mProfileConnector.connect(context, listener);
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
mCloseGuard = new CloseGuard();
mCloseGuard.open("close");
}
@@ -586,6 +435,17 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
* @hide
*/
public void close() {
+ if (VDBG) log("close()");
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+
mProfileConnector.disconnect();
}
@@ -621,8 +481,36 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
+ if (!isEnabled()) {
+ throw new IllegalStateException("service not enabled");
+ }
+
if (DBG) log("registerCallback");
- throw new UnsupportedOperationException("Not Implemented");
+
+ synchronized (mCallbackExecutorMap) {
+ // If the callback map is empty, we register the service-to-app callback
+ if (mCallbackExecutorMap.isEmpty()) {
+ try {
+ final IBluetoothHapClient service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv =
+ new SynchronousResultReceiver();
+ service.registerCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (IllegalStateException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ // Adds the passed in callback to our map of callbacks to executors
+ if (mCallbackExecutorMap.containsKey(callback)) {
+ throw new IllegalArgumentException("This callback has already been registered");
+ }
+ mCallbackExecutorMap.put(callback, executor);
+ }
}
/**
@@ -646,8 +534,30 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
+
if (DBG) log("unregisterCallback");
- throw new UnsupportedOperationException("Not Implemented");
+
+ synchronized (mCallbackExecutorMap) {
+ if (mCallbackExecutorMap.remove(callback) != null) {
+ throw new IllegalArgumentException("This callback has not been registered");
+ }
+ }
+
+ // If the callback map is empty, we unregister the service-to-app callback
+ if (mCallbackExecutorMap.isEmpty()) {
+ try {
+ final IBluetoothHapClient service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.unregisterCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -683,8 +593,10 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
+ } catch (TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
return defaultValue;
@@ -719,8 +631,10 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
service.getConnectionPolicy(device, mAttributionSource, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
+ } catch (TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
return defaultValue;
@@ -748,9 +662,13 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
try {
final SynchronousResultReceiver<List> recv = new SynchronousResultReceiver();
service.getConnectedDevices(mAttributionSource, recv);
- return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
return defaultValue;
@@ -779,9 +697,13 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
try {
final SynchronousResultReceiver<List> recv = new SynchronousResultReceiver();
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
- return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
+ return Attributable.setAttributionSource(
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+ mAttributionSource);
+ } catch (TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
return defaultValue;
@@ -811,8 +733,10 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
service.getConnectionState(device, mAttributionSource, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
+ } catch (TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
return defaultValue;
@@ -828,12 +752,11 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
* require individual device calls.
*
* <p>Note that some binaural HA devices may not support group operations,
- * therefore are not considered a valid HAP group. In such case the
- * {@link #HAP_GROUP_UNAVAILABLE} is returned even when such
- * device is a valid Le Audio Coordinated Set member.
+ * therefore are not considered a valid HAP group. In such case -1 is returned
+ * even if such device is a valid Le Audio Coordinated Set member.
*
* @param device
- * @return valid group identifier or {@link #HAP_GROUP_UNAVAILABLE}
+ * @return valid group identifier or -1
* @hide
*/
@RequiresBluetoothConnectPermission
@@ -843,7 +766,7 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public int getHapGroup(@NonNull BluetoothDevice device) {
final IBluetoothHapClient service = getService();
- final int defaultValue = HAP_GROUP_UNAVAILABLE;
+ final int defaultValue = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -852,8 +775,10 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
service.getHapGroup(device, mAttributionSource, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
+ } catch (TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
return defaultValue;
@@ -878,14 +803,15 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
- // TODO(b/216639668)
- // try {
- // final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- // service.getActivePresetIndex(device, mAttributionSource, recv);
- // return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- // } catch (RemoteException | TimeoutException e) {
- // Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
- // }
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getActivePresetIndex(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
return defaultValue;
}
@@ -905,8 +831,25 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
android.Manifest.permission.BLUETOOTH_PRIVILEGED
})
public @Nullable BluetoothHapPresetInfo getActivePresetInfo(@NonNull BluetoothDevice device) {
- // TODO(b/216639668)
- return null;
+ final IBluetoothHapClient service = getService();
+ final BluetoothHapPresetInfo defaultValue = null;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<BluetoothHapPresetInfo> recv =
+ new SynchronousResultReceiver();
+ service.getActivePresetInfo(device, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ return defaultValue;
}
/**
@@ -928,18 +871,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public void selectPreset(@NonNull BluetoothDevice device, int presetIndex) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.selectActivePreset(device, presetIndex, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.selectPreset(device, presetIndex, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -968,18 +907,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public void selectPresetForGroup(int groupId, int presetIndex) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled()) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.groupSelectActivePreset(groupId, presetIndex, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.selectPresetForGroup(groupId, presetIndex, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1000,18 +935,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public void switchToNextPreset(@NonNull BluetoothDevice device) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.nextActivePreset(device, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.switchToNextPreset(device, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1034,18 +965,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public void switchToNextPresetForGroup(int groupId) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled()) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.groupNextActivePreset(groupId, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.switchToNextPresetForGroup(groupId, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1066,18 +993,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public void switchToPreviousPreset(@NonNull BluetoothDevice device) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.previousActivePreset(device, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.switchToPreviousPreset(device, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1100,18 +1023,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public void switchToPreviousPresetForGroup(int groupId) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled()) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.groupPreviousActivePreset(groupId, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.switchToPreviousPresetForGroup(groupId, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1129,24 +1048,26 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
android.Manifest.permission.BLUETOOTH_CONNECT,
android.Manifest.permission.BLUETOOTH_PRIVILEGED
})
- public @NonNull BluetoothHapPresetInfo getPresetInfo(@NonNull BluetoothDevice device,
+ public @Nullable BluetoothHapPresetInfo getPresetInfo(@NonNull BluetoothDevice device,
int presetIndex) {
final IBluetoothHapClient service = getService();
- final BluetoothHapPresetInfo.Builder builder = new BluetoothHapPresetInfo.Builder();
+ final BluetoothHapPresetInfo defaultValue = null;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
- // TODO(b/216639668)
- // try {
- // final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- // service.getPresetInfo(device, presetIndex, mAttributionSource, recv);
- // return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(builder);
- // } catch (RemoteException | TimeoutException e) {
- // Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
- // }
+ try {
+ final SynchronousResultReceiver<BluetoothHapPresetInfo> recv =
+ new SynchronousResultReceiver();
+ service.getPresetInfo(device, presetIndex, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
- return builder.build();
+ return defaultValue;
}
/**
@@ -1169,14 +1090,16 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
- // TODO(b/216639668)
- // try {
- // final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- // service.getAllPresetsInfo(device, mAttributionSource, recv);
- // return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- // } catch (RemoteException | TimeoutException e) {
- // Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
- // }
+ try {
+ final SynchronousResultReceiver<List<BluetoothHapPresetInfo>> recv =
+ new SynchronousResultReceiver();
+ service.getAllPresetInfo(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
return defaultValue;
}
@@ -1193,19 +1116,21 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
android.Manifest.permission.BLUETOOTH_CONNECT,
android.Manifest.permission.BLUETOOTH_PRIVILEGED
})
- public boolean getFeatures(@NonNull BluetoothDevice device) {
+ public int getFeatures(@NonNull BluetoothDevice device) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
+ final int defaultValue = 0x00;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
try {
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
service.getFeatures(device, mAttributionSource, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
+ } catch (TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
return defaultValue;
@@ -1235,18 +1160,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
public void setPresetName(@NonNull BluetoothDevice device, int presetIndex,
@NonNull String name) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.setPresetName(device, presetIndex, name, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.setPresetName(device, presetIndex, name, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
@@ -1274,18 +1195,14 @@ public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable
})
public void setPresetNameForGroup(int groupId, int presetIndex, @NonNull String name) {
final IBluetoothHapClient service = getService();
- final boolean defaultValue = false;
if (service == null) {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled()) {
try {
- // TODO(b/216639668)
- final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
- service.groupSetPresetName(groupId, presetIndex, name, mAttributionSource, recv);
- recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
- } catch (RemoteException | TimeoutException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ service.setPresetNameForGroup(groupId, presetIndex, name, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
}
diff --git a/framework/java/android/bluetooth/BluetoothUuid.java b/framework/java/android/bluetooth/BluetoothUuid.java
index b41d78b498..7847718c5d 100644
--- a/framework/java/android/bluetooth/BluetoothUuid.java
+++ b/framework/java/android/bluetooth/BluetoothUuid.java
@@ -158,9 +158,8 @@ public final class BluetoothUuid {
/** @hide */
@NonNull
@SystemApi
- /* FIXME: Not known yet, using a placeholder instead. */
public static final ParcelUuid HAS =
- ParcelUuid.fromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE");
+ ParcelUuid.fromString("00001854-0000-1000-8000-00805F9B34FB");
/** @hide */
@NonNull
@SystemApi
diff --git a/system/binder/Android.bp b/system/binder/Android.bp
index 54b7f01be7..44cbc36339 100644
--- a/system/binder/Android.bp
+++ b/system/binder/Android.bp
@@ -25,6 +25,7 @@ filegroup {
"android/bluetooth/IBluetoothHeadset.aidl",
"android/bluetooth/IBluetoothHearingAid.aidl",
"android/bluetooth/IBluetoothHapClient.aidl",
+ "android/bluetooth/IBluetoothHapClientCallback.aidl",
"android/bluetooth/IBluetoothVolumeControl.aidl",
"android/bluetooth/IBluetoothHidHost.aidl",
"android/bluetooth/IBluetoothLeAudio.aidl",
diff --git a/system/binder/android/bluetooth/IBluetoothHapClient.aidl b/system/binder/android/bluetooth/IBluetoothHapClient.aidl
index 672ab409c6..ee956c3210 100644
--- a/system/binder/android/bluetooth/IBluetoothHapClient.aidl
+++ b/system/binder/android/bluetooth/IBluetoothHapClient.aidl
@@ -18,6 +18,7 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.IBluetoothHapClientCallback;
import android.content.AttributionSource;
import com.android.modules.utils.SynchronousResultReceiver;
@@ -30,10 +31,6 @@ import com.android.modules.utils.SynchronousResultReceiver;
oneway interface IBluetoothHapClient {
// Public API
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void connect(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void disconnect(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getConnectedDevices(in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getDevicesMatchingConnectionStates(in int[] states, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@@ -43,55 +40,46 @@ oneway interface IBluetoothHapClient {
void setConnectionPolicy(in BluetoothDevice device, int connectionPolicy, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getConnectionPolicy(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
-
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getHapGroup(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getActivePresetIndex(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void getActivePresetInfo(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void selectActivePreset(in BluetoothDevice device, int presetIndex, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void selectPreset(in BluetoothDevice device, int presetIndex, in AttributionSource attributionSource);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void groupSelectActivePreset(int groupId, int presetIndex, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void selectPresetForGroup(int groupId, int presetIndex, in AttributionSource attributionSource);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void nextActivePreset(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void switchToNextPreset(in BluetoothDevice device, in AttributionSource attributionSource);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void groupNextActivePreset(int groupId, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void switchToNextPresetForGroup(int groupId, in AttributionSource attributionSource);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void previousActivePreset(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void switchToPreviousPreset(in BluetoothDevice device, in AttributionSource attributionSource);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void groupPreviousActivePreset(int groupId, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void switchToPreviousPresetForGroup(int groupId, in AttributionSource attributionSource);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getPresetInfo(in BluetoothDevice device, int presetIndex, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void getAllPresetsInfo(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ void getAllPresetInfo(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getFeatures(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void setPresetName(in BluetoothDevice device, int presetIndex, in String name, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void setPresetName(in BluetoothDevice device, int presetIndex, in String name, in AttributionSource attributionSource);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
- void groupSetPresetName(int groupId, int presetIndex, in String name, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ oneway void setPresetNameForGroup(int groupId, int presetIndex, in String name, in AttributionSource attributionSource);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void registerCallback(in IBluetoothHapClientCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void unregisterCallback(in IBluetoothHapClientCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
const int PRESET_INDEX_UNAVAILABLE = 0;
const int GROUP_ID_UNAVAILABLE = -1;
- const int STATUS_SET_NAME_NOT_ALLOWED = 1;
- const int STATUS_OPERATION_NOT_SUPPORTED = 2;
- const int STATUS_OPERATION_NOT_POSSIBLE = 3;
- const int STATUS_INVALID_PRESET_NAME_LENGTH = 4;
- const int STATUS_INVALID_PRESET_INDEX = 5;
- const int STATUS_GROUP_OPERATION_NOT_SUPPORTED = 6;
- const int STATUS_PROCEDURE_ALREADY_IN_PROGRESS = 7;
-
const int FEATURE_BIT_NUM_TYPE_MONAURAL = 0;
const int FEATURE_BIT_NUM_TYPE_BANDED = 1;
const int FEATURE_BIT_NUM_SYNCHRONIZATED_PRESETS = 2;
const int FEATURE_BIT_NUM_INDEPENDENT_PRESETS = 3;
const int FEATURE_BIT_NUM_DYNAMIC_PRESETS = 4;
const int FEATURE_BIT_NUM_WRITABLE_PRESETS = 5;
-
- const int PRESET_INFO_REASON_ALL_PRESET_INFO = 0;
- const int PRESET_INFO_REASON_PRESET_INFO_UPDATE = 1;
- const int PRESET_INFO_REASON_PRESET_DELETED = 2;
- const int PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED = 3;
- const int PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE = 4;
}
diff --git a/system/binder/android/bluetooth/IBluetoothHapClientCallback.aidl b/system/binder/android/bluetooth/IBluetoothHapClientCallback.aidl
new file mode 100644
index 0000000000..5e2d8fa30b
--- /dev/null
+++ b/system/binder/android/bluetooth/IBluetoothHapClientCallback.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapPresetInfo;
+
+import java.util.List;
+
+/**
+ * Callback definitions for interacting with HAP Client service
+ *
+ * @hide
+ */
+oneway interface IBluetoothHapClientCallback {
+ void onActivePresetChanged(in BluetoothDevice device, in int presetIndex);
+ void onSelectActivePresetFailed(in BluetoothDevice device, in int statusCode);
+ void onSelectActivePresetForGroupFailed(in int hapGroupId, in int statusCode);
+ void onPresetInfoChanged(in BluetoothDevice device,
+ in List<BluetoothHapPresetInfo> presetInfoList,
+ in int statusCode);
+ void onHapFeaturesAvailable(in BluetoothDevice device, in int hapFeatures);
+ void onSetPresetNameFailed(in BluetoothDevice device, in int status);
+ void onSetPresetNameForGroupFailed(in int hapGroupId, in int status);
+}
diff --git a/system/bta/has/has_client.cc b/system/bta/has/has_client.cc
index 7c6bbdc3c9..044713437f 100644
--- a/system/bta/has/has_client.cc
+++ b/system/bta/has/has_client.cc
@@ -1044,14 +1044,14 @@ class HasClientImpl : public HasClient {
/* Journal update */
device->has_journal_.Append(HasJournalRecord(features, true));
- /* When service is not yet validated, report the available device and
- * notify features otherwise.
+ /* When service is not yet validated, report the available device with
+ * features.
*/
- if (!device->isGattServiceValid()) {
+ if (!device->isGattServiceValid())
callbacks_->OnDeviceAvailable(device->addr, device->GetFeatures());
- } else {
- callbacks_->OnFeaturesUpdate(device->addr, device->GetFeatures());
- }
+
+ /* Notify features */
+ callbacks_->OnFeaturesUpdate(device->addr, device->GetFeatures());
MarkDeviceValidIfInInitialDiscovery(*device);
}
diff --git a/system/bta/has/has_types.h b/system/bta/has/has_types.h
index 89ed82a8ce..d726f07cf7 100644
--- a/system/bta/has/has_types.h
+++ b/system/bta/has/has_types.h
@@ -72,7 +72,7 @@ static_assert(sizeof(HasGattOpContext) <= sizeof(void*));
/* Service UUIDs */
/* FIXME: actually these were not yet assigned - using placeholders for now. */
static const bluetooth::Uuid kUuidHearingAccessService =
- bluetooth::Uuid::From16Bit(0xEEEE);
+ bluetooth::Uuid::From16Bit(0x1854);
static const bluetooth::Uuid kUuidHearingAidFeatures =
bluetooth::Uuid::From16Bit(0xEEED);
static const bluetooth::Uuid kUuidHearingAidPresetControlPoint =
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index c427f50e4e..28abdabf5e 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -95,8 +95,7 @@ const Uuid UUID_VC = Uuid::FromString("1844");
const Uuid UUID_CSIS = Uuid::FromString("1846");
const Uuid UUID_LE_AUDIO = Uuid::FromString("184E");
const Uuid UUID_LE_MIDI = Uuid::FromString("03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
-/* FIXME: Not known yet, using a placeholder instead. */
-const Uuid UUID_HAS = Uuid::FromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE");
+const Uuid UUID_HAS = Uuid::FromString("1854");
const bool enable_address_consolidate = true; // TODO remove
#define COD_MASK 0x07FF
diff --git a/system/include/hardware/bt_has.h b/system/include/hardware/bt_has.h
index 4fdd316852..739edc041f 100644
--- a/system/include/hardware/bt_has.h
+++ b/system/include/hardware/bt_has.h
@@ -36,9 +36,10 @@ enum class ConnectionState : uint8_t {
/** Results codes for the failed preset operations */
enum class ErrorCode : uint8_t {
NO_ERROR = 0,
- SET_NAME_NOT_ALLOWED,
- OPERATION_NOT_SUPPORTED,
- OPERATION_NOT_POSSIBLE,
+ SET_NAME_NOT_ALLOWED, // Preset cannot be written (read only preset)
+ OPERATION_NOT_SUPPORTED, // If theres no optional characteristic,
+ // or request opcode is invalid or not supported
+ OPERATION_NOT_POSSIBLE, // Operation cannot be performed at this time
INVALID_PRESET_NAME_LENGTH,
INVALID_PRESET_INDEX,
GROUP_OPERATION_NOT_SUPPORTED,