diff options
60 files changed, 1899 insertions, 1252 deletions
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index 0ccf2ac7c1..1cb8f08cb0 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -1090,7 +1090,7 @@ public class AdapterService extends Service { * @return true if any profile is enabled, false otherwise */ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - private boolean isAnyProfileEnabled(BluetoothDevice device) { + boolean isAnyProfileEnabled(BluetoothDevice device) { if (mA2dpService != null && mA2dpService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 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/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java index 4f9f7bb990..2d8915388d 100644 --- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java @@ -847,6 +847,12 @@ final class RemoteDevices { if (sAdapterService.getConnectionState(device) == 0) { resetBatteryLevel(device); } + if (!sAdapterService.isAnyProfileEnabled(device)) { + DeviceProperties deviceProp = getDeviceProperties(device); + if (deviceProp != null) { + deviceProp.setBondingInitiatedLocally(false); + } + } debugLog( "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state) + " Disconnected: " + device 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 ef1a979ca9..d2e8b758da 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattService.java +++ b/android/app/src/com/android/bluetooth/gatt/GattService.java @@ -167,8 +167,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 f082b8fe82..4a44daf8b7 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, Context.RECEIVER_NOT_EXPORTED); - // 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/floss/OWNERS b/floss/OWNERS new file mode 100644 index 0000000000..45bfdd2be4 --- /dev/null +++ b/floss/OWNERS @@ -0,0 +1 @@ +include platform/packages/modules/Bluetooth:/OWNERS_chromeos diff --git a/floss/build/Dockerfile b/floss/build/Dockerfile new file mode 100644 index 0000000000..7e938f9a10 --- /dev/null +++ b/floss/build/Dockerfile @@ -0,0 +1,76 @@ +# Build environment for Floss +# +# This dockerfile generates the build environment required to build Floss, which +# is the Linux build for the Fluoride Bluetooth stack. + +# Inherit from a recent Debian version. The slim version is a smaller variant +# meant for containers. +FROM debian:bookworm-slim + +# First install all required apt packages. +RUN apt-get update && \ + apt-get install -y \ + bison \ + build-essential \ + clang \ + cmake \ + curl \ + debmake \ + flatbuffers-compiler \ + flex \ + g++-multilib \ + gcc-multilib \ + generate-ninja \ + gnupg \ + gperf \ + libabsl-dev \ + libc++abi-dev \ + libc++-dev \ + libdbus-1-dev \ + libdouble-conversion-dev \ + libevent-dev \ + libflatbuffers-dev \ + libflatbuffers1 \ + libgl1-mesa-dev \ + libglib2.0-dev \ + libgtest-dev \ + libgmock-dev \ + liblz4-tool \ + libncurses5 \ + libnss3-dev \ + libprotobuf-dev \ + libre2-9 \ + libre2-dev \ + libssl-dev \ + libtinyxml2-dev \ + libx11-dev \ + libxml2-utils \ + ninja-build \ + openssl \ + protobuf-compiler \ + python3 \ + unzip \ + x11proto-core-dev \ + xsltproc \ + zip \ + zlib1g-dev \ + ; + +# Next install the Rust toolchain. +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +# Add .cargo/bin to $PATH +ENV PATH="/root/.cargo/bin:${PATH}" + +# Install cargo packages required on build image. +RUN cargo install cxxbridge-cmd --version 1.0.42 + +# Rename llvm packages. By default, they are named 11vm-ar-13, etc. which won't +# work properly with the build. +ADD llvm-rename.sh /tmp +RUN /tmp/llvm-rename.sh 13 + +# At this point, the base container is ready. Now we need to build and install +# both libchrome and modp-b64. If you ran this via `docker-build-image.py`, this +# will be done after the image is created and tagged. Otherwise, you need to +# manually mount the source and run the dpkg builders in `system/build/dpkg`. diff --git a/floss/build/README.md b/floss/build/README.md new file mode 100644 index 0000000000..ac58410fb1 --- /dev/null +++ b/floss/build/README.md @@ -0,0 +1,23 @@ +# Docker build for Floss + +This repo contains the Docker image build rule, used to generate the docker +image necessary to build Floss. If building a new docker image, run +`docker-build-image.py` with the tag `floss:latest`. + +## Using the docker image to build + +Once the Docker image is built (and assuming it's tagged as `floss:latest`), you +should use the `build-in-docker.py` script to build the current repo. + +This script will use the local `floss:latest` (or pull it from the registry), +mount (or create) the `floss-out` volume to `/root/.floss` and the current +source to `/root/src` before running these commands in the container: + +* `cd /root/src` +* `./build.py --run-bootstrap` +* `./build.py --libdir=/usr/lib/x86-64_linux_gnu/` + +If you want to run the build more quickly (or pass other commands), run +`build-in-docker.py --only-start`. This will only start the container for you +(doing the correct mounts) and will print the commands it would have run via +docker exec normally. diff --git a/floss/build/build-in-docker.py b/floss/build/build-in-docker.py new file mode 100755 index 0000000000..a84d2dd7c9 --- /dev/null +++ b/floss/build/build-in-docker.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 + +import argparse +import os +import subprocess +import sys + + +class FlossDockerRunner: + """Runs Floss build inside docker container.""" + + # Commands to run for build + BUILD_COMMANDS = [ + # First run bootstrap to get latest code + create symlinks + ['/root/src/build.py', '--run-bootstrap'], + + # Clean up any previous artifacts inside the volume + ['/root/src/build.py', '--target', 'clean'], + + # Run normal code builder + ['/root/src/build.py', '--target', 'all'], + + # Run tests + ['/root/src/build.py', '--target', 'test'], + ] + + def __init__(self, workdir, rootdir, image_tag, volume_tag): + """ Constructor. + + Args: + workdir: Current working directory (should be the script path). + rootdir: Root directory for Bluetooth. + build_tag: Tag for docker image used for building. + """ + self.workdir = workdir + self.rootdir = rootdir + self.image_tag = image_tag + self.env = os.environ.copy() + + # Name of running container + self.container_name = 'floss-docker-runner' + + # Name of volume where we'll send build output + self.volume_name = volume_tag + + def run_command(self, target, args, cwd=None, env=None, ignore_rc=False): + """ Run command and stream the output. + """ + # Set some defaults + if not cwd: + cwd = self.workdir + if not env: + env = self.env + + rc = 0 + process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE) + while True: + line = process.stdout.readline() + print(line.decode('utf-8'), end="") + if not line: + rc = process.poll() + if rc is not None: + break + + time.sleep(0.1) + + if rc != 0 and not ignore_rc: + raise Exception("{} failed. Return code is {}".format(target, rc)) + + def _create_volume_if_needed(self): + # Check if the volume exists. Otherwise create it. + try: + subprocess.check_output(['docker', 'volume', 'inspect', self.volume_name]) + finally: + self.run_command('docker volume create', ['docker', 'volume', 'create', self.volume_name]) + + def start_container(self): + """Starts the docker container with correct mounts.""" + # Stop any previously started container. + self.stop_container(ignore_error=True) + + # Create volume and create mount string + self._create_volume_if_needed() + mount_output_volume = 'type=volume,src={},dst=/root/.floss'.format(self.volume_name) + + # Mount the source directory + mount_src_dir = 'type=bind,src={},dst=/root/src'.format(self.rootdir) + + # Run the docker image. It will run `tail` indefinitely so the container + # doesn't close and we can run `docker exec` on it. + self.run_command('docker run', [ + 'docker', 'run', '--name', self.container_name, '--mount', mount_output_volume, '--mount', mount_src_dir, + '-d', self.image_tag, 'tail', '-f', '/dev/null' + ]) + + def stop_container(self, ignore_error=False): + """Stops the docker container for build.""" + self.run_command('docker stop', ['docker', 'stop', '-t', '1', self.container_name], ignore_rc=ignore_error) + self.run_command('docker rm', ['docker', 'rm', self.container_name], ignore_rc=ignore_error) + + def do_build(self): + """Runs the basic build commands.""" + # Start container before building + self.start_container() + + # Run all commands + for i, cmd in enumerate(self.BUILD_COMMANDS): + self.run_command('docker exec #{}'.format(i), ['docker', 'exec', '-it', self.container_name] + cmd) + + # Stop container before exiting + self.stop_container() + + def print_do_build(self): + """Prints the commands for building.""" + docker_exec = ['docker', 'exec', '-it', self.container_name] + print('Normally, build would run the following commands: \n') + for cmd in self.BUILD_COMMANDS: + print(' '.join(docker_exec + cmd)) + + def check_docker_runnable(self): + try: + subprocess.check_output(['docker', 'ps'], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as err: + if 'denied' in err.output.decode('utf-8'): + print('Run script as sudo') + else: + print('Unexpected error: {}'.format(err.output.decode('utf-8'))) + + return False + + # No exception means docker is ok + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser('Builder Floss inside docker image.') + parser.add_argument( + '--only-start', + action='store_true', + default=False, + help='Only start the container. Prints the commands it would have ran.') + parser.add_argument('--only-stop', action='store_true', default=False, help='Only stop the container and exit.') + parser.add_argument('--image-tag', default='floss:latest', help='Docker image to use to build.') + parser.add_argument('--volume-tag', default='floss-out', help='Name of volume to use.') + args = parser.parse_args() + + # cwd should be set to same directory as this script (that's where Dockerfile + # is kept). + workdir = os.path.dirname(os.path.abspath(sys.argv[0])) + rootdir = os.path.abspath(os.path.join(workdir, '../..')) + + fdr = FlossDockerRunner(workdir, rootdir, args.image_tag, args.volume_tag) + + # Make sure docker is runnable before continuing + if fdr.check_docker_runnable(): + # Handle some flags + if args.only_start: + fdr.start_container() + fdr.print_do_build() + elif args.only_stop: + fdr.stop_container() + else: + fdr.do_build() diff --git a/floss/build/docker-build-image.py b/floss/build/docker-build-image.py new file mode 100755 index 0000000000..81b622b87b --- /dev/null +++ b/floss/build/docker-build-image.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import subprocess + +SRC_MOUNT = "/root/src" + + +class DockerImageBuilder: + """Builds the docker image for Floss build environment.""" + + def __init__(self, workdir, rootdir, tag): + """ Constructor. + + Args: + workdir: Working directory for this script. Dockerfile should exist here. + rootdir: Root directory for Bluetooth. + tag: Label in format |name:version|. + """ + self.workdir = workdir + self.rootdir = rootdir + (self.name, self.version) = tag.split(':') + self.build_tag = '{}:{}'.format(self.name, 'buildtemp') + self.container_name = 'floss-buildtemp' + self.final_tag = tag + self.env = os.environ.copy() + + # Mark dpkg builders for docker + self.env['LIBCHROME_DOCKER'] = '1' + self.env['MODP_DOCKER'] = '1' + + def run_command(self, target, args, cwd=None, env=None, ignore_rc=False): + """ Run command and stream the output. + """ + # Set some defaults + if not cwd: + cwd = self.workdir + if not env: + env = self.env + + rc = 0 + process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE) + while True: + line = process.stdout.readline() + print(line.decode('utf-8'), end="") + if not line: + rc = process.poll() + if rc is not None: + break + + time.sleep(0.1) + + if rc != 0 and not ignore_rc: + raise Exception("{} failed. Return code is {}".format(target, rc)) + + def _docker_build(self): + self.run_command('docker build', ['docker', 'build', '-t', self.build_tag, '.']) + + def _build_dpkg_and_commit(self): + # Try to remove any previous instance of the container that may be + # running if this script didn't complete cleanly last time. + self.run_command('docker stop', ['docker', 'stop', '-t', '1', self.container_name], ignore_rc=True) + self.run_command('docker rm', ['docker', 'rm', self.container_name], ignore_rc=True) + + # Runs never terminating application on the newly built image in detached mode + mount_str = 'type=bind,src={},dst={},readonly'.format(self.rootdir, SRC_MOUNT) + self.run_command('docker run', [ + 'docker', 'run', '--name', self.container_name, '--mount', mount_str, '-d', self.build_tag, 'tail', '-f', + '/dev/null' + ]) + + commands = [ + # Create the output directories + ['mkdir', '-p', '/tmp/libchrome', '/tmp/modpb64'], + + # Run the dpkg builder for modp_b64 + ['/root/src/system/build/dpkg/modp_b64/gen-src-pkg.sh', '/tmp/modpb64'], + + # Install modp_b64 since libchrome depends on it + ['find', '/tmp/modpb64', '-name', 'modp*.deb', '-exec', 'dpkg', '-i', '{}', '+'], + + # Run the dpkg builder for libchrome + ['/root/src/system/build/dpkg/libchrome/gen-src-pkg.sh', '/tmp/libchrome'], + + # Install libchrome. + ['find', '/tmp/libchrome', '-name', 'libchrome_*.deb', '-exec', 'dpkg', '-i', '{}', '+'], + + # Delete intermediate files + ['rm', '-rf', '/tmp/libchrome', '/tmp/modpb64'], + ] + + # Run commands in container first to install everything. + for i, cmd in enumerate(commands): + self.run_command('docker exec #{}'.format(i), ['docker', 'exec', '-it', self.container_name] + cmd) + + # Commit changes into the final tag name + self.run_command('docker commit', ['docker', 'commit', self.container_name, self.final_tag]) + + # Stop running the container and remove it + self.run_command('docker stop', ['docker', 'stop', '-t', '1', self.container_name]) + self.run_command('docker rm', ['docker', 'rm', self.container_name]) + + def _check_docker_runnable(self): + try: + subprocess.check_output(['docker', 'ps'], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as err: + if 'denied' in err.output.decode('utf-8'): + print('Run script as sudo') + else: + print('Unexpected error: {}'.format(err.output.decode('utf-8'))) + + return False + + # No exception means docker is ok + return True + + def build(self): + if not self._check_docker_runnable(): + return + + # First build the docker image + self._docker_build() + + # Then build libchrome and modp-b64 inside the docker image and install + # them. Commit those changes to the final label. + self._build_dpkg_and_commit() + + +def main(): + parser = argparse.ArgumentParser(description='Build docker image for Floss build environment.') + parser.add_argument('--tag', required=True, help='Tag for docker image. i.e. floss:latest') + args = parser.parse_args() + + # cwd should be set to same directory as this script (that's where Dockerfile + # is kept). + workdir = os.path.dirname(os.path.abspath(sys.argv[0])) + rootdir = os.path.abspath(os.path.join(workdir, '../..')) + + # Build the docker image + dib = DockerImageBuilder(workdir, rootdir, args.tag) + dib.build() + + +if __name__ == '__main__': + main() diff --git a/floss/build/llvm-rename.sh b/floss/build/llvm-rename.sh new file mode 100755 index 0000000000..454d19bf99 --- /dev/null +++ b/floss/build/llvm-rename.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# Rename all llvm binaries using update-alternatives +# Without this, the llvm binaries must have the version appended at the end +# instead of just using the base name +function rename_llvm_binaries { + version=$1 + priority=100 + + update-alternatives \ + --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-${version} ${priority} \ + --slave /usr/bin/llvm-ar llvm-ar /usr/bin/llvm-ar-${version} \ + --slave /usr/bin/llvm-as llvm-as /usr/bin/llvm-as-${version} \ + --slave /usr/bin/llvm-bcanalyzer llvm-bcanalyzer /usr/bin/llvm-bcanalyzer-${version} \ + --slave /usr/bin/llvm-cov llvm-cov /usr/bin/llvm-cov-${version} \ + --slave /usr/bin/llvm-diff llvm-diff /usr/bin/llvm-diff-${version} \ + --slave /usr/bin/llvm-dis llvm-dis /usr/bin/llvm-dis-${version} \ + --slave /usr/bin/llvm-dwarfdump llvm-dwarfdump /usr/bin/llvm-dwarfdump-${version} \ + --slave /usr/bin/llvm-extract llvm-extract /usr/bin/llvm-extract-${version} \ + --slave /usr/bin/llvm-link llvm-link /usr/bin/llvm-link-${version} \ + --slave /usr/bin/llvm-mc llvm-mc /usr/bin/llvm-mc-${version} \ + --slave /usr/bin/llvm-mcmarkup llvm-mcmarkup /usr/bin/llvm-mcmarkup-${version} \ + --slave /usr/bin/llvm-nm llvm-nm /usr/bin/llvm-nm-${version} \ + --slave /usr/bin/llvm-objdump llvm-objdump /usr/bin/llvm-objdump-${version} \ + --slave /usr/bin/llvm-ranlib llvm-ranlib /usr/bin/llvm-ranlib-${version} \ + --slave /usr/bin/llvm-readobj llvm-readobj /usr/bin/llvm-readobj-${version} \ + --slave /usr/bin/llvm-rtdyld llvm-rtdyld /usr/bin/llvm-rtdyld-${version} \ + --slave /usr/bin/llvm-size llvm-size /usr/bin/llvm-size-${version} \ + --slave /usr/bin/llvm-stress llvm-stress /usr/bin/llvm-stress-${version} \ + --slave /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-${version} \ + --slave /usr/bin/llvm-tblgen llvm-tblgen /usr/bin/llvm-tblgen-${version} + + update-alternatives \ + --install /usr/bin/clang clang /usr/bin/clang-${version} ${priority} \ + --slave /usr/bin/clang++ clang++ /usr/bin/clang++-${version} \ + --slave /usr/bin/asan_symbolize asan_symbolize /usr/bin/asan_symbolize-${version} \ + --slave /usr/bin/c-index-test c-index-test /usr/bin/c-index-test-${version} \ + --slave /usr/bin/clang-check clang-check /usr/bin/clang-check-${version} \ + --slave /usr/bin/clang-cl clang-cl /usr/bin/clang-cl-${version} \ + --slave /usr/bin/clang-cpp clang-cpp /usr/bin/clang-cpp-${version} \ + --slave /usr/bin/clang-format clang-format /usr/bin/clang-format-${version} \ + --slave /usr/bin/clang-format-diff clang-format-diff /usr/bin/clang-format-diff-${version} \ + --slave /usr/bin/clang-import-test clang-import-test /usr/bin/clang-import-test-${version} \ + --slave /usr/bin/clang-include-fixer clang-include-fixer /usr/bin/clang-include-fixer-${version} \ + --slave /usr/bin/clang-offload-bundler clang-offload-bundler /usr/bin/clang-offload-bundler-${version} \ + --slave /usr/bin/clang-query clang-query /usr/bin/clang-query-${version} \ + --slave /usr/bin/clang-rename clang-rename /usr/bin/clang-rename-${version} \ + --slave /usr/bin/clang-reorder-fields clang-reorder-fields /usr/bin/clang-reorder-fields-${version} \ + --slave /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-${version} \ + --slave /usr/bin/lldb lldb /usr/bin/lldb-${version} \ + --slave /usr/bin/lldb-server lldb-server /usr/bin/lldb-server-${version} + + # Use clang instead of cc by default + update-alternatives --install /usr/bin/cc cc /usr/bin/clang $priority +} + +rename_llvm_binaries $@ diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 4042e66057..a47cc0a506 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -697,6 +697,7 @@ package android.bluetooth { field @NonNull public static final android.os.ParcelUuid AVRCP_CONTROLLER; field @NonNull public static final android.os.ParcelUuid AVRCP_TARGET; field @NonNull public static final android.os.ParcelUuid BASE_UUID; + field @NonNull public static final android.os.ParcelUuid BASS; field @NonNull public static final android.os.ParcelUuid BNEP; field @NonNull public static final android.os.ParcelUuid CAP; field @NonNull public static final android.os.ParcelUuid COORDINATED_SET; 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/BluetoothHeadset.java b/framework/java/android/bluetooth/BluetoothHeadset.java index 4e57c59643..d5af45b172 100644 --- a/framework/java/android/bluetooth/BluetoothHeadset.java +++ b/framework/java/android/bluetooth/BluetoothHeadset.java @@ -947,10 +947,21 @@ public final class BluetoothHeadset implements BluetoothProfile { BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, BluetoothStatusCodes.ERROR_TIMEOUT, + BluetoothStatusCodes.ERROR_UNKNOWN, + }) + public @interface SetAudioRouteAllowedReturnValues {} + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { BluetoothStatusCodes.ALLOWED, BluetoothStatusCodes.NOT_ALLOWED, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, + BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, + BluetoothStatusCodes.ERROR_TIMEOUT, + BluetoothStatusCodes.ERROR_UNKNOWN, }) - public @interface AudioRouteAllowedReturnValues {} + public @interface GetAudioRouteAllowedReturnValues {} /** * Sets whether audio routing is allowed. When set to {@code false}, the AG @@ -971,7 +982,7 @@ public final class BluetoothHeadset implements BluetoothProfile { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, }) - public @AudioRouteAllowedReturnValues int setAudioRouteAllowed(boolean allowed) { + public @SetAudioRouteAllowedReturnValues int setAudioRouteAllowed(boolean allowed) { if (VDBG) log("setAudioRouteAllowed"); final IBluetoothHeadset service = mService; if (service == null) { @@ -1010,7 +1021,7 @@ public final class BluetoothHeadset implements BluetoothProfile { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, }) - public @AudioRouteAllowedReturnValues int getAudioRouteAllowed() { + public @GetAudioRouteAllowedReturnValues int getAudioRouteAllowed() { if (VDBG) log("getAudioRouteAllowed"); final IBluetoothHeadset service = mService; if (service == null) { diff --git a/framework/java/android/bluetooth/BluetoothUuid.java b/framework/java/android/bluetooth/BluetoothUuid.java index 5eee4988b1..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 @@ -199,6 +198,11 @@ public final class BluetoothUuid { /** @hide */ @NonNull @SystemApi + public static final ParcelUuid BASS = + ParcelUuid.fromString("0000184F-0000-1000-8000-00805F9B34FB"); + /** @hide */ + @NonNull + @SystemApi public static final ParcelUuid BASE_UUID = ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); diff --git a/system/Android.bp b/system/Android.bp index df038087c0..f3bbab694e 100644 --- a/system/Android.bp +++ b/system/Android.bp @@ -176,7 +176,7 @@ genrule { "protoc-gen-grpc-python-plugin", "soong_zip", ], - cmd: "$(location aprotoc) --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-python-plugin) -Ipackages/modules/Bluetooth/system $(in) --grpc_out=$(genDir) --python_out=$(genDir)", + cmd: "$(location aprotoc) --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-python-plugin) -Iexternal/protobuf/src -Ipackages/modules/Bluetooth/system $(in) --grpc_out=$(genDir) --python_out=$(genDir)", srcs: ["blueberry/facade/topshim/facade.proto"], out: [ "blueberry/facade/topshim/facade_pb2.py", 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/blueberry/facade/topshim/facade.proto b/system/blueberry/facade/topshim/facade.proto index 0414b77b44..9a8679c525 100644 --- a/system/blueberry/facade/topshim/facade.proto +++ b/system/blueberry/facade/topshim/facade.proto @@ -1,11 +1,14 @@ syntax = "proto3"; +import "google/protobuf/empty.proto"; + package blueberry.facade.topshim; service AdapterService { rpc FetchEvents(FetchEventsRequest) returns (stream FetchEventsResponse) {} rpc ToggleStack(ToggleStackRequest) returns (ToggleStackResponse) {} rpc SetDiscoveryMode(SetDiscoveryModeRequest) returns (SetDiscoveryModeResponse) {} + rpc ClearEventFilter(google.protobuf.Empty) returns (google.protobuf.Empty) {} } enum EventType { diff --git a/system/blueberry/tests/gd/rust/topshim/facade/automation_helper.py b/system/blueberry/tests/gd/rust/topshim/facade/automation_helper.py index 224dffb32a..63e72c58c4 100644 --- a/system/blueberry/tests/gd/rust/topshim/facade/automation_helper.py +++ b/system/blueberry/tests/gd/rust/topshim/facade/automation_helper.py @@ -20,6 +20,8 @@ import grpc from blueberry.facade.topshim import facade_pb2 from blueberry.facade.topshim import facade_pb2_grpc +from google.protobuf import empty_pb2 as empty_proto + class AdapterAutomationHelper(): # Timeout for async wait @@ -60,6 +62,9 @@ class AdapterAutomationHelper(): async def verify_adapter_started(self): await asyncio.wait_for(self.pending_future, AdapterAutomationHelper.DEFAULT_TIMEOUT) + async def clear_event_filter(self): + await self.adapter_stub.ClearEventFilter(empty_proto.Empty()) + class A2dpAutomationHelper(): """Invoke gRPC on topshim for A2DP testing""" diff --git a/system/blueberry/tests/gd/rust/topshim/facade/suspend_test.py b/system/blueberry/tests/gd/rust/topshim/facade/suspend_test.py new file mode 100644 index 0000000000..d6885bdf31 --- /dev/null +++ b/system/blueberry/tests/gd/rust/topshim/facade/suspend_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# +# Copyright 2021 - 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. + +import asyncio +from mobly import test_runner +from blueberry.tests.gd.rust.topshim.facade import topshim_base_test +from blueberry.tests.gd.rust.topshim.facade.automation_helper import AdapterAutomationHelper +import time + + +class SuspendTest(topshim_base_test.TopshimBaseTest): + + async def _test_verify_event_filter_cleared(self): + self.dut_adapter = AdapterAutomationHelper(port=self.dut_port) + event_loop = asyncio.get_running_loop() + self.dut_adapter.fetch_events(event_loop) + self.dut_adapter.pending_future = event_loop.create_future() + await self.dut_adapter.clear_event_filter() + #TODO(optedoblivion): Replace sleep with a call to LeGetRandom and synchronize on + # the callback + time.sleep(1) + self.dut_adapter.event_handler.cancel() + + def test_verify_event_filter_cleared(self): + asyncio.run(self._test_verify_event_filter_cleared()) + + +if __name__ == "__main__": + test_runner.main() diff --git a/system/bta/ag/bta_ag_act.cc b/system/bta/ag/bta_ag_act.cc index adf08beafc..1ba68bcc44 100644 --- a/system/bta/ag/bta_ag_act.cc +++ b/system/bta/ag/bta_ag_act.cc @@ -373,7 +373,6 @@ void bta_ag_rfc_close(tBTA_AG_SCB* p_scb, p_scb->codec_fallback = false; p_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T2; p_scb->role = 0; - p_scb->post_sco = BTA_AG_POST_SCO_NONE; p_scb->svc_conn = false; p_scb->hsp_version = HSP_VERSION_1_2; bta_ag_at_reinit(&p_scb->at_cb); 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/build/dpkg/libchrome-822064/debian/README.Debian b/system/build/dpkg/libchrome-822064/debian/README.Debian deleted file mode 100644 index 773bbf318e..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/README.Debian +++ /dev/null @@ -1 +0,0 @@ -libchrome for Debian diff --git a/system/build/dpkg/libchrome-822064/debian/changelog b/system/build/dpkg/libchrome-822064/debian/changelog deleted file mode 100644 index 113a5e756a..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -libchrome (822064-1) buster; urgency=low - - * Initial release. - - -- Sonny Sasaka <sonnysasaka@chromium.org> Fri, 30 Apr 2021 19:41:40 +0000 diff --git a/system/build/dpkg/libchrome-822064/debian/compat b/system/build/dpkg/libchrome-822064/debian/compat deleted file mode 100644 index f599e28b8a..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/compat +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/system/build/dpkg/libchrome-822064/debian/control b/system/build/dpkg/libchrome-822064/debian/control deleted file mode 100644 index b11c23fbc2..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/control +++ /dev/null @@ -1,28 +0,0 @@ -Source: libchrome -Section: libs -Priority: optional -Maintainer: Sonny Sasaka <sonnysasaka@chromium.org> -Standards-Version: 4.1.4 -Homepage: https://chromium.googlesource.com/aosp/platform/external/libchrome/ -Build-Depends: - debhelper (>=11~), - clang, - python3, - pkg-config, - ninja-build, - libglib2.0-dev, - libevent-dev, - libnss3-dev, - libdbus-1-dev, - libprotobuf-dev, - googletest, - libre2-dev, - libdouble-conversion-dev, - libssl-dev, - libabsl-dev - -Package: libchrome -Architecture: any -Multi-Arch: foreign -Depends: ${misc:Depends}, ${shlibs:Depends} -Description: Chromium's base library diff --git a/system/build/dpkg/libchrome-822064/debian/install_headers.sh b/system/build/dpkg/libchrome-822064/debian/install_headers.sh deleted file mode 100755 index 7157b1499d..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/install_headers.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -destdir="$1" - -header_dirs=( - base - base/allocator - base/containers - base/debug - base/files - base/hash - base/i18n - base/json - base/memory - base/message_loop - base/metrics - base/numerics - base/posix - base/process - base/ranges - base/strings - base/synchronization - base/system - base/task - base/task/common - base/task/sequence_manager - base/task/thread_pool - base/test - base/third_party/icu - base/third_party/nspr - base/third_party/valgrind - base/threading - base/time - base/timer - base/trace_event - base/trace_event/common - build - components/policy - components/policy/core/common - testing/gmock/include/gmock - testing/gtest/include/gtest - dbus - third_party/abseil-cpp/absl/types - ) - -# Install header files. -for d in "${header_dirs[@]}" ; do - mkdir -p "${destdir}/usr/include/libchrome/${d}" - cp libchrome/"${d}"/*.h "${destdir}/usr/include/libchrome/${d}" -done diff --git a/system/build/dpkg/libchrome-822064/debian/libchrome.install b/system/build/dpkg/libchrome-822064/debian/libchrome.install deleted file mode 100644 index 9d381c1e2b..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/libchrome.install +++ /dev/null @@ -1,4 +0,0 @@ -out/Release/lib/libbase*.so /usr/lib -out/Release/libbase*.a /usr/lib -out/Release/obj/libchrome/libchrome*.pc /usr/lib/pkgconfig -usr/include /usr diff --git a/system/build/dpkg/libchrome-822064/debian/patches/0001-Add-missing-includes.patch b/system/build/dpkg/libchrome-822064/debian/patches/0001-Add-missing-includes.patch deleted file mode 100644 index a2f2b4b9ad..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/patches/0001-Add-missing-includes.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 50a4636886c958717213856132fcbb57c3b8ea2a Mon Sep 17 00:00:00 2001 -From: Sonny Sasaka <sonnysasaka@chromium.org> -Date: Fri, 19 Mar 2021 16:18:07 -0700 -Subject: [PATCH] Add missing includes - ---- - base/hash/md5.cc | 1 + - crypto/p224_spake.cc | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/libchrome/base/hash/md5.cc b/libchrome/base/hash/md5.cc -index bdb9990a9..ef8954eaf 100644 ---- a/libchrome/base/hash/md5.cc -+++ b/libchrome/base/hash/md5.cc -@@ -24,6 +24,7 @@ - #include "base/hash/md5.h" - - #include <stddef.h> -+#include <string.h> - - namespace { - -diff --git a/libchrome/crypto/p224_spake.cc b/libchrome/crypto/p224_spake.cc -index 157410537..de0af5466 100644 ---- a/libchrome/crypto/p224_spake.cc -+++ b/libchrome/crypto/p224_spake.cc -@@ -8,6 +8,7 @@ - #include <crypto/p224_spake.h> - - #include <algorithm> -+#include <string.h> - - #include <base/logging.h> - #include <crypto/p224.h> --- -2.20.1 - diff --git a/system/build/dpkg/libchrome-822064/debian/patches/0001-rebase_path-for-write_args.patch b/system/build/dpkg/libchrome-822064/debian/patches/0001-rebase_path-for-write_args.patch deleted file mode 100644 index 9b359c0e8d..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/patches/0001-rebase_path-for-write_args.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 6875449497baf026fb8228668930a715ffcc7082 Mon Sep 17 00:00:00 2001 -From: Sonny Sasaka <sonnysasaka@chromium.org> -Date: Fri, 19 Mar 2021 16:56:59 -0700 -Subject: [PATCH] rebase_path for write_args - ---- - BUILD.gn | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/libchrome/BUILD.gn b/libchrome/BUILD.gn -index a846d8f52..66ac10a55 100644 ---- a/libchrome/BUILD.gn -+++ b/libchrome/BUILD.gn -@@ -556,7 +556,7 @@ action("base") { - - script = "//common-mk/write_args.py" - outputs = [ "${root_out_dir}/lib/lib${target_name}.so" ] -- args = [ "--output" ] + outputs + [ "--" ] + [ -+ args = [ "--output" ] + rebase_path(outputs) + [ "--" ] + [ - "GROUP", - "(", - "AS_NEEDED", -@@ -618,7 +618,7 @@ action("base-test") { - - script = "//common-mk/write_args.py" - outputs = [ "${root_out_dir}/lib${target_name}.a" ] -- args = [ "--output" ] + outputs + [ "--" ] + [ -+ args = [ "--output" ] + rebase_path(outputs) + [ "--" ] + [ - "GROUP", - "(", - "AS_NEEDED", --- -2.20.1 - diff --git a/system/build/dpkg/libchrome-822064/debian/patches/series b/system/build/dpkg/libchrome-822064/debian/patches/series deleted file mode 100644 index 9128588280..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/patches/series +++ /dev/null @@ -1,3 +0,0 @@ -0001-Add-missing-includes.patch -0001-rebase_path-for-write_args.patch -0001-Remove-absl-from-pkgconfig.patch diff --git a/system/build/dpkg/libchrome-822064/debian/rules b/system/build/dpkg/libchrome-822064/debian/rules deleted file mode 100755 index 60b6792048..0000000000 --- a/system/build/dpkg/libchrome-822064/debian/rules +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/make -f - -# gn args -defines = -defines += pkg_config=\"pkg-config\" -defines += libbase_ver=\"822064\" -defines += platform2_root=\"$(shell pwd)/\" -defines += platform_subdir=\"libchrome\" -defines += cxx=\"clang++\" -defines += cc=\"clang\" -defines += ar=\"ar\" -defines += external_cxxflags=[\"-DNDEBUG\", \"-I/usr/src/googletest/googletest/include\", \"-I/usr/src/googletest/googlemock/include\", \"-Wno-unknown-warning-option\", \"-Wno-unused-command-line-argument\"] -defines += external_ldflags=[\"-latomic\", \"-labsl_base\", \"-labsl_bad_variant_access\"] -defines += enable_werror=false -defines += libdir=\"/usr/lib\" -defines += use={mojo=false asan=false coverage=false crypto=true dbus=true fuzzer=false timers=true cros_host=false profiling=false tcmalloc=false} - -# handle parallel build options -njobs=1 -ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) -njobs=$(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) -endif - -%: - dh $@ --parallel - -override_dh_auto_build-arch: - gn gen out/Release --args="$(defines)" - ninja -j$(njobs) -C out/Release - -override_dh_auto_clean: - rm -rf out - find . -name \*.pyc -execdir rm -f {} \; - dh_auto_clean - -override_dh_auto_install-arch: - dh_auto_install - debian/install_headers.sh debian/tmp diff --git a/system/build/dpkg/libchrome-822064/gen-src-pkg.sh b/system/build/dpkg/libchrome-822064/gen-src-pkg.sh deleted file mode 100755 index cbb5dce54a..0000000000 --- a/system/build/dpkg/libchrome-822064/gen-src-pkg.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -# Generates Debian source and binary packages of libchrome. - -if [ -z "$1" ]; then - echo "Usage: gen-src-pkg.sh <output-dir>" - exit 1 -fi - -outdir="$1" -pkgdir=libchrome-822064 -origtar=libchrome_822064.orig.tar.gz -scriptdir="$( cd "$( dirname "$0" )" && pwd )" -branch=release-R91-13904.B - -tmpdir=$(mktemp -d) -echo Generating source package in "${tmpdir}". - -# Download platform2 source. -cd "${tmpdir}" -git clone --branch "${branch}" https://chromium.googlesource.com/chromiumos/platform2 || exit 1 -mkdir "${pkgdir}" -cd "${pkgdir}" -# Trim platform2, only common-mk is needed. -cp -a ../platform2/{common-mk,.gn} . - -# Download libchrome source and apply Chrome OS's patches. -git clone --branch "${branch}" https://chromium.googlesource.com/aosp/platform/external/libchrome || exit 1 -cd libchrome -rm -rf .git -while read -r patch; do - patch -p1 < "libchrome_tools/patches/${patch}" -done < <(grep -E '^[^#]' "libchrome_tools/patches/patches") - -# Clean up temporary platform2 checkout. -cd ../.. -rm -rf platform2 - -# Debian requires creating .orig.tar.gz. -tar czf "${origtar}" "${pkgdir}" - -# Debianize the source. -cd "${pkgdir}" -yes | debmake || exit 1 -cp -aT "${scriptdir}/debian/" "${tmpdir}/${pkgdir}/debian/" - -# Build source package and binary package. -cd "${tmpdir}/${pkgdir}" -dpkg-buildpackage --no-sign || exit 1 - -# Copy the results to output dir. -cd "${tmpdir}" -mkdir -p "${outdir}/src" -cp *.dsc *.orig.tar.gz *.debian.tar.xz "${outdir}/src" -cp *.deb "${outdir}" -cd / - -echo Removing temporary directory "${tmpdir}". -rm -rf "${tmpdir}" - -echo Done. Check out Debian source package in "${outdir}". diff --git a/system/build/dpkg/libchrome/debian/changelog b/system/build/dpkg/libchrome/debian/changelog index 3cdedf6890..e8985eae22 100644 --- a/system/build/dpkg/libchrome/debian/changelog +++ b/system/build/dpkg/libchrome/debian/changelog @@ -1,5 +1,5 @@ -libchrome (780652-1) buster; urgency=low +libchrome (930012-1) bullseye; urgency=low - * Initial release. + * Upgrade to BASE_VER = 930012 - -- Sonny Sasaka <sonnysasaka@chromium.org> Fri, 19 Mar 2021 19:41:40 +0000 + -- Abhishek Pandit-Subedi <abhishekpandit@chromium.org> Fri, 11 Feb 2022 14:56:00 +0000 diff --git a/system/build/dpkg/libchrome/debian/control b/system/build/dpkg/libchrome/debian/control index d804b4d88d..b11c23fbc2 100644 --- a/system/build/dpkg/libchrome/debian/control +++ b/system/build/dpkg/libchrome/debian/control @@ -18,7 +18,8 @@ Build-Depends: googletest, libre2-dev, libdouble-conversion-dev, - libssl-dev + libssl-dev, + libabsl-dev Package: libchrome Architecture: any diff --git a/system/build/dpkg/libchrome/debian/install_headers.sh b/system/build/dpkg/libchrome/debian/install_headers.sh index 19cb5efa80..817f1a6efc 100755 --- a/system/build/dpkg/libchrome/debian/install_headers.sh +++ b/system/build/dpkg/libchrome/debian/install_headers.sh @@ -5,9 +5,12 @@ destdir="$1" header_dirs=( base base/allocator + base/allocator/partition_allocator + base/allocator/partition_allocator/starscan base/containers base/debug base/files + base/functional base/hash base/i18n base/json @@ -17,6 +20,7 @@ header_dirs=( base/numerics base/posix base/process + base/ranges base/strings base/synchronization base/system @@ -33,13 +37,18 @@ header_dirs=( base/timer base/trace_event base/trace_event/common + base/types build components/policy components/policy/core/common testing/gmock/include/gmock testing/gtest/include/gtest dbus - ) + third_party/abseil-cpp/absl/types + third_party/perfetto/include/perfetto/tracing/ + third_party/perfetto/include/perfetto/protozero/ + third_party/perfetto/protos/perfetto/trace/track_event/ +) # Install header files. for d in "${header_dirs[@]}" ; do diff --git a/system/build/dpkg/libchrome/debian/libchrome.install.docker b/system/build/dpkg/libchrome/debian/libchrome.install.docker new file mode 100644 index 0000000000..b4034e7457 --- /dev/null +++ b/system/build/dpkg/libchrome/debian/libchrome.install.docker @@ -0,0 +1,5 @@ +out/Release/lib/libbase*.so /usr/lib/x86_64-linux-gnu +out/Release/libbase*.a /usr/lib/x86_64-linux-gnu +out/Release/obj/libchrome/libchrome*.pc /usr/lib/x86_64-linux-gnu/pkgconfig +usr/include /usr + diff --git a/system/build/dpkg/libchrome/debian/patches/0001-Fix-build-issues-on-930012.patch b/system/build/dpkg/libchrome/debian/patches/0001-Fix-build-issues-on-930012.patch new file mode 100644 index 0000000000..61d044b4fb --- /dev/null +++ b/system/build/dpkg/libchrome/debian/patches/0001-Fix-build-issues-on-930012.patch @@ -0,0 +1,37 @@ +From 1153211a5615156f450a4f521da284a7df4d4e5f Mon Sep 17 00:00:00 2001 +From: Abhishek Pandit-Subedi <abhishekpandit@google.com> +Date: Mon, 14 Feb 2022 14:40:41 -0800 +Subject: [PATCH] Fix build issues on 930012 + +--- + BUILD.gn | 1 + + base/command_line.h | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/libchrome/BUILD.gn b/libchrome/BUILD.gn +index 292c08565a..dbe3fb0981 100644 +--- a/libchrome/BUILD.gn ++++ b/libchrome/BUILD.gn +@@ -42,6 +42,7 @@ config("libchrome_config") { + "-Wno-unreachable-code-return", + "-Wno-unused-local-typedefs", + "-Xclang-only=-Wno-char-subscripts", ++ "-Wno-implicit-int-float-conversion", + ] + + # Address sanitizer + coverage builds do not support -z,defs. +diff --git a/libchrome/base/command_line.h b/libchrome/base/command_line.h +index 706726a73e..ad0281283a 100644 +--- a/libchrome/base/command_line.h ++++ b/libchrome/base/command_line.h +@@ -19,6 +19,7 @@ + #include <stddef.h> + #include <functional> + #include <map> ++#include <memory> + #include <string> + #include <vector> + +-- +2.35.1.265.g69c8d7142f-goog + diff --git a/system/build/dpkg/libchrome-822064/debian/patches/0001-Remove-absl-from-pkgconfig.patch b/system/build/dpkg/libchrome/debian/patches/0001-Remove-absl-from-pkgconfig.patch index 73b8c48cd6..73b8c48cd6 100644 --- a/system/build/dpkg/libchrome-822064/debian/patches/0001-Remove-absl-from-pkgconfig.patch +++ b/system/build/dpkg/libchrome/debian/patches/0001-Remove-absl-from-pkgconfig.patch diff --git a/system/build/dpkg/libchrome/debian/patches/0001-common-mk-rebase_path-output-location-of-generate-pc.patch b/system/build/dpkg/libchrome/debian/patches/0001-common-mk-rebase_path-output-location-of-generate-pc.patch deleted file mode 100644 index 418efcc475..0000000000 --- a/system/build/dpkg/libchrome/debian/patches/0001-common-mk-rebase_path-output-location-of-generate-pc.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 04fa5e1ade08696b5a2cc3b65bf0fd26c43251c7 Mon Sep 17 00:00:00 2001 -From: Sonny Sasaka <sonnysasaka@chromium.org> -Date: Fri, 19 Mar 2021 11:17:43 -0700 -Subject: [PATCH] common-mk: rebase_path output location of generate-pc.py - -Without rebase_path, the generate-pc.py would be called like -`generate-pc.py --output //out/Release` if the output is inside the -source directory and this gn path isn't recognized as a generic -filesystem path. - -BUG=b:183216216 -TEST=with modp_b64, call -$ gn gen out/Release --args=... -$ ninja - -Change-Id: Ic9d9b3d01d52d483e3d81ca2e8d514b47900f5bb ---- - common-mk/pkg_config.gni | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/common-mk/pkg_config.gni b/common-mk/pkg_config.gni -index 24e2cf1401..b2c58845d4 100644 ---- a/common-mk/pkg_config.gni -+++ b/common-mk/pkg_config.gni -@@ -84,7 +84,7 @@ template("generate_pkg_config") { - outputs = [ "${target_out_dir}/${output_name}.pc" ] - - script = "//common-mk/generate-pc.py" -- args = [ "--output" ] + outputs + [ "--name=" + name ] -+ args = [ "--output" ] + rebase_path(outputs) + [ "--name=" + name ] - if (defined(description)) { - args += [ "--description=" + description ] - } --- -2.29.2 - diff --git a/system/build/dpkg/libchrome/debian/patches/series b/system/build/dpkg/libchrome/debian/patches/series index 5a26f7be70..de8be97780 100644 --- a/system/build/dpkg/libchrome/debian/patches/series +++ b/system/build/dpkg/libchrome/debian/patches/series @@ -1,3 +1,4 @@ -0001-common-mk-rebase_path-output-location-of-generate-pc.patch 0001-Add-missing-includes.patch 0001-rebase_path-for-write_args.patch +0001-Remove-absl-from-pkgconfig.patch +0001-Fix-build-issues-on-930012.patch diff --git a/system/build/dpkg/libchrome/debian/rules b/system/build/dpkg/libchrome/debian/rules index 6ac17835c4..3374099c98 100755 --- a/system/build/dpkg/libchrome/debian/rules +++ b/system/build/dpkg/libchrome/debian/rules @@ -3,17 +3,17 @@ # gn args defines = defines += pkg_config=\"pkg-config\" -defines += libbase_ver=\"780652\" +defines += libbase_ver=\"930012\" defines += platform2_root=\"$(shell pwd)/\" defines += platform_subdir=\"libchrome\" defines += cxx=\"clang++\" defines += cc=\"clang\" defines += ar=\"ar\" -defines += external_cxxflags=[\"-I/usr/src/googletest/googletest/include\", \"-I/usr/src/googletest/googlemock/include\"] -defines += external_ldflags=[\"-latomic\"] +defines += external_cxxflags=[\"-DNDEBUG\", \"-I/usr/src/googletest/googletest/include\", \"-I/usr/src/googletest/googlemock/include\", \"-Wno-unknown-warning-option\", \"-Wno-unused-command-line-argument\", \"-Wno-implicit-int-float-conversion\"] +defines += external_ldflags=[\"-latomic\", \"-labsl_base\", \"-labsl_bad_variant_access\", \"-labsl_bad_optional_access\"] defines += enable_werror=false defines += libdir=\"/usr/lib\" -defines += use={mojo=false asan=false coverage=false crypto=true dbus=true fuzzer=false timers=true cros_host=false profiling=false tcmalloc=false} +defines += use={mojo=false asan=false coverage=false crypto=true dbus=true fuzzer=false timers=true cros_host=false profiling=false tcmalloc=false test=false} # handle parallel build options njobs=1 diff --git a/system/build/dpkg/libchrome/gen-src-pkg.sh b/system/build/dpkg/libchrome/gen-src-pkg.sh index 3cee0e4612..01390f126d 100755 --- a/system/build/dpkg/libchrome/gen-src-pkg.sh +++ b/system/build/dpkg/libchrome/gen-src-pkg.sh @@ -7,25 +7,34 @@ if [ -z "$1" ]; then fi outdir="$1" -pkgdir=libchrome-780652 -origtar=libchrome_780652.orig.tar.gz +pkgdir=libchrome-930012 +origtar=libchrome_930012.orig.tar.gz scriptdir="$( cd "$( dirname "$0" )" && pwd )" -branch=release-R90-13816.B + +# Pin the libchrome branch + commit +libchrome_branch=master +libchrome_commit=4b86c42f09b7c8d88b0233c60f59bafeb4d8df19 + +# Pin the platform2 branch + commit +platform2_branch=main +platform2_commit=4567e833015453b3ea322eec1201cc41ecdfdec0 tmpdir=$(mktemp -d) echo Generating source package in "${tmpdir}". # Download platform2 source. cd "${tmpdir}" -git clone --branch "${branch}" https://chromium.googlesource.com/chromiumos/platform2 || exit 1 +git clone --branch "${platform2_branch}" https://chromium.googlesource.com/chromiumos/platform2 || exit 1 +(cd platform2 && git checkout "${platform2_commit}") mkdir "${pkgdir}" cd "${pkgdir}" # Trim platform2, only common-mk is needed. cp -a ../platform2/{common-mk,.gn} . # Download libchrome source and apply Chrome OS's patches. -git clone --branch "${branch}" https://chromium.googlesource.com/aosp/platform/external/libchrome || exit 1 +git clone --branch "${libchrome_branch}" https://chromium.googlesource.com/aosp/platform/external/libchrome || exit 1 cd libchrome +git checkout "${libchrome_commit}" rm -rf .git while read -r patch; do patch -p1 < "libchrome_tools/patches/${patch}" @@ -43,6 +52,14 @@ cd "${pkgdir}" yes | debmake || exit 1 cp -aT "${scriptdir}/debian/" "${tmpdir}/${pkgdir}/debian/" +# If building for docker, use the right install script. +if [ ! -z "${LIBCHROME_DOCKER}" ]; then + mv "${tmpdir}/${pkgdir}/debian/libchrome.install.docker" \ + "${tmpdir}/${pkgdir}/debian/libchrome.install" +else + rm -f "${tmpdir}/${pkgdir}/debian/libchrome.install.docker" +fi + # Build source package and binary package. cd "${tmpdir}/${pkgdir}" dpkg-buildpackage --no-sign || exit 1 diff --git a/system/build/dpkg/modp_b64/debian/modp-b64.install.docker b/system/build/dpkg/modp_b64/debian/modp-b64.install.docker new file mode 100644 index 0000000000..a028ca4955 --- /dev/null +++ b/system/build/dpkg/modp_b64/debian/modp-b64.install.docker @@ -0,0 +1,3 @@ +modp_b64/modp_b64 /usr/include +out/Release/libmodp_b64.a /usr/lib/x86_64-linux-gnu +out/Release/obj/modp_b64/libmodp_b64.pc /usr/lib/x86_64-linux-gnu/pkgconfig diff --git a/system/build/dpkg/modp_b64/gen-src-pkg.sh b/system/build/dpkg/modp_b64/gen-src-pkg.sh index cc22f678f2..5e62160159 100755 --- a/system/build/dpkg/modp_b64/gen-src-pkg.sh +++ b/system/build/dpkg/modp_b64/gen-src-pkg.sh @@ -40,6 +40,15 @@ cd "${pkgdir}" yes | debmake || exit 1 cp -aT "${scriptdir}/debian/" "${tmpdir}/${pkgdir}/debian/" +# If building for docker, use the right install script. +if [ ! -z "${MODP_DOCKER}" ]; then + mv "${tmpdir}/${pkgdir}/debian/modp-b64.install.docker" \ + "${tmpdir}/${pkgdir}/debian/modp-b64.install" +else + rm -f "${tmpdir}/${pkgdir}/debian/modp-b64.install.docker" +fi + + # Build source package and binary package. cd "${tmpdir}/${pkgdir}" dpkg-buildpackage --no-sign || exit 1 diff --git a/system/gd/cert/run_topshim b/system/gd/cert/run_topshim index 50a01343df..a2c04848c4 100755 --- a/system/gd/cert/run_topshim +++ b/system/gd/cert/run_topshim @@ -20,7 +20,8 @@ import os import argparse TEST_SUITES = [ - "blueberry.tests.gd.rust.topshim.facade.adapter_test" + "blueberry.tests.gd.rust.topshim.facade.adapter_test", + "blueberry.tests.gd.rust.topshim.facade.suspend_test" ] SOONG_UI_BASH = 'build/soong/soong_ui.bash' @@ -135,7 +136,7 @@ def main(): if not all(test_results): failures = [i for i, x in enumerate(test_results) if not x] for index in failures: - print('TEST FAILLED: ' + TEST_SUITES[index]) + print('TEST FAILED: ' + TEST_SUITES[index]) sys.exit(0) print('TEST PASSED ' + str(len(test_results)) + ' tests were run') diff --git a/system/gd/rust/topshim/facade/src/adapter_service.rs b/system/gd/rust/topshim/facade/src/adapter_service.rs index efd96eebd4..1243b07b16 100644 --- a/system/gd/rust/topshim/facade/src/adapter_service.rs +++ b/system/gd/rust/topshim/facade/src/adapter_service.rs @@ -3,6 +3,7 @@ use bt_topshim::btif; use bt_topshim::btif::{BaseCallbacks, BaseCallbacksDispatcher, BluetoothInterface}; +use bt_topshim_facade_protobuf::empty::Empty; use bt_topshim_facade_protobuf::facade::{ EventType, FetchEventsRequest, FetchEventsResponse, SetDiscoveryModeRequest, SetDiscoveryModeResponse, ToggleStackRequest, ToggleStackResponse, @@ -118,4 +119,11 @@ impl AdapterService for AdapterServiceImpl { sink.success(SetDiscoveryModeResponse::default()).await.unwrap(); }) } + + fn clear_event_filter(&mut self, ctx: RpcContext<'_>, _req: Empty, sink: UnarySink<Empty>) { + self.btif_intf.lock().unwrap().clear_event_filter(); + ctx.spawn(async move { + sink.success(Empty::default()).await.unwrap(); + }) + } } diff --git a/system/gd/rust/topshim/src/btif.rs b/system/gd/rust/topshim/src/btif.rs index 6b76eac797..9febbaca3c 100644 --- a/system/gd/rust/topshim/src/btif.rs +++ b/system/gd/rust/topshim/src/btif.rs @@ -1004,6 +1004,10 @@ impl BluetoothInterface { ccall!(self, ssp_reply, ffi_addr, cvariant, accept, passkey) } + pub fn clear_event_filter(&self) -> i32 { + ccall!(self, clear_event_filter) + } + pub(crate) fn get_profile_interface( &self, profile: SupportedProfiles, 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, diff --git a/system/stack/btm/btm_iso_impl.h b/system/stack/btm/btm_iso_impl.h index dafd76b689..e9cd6e8287 100644 --- a/system/stack/btm/btm_iso_impl.h +++ b/system/stack/btm/btm_iso_impl.h @@ -316,7 +316,7 @@ struct iso_impl { void remove_iso_data_path(uint16_t iso_handle, uint8_t data_path_dir) { iso_base* iso = GetIsoIfKnown(iso_handle); - LOG_ASSERT(iso != nullptr) << "No such iso connection: " << +iso_handle; + LOG_ASSERT(iso != nullptr) << "No such iso connection: " << loghex(iso_handle); LOG_ASSERT((iso->state_flags & kStateFlagHasDataPathSet) == kStateFlagHasDataPathSet) << "Data path not set"; @@ -373,7 +373,7 @@ struct iso_impl { void read_iso_link_quality(uint16_t iso_handle) { iso_base* iso = GetIsoIfKnown(iso_handle); if (iso == nullptr) { - LOG(ERROR) << __func__ << "No such iso connection: " << +iso_handle; + LOG(ERROR) << __func__ << "No such iso connection: " << loghex(iso_handle); return; } @@ -416,14 +416,17 @@ struct iso_impl { uint16_t data_len) { iso_base* iso = GetIsoIfKnown(iso_handle); LOG_ASSERT(iso != nullptr) - << "No such iso connection handle: " << +iso_handle; + << "No such iso connection handle: " << loghex(iso_handle); if (!(iso->state_flags & kStateFlagIsBroadcast)) { - LOG_ASSERT(iso->state_flags & kStateFlagIsConnected) - << "CIS not established"; + if (!(iso->state_flags & kStateFlagIsConnected)) { + LOG(WARNING) << __func__ << "Cis handle: " << loghex(iso_handle) + << " not established"; + return; + } } LOG_ASSERT(iso->state_flags & kStateFlagHasDataPathSet) - << "Data path not set for handle: " << +iso_handle; + << "Data path not set for handle: " << loghex(iso_handle); /* Calculate sequence number for the ISO data packet. * It should be incremented by 1 every SDU Interval. diff --git a/system/stack/test/btm_iso_test.cc b/system/stack/test/btm_iso_test.cc index bae5c005a5..f4984296f7 100644 --- a/system/stack/test/btm_iso_test.cc +++ b/system/stack/test/btm_iso_test.cc @@ -1805,6 +1805,17 @@ TEST_F(IsoManagerTest, RemoveIsoDataPathInvalidStatus) { iso_handle, kDefaultIsoDataPathParams.data_path_dir); } +TEST_F(IsoManagerTest, SendIsoDataWithNoCigConnected) { + std::vector<uint8_t> data_vec(108, 0); + IsoManager::GetInstance()->CreateCig( + volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams); + + auto handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0]; + IsoManager::GetInstance()->SendIsoData(handle, data_vec.data(), + data_vec.size()); + EXPECT_CALL(bte_interface_, HciSend).Times(0); +} + TEST_F(IsoManagerTest, SendIsoDataCigValid) { IsoManager::GetInstance()->CreateCig( volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams); @@ -2137,17 +2148,6 @@ TEST_F(IsoManagerDeathTest, SendIsoDataWithNoCigBigHandle) { ::testing::KilledBySignal(SIGABRT), "No such iso"); } -TEST_F(IsoManagerDeathTest, SendIsoDataWithNoCigConnected) { - std::vector<uint8_t> data_vec(108, 0); - IsoManager::GetInstance()->CreateCig( - volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams); - - auto handle = volatile_test_cig_create_cmpl_evt_.conn_handles[0]; - ASSERT_EXIT(IsoManager::GetInstance()->SendIsoData(handle, data_vec.data(), - data_vec.size()), - ::testing::KilledBySignal(SIGABRT), "CIS not established"); -} - TEST_F(IsoManagerTest, HandleDisconnectNoSuchHandle) { // Don't expect any callbacks when connection handle is not for ISO. EXPECT_CALL(*cig_callbacks_, OnCigEvent).Times(0); diff --git a/system/vendor_libs/test_vendor_lib/net/posix/posix_async_socket.cc b/system/vendor_libs/test_vendor_lib/net/posix/posix_async_socket.cc index c5e8af3a94..1d57d10f34 100644 --- a/system/vendor_libs/test_vendor_lib/net/posix/posix_async_socket.cc +++ b/system/vendor_libs/test_vendor_lib/net/posix/posix_async_socket.cc @@ -41,7 +41,7 @@ PosixAsyncSocket::PosixAsyncSocket(int fd, AsyncManager* am) fcntl(fd, F_SETFL, flags | O_NONBLOCK); flags = fcntl(fd, F_GETFD); - fcntl(fd, F_SETFD, flags | O_CLOEXEC); + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); #ifdef SO_NOSIGPIPE // Disable SIGPIPE generation on Darwin. |