1 /* 2 * Copyright (C) 2011 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.IBinder; 24 import android.os.ParcelFileDescriptor; 25 import android.os.RemoteException; 26 import android.os.ServiceManager; 27 import android.util.Log; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Public API for Bluetooth Health Profile. 34 * 35 * <p>BluetoothHealth is a proxy object for controlling the Bluetooth 36 * Service via IPC. 37 * 38 * <p> How to connect to a health device which is acting in the source role. 39 * <li> Use {@link BluetoothAdapter#getProfileProxy} to get 40 * the BluetoothHealth proxy object. </li> 41 * <li> Create an {@link BluetoothHealth} callback and call 42 * {@link #registerSinkAppConfiguration} to register an application 43 * configuration </li> 44 * <li> Pair with the remote device. This currently needs to be done manually 45 * from Bluetooth Settings </li> 46 * <li> Connect to a health device using {@link #connectChannelToSource}. Some 47 * devices will connect the channel automatically. The {@link BluetoothHealth} 48 * callback will inform the application of channel state change. </li> 49 * <li> Use the file descriptor provided with a connected channel to read and 50 * write data to the health channel. </li> 51 * <li> The received data needs to be interpreted using a health manager which 52 * implements the IEEE 11073-xxxxx specifications. 53 * <li> When done, close the health channel by calling {@link #disconnectChannel} 54 * and unregister the application configuration calling 55 * {@link #unregisterAppConfiguration} 56 * 57 */ 58 public final class BluetoothHealth implements BluetoothProfile { 59 private static final String TAG = "BluetoothHealth"; 60 private static final boolean DBG = true; 61 private static final boolean VDBG = false; 62 63 /** 64 * Health Profile Source Role - the health device. 65 */ 66 public static final int SOURCE_ROLE = 1 << 0; 67 68 /** 69 * Health Profile Sink Role the device talking to the health device. 70 */ 71 public static final int SINK_ROLE = 1 << 1; 72 73 /** 74 * Health Profile - Channel Type used - Reliable 75 */ 76 public static final int CHANNEL_TYPE_RELIABLE = 10; 77 78 /** 79 * Health Profile - Channel Type used - Streaming 80 */ 81 public static final int CHANNEL_TYPE_STREAMING = 11; 82 83 /** 84 * @hide 85 */ 86 public static final int CHANNEL_TYPE_ANY = 12; 87 88 /** @hide */ 89 public static final int HEALTH_OPERATION_SUCCESS = 6000; 90 /** @hide */ 91 public static final int HEALTH_OPERATION_ERROR = 6001; 92 /** @hide */ 93 public static final int HEALTH_OPERATION_INVALID_ARGS = 6002; 94 /** @hide */ 95 public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003; 96 /** @hide */ 97 public static final int HEALTH_OPERATION_NOT_FOUND = 6004; 98 /** @hide */ 99 public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005; 100 101 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 102 new IBluetoothStateChangeCallback.Stub() { 103 public void onBluetoothStateChange(boolean up) { 104 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 105 if (!up) { 106 if (VDBG) Log.d(TAG,"Unbinding service..."); 107 synchronized (mConnection) { 108 try { 109 mService = null; 110 mContext.unbindService(mConnection); 111 } catch (Exception re) { 112 Log.e(TAG,"",re); 113 } 114 } 115 } else { 116 synchronized (mConnection) { 117 try { 118 if (mService == null) { 119 if (VDBG) Log.d(TAG,"Binding service..."); 120 doBind(); 121 } 122 } catch (Exception re) { 123 Log.e(TAG,"",re); 124 } 125 } 126 } 127 } 128 }; 129 130 131 /** 132 * Register an application configuration that acts as a Health SINK. 133 * This is the configuration that will be used to communicate with health devices 134 * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so 135 * the callback is used to notify success or failure if the function returns true. 136 * 137 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 138 * 139 * @param name The friendly name associated with the application or configuration. 140 * @param dataType The dataType of the Source role of Health Profile to which 141 * the sink wants to connect to. 142 * @param callback A callback to indicate success or failure of the registration and 143 * all operations done on this application configuration. 144 * @return If true, callback will be called. 145 */ 146 public boolean registerSinkAppConfiguration(String name, int dataType, 147 BluetoothHealthCallback callback) { 148 if (!isEnabled() || name == null) return false; 149 150 if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")"); 151 return registerAppConfiguration(name, dataType, SINK_ROLE, 152 CHANNEL_TYPE_ANY, callback); 153 } 154 155 /** 156 * Register an application configuration that acts as a Health SINK or in a Health 157 * SOURCE role.This is an asynchronous call and so 158 * the callback is used to notify success or failure if the function returns true. 159 * 160 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 161 * 162 * @param name The friendly name associated with the application or configuration. 163 * @param dataType The dataType of the Source role of Health Profile. 164 * @param channelType The channel type. Will be one of 165 * {@link #CHANNEL_TYPE_RELIABLE} or 166 * {@link #CHANNEL_TYPE_STREAMING} 167 * @param callback - A callback to indicate success or failure. 168 * @return If true, callback will be called. 169 * @hide 170 */ 171 public boolean registerAppConfiguration(String name, int dataType, int role, 172 int channelType, BluetoothHealthCallback callback) { 173 boolean result = false; 174 if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result; 175 176 if (VDBG) log("registerApplication(" + name + ":" + dataType + ")"); 177 BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback); 178 BluetoothHealthAppConfiguration config = 179 new BluetoothHealthAppConfiguration(name, dataType, role, channelType); 180 181 if (mService != null) { 182 try { 183 result = mService.registerAppConfiguration(config, wrapper); 184 } catch (RemoteException e) { 185 Log.e(TAG, e.toString()); 186 } 187 } else { 188 Log.w(TAG, "Proxy not attached to service"); 189 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 190 } 191 return result; 192 } 193 194 /** 195 * Unregister an application configuration that has been registered using 196 * {@link #registerSinkAppConfiguration} 197 * 198 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 199 * 200 * @param config The health app configuration 201 * @return Success or failure. 202 */ 203 public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { 204 boolean result = false; 205 if (mService != null && isEnabled() && config != null) { 206 try { 207 result = mService.unregisterAppConfiguration(config); 208 } catch (RemoteException e) { 209 Log.e(TAG, e.toString()); 210 } 211 } else { 212 Log.w(TAG, "Proxy not attached to service"); 213 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 214 } 215 216 return result; 217 } 218 219 /** 220 * Connect to a health device which has the {@link #SOURCE_ROLE}. 221 * This is an asynchronous call. If this function returns true, the callback 222 * associated with the application configuration will be called. 223 * 224 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 225 * 226 * @param device The remote Bluetooth device. 227 * @param config The application configuration which has been registered using 228 * {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } 229 * @return If true, the callback associated with the application config will be called. 230 */ 231 public boolean connectChannelToSource(BluetoothDevice device, 232 BluetoothHealthAppConfiguration config) { 233 if (mService != null && isEnabled() && isValidDevice(device) && 234 config != null) { 235 try { 236 return mService.connectChannelToSource(device, config); 237 } catch (RemoteException e) { 238 Log.e(TAG, e.toString()); 239 } 240 } else { 241 Log.w(TAG, "Proxy not attached to service"); 242 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 243 } 244 return false; 245 } 246 247 /** 248 * Connect to a health device which has the {@link #SINK_ROLE}. 249 * This is an asynchronous call. If this function returns true, the callback 250 * associated with the application configuration will be called. 251 * 252 *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 253 * 254 * @param device The remote Bluetooth device. 255 * @param config The application configuration which has been registered using 256 * {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } 257 * @return If true, the callback associated with the application config will be called. 258 * @hide 259 */ 260 public boolean connectChannelToSink(BluetoothDevice device, 261 BluetoothHealthAppConfiguration config, int channelType) { 262 if (mService != null && isEnabled() && isValidDevice(device) && 263 config != null) { 264 try { 265 return mService.connectChannelToSink(device, config, channelType); 266 } catch (RemoteException e) { 267 Log.e(TAG, e.toString()); 268 } 269 } else { 270 Log.w(TAG, "Proxy not attached to service"); 271 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 272 } 273 return false; 274 } 275 276 /** 277 * Disconnect a connected health channel. 278 * This is an asynchronous call. If this function returns true, the callback 279 * associated with the application configuration will be called. 280 * 281 *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 282 * 283 * @param device The remote Bluetooth device. 284 * @param config The application configuration which has been registered using 285 * {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } 286 * @param channelId The channel id associated with the channel 287 * @return If true, the callback associated with the application config will be called. 288 */ 289 public boolean disconnectChannel(BluetoothDevice device, 290 BluetoothHealthAppConfiguration config, int channelId) { 291 if (mService != null && isEnabled() && isValidDevice(device) && 292 config != null) { 293 try { 294 return mService.disconnectChannel(device, config, channelId); 295 } catch (RemoteException e) { 296 Log.e(TAG, e.toString()); 297 } 298 } else { 299 Log.w(TAG, "Proxy not attached to service"); 300 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 301 } 302 return false; 303 } 304 305 /** 306 * Get the file descriptor of the main channel associated with the remote device 307 * and application configuration. 308 * 309 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 310 * 311 * <p> Its the responsibility of the caller to close the ParcelFileDescriptor 312 * when done. 313 * 314 * @param device The remote Bluetooth health device 315 * @param config The application configuration 316 * @return null on failure, ParcelFileDescriptor on success. 317 */ 318 public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, 319 BluetoothHealthAppConfiguration config) { 320 if (mService != null && isEnabled() && isValidDevice(device) && 321 config != null) { 322 try { 323 return mService.getMainChannelFd(device, config); 324 } catch (RemoteException e) { 325 Log.e(TAG, e.toString()); 326 } 327 } else { 328 Log.w(TAG, "Proxy not attached to service"); 329 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 330 } 331 return null; 332 } 333 334 /** 335 * Get the current connection state of the profile. 336 * 337 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 338 * 339 * This is not specific to any application configuration but represents the connection 340 * state of the local Bluetooth adapter with the remote device. This can be used 341 * by applications like status bar which would just like to know the state of the 342 * local adapter. 343 * 344 * @param device Remote bluetooth device. 345 * @return State of the profile connection. One of 346 * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING}, 347 * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} 348 */ 349 @Override 350 public int getConnectionState(BluetoothDevice device) { 351 if (mService != null && isEnabled() && isValidDevice(device)) { 352 try { 353 return mService.getHealthDeviceConnectionState(device); 354 } catch (RemoteException e) { 355 Log.e(TAG, e.toString()); 356 } 357 } else { 358 Log.w(TAG, "Proxy not attached to service"); 359 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 360 } 361 return STATE_DISCONNECTED; 362 } 363 364 /** 365 * Get connected devices for the health profile. 366 * 367 * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} 368 * 369 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 370 * 371 * This is not specific to any application configuration but represents the connection 372 * state of the local Bluetooth adapter for this profile. This can be used 373 * by applications like status bar which would just like to know the state of the 374 * local adapter. 375 * @return List of devices. The list will be empty on error. 376 */ 377 @Override 378 public List<BluetoothDevice> getConnectedDevices() { 379 if (mService != null && isEnabled()) { 380 try { 381 return mService.getConnectedHealthDevices(); 382 } catch (RemoteException e) { 383 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 384 return new ArrayList<BluetoothDevice>(); 385 } 386 } 387 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 388 return new ArrayList<BluetoothDevice>(); 389 } 390 391 /** 392 * Get a list of devices that match any of the given connection 393 * states. 394 * 395 * <p> If none of the devices match any of the given states, 396 * an empty list will be returned. 397 * 398 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 399 * This is not specific to any application configuration but represents the connection 400 * state of the local Bluetooth adapter for this profile. This can be used 401 * by applications like status bar which would just like to know the state of the 402 * local adapter. 403 * 404 * @param states Array of states. States can be one of 405 * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING}, 406 * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, 407 * @return List of devices. The list will be empty on error. 408 */ 409 @Override 410 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 411 if (mService != null && isEnabled()) { 412 try { 413 return mService.getHealthDevicesMatchingConnectionStates(states); 414 } catch (RemoteException e) { 415 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 416 return new ArrayList<BluetoothDevice>(); 417 } 418 } 419 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 420 return new ArrayList<BluetoothDevice>(); 421 } 422 423 private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub { 424 private BluetoothHealthCallback mCallback; 425 426 public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) { 427 mCallback = callback; 428 } 429 430 @Override 431 public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, 432 int status) { 433 mCallback.onHealthAppConfigurationStatusChange(config, status); 434 } 435 436 @Override 437 public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config, 438 BluetoothDevice device, int prevState, int newState, 439 ParcelFileDescriptor fd, int channelId) { 440 mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd, 441 channelId); 442 } 443 } 444 445 /** Health Channel Connection State - Disconnected */ 446 public static final int STATE_CHANNEL_DISCONNECTED = 0; 447 /** Health Channel Connection State - Connecting */ 448 public static final int STATE_CHANNEL_CONNECTING = 1; 449 /** Health Channel Connection State - Connected */ 450 public static final int STATE_CHANNEL_CONNECTED = 2; 451 /** Health Channel Connection State - Disconnecting */ 452 public static final int STATE_CHANNEL_DISCONNECTING = 3; 453 454 /** Health App Configuration registration success */ 455 public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; 456 /** Health App Configuration registration failure */ 457 public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; 458 /** Health App Configuration un-registration success */ 459 public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; 460 /** Health App Configuration un-registration failure */ 461 public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; 462 463 private Context mContext; 464 private ServiceListener mServiceListener; 465 private IBluetoothHealth mService; 466 BluetoothAdapter mAdapter; 467 468 /** 469 * Create a BluetoothHealth proxy object. 470 */ 471 /*package*/ BluetoothHealth(Context context, ServiceListener l) { 472 mContext = context; 473 mServiceListener = l; 474 mAdapter = BluetoothAdapter.getDefaultAdapter(); 475 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 476 if (mgr != null) { 477 try { 478 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 479 } catch (RemoteException e) { 480 Log.e(TAG,"",e); 481 } 482 } 483 484 doBind(); 485 } 486 487 boolean doBind() { 488 Intent intent = new Intent(IBluetoothHealth.class.getName()); 489 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); 490 intent.setComponent(comp); 491 if (comp == null || !mContext.bindService(intent, mConnection, 0)) { 492 Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent); 493 return false; 494 } 495 return true; 496 } 497 498 /*package*/ void close() { 499 if (VDBG) log("close()"); 500 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 501 if (mgr != null) { 502 try { 503 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 504 } catch (Exception e) { 505 Log.e(TAG,"",e); 506 } 507 } 508 509 synchronized (mConnection) { 510 if (mService != null) { 511 try { 512 mService = null; 513 mContext.unbindService(mConnection); 514 } catch (Exception re) { 515 Log.e(TAG,"",re); 516 } 517 } 518 } 519 mServiceListener = null; 520 } 521 522 private final ServiceConnection mConnection = new ServiceConnection() { 523 public void onServiceConnected(ComponentName className, IBinder service) { 524 if (DBG) Log.d(TAG, "Proxy object connected"); 525 mService = IBluetoothHealth.Stub.asInterface(service); 526 527 if (mServiceListener != null) { 528 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this); 529 } 530 } 531 public void onServiceDisconnected(ComponentName className) { 532 if (DBG) Log.d(TAG, "Proxy object disconnected"); 533 mService = null; 534 if (mServiceListener != null) { 535 mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH); 536 } 537 } 538 }; 539 540 private boolean isEnabled() { 541 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 542 543 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; 544 log("Bluetooth is Not enabled"); 545 return false; 546 } 547 548 private boolean isValidDevice(BluetoothDevice device) { 549 if (device == null) return false; 550 551 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 552 return false; 553 } 554 555 private boolean checkAppParam(String name, int role, int channelType, 556 BluetoothHealthCallback callback) { 557 if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) || 558 (channelType != CHANNEL_TYPE_RELIABLE && 559 channelType != CHANNEL_TYPE_STREAMING && 560 channelType != CHANNEL_TYPE_ANY) || callback == null) { 561 return false; 562 } 563 if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false; 564 return true; 565 } 566 567 private static void log(String msg) { 568 Log.d(TAG, msg); 569 } 570 } 571