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