diff options
author | Jack He <siyuanh@google.com> | 2022-02-23 16:58:03 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-02-23 16:58:03 +0000 |
commit | 3a88d77b08c87cb1517fa63cbdf7f99ca10100db (patch) | |
tree | c2c8d3e52f453cfd79776457c35d2935a698b93c | |
parent | 89b965d721c1b41755dc4935e576158d554714e5 (diff) | |
parent | 2e6d4b42917664491cd42bc0e13a2961ef058db4 (diff) |
Merge changes I343ff48c,I4b1ecf81 am: 2e6d4b4291
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/1985329
Change-Id: Id92c7e06e2d4a4ecbbf63942a922613ce6484020
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, |