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