Home | History | Annotate | Download | only in hdp
      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 {
     76         classInitNative();
     77     }
     78 
     79     protected String getName() {
     80         return TAG;
     81     }
     82 
     83     protected IProfileServiceBinder initBinder() {
     84         return new BluetoothHealthBinder(this);
     85     }
     86 
     87     protected boolean start() {
     88         mHealthChannels = Collections.synchronizedList(new ArrayList<HealthChannel>());
     89         mApps = Collections.synchronizedMap(new HashMap<BluetoothHealthAppConfiguration,
     90                                             AppInfo>());
     91         mHealthDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
     92 
     93         HandlerThread thread = new HandlerThread("BluetoothHdpHandler");
     94         thread.start();
     95         Looper looper = thread.getLooper();
     96         mHandler = new HealthServiceMessageHandler(looper);
     97         initializeNative();
     98         mNativeAvailable=true;
     99         return true;
    100     }
    101 
    102     protected boolean stop() {
    103         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 
    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     }
    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 {
    147         private HealthServiceMessageHandler(Looper looper) {
    148             super(looper);
    149         }
    150 
    151         @Override
    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 
    300         public BluetoothHealthDeathRecipient(HealthService service, BluetoothHealthAppConfiguration config) {
    301             mService = service;
    302             mConfig = config;
    303         }
    304 
    305         public void binderDied() {
    306             if (DBG) Log.d(TAG,"Binder is dead.");
    307             mService.unregisterAppConfiguration(mConfig);
    308         }
    309 
    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 
    322         public BluetoothHealthBinder(HealthService svc) {
    323             mService = svc;
    324         }
    325 
    326         public boolean cleanup()  {
    327             mService = null;
    328             return true;
    329         }
    330 
    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 
    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 
    350         public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
    351             HealthService service = getService();
    352             if (service == null) return false;
    353             return service.unregisterAppConfiguration(config);
    354         }
    355 
    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 
    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 
    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 
    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 
    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 
    390         public List<BluetoothDevice> getConnectedHealthDevices() {
    391             HealthService service = getService();
    392             if (service == null) return new ArrayList<BluetoothDevice> (0);
    393             return service.getConnectedHealthDevices();
    394         }
    395 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    469     int getHealthDeviceConnectionState(BluetoothDevice device) {
    470         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    471         return getConnectionState(device);
    472     }
    473 
    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 
    481     List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
    482         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    483         List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
    484         return devices;
    485     }
    486 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    813         private AppInfo(IBluetoothHealthCallback callback) {
    814             mCallback = callback;
    815             mRcpObj = null;
    816             mAppId = -1;
    817         }
    818 
    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 
    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 
    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 
    903     private native static void classInitNative();
    904     private native void initializeNative();
    905     private native void cleanupNative();
    906     private native int registerHealthAppNative(int dataType, int role, String name, int channelType);
    907     private native boolean unregisterHealthAppNative(int appId);
    908     private native int connectChannelNative(byte[] btAddress, int appId);
    909     private native boolean disconnectChannelNative(int channelId);
    910 
    911 }
    912