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