1 /* 2 * Copyright (C) 2008 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.os.Binder; 24 import android.os.IBinder; 25 import android.os.RemoteException; 26 import android.util.Log; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 /** 32 * This class provides the APIs to control the Bluetooth MAP 33 * Profile. 34 *@hide 35 */ 36 public final class BluetoothMap implements BluetoothProfile { 37 38 private static final String TAG = "BluetoothMap"; 39 private static final boolean DBG = true; 40 private static final boolean VDBG = false; 41 42 public static final String ACTION_CONNECTION_STATE_CHANGED = 43 "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; 44 45 private volatile IBluetoothMap mService; 46 private final Context mContext; 47 private ServiceListener mServiceListener; 48 private BluetoothAdapter mAdapter; 49 50 /** There was an error trying to obtain the state */ 51 public static final int STATE_ERROR = -1; 52 53 public static final int RESULT_FAILURE = 0; 54 public static final int RESULT_SUCCESS = 1; 55 /** Connection canceled before completion. */ 56 public static final int RESULT_CANCELED = 2; 57 58 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 59 new IBluetoothStateChangeCallback.Stub() { 60 public void onBluetoothStateChange(boolean up) { 61 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 62 if (!up) { 63 if (VDBG) Log.d(TAG,"Unbinding service..."); 64 synchronized (mConnection) { 65 try { 66 mService = null; 67 mContext.unbindService(mConnection); 68 } catch (Exception re) { 69 Log.e(TAG,"",re); 70 } 71 } 72 } else { 73 synchronized (mConnection) { 74 try { 75 if (mService == null) { 76 if (VDBG) Log.d(TAG,"Binding service..."); 77 doBind(); 78 } 79 } catch (Exception re) { 80 Log.e(TAG,"",re); 81 } 82 } 83 } 84 } 85 }; 86 87 /** 88 * Create a BluetoothMap proxy object. 89 */ BluetoothMap(Context context, ServiceListener l)90 /*package*/ BluetoothMap(Context context, ServiceListener l) { 91 if (DBG) Log.d(TAG, "Create BluetoothMap proxy object"); 92 mContext = context; 93 mServiceListener = l; 94 mAdapter = BluetoothAdapter.getDefaultAdapter(); 95 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 96 if (mgr != null) { 97 try { 98 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 99 } catch (RemoteException e) { 100 Log.e(TAG,"",e); 101 } 102 } 103 doBind(); 104 } 105 doBind()106 boolean doBind() { 107 Intent intent = new Intent(IBluetoothMap.class.getName()); 108 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); 109 intent.setComponent(comp); 110 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, 111 android.os.Process.myUserHandle())) { 112 Log.e(TAG, "Could not bind to Bluetooth MAP Service with " + intent); 113 return false; 114 } 115 return true; 116 } 117 finalize()118 protected void finalize() throws Throwable { 119 try { 120 close(); 121 } finally { 122 super.finalize(); 123 } 124 } 125 126 /** 127 * Close the connection to the backing service. 128 * Other public functions of BluetoothMap will return default error 129 * results once close() has been called. Multiple invocations of close() 130 * are ok. 131 */ close()132 public synchronized void close() { 133 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 134 if (mgr != null) { 135 try { 136 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 137 } catch (Exception e) { 138 Log.e(TAG,"",e); 139 } 140 } 141 142 synchronized (mConnection) { 143 if (mService != null) { 144 try { 145 mService = null; 146 mContext.unbindService(mConnection); 147 } catch (Exception re) { 148 Log.e(TAG,"",re); 149 } 150 } 151 } 152 mServiceListener = null; 153 } 154 155 /** 156 * Get the current state of the BluetoothMap service. 157 * @return One of the STATE_ return codes, or STATE_ERROR if this proxy 158 * object is currently not connected to the Map service. 159 */ getState()160 public int getState() { 161 if (VDBG) log("getState()"); 162 final IBluetoothMap service = mService; 163 if (service != null) { 164 try { 165 return service.getState(); 166 } catch (RemoteException e) { 167 Log.e(TAG, e.toString()); 168 } 169 } else { 170 Log.w(TAG, "Proxy not attached to service"); 171 if (DBG) log(Log.getStackTraceString(new Throwable())); 172 } 173 return BluetoothMap.STATE_ERROR; 174 } 175 176 /** 177 * Get the currently connected remote Bluetooth device (PCE). 178 * @return The remote Bluetooth device, or null if not in connected or 179 * connecting state, or if this proxy object is not connected to 180 * the Map service. 181 */ getClient()182 public BluetoothDevice getClient() { 183 if (VDBG) log("getClient()"); 184 final IBluetoothMap service = mService; 185 if (service != null) { 186 try { 187 return service.getClient(); 188 } catch (RemoteException e) { 189 Log.e(TAG, e.toString()); 190 } 191 } else { 192 Log.w(TAG, "Proxy not attached to service"); 193 if (DBG) log(Log.getStackTraceString(new Throwable())); 194 } 195 return null; 196 } 197 198 /** 199 * Returns true if the specified Bluetooth device is connected. 200 * Returns false if not connected, or if this proxy object is not 201 * currently connected to the Map service. 202 */ isConnected(BluetoothDevice device)203 public boolean isConnected(BluetoothDevice device) { 204 if (VDBG) log("isConnected(" + device + ")"); 205 final IBluetoothMap service = mService; 206 if (service != null) { 207 try { 208 return service.isConnected(device); 209 } catch (RemoteException e) { 210 Log.e(TAG, e.toString()); 211 } 212 } else { 213 Log.w(TAG, "Proxy not attached to service"); 214 if (DBG) log(Log.getStackTraceString(new Throwable())); 215 } 216 return false; 217 } 218 219 /** 220 * Initiate connection. Initiation of outgoing connections is not 221 * supported for MAP server. 222 */ connect(BluetoothDevice device)223 public boolean connect(BluetoothDevice device) { 224 if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); 225 return false; 226 } 227 228 /** 229 * Initiate disconnect. 230 * 231 * @param device Remote Bluetooth Device 232 * @return false on error, 233 * true otherwise 234 */ disconnect(BluetoothDevice device)235 public boolean disconnect(BluetoothDevice device) { 236 if (DBG) log("disconnect(" + device + ")"); 237 final IBluetoothMap service = mService; 238 if (service != null && isEnabled() && isValidDevice(device)) { 239 try { 240 return service.disconnect(device); 241 } catch (RemoteException e) { 242 Log.e(TAG, Log.getStackTraceString(new Throwable())); 243 return false; 244 } 245 } 246 if (service == null) Log.w(TAG, "Proxy not attached to service"); 247 return false; 248 } 249 250 /** 251 * Check class bits for possible Map support. 252 * This is a simple heuristic that tries to guess if a device with the 253 * given class bits might support Map. It is not accurate for all 254 * devices. It tries to err on the side of false positives. 255 * @return True if this device might support Map. 256 */ doesClassMatchSink(BluetoothClass btClass)257 public static boolean doesClassMatchSink(BluetoothClass btClass) { 258 // TODO optimize the rule 259 switch (btClass.getDeviceClass()) { 260 case BluetoothClass.Device.COMPUTER_DESKTOP: 261 case BluetoothClass.Device.COMPUTER_LAPTOP: 262 case BluetoothClass.Device.COMPUTER_SERVER: 263 case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: 264 return true; 265 default: 266 return false; 267 } 268 } 269 270 /** 271 * Get the list of connected devices. Currently at most one. 272 * 273 * @return list of connected devices 274 */ getConnectedDevices()275 public List<BluetoothDevice> getConnectedDevices() { 276 if (DBG) log("getConnectedDevices()"); 277 final IBluetoothMap service = mService; 278 if (service != null && isEnabled()) { 279 try { 280 return service.getConnectedDevices(); 281 } catch (RemoteException e) { 282 Log.e(TAG, Log.getStackTraceString(new Throwable())); 283 return new ArrayList<BluetoothDevice>(); 284 } 285 } 286 if (service == null) Log.w(TAG, "Proxy not attached to service"); 287 return new ArrayList<BluetoothDevice>(); 288 } 289 290 /** 291 * Get the list of devices matching specified states. Currently at most one. 292 * 293 * @return list of matching devices 294 */ getDevicesMatchingConnectionStates(int[] states)295 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 296 if (DBG) log("getDevicesMatchingStates()"); 297 final IBluetoothMap service = mService; 298 if (service != null && isEnabled()) { 299 try { 300 return service.getDevicesMatchingConnectionStates(states); 301 } catch (RemoteException e) { 302 Log.e(TAG, Log.getStackTraceString(new Throwable())); 303 return new ArrayList<BluetoothDevice>(); 304 } 305 } 306 if (service == null) Log.w(TAG, "Proxy not attached to service"); 307 return new ArrayList<BluetoothDevice>(); 308 } 309 310 /** 311 * Get connection state of device 312 * 313 * @return device connection state 314 */ getConnectionState(BluetoothDevice device)315 public int getConnectionState(BluetoothDevice device) { 316 if (DBG) log("getConnectionState(" + device + ")"); 317 final IBluetoothMap service = mService; 318 if (service != null && isEnabled() && isValidDevice(device)) { 319 try { 320 return service.getConnectionState(device); 321 } catch (RemoteException e) { 322 Log.e(TAG, Log.getStackTraceString(new Throwable())); 323 return BluetoothProfile.STATE_DISCONNECTED; 324 } 325 } 326 if (service == null) Log.w(TAG, "Proxy not attached to service"); 327 return BluetoothProfile.STATE_DISCONNECTED; 328 } 329 330 /** 331 * Set priority of the profile 332 * 333 * <p> The device should already be paired. 334 * Priority can be one of {@link #PRIORITY_ON} or 335 * {@link #PRIORITY_OFF}, 336 * 337 * @param device Paired bluetooth device 338 * @param priority 339 * @return true if priority is set, false on error 340 */ setPriority(BluetoothDevice device, int priority)341 public boolean setPriority(BluetoothDevice device, int priority) { 342 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 343 final IBluetoothMap service = mService; 344 if (service != null && isEnabled() && isValidDevice(device)) { 345 if (priority != BluetoothProfile.PRIORITY_OFF 346 && priority != BluetoothProfile.PRIORITY_ON) { 347 return false; 348 } 349 try { 350 return service.setPriority(device, priority); 351 } catch (RemoteException e) { 352 Log.e(TAG, Log.getStackTraceString(new Throwable())); 353 return false; 354 } 355 } 356 if (service == null) Log.w(TAG, "Proxy not attached to service"); 357 return false; 358 } 359 360 /** 361 * Get the priority of the profile. 362 * 363 * <p> The priority can be any of: 364 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 365 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 366 * 367 * @param device Bluetooth device 368 * @return priority of the device 369 */ getPriority(BluetoothDevice device)370 public int getPriority(BluetoothDevice device) { 371 if (VDBG) log("getPriority(" + device + ")"); 372 final IBluetoothMap service = mService; 373 if (service != null && isEnabled() && isValidDevice(device)) { 374 try { 375 return service.getPriority(device); 376 } catch (RemoteException e) { 377 Log.e(TAG, Log.getStackTraceString(new Throwable())); 378 return PRIORITY_OFF; 379 } 380 } 381 if (service == null) Log.w(TAG, "Proxy not attached to service"); 382 return PRIORITY_OFF; 383 } 384 385 private final ServiceConnection mConnection = new ServiceConnection() { 386 public void onServiceConnected(ComponentName className, IBinder service) { 387 if (DBG) log("Proxy object connected"); 388 mService = IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service)); 389 if (mServiceListener != null) { 390 mServiceListener.onServiceConnected(BluetoothProfile.MAP, BluetoothMap.this); 391 } 392 } 393 public void onServiceDisconnected(ComponentName className) { 394 if (DBG) log("Proxy object disconnected"); 395 mService = null; 396 if (mServiceListener != null) { 397 mServiceListener.onServiceDisconnected(BluetoothProfile.MAP); 398 } 399 } 400 }; 401 log(String msg)402 private static void log(String msg) { 403 Log.d(TAG, msg); 404 } 405 isEnabled()406 private boolean isEnabled() { 407 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 408 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; 409 log("Bluetooth is Not enabled"); 410 return false; 411 } isValidDevice(BluetoothDevice device)412 private static boolean isValidDevice(BluetoothDevice device) { 413 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 414 } 415 416 } 417