blob: 81ad3c6ee743677ecfb46557a70e059fb2bce41b [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;
Mike Lockwood517b04f2014-06-02 16:20:37 -070028import android.os.IBinder;
29import 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 *
Jack He910201b2017-08-22 16:06:54 -070042 * <p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
Mike Lockwood517b04f2014-06-02 16:20:37 -070043 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
44 * the BluetoothAvrcpController proxy object.
45 *
46 * {@hide}
47 */
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 /**
54 * Intent used to broadcast the change in connection state of the AVRCP Controller
55 * profile.
56 *
57 * <p>This intent will have 3 extras:
58 * <ul>
Jack He910201b2017-08-22 16:06:54 -070059 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
60 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
61 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
Mike Lockwood517b04f2014-06-02 16:20:37 -070062 * </ul>
63 *
64 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
65 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
66 * {@link #STATE_CONNECTED}, {@link #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:
79 * <ul>
Jack He910201b2017-08-22 16:06:54 -070080 * <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
81 * most recent player setting. </li>
Sanket Agarwal25e84d42015-10-21 18:23:27 -070082 * </ul>
83 */
Jeff Sharkeyd7c55662021-04-20 12:30:37 -060084 @RequiresBluetoothConnectPermission
85 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
86 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Sanket Agarwal25e84d42015-10-21 18:23:27 -070087 public static final String ACTION_PLAYER_SETTING =
Jack He910201b2017-08-22 16:06:54 -070088 "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
Sanket Agarwal25e84d42015-10-21 18:23:27 -070089
Sanket Agarwal25e84d42015-10-21 18:23:27 -070090 public static final String EXTRA_PLAYER_SETTING =
91 "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
92
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -060093 private final BluetoothAdapter mAdapter;
94 private final AttributionSource mAttributionSource;
Ugo Yu70d76592019-03-26 21:38:08 +080095 private final BluetoothProfileConnector<IBluetoothAvrcpController> mProfileConnector =
96 new BluetoothProfileConnector(this, BluetoothProfile.AVRCP_CONTROLLER,
97 "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) {
98 @Override
99 public IBluetoothAvrcpController getServiceInterface(IBinder service) {
William Escande9a4b7c12021-12-16 16:07:55 +0100100 return IBluetoothAvrcpController.Stub.asInterface(service);
Mike Lockwood517b04f2014-06-02 16:20:37 -0700101 }
Ugo Yu70d76592019-03-26 21:38:08 +0800102 };
Mike Lockwood517b04f2014-06-02 16:20:37 -0700103
104 /**
105 * Create a BluetoothAvrcpController proxy object for interacting with the local
106 * Bluetooth AVRCP service.
Mike Lockwood517b04f2014-06-02 16:20:37 -0700107 */
Jeff Sharkeyf9e176c2021-04-22 16:01:29 -0600108 /* package */ BluetoothAvrcpController(Context context, ServiceListener listener,
109 BluetoothAdapter adapter) {
110 mAdapter = adapter;
111 mAttributionSource = adapter.getAttributionSource();
Ugo Yu70d76592019-03-26 21:38:08 +0800112 mProfileConnector.connect(context, listener);
Mike Lockwood517b04f2014-06-02 16:20:37 -0700113 }
114
115 /*package*/ void close() {
Ugo Yu70d76592019-03-26 21:38:08 +0800116 mProfileConnector.disconnect();
117 }
Mike Lockwood517b04f2014-06-02 16:20:37 -0700118
Ugo Yu70d76592019-03-26 21:38:08 +0800119 private IBluetoothAvrcpController getService() {
120 return mProfileConnector.getService();
Mike Lockwood517b04f2014-06-02 16:20:37 -0700121 }
122
Jack He9e045d22017-08-22 21:21:23 -0700123 @Override
Mike Lockwood517b04f2014-06-02 16:20:37 -0700124 public void finalize() {
125 close();
126 }
127
128 /**
129 * {@inheritDoc}
130 */
Jack He9e045d22017-08-22 21:21:23 -0700131 @Override
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600132 @RequiresBluetoothConnectPermission
133 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Mike Lockwood517b04f2014-06-02 16:20:37 -0700134 public List<BluetoothDevice> getConnectedDevices() {
135 if (VDBG) log("getConnectedDevices()");
William Escande9a4b7c12021-12-16 16:07:55 +0100136 final IBluetoothAvrcpController service = getService();
137 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
138 if (service == null) {
139 Log.w(TAG, "Proxy not attached to service");
140 if (DBG) log(Log.getStackTraceString(new Throwable()));
141 } else if (isEnabled()) {
Mike Lockwood517b04f2014-06-02 16:20:37 -0700142 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100143 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700144 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100145 service.getConnectedDevices(mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600146 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100147 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
148 mAttributionSource);
149 } catch (RemoteException | TimeoutException e) {
150 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Mike Lockwood517b04f2014-06-02 16:20:37 -0700151 }
152 }
William Escande9a4b7c12021-12-16 16:07:55 +0100153 return defaultValue;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700154 }
155
156 /**
157 * {@inheritDoc}
158 */
Jack He9e045d22017-08-22 21:21:23 -0700159 @Override
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600160 @RequiresBluetoothConnectPermission
161 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Mike Lockwood517b04f2014-06-02 16:20:37 -0700162 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
163 if (VDBG) log("getDevicesMatchingStates()");
William Escande9a4b7c12021-12-16 16:07:55 +0100164 final IBluetoothAvrcpController service = getService();
165 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
166 if (service == null) {
167 Log.w(TAG, "Proxy not attached to service");
168 if (DBG) log(Log.getStackTraceString(new Throwable()));
169 } else if (isEnabled()) {
Mike Lockwood517b04f2014-06-02 16:20:37 -0700170 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100171 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700172 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100173 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
Jeff Sharkey98f30442021-06-03 09:26:53 -0600174 return Attributable.setAttributionSource(
William Escande9a4b7c12021-12-16 16:07:55 +0100175 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
Jeff Sharkey43ee69e2021-04-23 14:13:57 -0600176 mAttributionSource);
William Escande9a4b7c12021-12-16 16:07:55 +0100177 } catch (RemoteException | TimeoutException e) {
178 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Mike Lockwood517b04f2014-06-02 16:20:37 -0700179 }
180 }
William Escande9a4b7c12021-12-16 16:07:55 +0100181 return defaultValue;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700182 }
183
184 /**
185 * {@inheritDoc}
186 */
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");
218 BluetoothAvrcpPlayerSettings settings = null;
William Escande9a4b7c12021-12-16 16:07:55 +0100219 final IBluetoothAvrcpController service = getService();
220 final BluetoothAvrcpPlayerSettings defaultValue = null;
221 if (service == null) {
222 Log.w(TAG, "Proxy not attached to service");
223 if (DBG) log(Log.getStackTraceString(new Throwable()));
224 } else if (isEnabled()) {
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700225 try {
William Escande9a4b7c12021-12-16 16:07:55 +0100226 final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv =
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700227 SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100228 service.getPlayerSettings(device, mAttributionSource, recv);
229 settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
230 } catch (RemoteException | TimeoutException e) {
231 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700232 }
233 }
William Escande9a4b7c12021-12-16 16:07:55 +0100234 return defaultValue;
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700235 }
236
237 /**
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700238 * Sets the player app setting for current player.
239 * returns true in case setting is supported by remote, false otherwise
240 */
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600241 @RequiresBluetoothConnectPermission
242 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700243 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
244 if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
William Escande9a4b7c12021-12-16 16:07:55 +0100245 final IBluetoothAvrcpController service = getService();
246 final boolean defaultValue = false;
247 if (service == null) {
248 Log.w(TAG, "Proxy not attached to service");
249 if (DBG) log(Log.getStackTraceString(new Throwable()));
250 } else if (isEnabled()) {
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700251 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700252 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100253 service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv);
254 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
255 } catch (RemoteException | TimeoutException e) {
256 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700257 }
258 }
William Escande9a4b7c12021-12-16 16:07:55 +0100259 return defaultValue;
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700260 }
261
Jack He9e045d22017-08-22 21:21:23 -0700262 /**
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700263 * Send Group Navigation Command to Remote.
264 * possible keycode values: next_grp, previous_grp defined above
265 */
Jeff Sharkey3614e0a2021-04-16 15:34:54 -0600266 @RequiresBluetoothConnectPermission
267 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700268 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
Jack He910201b2017-08-22 16:06:54 -0700269 Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = "
270 + keyState);
William Escande9a4b7c12021-12-16 16:07:55 +0100271 final IBluetoothAvrcpController service = getService();
272 if (service == null) {
273 Log.w(TAG, "Proxy not attached to service");
274 if (DBG) log(Log.getStackTraceString(new Throwable()));
275 } else if (isEnabled()) {
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700276 try {
Etienne Ruffieux02dcbe72022-06-22 13:54:25 -0700277 final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
William Escande9a4b7c12021-12-16 16:07:55 +0100278 service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv);
279 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700280 return;
William Escande9a4b7c12021-12-16 16:07:55 +0100281 } catch (RemoteException | TimeoutException e) {
282 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700283 }
284 }
Sanket Agarwal25e84d42015-10-21 18:23:27 -0700285 }
286
Mike Lockwood517b04f2014-06-02 16:20:37 -0700287 private boolean isEnabled() {
Jack He1f686f62017-08-17 12:11:18 -0700288 return mAdapter.getState() == BluetoothAdapter.STATE_ON;
Mike Lockwood517b04f2014-06-02 16:20:37 -0700289 }
290
Jack He1f686f62017-08-17 12:11:18 -0700291 private static boolean isValidDevice(BluetoothDevice device) {
292 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
Mike Lockwood517b04f2014-06-02 16:20:37 -0700293 }
294
295 private static void log(String msg) {
Jack He910201b2017-08-22 16:06:54 -0700296 Log.d(TAG, msg);
Mike Lockwood517b04f2014-06-02 16:20:37 -0700297 }
298}