1 /*
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 
17 package android.bluetooth;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.media.MediaMetadata;
24 import android.media.session.PlaybackState;
25 import android.os.Binder;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
35  * supports player information, playback support and track metadata.
36  *
37  *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
38  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
39  * the BluetoothAvrcpController proxy object.
40  *
41  * {@hide}
42  */
43 public final class BluetoothAvrcpController implements BluetoothProfile {
44     private static final String TAG = "BluetoothAvrcpController";
45     private static final boolean DBG = false;
46     private static final boolean VDBG = false;
47 
48     /**
49      * Intent used to broadcast the change in connection state of the AVRCP Controller
50      * profile.
51      *
52      * <p>This intent will have 3 extras:
53      * <ul>
54      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
55      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
56      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
57      * </ul>
58      *
59      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
60      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
61      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
62      *
63      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
64      * receive.
65      */
66     public static final String ACTION_CONNECTION_STATE_CHANGED =
67         "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
68 
69     /**
70      * Intent used to broadcast the change in player application setting state on AVRCP AG.
71      *
72      * <p>This intent will have the following extras:
73      * <ul>
74      *    <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
75      *    most recent player setting. </li>
76      * </ul>
77      */
78     public static final String ACTION_PLAYER_SETTING =
79         "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
80 
81     public static final String EXTRA_PLAYER_SETTING =
82             "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
83 
84     private Context mContext;
85     private ServiceListener mServiceListener;
86     private IBluetoothAvrcpController mService;
87     private BluetoothAdapter mAdapter;
88 
89     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
90         new IBluetoothStateChangeCallback.Stub() {
91             public void onBluetoothStateChange(boolean up) {
92                 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
93                 if (!up) {
94                     if (VDBG) Log.d(TAG,"Unbinding service...");
95                     synchronized (mConnection) {
96                         try {
97                             mService = null;
98                             mContext.unbindService(mConnection);
99                         } catch (Exception re) {
100                             Log.e(TAG,"",re);
101                         }
102                     }
103                 } else {
104                     synchronized (mConnection) {
105                         try {
106                             if (mService == null) {
107                                 if (VDBG) Log.d(TAG,"Binding service...");
108                                 doBind();
109                             }
110                         } catch (Exception re) {
111                             Log.e(TAG,"",re);
112                         }
113                     }
114                 }
115             }
116       };
117 
118     /**
119      * Create a BluetoothAvrcpController proxy object for interacting with the local
120      * Bluetooth AVRCP service.
121      *
122      */
BluetoothAvrcpController(Context context, ServiceListener l)123     /*package*/ BluetoothAvrcpController(Context context, ServiceListener l) {
124         mContext = context;
125         mServiceListener = l;
126         mAdapter = BluetoothAdapter.getDefaultAdapter();
127         IBluetoothManager mgr = mAdapter.getBluetoothManager();
128         if (mgr != null) {
129             try {
130                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
131             } catch (RemoteException e) {
132                 Log.e(TAG,"",e);
133             }
134         }
135 
136         doBind();
137     }
138 
doBind()139     boolean doBind() {
140         Intent intent = new Intent(IBluetoothAvrcpController.class.getName());
141         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
142         intent.setComponent(comp);
143         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
144                 android.os.Process.myUserHandle())) {
145             Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent);
146             return false;
147         }
148         return true;
149     }
150 
close()151     /*package*/ void close() {
152         mServiceListener = null;
153         IBluetoothManager mgr = mAdapter.getBluetoothManager();
154         if (mgr != null) {
155             try {
156                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
157             } catch (Exception e) {
158                 Log.e(TAG,"",e);
159             }
160         }
161 
162         synchronized (mConnection) {
163             if (mService != null) {
164                 try {
165                     mService = null;
166                     mContext.unbindService(mConnection);
167                 } catch (Exception re) {
168                     Log.e(TAG,"",re);
169                 }
170             }
171         }
172     }
173 
finalize()174     public void finalize() {
175         close();
176     }
177 
178     /**
179      * {@inheritDoc}
180      */
getConnectedDevices()181     public List<BluetoothDevice> getConnectedDevices() {
182         if (VDBG) log("getConnectedDevices()");
183         if (mService != null && isEnabled()) {
184             try {
185                 return mService.getConnectedDevices();
186             } catch (RemoteException e) {
187                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
188                 return new ArrayList<BluetoothDevice>();
189             }
190         }
191         if (mService == null) Log.w(TAG, "Proxy not attached to service");
192         return new ArrayList<BluetoothDevice>();
193     }
194 
195     /**
196      * {@inheritDoc}
197      */
getDevicesMatchingConnectionStates(int[] states)198     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
199         if (VDBG) log("getDevicesMatchingStates()");
200         if (mService != null && isEnabled()) {
201             try {
202                 return mService.getDevicesMatchingConnectionStates(states);
203             } catch (RemoteException e) {
204                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
205                 return new ArrayList<BluetoothDevice>();
206             }
207         }
208         if (mService == null) Log.w(TAG, "Proxy not attached to service");
209         return new ArrayList<BluetoothDevice>();
210     }
211 
212     /**
213      * {@inheritDoc}
214      */
getConnectionState(BluetoothDevice device)215     public int getConnectionState(BluetoothDevice device) {
216         if (VDBG) log("getState(" + device + ")");
217         if (mService != null && isEnabled()
218             && isValidDevice(device)) {
219             try {
220                 return mService.getConnectionState(device);
221             } catch (RemoteException e) {
222                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
223                 return BluetoothProfile.STATE_DISCONNECTED;
224             }
225         }
226         if (mService == null) Log.w(TAG, "Proxy not attached to service");
227         return BluetoothProfile.STATE_DISCONNECTED;
228     }
229 
230     /**
231      * Gets the player application settings.
232      *
233      * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
234      */
getPlayerSettings(BluetoothDevice device)235     public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
236         if (DBG) Log.d(TAG, "getPlayerSettings");
237         BluetoothAvrcpPlayerSettings settings = null;
238         if (mService != null && isEnabled()) {
239             try {
240                 settings = mService.getPlayerSettings(device);
241             } catch (RemoteException e) {
242                 Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
243                 return null;
244             }
245         }
246         return settings;
247     }
248 
249     /**
250      * Sets the player app setting for current player.
251      * returns true in case setting is supported by remote, false otherwise
252      */
setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting)253     public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
254         if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
255         if (mService != null && isEnabled()) {
256             try {
257                 return mService.setPlayerApplicationSetting(plAppSetting);
258             } catch (RemoteException e) {
259                 Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
260                 return false;
261             }
262         }
263         if (mService == null) Log.w(TAG, "Proxy not attached to service");
264         return false;
265     }
266 
267     /*
268      * Send Group Navigation Command to Remote.
269      * possible keycode values: next_grp, previous_grp defined above
270      */
sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState)271     public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
272         Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState);
273         if (mService != null && isEnabled()) {
274             try {
275                 mService.sendGroupNavigationCmd(device, keyCode, keyState);
276                 return;
277             } catch (RemoteException e) {
278                 Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
279                 return;
280             }
281         }
282         if (mService == null) Log.w(TAG, "Proxy not attached to service");
283     }
284 
285     private final ServiceConnection mConnection = new ServiceConnection() {
286         public void onServiceConnected(ComponentName className, IBinder service) {
287             if (DBG) Log.d(TAG, "Proxy object connected");
288             mService = IBluetoothAvrcpController.Stub.asInterface(Binder.allowBlocking(service));
289 
290             if (mServiceListener != null) {
291                 mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
292                         BluetoothAvrcpController.this);
293             }
294         }
295         public void onServiceDisconnected(ComponentName className) {
296             if (DBG) Log.d(TAG, "Proxy object disconnected");
297             mService = null;
298             if (mServiceListener != null) {
299                 mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER);
300             }
301         }
302     };
303 
isEnabled()304     private boolean isEnabled() {
305        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
306        return false;
307     }
308 
isValidDevice(BluetoothDevice device)309     private boolean isValidDevice(BluetoothDevice device) {
310        if (device == null) return false;
311 
312        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
313        return false;
314     }
315 
log(String msg)316     private static void log(String msg) {
317       Log.d(TAG, msg);
318     }
319 }
320