summaryrefslogtreecommitdiff
path: root/framework/java/android/bluetooth/BluetoothVolumeControl.java
diff options
context:
space:
mode:
authorŁukasz Rymanowski <rlukasz@google.com>2022-03-03 16:33:53 +0000
committerŁukasz Rymanowski <rlukasz@google.com>2022-03-08 13:57:42 +0000
commit0037dc0fa7489f11a84e9d2ab0969bb58a1c97fc (patch)
treed6940bbf548f5a5f108cceab67c1b9fed929dcc7 /framework/java/android/bluetooth/BluetoothVolumeControl.java
parente1a823ea973ce5d277398f8034200c4d81d64faf (diff)
BluetoothVolumeControl: Add missing API implementation
Bug: 150670922 Sponsor: @jpawlowski Test: atest CtsBluetoothTestCases:android.bluetooth.cts.BluetoothVolumeControlTest Change-Id: I6d41c13ee0026dfbb841543ff2a20800d864b82c
Diffstat (limited to 'framework/java/android/bluetooth/BluetoothVolumeControl.java')
-rw-r--r--framework/java/android/bluetooth/BluetoothVolumeControl.java154
1 files changed, 142 insertions, 12 deletions
diff --git a/framework/java/android/bluetooth/BluetoothVolumeControl.java b/framework/java/android/bluetooth/BluetoothVolumeControl.java
index fb8abb1cf2..a359c91206 100644
--- a/framework/java/android/bluetooth/BluetoothVolumeControl.java
+++ b/framework/java/android/bluetooth/BluetoothVolumeControl.java
@@ -40,7 +40,10 @@ import android.util.Log;
import com.android.modules.utils.SynchronousResultReceiver;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
@@ -59,6 +62,7 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
private static final boolean VDBG = false;
private CloseGuard mCloseGuard;
+ private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>();
/**
* This class provides a callback that is invoked when volume offset value changes on
@@ -87,6 +91,21 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
@IntRange(from = -255, to = 255) int volumeOffset);
}
+ @SuppressLint("AndroidFrameworkBluetoothPermission")
+ private final IBluetoothVolumeControlCallback mCallback =
+ new IBluetoothVolumeControlCallback.Stub() {
+ @Override
+ public void onVolumeOffsetChanged(@NonNull BluetoothDevice device, int volumeOffset) {
+ Attributable.setAttributionSource(device, mAttributionSource);
+ for (Map.Entry<BluetoothVolumeControl.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothVolumeControl.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onVolumeOffsetChanged(device, volumeOffset));
+ }
+ }
+ };
+
/**
* Intent used to broadcast the change in connection state of the Volume Control
* profile.
@@ -123,6 +142,37 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
}
};
+ @SuppressLint("AndroidFrameworkBluetoothPermission")
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ return;
+ }
+ // re-register the service-to-app callback
+ synchronized (mCallbackExecutorMap) {
+ if (!mCallbackExecutorMap.isEmpty()) {
+ try {
+ final IBluetoothVolumeControl service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv =
+ new SynchronousResultReceiver();
+ service.registerCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "onBluetoothServiceUp: Failed to register"
+ + "Volume Control callback", e);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n"
+ + Log.getStackTraceString(new Throwable()));
+ }
+ }
+ }
+ }
+ };
+
/**
* Create a BluetoothVolumeControl proxy object for interacting with the local
* Bluetooth Volume Control service.
@@ -132,6 +182,17 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
mAdapter = adapter;
mAttributionSource = adapter.getAttributionSource();
mProfileConnector.connect(context, listener);
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
mCloseGuard = new CloseGuard();
mCloseGuard.open("close");
}
@@ -146,6 +207,16 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
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.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+ }
mProfileConnector.disconnect();
}
@@ -267,14 +338,34 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
})
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) {
- if (executor == null) {
- throw new IllegalArgumentException("executor cannot be null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback cannot be null");
- }
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
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 IBluetoothVolumeControl service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv =
+ new SynchronousResultReceiver();
+ service.registerCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ throw e.rethrowFromSystemServer();
+ } catch (IllegalStateException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+ }
+
+ // 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);
+ }
}
/**
@@ -295,11 +386,30 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
})
public void unregisterCallback(@NonNull Callback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback cannot be null");
- }
+ Objects.requireNonNull(callback, "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 IBluetoothVolumeControl service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.unregisterCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ throw e.rethrowFromSystemServer();
+ } catch (IllegalStateException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+ }
}
/**
@@ -353,7 +463,27 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
})
public boolean isVolumeOffsetAvailable(@NonNull BluetoothDevice device) {
if (DBG) log("isVolumeOffsetAvailable(" + device + ")");
- return false;
+ final IBluetoothVolumeControl service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+
+ if (!isEnabled()) {
+ return false;
+ }
+
+ final boolean defaultValue = false;
+ try {
+ final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+ service.isVolumeOffsetAvailable(device, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+
+ return defaultValue;
}
/**