HealthService.java revision ede67c26e7b2564ea35db6d9b3027a269c150e13
1/* 2 * Copyright (C) 2012 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 com.android.bluetooth.hdp; 18 19import android.bluetooth.BluetoothDevice; 20import android.bluetooth.BluetoothHealth; 21import android.bluetooth.BluetoothHealthAppConfiguration; 22import android.bluetooth.BluetoothProfile; 23import android.bluetooth.IBluetooth; 24import android.bluetooth.IBluetoothHealth; 25import android.bluetooth.IBluetoothHealthCallback; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.os.Handler; 29import android.os.HandlerThread; 30import android.os.IBinder; 31import android.os.IBinder.DeathRecipient; 32import android.os.Looper; 33import android.os.Message; 34import android.os.ParcelFileDescriptor; 35import android.os.RemoteException; 36import android.os.ServiceManager; 37import android.util.Log; 38import com.android.bluetooth.btservice.ProfileService; 39import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; 40import com.android.bluetooth.Utils; 41import java.io.FileDescriptor; 42import java.io.IOException; 43import java.util.ArrayList; 44import java.util.Collections; 45import java.util.HashMap; 46import java.util.List; 47import java.util.Map; 48import java.util.Map.Entry; 49import java.util.NoSuchElementException; 50 51 52/** 53 * Provides Bluetooth Health Device profile, as a service in 54 * the Bluetooth application. 55 * @hide 56 */ 57public class HealthService extends ProfileService { 58 private static final boolean DBG = false; 59 private static final String TAG="HealthService"; 60 61 private List<HealthChannel> mHealthChannels; 62 private Map <BluetoothHealthAppConfiguration, AppInfo> mApps; 63 private Map <BluetoothDevice, Integer> mHealthDevices; 64 private boolean mNativeAvailable; 65 private HealthServiceMessageHandler mHandler; 66 private static final int MESSAGE_REGISTER_APPLICATION = 1; 67 private static final int MESSAGE_UNREGISTER_APPLICATION = 2; 68 private static final int MESSAGE_CONNECT_CHANNEL = 3; 69 private static final int MESSAGE_DISCONNECT_CHANNEL = 4; 70 private static final int MESSAGE_APP_REGISTRATION_CALLBACK = 11; 71 private static final int MESSAGE_CHANNEL_STATE_CALLBACK = 12; 72 73 static { 74 classInitNative(); 75 } 76 77 protected String getName() { 78 return TAG; 79 } 80 81 protected IProfileServiceBinder initBinder() { 82 return new BluetoothHealthBinder(this); 83 } 84 85 protected boolean start() { 86 mHealthChannels = Collections.synchronizedList(new ArrayList<HealthChannel>()); 87 mApps = Collections.synchronizedMap(new HashMap<BluetoothHealthAppConfiguration, 88 AppInfo>()); 89 mHealthDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>()); 90 91 HandlerThread thread = new HandlerThread("BluetoothHdpHandler"); 92 thread.start(); 93 Looper looper = thread.getLooper(); 94 mHandler = new HealthServiceMessageHandler(looper); 95 initializeNative(); 96 mNativeAvailable=true; 97 return true; 98 } 99 100 protected boolean stop() { 101 mHandler.removeCallbacksAndMessages(null); 102 Looper looper = mHandler.getLooper(); 103 if (looper != null) { 104 looper.quit(); 105 } 106 cleanupApps(); 107 return true; 108 } 109 110 private void cleanupApps(){ 111 for (Entry<BluetoothHealthAppConfiguration, AppInfo> entry : mApps.entrySet()) { 112 try{ 113 AppInfo appInfo = entry.getValue(); 114 appInfo.cleanup(); 115 mApps.remove(entry.getKey()); 116 }catch(Exception e){ 117 Log.e(TAG,"Failed to cleanup appInfo for appid ="+entry.getKey()); 118 } 119 } 120 } 121 protected boolean cleanup() { 122 mHandler = null; 123 //Cleanup native 124 if (mNativeAvailable) { 125 cleanupNative(); 126 mNativeAvailable=false; 127 } 128 if(mHealthChannels != null) { 129 mHealthChannels.clear(); 130 } 131 if(mHealthDevices != null) { 132 mHealthDevices.clear(); 133 } 134 if(mApps != null) { 135 mApps.clear(); 136 } 137 return true; 138 } 139 140 private final class HealthServiceMessageHandler extends Handler { 141 private HealthServiceMessageHandler(Looper looper) { 142 super(looper); 143 } 144 145 @Override 146 public void handleMessage(Message msg) { 147 switch (msg.what) { 148 case MESSAGE_REGISTER_APPLICATION: 149 { 150 BluetoothHealthAppConfiguration appConfig = 151 (BluetoothHealthAppConfiguration) msg.obj; 152 AppInfo appInfo = mApps.get(appConfig); 153 if (appInfo == null) break; 154 int halRole = convertRoleToHal(appConfig.getRole()); 155 int halChannelType = convertChannelTypeToHal(appConfig.getChannelType()); 156 if (DBG) log("register datatype: " + appConfig.getDataType() + " role: " + 157 halRole + " name: " + appConfig.getName() + " channeltype: " + 158 halChannelType); 159 int appId = registerHealthAppNative(appConfig.getDataType(), halRole, 160 appConfig.getName(), halChannelType); 161 if (appId == -1) { 162 callStatusCallback(appConfig, 163 BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE); 164 appInfo.cleanup(); 165 mApps.remove(appConfig); 166 } else { 167 //link to death with a recipient object to implement binderDead() 168 appInfo.mRcpObj = new BluetoothHealthDeathRecipient(HealthService.this,appConfig); 169 IBinder binder = appInfo.mCallback.asBinder(); 170 try { 171 binder.linkToDeath(appInfo.mRcpObj,0); 172 } catch (RemoteException e) { 173 Log.e(TAG,"LinktoDeath Exception:"+e); 174 } 175 appInfo.mAppId = appId; 176 callStatusCallback(appConfig, 177 BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS); 178 } 179 } 180 break; 181 case MESSAGE_UNREGISTER_APPLICATION: 182 { 183 BluetoothHealthAppConfiguration appConfig = 184 (BluetoothHealthAppConfiguration) msg.obj; 185 int appId = (mApps.get(appConfig)).mAppId; 186 if (!unregisterHealthAppNative(appId)) { 187 Log.e(TAG, "Failed to unregister application: id: " + appId); 188 callStatusCallback(appConfig, 189 BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE); 190 } 191 } 192 break; 193 case MESSAGE_CONNECT_CHANNEL: 194 { 195 HealthChannel chan = (HealthChannel) msg.obj; 196 byte[] devAddr = Utils.getByteAddress(chan.mDevice); 197 int appId = (mApps.get(chan.mConfig)).mAppId; 198 chan.mChannelId = connectChannelNative(devAddr, appId); 199 if (chan.mChannelId == -1) { 200 callHealthChannelCallback(chan.mConfig, chan.mDevice, 201 BluetoothHealth.STATE_CHANNEL_DISCONNECTING, 202 BluetoothHealth.STATE_CHANNEL_DISCONNECTED, 203 chan.mChannelFd, chan.mChannelId); 204 callHealthChannelCallback(chan.mConfig, chan.mDevice, 205 BluetoothHealth.STATE_CHANNEL_DISCONNECTED, 206 BluetoothHealth.STATE_CHANNEL_DISCONNECTING, 207 chan.mChannelFd, chan.mChannelId); 208 } 209 } 210 break; 211 case MESSAGE_DISCONNECT_CHANNEL: 212 { 213 HealthChannel chan = (HealthChannel) msg.obj; 214 if (!disconnectChannelNative(chan.mChannelId)) { 215 callHealthChannelCallback(chan.mConfig, chan.mDevice, 216 BluetoothHealth.STATE_CHANNEL_DISCONNECTING, 217 BluetoothHealth.STATE_CHANNEL_CONNECTED, 218 chan.mChannelFd, chan.mChannelId); 219 callHealthChannelCallback(chan.mConfig, chan.mDevice, 220 BluetoothHealth.STATE_CHANNEL_CONNECTED, 221 BluetoothHealth.STATE_CHANNEL_DISCONNECTING, 222 chan.mChannelFd, chan.mChannelId); 223 } 224 } 225 break; 226 case MESSAGE_APP_REGISTRATION_CALLBACK: 227 { 228 BluetoothHealthAppConfiguration appConfig = findAppConfigByAppId(msg.arg1); 229 if (appConfig == null) break; 230 231 int regStatus = convertHalRegStatus(msg.arg2); 232 callStatusCallback(appConfig, regStatus); 233 if (regStatus == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE || 234 regStatus == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) { 235 //unlink to death once app is unregistered 236 AppInfo appInfo = mApps.get(appConfig); 237 appInfo.cleanup(); 238 mApps.remove(appConfig); 239 } 240 } 241 break; 242 case MESSAGE_CHANNEL_STATE_CALLBACK: 243 { 244 ChannelStateEvent channelStateEvent = (ChannelStateEvent) msg.obj; 245 HealthChannel chan = findChannelById(channelStateEvent.mChannelId); 246 int newState; 247 if (chan == null) { 248 // incoming connection 249 BluetoothHealthAppConfiguration appConfig = 250 findAppConfigByAppId(channelStateEvent.mAppId); 251 BluetoothDevice device = getDevice(channelStateEvent.mAddr); 252 chan = new HealthChannel(device, appConfig, appConfig.getChannelType()); 253 chan.mChannelId = channelStateEvent.mChannelId; 254 mHealthChannels.add(chan); 255 } 256 newState = convertHalChannelState(channelStateEvent.mState); 257 if (newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) { 258 try { 259 chan.mChannelFd = ParcelFileDescriptor.dup(channelStateEvent.mFd); 260 } catch (IOException e) { 261 Log.e(TAG, "failed to dup ParcelFileDescriptor"); 262 break; 263 } 264 } 265 /*set the channel fd to null if channel state isnot equal to connected*/ 266 else{ 267 chan.mChannelFd = null; 268 } 269 callHealthChannelCallback(chan.mConfig, chan.mDevice, newState, 270 chan.mState, chan.mChannelFd, chan.mChannelId); 271 chan.mState = newState; 272 if (channelStateEvent.mState == CONN_STATE_DESTROYED) { 273 mHealthChannels.remove(chan); 274 } 275 } 276 break; 277 } 278 } 279 } 280 281//Handler for DeathReceipient 282 private static class BluetoothHealthDeathRecipient implements IBinder.DeathRecipient{ 283 private BluetoothHealthAppConfiguration mConfig; 284 private HealthService mService; 285 286 public BluetoothHealthDeathRecipient(HealthService service, BluetoothHealthAppConfiguration config) { 287 mService = service; 288 mConfig = config; 289 } 290 291 public void binderDied() { 292 Log.d(TAG,"Binder is dead."); 293 mService.unregisterAppConfiguration(mConfig); 294 } 295 296 public void cleanup(){ 297 mService = null; 298 mConfig = null; 299 } 300 } 301 302 /** 303 * Handlers for incoming service calls 304 */ 305 private static class BluetoothHealthBinder extends IBluetoothHealth.Stub implements IProfileServiceBinder { 306 private HealthService mService; 307 308 public BluetoothHealthBinder(HealthService svc) { 309 mService = svc; 310 } 311 312 public boolean cleanup() { 313 mService = null; 314 return true; 315 } 316 317 private HealthService getService() { 318 if (!Utils.checkCaller()) { 319 Log.w(TAG,"Health call not allowed for non-active user"); 320 return null; 321 } 322 323 if (mService != null && mService.isAvailable()) { 324 return mService; 325 } 326 return null; 327 } 328 329 public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config, 330 IBluetoothHealthCallback callback) { 331 HealthService service = getService(); 332 if (service == null) return false; 333 return service.registerAppConfiguration(config, callback); 334 } 335 336 public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { 337 HealthService service = getService(); 338 if (service == null) return false; 339 return service.unregisterAppConfiguration(config); 340 } 341 342 public boolean connectChannelToSource(BluetoothDevice device, 343 BluetoothHealthAppConfiguration config) { 344 HealthService service = getService(); 345 if (service == null) return false; 346 return service.connectChannelToSource(device, config); 347 } 348 349 public boolean connectChannelToSink(BluetoothDevice device, 350 BluetoothHealthAppConfiguration config, int channelType) { 351 HealthService service = getService(); 352 if (service == null) return false; 353 return service.connectChannelToSink(device, config, channelType); 354 } 355 356 public boolean disconnectChannel(BluetoothDevice device, 357 BluetoothHealthAppConfiguration config, int channelId) { 358 HealthService service = getService(); 359 if (service == null) return false; 360 return service.disconnectChannel(device, config, channelId); 361 } 362 363 public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, 364 BluetoothHealthAppConfiguration config) { 365 HealthService service = getService(); 366 if (service == null) return null; 367 return service.getMainChannelFd(device, config); 368 } 369 370 public int getHealthDeviceConnectionState(BluetoothDevice device) { 371 HealthService service = getService(); 372 if (service == null) return BluetoothHealth.STATE_DISCONNECTED; 373 return service.getHealthDeviceConnectionState(device); 374 } 375 376 public List<BluetoothDevice> getConnectedHealthDevices() { 377 HealthService service = getService(); 378 if (service == null) return new ArrayList<BluetoothDevice> (0); 379 return service.getConnectedHealthDevices(); 380 } 381 382 public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) { 383 HealthService service = getService(); 384 if (service == null) return new ArrayList<BluetoothDevice> (0); 385 return service.getHealthDevicesMatchingConnectionStates(states); 386 } 387 }; 388 389 boolean registerAppConfiguration(BluetoothHealthAppConfiguration config, 390 IBluetoothHealthCallback callback) { 391 enforceCallingOrSelfPermission(BLUETOOTH_PERM, 392 "Need BLUETOOTH permission"); 393 if (mApps.get(config) != null) { 394 if (DBG) Log.d(TAG, "Config has already been registered"); 395 return false; 396 } 397 mApps.put(config, new AppInfo(callback)); 398 Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION,config); 399 mHandler.sendMessage(msg); 400 return true; 401 } 402 403 boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { 404 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 405 if (mApps.get(config) == null) { 406 if (DBG) Log.d(TAG,"unregisterAppConfiguration: no app found"); 407 return false; 408 } 409 Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION,config); 410 mHandler.sendMessage(msg); 411 return true; 412 } 413 414 boolean connectChannelToSource(BluetoothDevice device, 415 BluetoothHealthAppConfiguration config) { 416 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 417 return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY); 418 } 419 420 boolean connectChannelToSink(BluetoothDevice device, 421 BluetoothHealthAppConfiguration config, int channelType) { 422 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 423 return connectChannel(device, config, channelType); 424 } 425 426 boolean disconnectChannel(BluetoothDevice device, 427 BluetoothHealthAppConfiguration config, int channelId) { 428 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 429 HealthChannel chan = findChannelById(channelId); 430 if (chan == null) { 431 if (DBG) Log.d(TAG,"disconnectChannel: no channel found"); 432 return false; 433 } 434 Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL,chan); 435 mHandler.sendMessage(msg); 436 return true; 437 } 438 439 ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, 440 BluetoothHealthAppConfiguration config) { 441 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 442 HealthChannel healthChan = null; 443 for (HealthChannel chan: mHealthChannels) { 444 if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) { 445 healthChan = chan; 446 } 447 } 448 if (healthChan == null) { 449 Log.e(TAG, "No channel found for device: " + device + " config: " + config); 450 return null; 451 } 452 return healthChan.mChannelFd; 453 } 454 455 int getHealthDeviceConnectionState(BluetoothDevice device) { 456 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 457 return getConnectionState(device); 458 } 459 460 List<BluetoothDevice> getConnectedHealthDevices() { 461 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 462 List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates( 463 new int[] {BluetoothHealth.STATE_CONNECTED}); 464 return devices; 465 } 466 467 List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) { 468 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 469 List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states); 470 return devices; 471 } 472 473 private void onAppRegistrationState(int appId, int state) { 474 Message msg = mHandler.obtainMessage(MESSAGE_APP_REGISTRATION_CALLBACK); 475 msg.arg1 = appId; 476 msg.arg2 = state; 477 mHandler.sendMessage(msg); 478 } 479 480 private void onChannelStateChanged(int appId, byte[] addr, int cfgIndex, 481 int channelId, int state, FileDescriptor pfd) { 482 Message msg = mHandler.obtainMessage(MESSAGE_CHANNEL_STATE_CALLBACK); 483 ChannelStateEvent channelStateEvent = new ChannelStateEvent(appId, addr, cfgIndex, 484 channelId, state, pfd); 485 msg.obj = channelStateEvent; 486 mHandler.sendMessage(msg); 487 } 488 489 private String getStringChannelType(int type) { 490 if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) { 491 return "Reliable"; 492 } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) { 493 return "Streaming"; 494 } else { 495 return "Any"; 496 } 497 } 498 499 private void callStatusCallback(BluetoothHealthAppConfiguration config, int status) { 500 if (DBG) log ("Health Device Application: " + config + " State Change: status:" + status); 501 IBluetoothHealthCallback callback = (mApps.get(config)).mCallback; 502 if (callback == null) { 503 Log.e(TAG, "Callback object null"); 504 } 505 506 try { 507 callback.onHealthAppConfigurationStatusChange(config, status); 508 } catch (RemoteException e) { 509 Log.e(TAG, "Remote Exception:" + e); 510 } 511 } 512 513 private BluetoothHealthAppConfiguration findAppConfigByAppId(int appId) { 514 BluetoothHealthAppConfiguration appConfig = null; 515 for (Entry<BluetoothHealthAppConfiguration, AppInfo> e : mApps.entrySet()) { 516 if (appId == (e.getValue()).mAppId) { 517 appConfig = e.getKey(); 518 break; 519 } 520 } 521 if (appConfig == null) { 522 Log.e(TAG, "No appConfig found for " + appId); 523 } 524 return appConfig; 525 } 526 527 private int convertHalRegStatus(int halRegStatus) { 528 switch (halRegStatus) { 529 case APP_REG_STATE_REG_SUCCESS: 530 return BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS; 531 case APP_REG_STATE_REG_FAILED: 532 return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE; 533 case APP_REG_STATE_DEREG_SUCCESS: 534 return BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS; 535 case APP_REG_STATE_DEREG_FAILED: 536 return BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE; 537 } 538 Log.e(TAG, "Unexpected App Registration state: " + halRegStatus); 539 return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE; 540 } 541 542 private int convertHalChannelState(int halChannelState) { 543 switch (halChannelState) { 544 case CONN_STATE_CONNECTED: 545 return BluetoothHealth.STATE_CHANNEL_CONNECTED; 546 case CONN_STATE_CONNECTING: 547 return BluetoothHealth.STATE_CHANNEL_CONNECTING; 548 case CONN_STATE_DISCONNECTING: 549 return BluetoothHealth.STATE_CHANNEL_DISCONNECTING; 550 case CONN_STATE_DISCONNECTED: 551 return BluetoothHealth.STATE_CHANNEL_DISCONNECTED; 552 case CONN_STATE_DESTROYED: 553 // TODO(BT) add BluetoothHealth.STATE_CHANNEL_DESTROYED; 554 return BluetoothHealth.STATE_CHANNEL_DISCONNECTED; 555 default: 556 Log.e(TAG, "Unexpected channel state: " + halChannelState); 557 return BluetoothHealth.STATE_CHANNEL_DISCONNECTED; 558 } 559 } 560 561 private boolean connectChannel(BluetoothDevice device, 562 BluetoothHealthAppConfiguration config, int channelType) { 563 if (mApps.get(config) == null) { 564 Log.e(TAG, "connectChannel fail to get a app id from config"); 565 return false; 566 } 567 568 HealthChannel chan = new HealthChannel(device, config, channelType); 569 570 Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL); 571 msg.obj = chan; 572 mHandler.sendMessage(msg); 573 574 return true; 575 } 576 577 private void callHealthChannelCallback(BluetoothHealthAppConfiguration config, 578 BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id) { 579 broadcastHealthDeviceStateChange(device, state); 580 581 log("Health Device Callback: " + device + " State Change: " + prevState + "->" + 582 state); 583 584 ParcelFileDescriptor dupedFd = null; 585 if (fd != null) { 586 try { 587 dupedFd = fd.dup(); 588 } catch (IOException e) { 589 dupedFd = null; 590 Log.e(TAG, "Exception while duping: " + e); 591 } 592 } 593 594 IBluetoothHealthCallback callback = (mApps.get(config)).mCallback; 595 if (callback == null) { 596 Log.e(TAG, "No callback found for config: " + config); 597 return; 598 } 599 600 try { 601 callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id); 602 } catch (RemoteException e) { 603 Log.e(TAG, "Remote Exception:" + e); 604 } 605 } 606 607 /** 608 * This function sends the intent for the updates on the connection status to the remote device. 609 * Note that multiple channels can be connected to the remote device by multiple applications. 610 * This sends an intent for the update to the device connection status and not the channel 611 * connection status. Only the following state transitions are possible: 612 * 613 * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING} 614 * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED} 615 * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING} 616 * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED} 617 * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED} 618 * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED} 619 * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED} 620 * 621 * @param device 622 * @param prevChannelState 623 * @param newChannelState 624 * @hide 625 */ 626 private void broadcastHealthDeviceStateChange(BluetoothDevice device, int newChannelState) { 627 if (mHealthDevices.get(device) == null) { 628 mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED); 629 } 630 631 int currDeviceState = mHealthDevices.get(device); 632 int newDeviceState = convertState(newChannelState); 633 634 if (currDeviceState == newDeviceState) return; 635 636 boolean sendIntent = false; 637 List<HealthChannel> chan; 638 switch (currDeviceState) { 639 case BluetoothHealth.STATE_DISCONNECTED: 640 // there was no connection or connect/disconnect attemp with the remote device 641 sendIntent = true; 642 break; 643 case BluetoothHealth.STATE_CONNECTING: 644 // there was no connection, there was a connecting attempt going on 645 646 // Channel got connected. 647 if (newDeviceState == BluetoothHealth.STATE_CONNECTED) { 648 sendIntent = true; 649 } else { 650 // Channel got disconnected 651 chan = findChannelByStates(device, new int [] { 652 BluetoothHealth.STATE_CHANNEL_CONNECTING, 653 BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); 654 if (chan.isEmpty()) { 655 sendIntent = true; 656 } 657 } 658 break; 659 case BluetoothHealth.STATE_CONNECTED: 660 // there was at least one connection 661 662 // Channel got disconnected or is in disconnecting state. 663 chan = findChannelByStates(device, new int [] { 664 BluetoothHealth.STATE_CHANNEL_CONNECTING, 665 BluetoothHealth.STATE_CHANNEL_CONNECTED}); 666 if (chan.isEmpty()) { 667 sendIntent = true; 668 } 669 break; 670 case BluetoothHealth.STATE_DISCONNECTING: 671 // there was no connected channel with the remote device 672 // We were disconnecting all the channels with the remote device 673 674 // Channel got disconnected. 675 chan = findChannelByStates(device, new int [] { 676 BluetoothHealth.STATE_CHANNEL_CONNECTING, 677 BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); 678 if (chan.isEmpty()) { 679 updateAndSendIntent(device, newDeviceState, currDeviceState); 680 } 681 break; 682 } 683 if (sendIntent) 684 updateAndSendIntent(device, newDeviceState, currDeviceState); 685 } 686 687 private void updateAndSendIntent(BluetoothDevice device, int newDeviceState, 688 int prevDeviceState) { 689 if (newDeviceState == BluetoothHealth.STATE_DISCONNECTED) { 690 mHealthDevices.remove(device); 691 } else { 692 mHealthDevices.put(device, newDeviceState); 693 } 694 notifyProfileConnectionStateChanged(device, BluetoothProfile.HEALTH, newDeviceState, prevDeviceState); 695 } 696 697 /** 698 * This function converts the channel connection state to device connection state. 699 * 700 * @param state 701 * @return 702 */ 703 private int convertState(int state) { 704 switch (state) { 705 case BluetoothHealth.STATE_CHANNEL_CONNECTED: 706 return BluetoothHealth.STATE_CONNECTED; 707 case BluetoothHealth.STATE_CHANNEL_CONNECTING: 708 return BluetoothHealth.STATE_CONNECTING; 709 case BluetoothHealth.STATE_CHANNEL_DISCONNECTING: 710 return BluetoothHealth.STATE_DISCONNECTING; 711 case BluetoothHealth.STATE_CHANNEL_DISCONNECTED: 712 return BluetoothHealth.STATE_DISCONNECTED; 713 } 714 Log.e(TAG, "Mismatch in Channel and Health Device State: " + state); 715 return BluetoothHealth.STATE_DISCONNECTED; 716 } 717 718 private int convertRoleToHal(int role) { 719 if (role == BluetoothHealth.SOURCE_ROLE) return MDEP_ROLE_SOURCE; 720 if (role == BluetoothHealth.SINK_ROLE) return MDEP_ROLE_SINK; 721 Log.e(TAG, "unkonw role: " + role); 722 return MDEP_ROLE_SINK; 723 } 724 725 private int convertChannelTypeToHal(int channelType) { 726 if (channelType == BluetoothHealth.CHANNEL_TYPE_RELIABLE) return CHANNEL_TYPE_RELIABLE; 727 if (channelType == BluetoothHealth.CHANNEL_TYPE_STREAMING) return CHANNEL_TYPE_STREAMING; 728 if (channelType == BluetoothHealth.CHANNEL_TYPE_ANY) return CHANNEL_TYPE_ANY; 729 Log.e(TAG, "unkonw channel type: " + channelType); 730 return CHANNEL_TYPE_ANY; 731 } 732 733 private HealthChannel findChannelById(int id) { 734 for (HealthChannel chan : mHealthChannels) { 735 if (chan.mChannelId == id) return chan; 736 } 737 Log.e(TAG, "No channel found by id: " + id); 738 return null; 739 } 740 741 private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) { 742 List<HealthChannel> channels = new ArrayList<HealthChannel>(); 743 for (HealthChannel chan: mHealthChannels) { 744 if (chan.mDevice.equals(device)) { 745 for (int state : states) { 746 if (chan.mState == state) { 747 channels.add(chan); 748 } 749 } 750 } 751 } 752 return channels; 753 } 754 755 private int getConnectionState(BluetoothDevice device) { 756 if (mHealthDevices.get(device) == null) { 757 return BluetoothHealth.STATE_DISCONNECTED; 758 } 759 return mHealthDevices.get(device); 760 } 761 762 List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) { 763 List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>(); 764 765 for (BluetoothDevice device: mHealthDevices.keySet()) { 766 int healthDeviceState = getConnectionState(device); 767 for (int state : states) { 768 if (state == healthDeviceState) { 769 healthDevices.add(device); 770 break; 771 } 772 } 773 } 774 return healthDevices; 775 } 776 777 private static class AppInfo { 778 private IBluetoothHealthCallback mCallback; 779 private BluetoothHealthDeathRecipient mRcpObj; 780 private int mAppId; 781 782 private AppInfo(IBluetoothHealthCallback callback) { 783 mCallback = callback; 784 mRcpObj = null; 785 mAppId = -1; 786 } 787 788 private void cleanup(){ 789 if(mCallback != null){ 790 if(mRcpObj != null){ 791 IBinder binder = mCallback.asBinder(); 792 try{ 793 binder.unlinkToDeath(mRcpObj,0); 794 }catch(NoSuchElementException e){ 795 Log.e(TAG,"No death recipient registered"+e); 796 } 797 mRcpObj.cleanup(); 798 mRcpObj = null; 799 } 800 mCallback = null; 801 } 802 else if(mRcpObj != null){ 803 mRcpObj.cleanup(); 804 mRcpObj = null; 805 } 806 } 807 } 808 809 private class HealthChannel { 810 private ParcelFileDescriptor mChannelFd; 811 private BluetoothDevice mDevice; 812 private BluetoothHealthAppConfiguration mConfig; 813 // BluetoothHealth channel state 814 private int mState; 815 private int mChannelType; 816 private int mChannelId; 817 818 private HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, 819 int channelType) { 820 mChannelFd = null; 821 mDevice = device; 822 mConfig = config; 823 mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; 824 mChannelType = channelType; 825 mChannelId = -1; 826 } 827 } 828 829 // Channel state event from Hal 830 private class ChannelStateEvent { 831 int mAppId; 832 byte[] mAddr; 833 int mCfgIndex; 834 int mChannelId; 835 int mState; 836 FileDescriptor mFd; 837 838 private ChannelStateEvent(int appId, byte[] addr, int cfgIndex, 839 int channelId, int state, FileDescriptor fileDescriptor) { 840 mAppId = appId; 841 mAddr = addr; 842 mCfgIndex = cfgIndex; 843 mState = state; 844 mChannelId = channelId; 845 mFd = fileDescriptor; 846 } 847 } 848 849 // Constants matching Hal header file bt_hl.h 850 // bthl_app_reg_state_t 851 private static final int APP_REG_STATE_REG_SUCCESS = 0; 852 private static final int APP_REG_STATE_REG_FAILED = 1; 853 private static final int APP_REG_STATE_DEREG_SUCCESS = 2; 854 private static final int APP_REG_STATE_DEREG_FAILED = 3; 855 856 // bthl_channel_state_t 857 private static final int CONN_STATE_CONNECTING = 0; 858 private static final int CONN_STATE_CONNECTED = 1; 859 private static final int CONN_STATE_DISCONNECTING = 2; 860 private static final int CONN_STATE_DISCONNECTED = 3; 861 private static final int CONN_STATE_DESTROYED = 4; 862 863 // bthl_mdep_role_t 864 private static final int MDEP_ROLE_SOURCE = 0; 865 private static final int MDEP_ROLE_SINK = 1; 866 867 // bthl_channel_type_t 868 private static final int CHANNEL_TYPE_RELIABLE = 0; 869 private static final int CHANNEL_TYPE_STREAMING = 1; 870 private static final int CHANNEL_TYPE_ANY =2; 871 872 private native static void classInitNative(); 873 private native void initializeNative(); 874 private native void cleanupNative(); 875 private native int registerHealthAppNative(int dataType, int role, String name, int channelType); 876 private native boolean unregisterHealthAppNative(int appId); 877 private native int connectChannelNative(byte[] btAddress, int appId); 878 private native boolean disconnectChannelNative(int channelId); 879 880} 881