diff options
author | TreeHugger Robot <treehugger-gerrit@google.com> | 2022-01-18 23:59:58 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2022-01-18 23:59:58 +0000 |
commit | d15d8266b900c549d2eed52c56f82d7c3c3792c8 (patch) | |
tree | 0b7c4c70453c091e24e37758ff1b1fd49bf20eb3 /framework/java/android/bluetooth/BluetoothVolumeControl.java | |
parent | 7d69d903a5ee30dac4abc82e056f2b8ec0278708 (diff) | |
parent | aa6d8f71ae61c0e57b35258c00d28fcba5154f09 (diff) |
Merge changes from topic "migrate-sc-bt" into sc-v2-dev-plus-aosp
* changes:
Migrating BT files into packages/modules/Bluetooth
Merge history of Bluetooth API from frameworks/base
Diffstat (limited to 'framework/java/android/bluetooth/BluetoothVolumeControl.java')
-rw-r--r-- | framework/java/android/bluetooth/BluetoothVolumeControl.java | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/framework/java/android/bluetooth/BluetoothVolumeControl.java b/framework/java/android/bluetooth/BluetoothVolumeControl.java new file mode 100644 index 0000000000..27532aabc3 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothVolumeControl.java @@ -0,0 +1,337 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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 static android.bluetooth.BluetoothUtils.getSyncTimeout; + +import android.Manifest; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.content.AttributionSource; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.CloseGuard; +import android.util.Log; + +import com.android.modules.utils.SynchronousResultReceiver; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeoutException; + +/** + * This class provides the public APIs to control the Bluetooth Volume Control service. + * + * <p>BluetoothVolumeControl is a proxy object for controlling the Bluetooth VC + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothVolumeControl proxy object. + * @hide + */ +@SystemApi +public final class BluetoothVolumeControl implements BluetoothProfile, AutoCloseable { + private static final String TAG = "BluetoothVolumeControl"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + private CloseGuard mCloseGuard; + + /** + * Intent used to broadcast the change in connection state of the Volume Control + * profile. + * + * <p>This intent will have 3 extras: + * <ul> + * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> + * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED"; + + private BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; + private final BluetoothProfileConnector<IBluetoothVolumeControl> mProfileConnector = + new BluetoothProfileConnector(this, BluetoothProfile.VOLUME_CONTROL, TAG, + IBluetoothVolumeControl.class.getName()) { + @Override + public IBluetoothVolumeControl getServiceInterface(IBinder service) { + return IBluetoothVolumeControl.Stub.asInterface(service); + } + }; + + /** + * Create a BluetoothVolumeControl proxy object for interacting with the local + * Bluetooth Volume Control service. + */ + /*package*/ BluetoothVolumeControl(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); + mProfileConnector.connect(context, listener); + mCloseGuard = new CloseGuard(); + mCloseGuard.open("close"); + } + + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + protected void finalize() { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public void close() { + mProfileConnector.disconnect(); + } + + private IBluetoothVolumeControl getService() { return mProfileConnector.getService(); } + + /** + * Get the list of connected devices. Currently at most one. + * + * @return list of connected devices + * + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @NonNull List<BluetoothDevice> getConnectedDevices() { + if (DBG) log("getConnectedDevices()"); + final IBluetoothVolumeControl service = getService(); + final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } else if (isEnabled()) { + try { + final SynchronousResultReceiver<List<BluetoothDevice>> recv = + new SynchronousResultReceiver(); + service.getConnectedDevices(mAttributionSource, recv); + return Attributable.setAttributionSource( + recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), + mAttributionSource); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + return defaultValue; + } + + /** + * Get the list of devices matching specified states. Currently at most one. + * + * @return list of matching devices + * + * @hide + */ + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (DBG) log("getDevicesMatchingStates()"); + final IBluetoothVolumeControl service = getService(); + final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } else if (isEnabled()) { + try { + final SynchronousResultReceiver<List<BluetoothDevice>> recv = + new SynchronousResultReceiver(); + service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); + return Attributable.setAttributionSource( + recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), + mAttributionSource); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + return defaultValue; + } + + /** + * Get connection state of device + * + * @return device connection state + * + * @hide + */ + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + public int getConnectionState(BluetoothDevice device) { + if (DBG) log("getConnectionState(" + device + ")"); + final IBluetoothVolumeControl service = getService(); + final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; + 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<Integer> recv = new SynchronousResultReceiver(); + service.getConnectionState(device, mAttributionSource, recv); + return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + return defaultValue; + } + + /** + * Tells remote device to set an absolute volume. + * + * @param volume Absolute volume to be set on remote device. + * Minimum value is 0 and maximum value is 255 + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void setVolume(@Nullable BluetoothDevice device, + @IntRange(from = 0, to = 255) int volume) { + if (DBG) log("setVolume(" + volume + ")"); + final IBluetoothVolumeControl service = getService(); + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } else if (isEnabled()) { + try { + final SynchronousResultReceiver recv = new SynchronousResultReceiver(); + service.setVolume(device, volume, mAttributionSource, recv); + recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + } + + /** + * Set connection policy of the profile + * + * <p> The device should already be paired. + * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, + * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy is the connection policy to set to for this profile + * @return true if connectionPolicy is set, false on error + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + final IBluetoothVolumeControl 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) + && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { + try { + final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); + service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); + return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + return defaultValue; + } + + /** + * Get the connection policy of the profile. + * + * <p> The connection policy can be any of: + * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, + * {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Bluetooth device + * @return connection policy of the device + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { + if (VDBG) log("getConnectionPolicy(" + device + ")"); + final IBluetoothVolumeControl service = getService(); + final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + 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<Integer> recv = new SynchronousResultReceiver(); + service.getConnectionPolicy(device, mAttributionSource, recv); + return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); + } catch (RemoteException | TimeoutException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + return defaultValue; + } + + private boolean isEnabled() { + return mAdapter.getState() == BluetoothAdapter.STATE_ON; + } + + private static boolean isValidDevice(@Nullable BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} |