blob: 444e4293fe2bae757de17d1eef7adf0c0263cad4 [file] [log] [blame]
Mike Lockwood94b59de2014-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
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
Sanket Agarwal1bec6a52015-10-21 18:23:27 -070023import android.media.MediaMetadata;
24import android.media.session.PlaybackState;
Mike Lockwood94b59de2014-06-02 16:20:37 -070025import android.os.IBinder;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.util.ArrayList;
30import java.util.List;
31
32/**
Sanket Agarwal1bec6a52015-10-21 18:23:27 -070033 * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
34 * supports player information, playback support and track metadata.
Mike Lockwood94b59de2014-06-02 16:20:37 -070035 *
36 *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
37 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
38 * the BluetoothAvrcpController proxy object.
39 *
40 * {@hide}
41 */
42public final class BluetoothAvrcpController implements BluetoothProfile {
43 private static final String TAG = "BluetoothAvrcpController";
Sanket Agarwal1bec6a52015-10-21 18:23:27 -070044 private static final boolean DBG = false;
Mike Lockwood94b59de2014-06-02 16:20:37 -070045 private static final boolean VDBG = false;
46
47 /**
48 * Intent used to broadcast the change in connection state of the AVRCP Controller
49 * profile.
50 *
51 * <p>This intent will have 3 extras:
52 * <ul>
53 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
54 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
55 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
56 * </ul>
57 *
58 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
59 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
60 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
61 *
62 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
63 * receive.
64 */
65 public static final String ACTION_CONNECTION_STATE_CHANGED =
Sanket Agarwal1bec6a52015-10-21 18:23:27 -070066 "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
67
68 /**
69 * Intent used to broadcast the change in metadata state of playing track on the AVRCP
70 * AG.
71 *
72 * <p>This intent will have the two extras:
73 * <ul>
74 * <li> {@link #EXTRA_METADATA} - {@link MediaMetadata} containing the current metadata.</li>
75 * <li> {@link #EXTRA_PLAYBACK} - {@link PlaybackState} containing the current playback
76 * state. </li>
77 * </ul>
78 */
79 public static final String ACTION_TRACK_EVENT =
80 "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
81
82
83 /**
84 * Intent used to broadcast the change in player application setting state on AVRCP AG.
85 *
86 * <p>This intent will have the following extras:
87 * <ul>
88 * <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
89 * most recent player setting. </li>
90 * </ul>
91 */
92 public static final String ACTION_PLAYER_SETTING =
93 "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
94
95 public static final String EXTRA_METADATA =
96 "android.bluetooth.avrcp-controller.profile.extra.METADATA";
97
98 public static final String EXTRA_PLAYBACK =
99 "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
100
101 public static final String EXTRA_PLAYER_SETTING =
102 "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
103
104 /*
105 * KeyCoded for Pass Through Commands
106 */
107 public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
108 public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
109 public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
110 public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
111 public static final int PASS_THRU_CMD_ID_STOP = 0x45;
112 public static final int PASS_THRU_CMD_ID_FF = 0x49;
113 public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
114 public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
115 public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
116 /* Key State Variables */
117 public static final int KEY_STATE_PRESSED = 0;
118 public static final int KEY_STATE_RELEASED = 1;
119 /* Group Navigation Key Codes */
120 public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
121 public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
122
Mike Lockwood94b59de2014-06-02 16:20:37 -0700123
124 private Context mContext;
125 private ServiceListener mServiceListener;
126 private IBluetoothAvrcpController mService;
127 private BluetoothAdapter mAdapter;
128
129 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700130 new IBluetoothStateChangeCallback.Stub() {
131 public void onBluetoothStateChange(boolean up) {
132 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
133 if (!up) {
134 if (VDBG) Log.d(TAG,"Unbinding service...");
135 synchronized (mConnection) {
136 try {
137 mService = null;
138 mContext.unbindService(mConnection);
139 } catch (Exception re) {
140 Log.e(TAG,"",re);
Mike Lockwood94b59de2014-06-02 16:20:37 -0700141 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700142 }
143 } else {
144 synchronized (mConnection) {
145 try {
146 if (mService == null) {
147 if (VDBG) Log.d(TAG,"Binding service...");
148 doBind();
Mike Lockwood94b59de2014-06-02 16:20:37 -0700149 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700150 } catch (Exception re) {
151 Log.e(TAG,"",re);
Mike Lockwood94b59de2014-06-02 16:20:37 -0700152 }
153 }
154 }
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700155 }
156 };
Mike Lockwood94b59de2014-06-02 16:20:37 -0700157
158 /**
159 * Create a BluetoothAvrcpController proxy object for interacting with the local
160 * Bluetooth AVRCP service.
161 *
162 */
163 /*package*/ BluetoothAvrcpController(Context context, ServiceListener l) {
164 mContext = context;
165 mServiceListener = l;
166 mAdapter = BluetoothAdapter.getDefaultAdapter();
167 IBluetoothManager mgr = mAdapter.getBluetoothManager();
168 if (mgr != null) {
169 try {
170 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
171 } catch (RemoteException e) {
172 Log.e(TAG,"",e);
173 }
174 }
175
176 doBind();
177 }
178
179 boolean doBind() {
180 Intent intent = new Intent(IBluetoothAvrcpController.class.getName());
181 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
182 intent.setComponent(comp);
183 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
184 android.os.Process.myUserHandle())) {
185 Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent);
186 return false;
187 }
188 return true;
189 }
190
191 /*package*/ void close() {
192 mServiceListener = null;
193 IBluetoothManager mgr = mAdapter.getBluetoothManager();
194 if (mgr != null) {
195 try {
196 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
197 } catch (Exception e) {
198 Log.e(TAG,"",e);
199 }
200 }
201
202 synchronized (mConnection) {
203 if (mService != null) {
204 try {
205 mService = null;
206 mContext.unbindService(mConnection);
207 } catch (Exception re) {
208 Log.e(TAG,"",re);
209 }
210 }
211 }
212 }
213
214 public void finalize() {
215 close();
216 }
217
218 /**
219 * {@inheritDoc}
220 */
221 public List<BluetoothDevice> getConnectedDevices() {
222 if (VDBG) log("getConnectedDevices()");
223 if (mService != null && isEnabled()) {
224 try {
225 return mService.getConnectedDevices();
226 } catch (RemoteException e) {
227 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
228 return new ArrayList<BluetoothDevice>();
229 }
230 }
231 if (mService == null) Log.w(TAG, "Proxy not attached to service");
232 return new ArrayList<BluetoothDevice>();
233 }
234
235 /**
236 * {@inheritDoc}
237 */
238 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
239 if (VDBG) log("getDevicesMatchingStates()");
240 if (mService != null && isEnabled()) {
241 try {
242 return mService.getDevicesMatchingConnectionStates(states);
243 } catch (RemoteException e) {
244 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
245 return new ArrayList<BluetoothDevice>();
246 }
247 }
248 if (mService == null) Log.w(TAG, "Proxy not attached to service");
249 return new ArrayList<BluetoothDevice>();
250 }
251
252 /**
253 * {@inheritDoc}
254 */
255 public int getConnectionState(BluetoothDevice device) {
256 if (VDBG) log("getState(" + device + ")");
257 if (mService != null && isEnabled()
258 && isValidDevice(device)) {
259 try {
260 return mService.getConnectionState(device);
261 } catch (RemoteException e) {
262 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
263 return BluetoothProfile.STATE_DISCONNECTED;
264 }
265 }
266 if (mService == null) Log.w(TAG, "Proxy not attached to service");
267 return BluetoothProfile.STATE_DISCONNECTED;
268 }
269
270 public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
271 if (DBG) Log.d(TAG, "sendPassThroughCmd");
272 if (mService != null && isEnabled()) {
273 try {
274 mService.sendPassThroughCmd(device, keyCode, keyState);
275 return;
276 } catch (RemoteException e) {
277 Log.e(TAG, "Error talking to BT service in sendPassThroughCmd()", e);
278 return;
279 }
280 }
281 if (mService == null) Log.w(TAG, "Proxy not attached to service");
282 }
283
Sanket Agarwal1bec6a52015-10-21 18:23:27 -0700284 /**
285 * Gets the player application settings.
286 *
287 * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
288 */
289 public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
290 if (DBG) Log.d(TAG, "getPlayerSettings");
291 BluetoothAvrcpPlayerSettings settings = null;
292 if (mService != null && isEnabled()) {
293 try {
294 settings = mService.getPlayerSettings(device);
295 } catch (RemoteException e) {
296 Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
297 return null;
298 }
299 }
300 return settings;
301 }
302
303 /**
304 * Gets the metadata for the current track.
305 *
306 * This should be usually called when application UI needs to be updated, eg. when the track
307 * changes or immediately after connecting and getting the current state.
308 * @return the {@link MediaMetadata} or {@link null} if there is an error.
309 */
310 public MediaMetadata getMetadata(BluetoothDevice device) {
311 if (DBG) Log.d(TAG, "getMetadata");
312 MediaMetadata metadata = null;
313 if (mService != null && isEnabled()) {
314 try {
315 metadata = mService.getMetadata(device);
316 } catch (RemoteException e) {
317 Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
318 return null;
319 }
320 }
321 return metadata;
322 }
323
324 /**
325 * Gets the playback state for current track.
326 *
327 * When the application is first connecting it can use current track state to get playback info.
328 * For all further updates it should listen to notifications.
329 * @return the {@link PlaybackState} or {@link null} if there is an error.
330 */
331 public PlaybackState getPlaybackState(BluetoothDevice device) {
332 if (DBG) Log.d(TAG, "getPlaybackState");
333 PlaybackState playbackState = null;
334 if (mService != null && isEnabled()) {
335 try {
336 playbackState = mService.getPlaybackState(device);
337 } catch (RemoteException e) {
338 Log.e(TAG,
339 "Error talking to BT service in getPlaybackState() " + e);
340 return null;
341 }
342 }
343 return playbackState;
344 }
345
346 /**
347 * Sets the player app setting for current player.
348 * returns true in case setting is supported by remote, false otherwise
349 */
350 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
351 if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
352 if (mService != null && isEnabled()) {
353 try {
354 return mService.setPlayerApplicationSetting(plAppSetting);
355 } catch (RemoteException e) {
356 Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
357 return false;
358 }
359 }
360 if (mService == null) Log.w(TAG, "Proxy not attached to service");
361 return false;
362 }
363
364 /*
365 * Send Group Navigation Command to Remote.
366 * possible keycode values: next_grp, previous_grp defined above
367 */
368 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
369 Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState);
370 if (mService != null && isEnabled()) {
371 try {
372 mService.sendGroupNavigationCmd(device, keyCode, keyState);
373 return;
374 } catch (RemoteException e) {
375 Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
376 return;
377 }
378 }
379 if (mService == null) Log.w(TAG, "Proxy not attached to service");
380 }
381
Mike Lockwood94b59de2014-06-02 16:20:37 -0700382 private final ServiceConnection mConnection = new ServiceConnection() {
383 public void onServiceConnected(ComponentName className, IBinder service) {
384 if (DBG) Log.d(TAG, "Proxy object connected");
385 mService = IBluetoothAvrcpController.Stub.asInterface(service);
386
387 if (mServiceListener != null) {
388 mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
389 BluetoothAvrcpController.this);
390 }
391 }
392 public void onServiceDisconnected(ComponentName className) {
393 if (DBG) Log.d(TAG, "Proxy object disconnected");
394 mService = null;
395 if (mServiceListener != null) {
396 mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER);
397 }
398 }
399 };
400
401 private boolean isEnabled() {
402 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
403 return false;
404 }
405
406 private boolean isValidDevice(BluetoothDevice device) {
407 if (device == null) return false;
408
409 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
410 return false;
411 }
412
413 private static void log(String msg) {
414 Log.d(TAG, msg);
415 }
416}