Loading android/app/aidl/android/bluetooth/IBluetooth.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -280,6 +280,8 @@ interface IBluetooth @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") Bundle getPreferredAudioProfiles(in BluetoothDevice device, in AttributionSource source); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int isDualModeAudioEnabled(in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int registerPreferredAudioProfilesChangedCallback(in IBluetoothPreferredAudioProfilesCallback callback, in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int unregisterPreferredAudioProfilesChangedCallback(in IBluetoothPreferredAudioProfilesCallback callback, in AttributionSource attributionSource); Loading android/app/src/com/android/bluetooth/btservice/AdapterService.java +19 −1 Original line number Diff line number Diff line Loading @@ -3982,6 +3982,24 @@ public class AdapterService extends Service { return service.notifyActiveDeviceChangeApplied(device); } @Override public int isDualModeAudioEnabled(AttributionSource source) { AdapterService service = getService(); if (service == null) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } if (!Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) { return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION; } service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); if (!Utils.isDualModeAudioEnabled()) { return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; } return BluetoothStatusCodes.SUCCESS; } @Override public int registerPreferredAudioProfilesChangedCallback( IBluetoothPreferredAudioProfilesCallback callback, AttributionSource source) { Loading @@ -4000,7 +4018,7 @@ public class AdapterService extends Service { service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); // If LE only mode is enabled, the dual mode audio feature is disabled if (!isDualModeAudioEnabled()) { if (!Utils.isDualModeAudioEnabled()) { return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; } Loading framework/java/android/bluetooth/BluetoothAdapter.java +44 −76 Original line number Diff line number Diff line Loading @@ -889,8 +889,6 @@ public final class BluetoothAdapter { mMetadataListeners = new HashMap<>(); private final Map<BluetoothConnectionCallback, Executor> mBluetoothConnectionCallbackExecutorMap = new HashMap<>(); private final Map<PreferredAudioProfilesChangedCallback, Executor> mAudioProfilesChangedCallbackExecutorMap = new HashMap<>(); private static final class ProfileConnection { int mProfile; Loading Loading @@ -1128,6 +1126,28 @@ public final class BluetoothAdapter { new CallbackWrapper( registerQualityReportCallbackConsumer, unregisterQualityReportCallbackConsumer); Consumer<IBluetooth> registerAudioProfilesCallbackConsumer = (IBluetooth service) -> { try { service.registerPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } }; Consumer<IBluetooth> unregisterAudioProfilesCallbackConsumer = (IBluetooth service) -> { try { service.unregisterPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } }; mAudioProfilesCallbackWrapper = new CallbackWrapper( registerAudioProfilesCallbackConsumer, unregisterAudioProfilesCallbackConsumer); } /** Loading Loading @@ -3762,21 +3782,7 @@ public final class BluetoothAdapter { } }); } synchronized (mAudioProfilesChangedCallbackExecutorMap) { if (!mAudioProfilesChangedCallbackExecutorMap.isEmpty()) { try { mService.registerPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } catch (RemoteException e) { Log.e( TAG, "onBluetoothServiceUp: Failed to register bluetooth" + "connection callback", e); } } } mAudioProfilesCallbackWrapper.registerToNewService(mService); mQualityCallbackWrapper.registerToNewService(mService); } finally { mServiceLock.readLock().unlock(); Loading Loading @@ -5068,24 +5074,19 @@ public final class BluetoothAdapter { return BluetoothStatusCodes.ERROR_UNKNOWN; } @SuppressLint("AndroidFrameworkBluetoothPermission") private final CallbackWrapper<PreferredAudioProfilesChangedCallback, IBluetooth> mAudioProfilesCallbackWrapper; private final IBluetoothPreferredAudioProfilesCallback mPreferredAudioProfilesChangedCallback = new IBluetoothPreferredAudioProfilesCallback.Stub() { @Override public void onPreferredAudioProfilesChanged( BluetoothDevice device, Bundle preferredAudioProfiles, int status) { for (Map.Entry<PreferredAudioProfilesChangedCallback, Executor> callbackExecutorEntry : mAudioProfilesChangedCallbackExecutorMap.entrySet()) { PreferredAudioProfilesChangedCallback callback = callbackExecutorEntry.getKey(); Executor executor = callbackExecutorEntry.getValue(); executor.execute( () -> callback.onPreferredAudioProfilesChanged( mAudioProfilesCallbackWrapper.forEach( (cb) -> cb.onPreferredAudioProfilesChanged( device, preferredAudioProfiles, status)); } } }; /** @hide */ Loading Loading @@ -5126,37 +5127,21 @@ public final class BluetoothAdapter { @NonNull @CallbackExecutor Executor executor, @NonNull PreferredAudioProfilesChangedCallback callback) { if (DBG) Log.d(TAG, "registerPreferredAudioProfilesChangedCallback()"); requireNonNull(executor, "executor cannot be null"); requireNonNull(callback, "callback cannot be null"); requireNonNull(callback); requireNonNull(executor); synchronized (mAudioProfilesChangedCallbackExecutorMap) { // If the callback map is empty, we register the service-to-app callback if (mAudioProfilesChangedCallbackExecutorMap.isEmpty()) { int serviceCallStatus = BluetoothStatusCodes.ERROR_UNKNOWN; mServiceLock.readLock().lock(); try { if (mService != null) { serviceCallStatus = mService.registerPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); int status = mService.isDualModeAudioEnabled(mAttributionSource); if (status != BluetoothStatusCodes.SUCCESS) { return status; } mAudioProfilesCallbackWrapper.registerCallback(mService, callback, executor); } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } finally { mServiceLock.readLock().unlock(); } if (serviceCallStatus != BluetoothStatusCodes.SUCCESS) { return serviceCallStatus; } } // Adds the passed in callback to our local mapping if (mAudioProfilesChangedCallbackExecutorMap.containsKey(callback)) { throw new IllegalArgumentException("This callback has already been registered"); } else { mAudioProfilesChangedCallbackExecutorMap.put(callback, executor); } } return BluetoothStatusCodes.SUCCESS; } Loading Loading @@ -5196,32 +5181,15 @@ public final class BluetoothAdapter { public int unregisterPreferredAudioProfilesChangedCallback( @NonNull PreferredAudioProfilesChangedCallback callback) { if (DBG) Log.d(TAG, "unregisterPreferredAudioProfilesChangedCallback()"); requireNonNull(callback, "callback cannot be null"); synchronized (mAudioProfilesChangedCallbackExecutorMap) { if (mAudioProfilesChangedCallbackExecutorMap.remove(callback) == null) { throw new IllegalArgumentException("This callback has not been registered"); } } if (!mAudioProfilesChangedCallbackExecutorMap.isEmpty()) { return BluetoothStatusCodes.SUCCESS; } // If the callback map is empty, we unregister the service-to-app callback mServiceLock.readLock().lock(); try { if (mService != null) { return mService.unregisterPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); mAudioProfilesCallbackWrapper.unregisterCallback(mService, callback); } finally { mServiceLock.readLock().unlock(); } return BluetoothStatusCodes.ERROR_UNKNOWN; return BluetoothStatusCodes.SUCCESS; } /** Loading Loading
android/app/aidl/android/bluetooth/IBluetooth.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -280,6 +280,8 @@ interface IBluetooth @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") Bundle getPreferredAudioProfiles(in BluetoothDevice device, in AttributionSource source); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int isDualModeAudioEnabled(in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int registerPreferredAudioProfilesChangedCallback(in IBluetoothPreferredAudioProfilesCallback callback, in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int unregisterPreferredAudioProfilesChangedCallback(in IBluetoothPreferredAudioProfilesCallback callback, in AttributionSource attributionSource); Loading
android/app/src/com/android/bluetooth/btservice/AdapterService.java +19 −1 Original line number Diff line number Diff line Loading @@ -3982,6 +3982,24 @@ public class AdapterService extends Service { return service.notifyActiveDeviceChangeApplied(device); } @Override public int isDualModeAudioEnabled(AttributionSource source) { AdapterService service = getService(); if (service == null) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } if (!Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) { return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION; } service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); if (!Utils.isDualModeAudioEnabled()) { return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; } return BluetoothStatusCodes.SUCCESS; } @Override public int registerPreferredAudioProfilesChangedCallback( IBluetoothPreferredAudioProfilesCallback callback, AttributionSource source) { Loading @@ -4000,7 +4018,7 @@ public class AdapterService extends Service { service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); // If LE only mode is enabled, the dual mode audio feature is disabled if (!isDualModeAudioEnabled()) { if (!Utils.isDualModeAudioEnabled()) { return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; } Loading
framework/java/android/bluetooth/BluetoothAdapter.java +44 −76 Original line number Diff line number Diff line Loading @@ -889,8 +889,6 @@ public final class BluetoothAdapter { mMetadataListeners = new HashMap<>(); private final Map<BluetoothConnectionCallback, Executor> mBluetoothConnectionCallbackExecutorMap = new HashMap<>(); private final Map<PreferredAudioProfilesChangedCallback, Executor> mAudioProfilesChangedCallbackExecutorMap = new HashMap<>(); private static final class ProfileConnection { int mProfile; Loading Loading @@ -1128,6 +1126,28 @@ public final class BluetoothAdapter { new CallbackWrapper( registerQualityReportCallbackConsumer, unregisterQualityReportCallbackConsumer); Consumer<IBluetooth> registerAudioProfilesCallbackConsumer = (IBluetooth service) -> { try { service.registerPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } }; Consumer<IBluetooth> unregisterAudioProfilesCallbackConsumer = (IBluetooth service) -> { try { service.unregisterPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } }; mAudioProfilesCallbackWrapper = new CallbackWrapper( registerAudioProfilesCallbackConsumer, unregisterAudioProfilesCallbackConsumer); } /** Loading Loading @@ -3762,21 +3782,7 @@ public final class BluetoothAdapter { } }); } synchronized (mAudioProfilesChangedCallbackExecutorMap) { if (!mAudioProfilesChangedCallbackExecutorMap.isEmpty()) { try { mService.registerPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } catch (RemoteException e) { Log.e( TAG, "onBluetoothServiceUp: Failed to register bluetooth" + "connection callback", e); } } } mAudioProfilesCallbackWrapper.registerToNewService(mService); mQualityCallbackWrapper.registerToNewService(mService); } finally { mServiceLock.readLock().unlock(); Loading Loading @@ -5068,24 +5074,19 @@ public final class BluetoothAdapter { return BluetoothStatusCodes.ERROR_UNKNOWN; } @SuppressLint("AndroidFrameworkBluetoothPermission") private final CallbackWrapper<PreferredAudioProfilesChangedCallback, IBluetooth> mAudioProfilesCallbackWrapper; private final IBluetoothPreferredAudioProfilesCallback mPreferredAudioProfilesChangedCallback = new IBluetoothPreferredAudioProfilesCallback.Stub() { @Override public void onPreferredAudioProfilesChanged( BluetoothDevice device, Bundle preferredAudioProfiles, int status) { for (Map.Entry<PreferredAudioProfilesChangedCallback, Executor> callbackExecutorEntry : mAudioProfilesChangedCallbackExecutorMap.entrySet()) { PreferredAudioProfilesChangedCallback callback = callbackExecutorEntry.getKey(); Executor executor = callbackExecutorEntry.getValue(); executor.execute( () -> callback.onPreferredAudioProfilesChanged( mAudioProfilesCallbackWrapper.forEach( (cb) -> cb.onPreferredAudioProfilesChanged( device, preferredAudioProfiles, status)); } } }; /** @hide */ Loading Loading @@ -5126,37 +5127,21 @@ public final class BluetoothAdapter { @NonNull @CallbackExecutor Executor executor, @NonNull PreferredAudioProfilesChangedCallback callback) { if (DBG) Log.d(TAG, "registerPreferredAudioProfilesChangedCallback()"); requireNonNull(executor, "executor cannot be null"); requireNonNull(callback, "callback cannot be null"); requireNonNull(callback); requireNonNull(executor); synchronized (mAudioProfilesChangedCallbackExecutorMap) { // If the callback map is empty, we register the service-to-app callback if (mAudioProfilesChangedCallbackExecutorMap.isEmpty()) { int serviceCallStatus = BluetoothStatusCodes.ERROR_UNKNOWN; mServiceLock.readLock().lock(); try { if (mService != null) { serviceCallStatus = mService.registerPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); int status = mService.isDualModeAudioEnabled(mAttributionSource); if (status != BluetoothStatusCodes.SUCCESS) { return status; } mAudioProfilesCallbackWrapper.registerCallback(mService, callback, executor); } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } finally { mServiceLock.readLock().unlock(); } if (serviceCallStatus != BluetoothStatusCodes.SUCCESS) { return serviceCallStatus; } } // Adds the passed in callback to our local mapping if (mAudioProfilesChangedCallbackExecutorMap.containsKey(callback)) { throw new IllegalArgumentException("This callback has already been registered"); } else { mAudioProfilesChangedCallbackExecutorMap.put(callback, executor); } } return BluetoothStatusCodes.SUCCESS; } Loading Loading @@ -5196,32 +5181,15 @@ public final class BluetoothAdapter { public int unregisterPreferredAudioProfilesChangedCallback( @NonNull PreferredAudioProfilesChangedCallback callback) { if (DBG) Log.d(TAG, "unregisterPreferredAudioProfilesChangedCallback()"); requireNonNull(callback, "callback cannot be null"); synchronized (mAudioProfilesChangedCallbackExecutorMap) { if (mAudioProfilesChangedCallbackExecutorMap.remove(callback) == null) { throw new IllegalArgumentException("This callback has not been registered"); } } if (!mAudioProfilesChangedCallbackExecutorMap.isEmpty()) { return BluetoothStatusCodes.SUCCESS; } // If the callback map is empty, we unregister the service-to-app callback mServiceLock.readLock().lock(); try { if (mService != null) { return mService.unregisterPreferredAudioProfilesChangedCallback( mPreferredAudioProfilesChangedCallback, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); mAudioProfilesCallbackWrapper.unregisterCallback(mService, callback); } finally { mServiceLock.readLock().unlock(); } return BluetoothStatusCodes.ERROR_UNKNOWN; return BluetoothStatusCodes.SUCCESS; } /** Loading