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