blob: d033d5326ab06be4b760bb6119f3d6f27c0dcfed [file] [log] [blame]
Mike Lockwood517b04f2014-06-02 16:20:37 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth;
18
William Escande9a4b7c12021-12-16 16:07:55 +010019import static android.bluetooth.BluetoothUtils.getSyncTimeout;
20
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060021import android.annotation.RequiresPermission;
Jeff Sharkeyd7c55662021-04-20 12:30:37 -060022import android.annotation.SdkConstant;
Jeff Sharkeyd7c55662021-04-20 12:30:37 -060023import android.annotation.SdkConstant.SdkConstantType;
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060024import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
25import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -060026import android.content.AttributionSource;
Mike Lockwood517b04f2014-06-02 16:20:37 -070027import android.content.Context;
David Duartef5b3bc52023-10-17 00:55:06 +000028import android.os.IBinder;
Mike Lockwood517b04f2014-06-02 16:20:37 -070029import android.os.RemoteException;
30import android.util.Log;
31
William Escande9a4b7c12021-12-16 16:07:55 +010032import com.android.modules.utils.SynchronousResultReceiver;
33
Mike Lockwood517b04f2014-06-02 16:20:37 -070034import java.util.ArrayList;
35import java.util.List;
William Escande9a4b7c12021-12-16 16:07:55 +010036import java.util.concurrent.TimeoutException;
Mike Lockwood517b04f2014-06-02 16:20:37 -070037
38/**
Sanket Agarwal25e84d42015-10-21 18:23:27 -070039 * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
40 * supports player information, playback support and track metadata.
Mike Lockwood517b04f2014-06-02 16:20:37 -070041 *
David Duarteee52b7e2023-12-02 01:32:11 +000042 * <p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP Service via
43 * IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothAvrcpController proxy
44 * object.
Mike Lockwood517b04f2014-06-02 16:20:37 -070045 *
David Duarteee52b7e2023-12-02 01:32:11 +000046 * <p>{@hide}
Mike Lockwood517b04f2014-06-02 16:20:37 -070047 */
48public final class BluetoothAvrcpController implements BluetoothProfile {
49 private static final String TAG = "BluetoothAvrcpController";
Sanket Agarwal25e84d42015-10-21 18:23:27 -070050 private static final boolean DBG = false;
Mike Lockwood517b04f2014-06-02 16:20:37 -070051 private static final boolean VDBG = false;
52
53 /**
David Duarteee52b7e2023-12-02 01:32:11 +000054 * Intent used to broadcast the change in connection state of the AVRCP Controller profile.
Mike Lockwood517b04f2014-06-02 16:20:37 -070055 *
56 * <p>This intent will have 3 extras:
David Duarteee52b7e2023-12-02 01:32:11 +000057 *
Mike Lockwood517b04f2014-06-02 16:20:37 -070058 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +000059 * <li>{@link #EXTRA_STATE} - The current state of the profile.
60 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
61 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
Mike Lockwood517b04f2014-06-02 16:20:37 -070062 * </ul>
63 *
David Duarteee52b7e2023-12-02 01:32:11 +000064 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
65 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
66 * #STATE_DISCONNECTING}.
Mike Lockwood517b04f2014-06-02 16:20:37 -070067 */
Jeff Sharkey8f80e4a2021-04-02 08:06:09 -060068 @RequiresLegacyBluetoothPermission
69 @RequiresBluetoothConnectPermission
70 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Jeff Sharkeyd7c55662021-04-20 12:30:37 -060071 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Mike Lockwood517b04f2014-06-02 16:20:37 -070072 public static final String ACTION_CONNECTION_STATE_CHANGED =
Jack He910201b2017-08-22 16:06:54 -070073 "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
Sanket Agarwal25e84d42015-10-21 18:23:27 -070074
75 /**
Sanket Agarwal25e84d42015-10-21 18:23:27 -070076 * Intent used to broadcast the change in player application setting state on AVRCP AG.
77 *
78 * <p>This intent will have the following extras:
David Duarteee52b7e2023-12-02 01:32:11 +000079 *
Sanket Agarwal25e84d42015-10-21 18:23:27 -070080 * <ul>
David Duarteee52b7e2023-12-02 01:32:11 +000081 * <li>{@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
82 * most recent player setting.
Sanket Agarwal25e84d42015-10-21 18:23:27 -070083 * </ul>
84 */
Jeff Sharkeyd7c55662021-04-20 12:30:37 -060085 @RequiresBluetoothConnectPermission
86 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
87 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Sanket Agarwal25e84d42015-10-21 18:23:27 -070088 public static final String ACTION_PLAYER_SETTING =
Jack He910201b2017-08-22 16:06:54 -070089 "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
Sanket Agarwal25e84d42015-10-21 18:23:27 -070090
Sanket Agarwal25e84d42015-10-21 18:23:27 -070091 public static final String EXTRA_PLAYER_SETTING =
92 "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
93
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -060094 private final BluetoothAdapter mAdapter;
95 private final AttributionSource mAttributionSource;
David Duartef5b3bc52023-10-17 00:55:06 +000096
97 private IBluetoothAvrcpController mService;
Mike Lockwood517b04f2014-06-02 16:20:37 -070098
99 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000100 * Create a BluetoothAvrcpController proxy object for interacting with the local Bluetooth AVRCP
101 * service.
Mike Lockwood517b04f2014-06-02 16:20:37 -0700102 */
David Duartef5b3bc52023-10-17 00:55:06 +0000103 /* package */ BluetoothAvrcpController(Context context, BluetoothAdapter adapter) {
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600104 mAdapter = adapter;
105 mAttributionSource = adapter.getAttributionSource();
David Duartef5b3bc52023-10-17 00:55:06 +0000106 mService = null;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700107 }
108
Santiago Seifertefe40712023-01-06 13:54:17 +0000109 /** @hide */
110 @Override
David Duartef5b3bc52023-10-17 00:55:06 +0000111 public void onServiceConnected(IBinder service) {
112 mService = IBluetoothAvrcpController.Stub.asInterface(service);
113 }
114
115 /** @hide */
116 @Override
117 public void onServiceDisconnected() {
118 mService = null;
Ugo Yu70d76592019-03-26 21:38:08 +0800119 }
Mike Lockwood517b04f2014-06-02 16:20:37 -0700120
Ugo Yu70d76592019-03-26 21:38:08 +0800121 private IBluetoothAvrcpController getService() {
David Duartef5b3bc52023-10-17 00:55:06 +0000122 return mService;
123 }
124
125 /** @hide */
126 @Override
127 public BluetoothAdapter getAdapter() {
128 return mAdapter;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700129 }
130
Jack He9e045d22017-08-22 21:21:23 -0700131 @Override
David Duartef5b3bc52023-10-17 00:55:06 +0000132 public void finalize() {}
Mike Lockwood517b04f2014-06-02 16:20:37 -0700133
David Duarteee52b7e2023-12-02 01:32:11 +0000134 /** {@inheritDoc} */
Jack He9e045d22017-08-22 21:21:23 -0700135 @Override
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600136 @RequiresBluetoothConnectPermission
137 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Mike Lockwood517b04f2014-06-02 16:20:37 -0700138 public List<BluetoothDevice> getConnectedDevices() {
139 if (VDBG) log("getConnectedDevices()");
William Escande9a4b7c12021-12-16 16:07:55 +0100140 final IBluetoothAvrcpController service = getService();
141 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
142 if (service == null) {
143 Log.w(TAG, "Proxy not attached to service");
144 if (DBG) log(Log.getStackTraceString(new Throwable()));
145 } else if (isEnabled()) {
Mike Lockwood517b04f2014-06-02 16:20:37 -0700146 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100147 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700148 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100149 service.getConnectedDevices(mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600150 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100151 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
152 mAttributionSource);
153 } catch (RemoteException | TimeoutException e) {
154 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Mike Lockwood517b04f2014-06-02 16:20:37 -0700155 }
156 }
William Escande9a4b7c12021-12-16 16:07:55 +0100157 return defaultValue;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700158 }
159
David Duarteee52b7e2023-12-02 01:32:11 +0000160 /** {@inheritDoc} */
Jack He9e045d22017-08-22 21:21:23 -0700161 @Override
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600162 @RequiresBluetoothConnectPermission
163 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Mike Lockwood517b04f2014-06-02 16:20:37 -0700164 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
165 if (VDBG) log("getDevicesMatchingStates()");
William Escande9a4b7c12021-12-16 16:07:55 +0100166 final IBluetoothAvrcpController service = getService();
167 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
168 if (service == null) {
169 Log.w(TAG, "Proxy not attached to service");
170 if (DBG) log(Log.getStackTraceString(new Throwable()));
171 } else if (isEnabled()) {
Mike Lockwood517b04f2014-06-02 16:20:37 -0700172 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100173 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700174 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100175 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600176 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100177 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600178 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +0100179 } catch (RemoteException | TimeoutException e) {
180 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Mike Lockwood517b04f2014-06-02 16:20:37 -0700181 }
182 }
William Escande9a4b7c12021-12-16 16:07:55 +0100183 return defaultValue;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700184 }
185
David Duarteee52b7e2023-12-02 01:32:11 +0000186 /** {@inheritDoc} */
Jack He9e045d22017-08-22 21:21:23 -0700187 @Override
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600188 @RequiresBluetoothConnectPermission
189 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Mike Lockwood517b04f2014-06-02 16:20:37 -0700190 public int getConnectionState(BluetoothDevice device) {
191 if (VDBG) log("getState(" + device + ")");
William Escande9a4b7c12021-12-16 16:07:55 +0100192 final IBluetoothAvrcpController service = getService();
193 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
194 if (service == null) {
195 Log.w(TAG, "Proxy not attached to service");
196 if (DBG) log(Log.getStackTraceString(new Throwable()));
197 } else if (isEnabled() && isValidDevice(device)) {
Mike Lockwood517b04f2014-06-02 16:20:37 -0700198 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700199 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100200 service.getConnectionState(device, mAttributionSource, recv);
201 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
202 } catch (RemoteException | TimeoutException e) {
203 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Mike Lockwood517b04f2014-06-02 16:20:37 -0700204 }
205 }
William Escande9a4b7c12021-12-16 16:07:55 +0100206 return defaultValue;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700207 }
208
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700209 /**
210 * Gets the player application settings.
211 *
212 * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
213 */
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600214 @RequiresBluetoothConnectPermission
215 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700216 public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
217 if (DBG) Log.d(TAG, "getPlayerSettings");
William Escande9a4b7c12021-12-16 16:07:55 +0100218 final IBluetoothAvrcpController service = getService();
219 final BluetoothAvrcpPlayerSettings defaultValue = null;
220 if (service == null) {
221 Log.w(TAG, "Proxy not attached to service");
222 if (DBG) log(Log.getStackTraceString(new Throwable()));
223 } else if (isEnabled()) {
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700224 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100225 final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700226 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100227 service.getPlayerSettings(device, mAttributionSource, recv);
David Duartede2a05f2023-12-05 22:07:45 +0000228 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
William Escande9a4b7c12021-12-16 16:07:55 +0100229 } catch (RemoteException | TimeoutException e) {
230 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700231 }
232 }
William Escande9a4b7c12021-12-16 16:07:55 +0100233 return defaultValue;
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700234 }
235
236 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000237 * Sets the player app setting for current player. returns true in case setting is supported by
238 * remote, false otherwise
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700239 */
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600240 @RequiresBluetoothConnectPermission
241 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700242 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
243 if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
William Escande9a4b7c12021-12-16 16:07:55 +0100244 final IBluetoothAvrcpController service = getService();
245 final boolean defaultValue = false;
246 if (service == null) {
247 Log.w(TAG, "Proxy not attached to service");
248 if (DBG) log(Log.getStackTraceString(new Throwable()));
249 } else if (isEnabled()) {
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700250 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700251 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100252 service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv);
253 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
254 } catch (RemoteException | TimeoutException e) {
255 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700256 }
257 }
William Escande9a4b7c12021-12-16 16:07:55 +0100258 return defaultValue;
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700259 }
260
Jack He9e045d22017-08-22 21:21:23 -0700261 /**
David Duarteee52b7e2023-12-02 01:32:11 +0000262 * Send Group Navigation Command to Remote. possible keycode values: next_grp, previous_grp
263 * defined above
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700264 */
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600265 @RequiresBluetoothConnectPermission
266 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700267 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
David Duarteee52b7e2023-12-02 01:32:11 +0000268 Log.d(
269 TAG,
270 "sendGroupNavigationCmd dev = "
271 + device
272 + " key "
273 + keyCode
274 + " State = "
275 + keyState);
William Escande9a4b7c12021-12-16 16:07:55 +0100276 final IBluetoothAvrcpController service = getService();
277 if (service == null) {
278 Log.w(TAG, "Proxy not attached to service");
279 if (DBG) log(Log.getStackTraceString(new Throwable()));
280 } else if (isEnabled()) {
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700281 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700282 final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100283 service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv);
284 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700285 return;
William Escande9a4b7c12021-12-16 16:07:55 +0100286 } catch (RemoteException | TimeoutException e) {
287 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700288 }
289 }
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700290 }
291
Mike Lockwood517b04f2014-06-02 16:20:37 -0700292 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -0700293 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700294 }
295
Jack He1f686f62017-08-17 12:11:18 -0700296 private static boolean isValidDevice(BluetoothDevice device) {
297 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Mike Lockwood517b04f2014-06-02 16:20:37 -0700298 }
299
300 private static void log(String msg) {
Jack He910201b2017-08-22 16:06:54 -0700301 Log.d(TAG, msg);
Mike Lockwood517b04f2014-06-02 16:20:37 -0700302 }
303}