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