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