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