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