Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2011 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 android.server;
     18 
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.BluetoothHealth;
     22 import android.bluetooth.BluetoothHealthAppConfiguration;
     23 import android.bluetooth.BluetoothProfile;
     24 import android.bluetooth.IBluetoothHealthCallback;
     25 import android.content.Context;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import android.os.ParcelFileDescriptor;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 import java.io.IOException;
     33 import java.util.ArrayList;
     34 import java.util.HashMap;
     35 import java.util.List;
     36 import java.util.Map.Entry;
     37 import java.util.concurrent.atomic.AtomicInteger;
     38 
     39 /**
     40  * This handles all the operations on the Bluetooth Health profile.
     41  * All functions are called by BluetoothService, as Bluetooth Service
     42  * is the Service handler for the HDP profile.
     43  *
     44  * @hide
     45  */
     46 final class BluetoothHealthProfileHandler {
     47     private static final String TAG = "BluetoothHealthProfileHandler";
     48     private static final boolean DBG = false;
     49 
     50     private static BluetoothHealthProfileHandler sInstance;
     51     private BluetoothService mBluetoothService;
     52     private ArrayList<HealthChannel> mHealthChannels;
     53     private HashMap <BluetoothHealthAppConfiguration, String> mHealthAppConfigs;
     54     private HashMap <BluetoothDevice, Integer> mHealthDevices;
     55     private HashMap <BluetoothHealthAppConfiguration, IBluetoothHealthCallback> mCallbacks;
     56 
     57     private static final int MESSAGE_REGISTER_APPLICATION = 0;
     58     private static final int MESSAGE_UNREGISTER_APPLICATION = 1;
     59     private static final int MESSAGE_CONNECT_CHANNEL = 2;
     60     private static final AtomicInteger sChannelId = new AtomicInteger();
     61 
     62     class HealthChannel {
     63         private ParcelFileDescriptor mChannelFd;
     64         private boolean mMainChannel;
     65         private String mChannelPath;
     66         private BluetoothDevice mDevice;
     67         private BluetoothHealthAppConfiguration mConfig;
     68         private int mState;
     69         private int mChannelType;
     70         private int mId;
     71 
     72         HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
     73                 ParcelFileDescriptor fd, boolean mainChannel, String channelPath) {
     74              mChannelFd = fd;
     75              mMainChannel = mainChannel;
     76              mChannelPath = channelPath;
     77              mDevice = device;
     78              mConfig = config;
     79              mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
     80              mId = getChannelId();
     81         }
     82     }
     83 
     84     private final Handler mHandler = new Handler() {
     85         @Override
     86         public void handleMessage(Message msg) {
     87             switch (msg.what) {
     88             case MESSAGE_REGISTER_APPLICATION:
     89                 BluetoothHealthAppConfiguration registerApp =
     90                     (BluetoothHealthAppConfiguration) msg.obj;
     91                 int role = registerApp.getRole();
     92                 String path = null;
     93 
     94                 if (role == BluetoothHealth.SINK_ROLE) {
     95                     path = mBluetoothService.registerHealthApplicationNative(
     96                             registerApp.getDataType(), getStringRole(role), registerApp.getName());
     97                 } else {
     98                     path = mBluetoothService.registerHealthApplicationNative(
     99                             registerApp.getDataType(), getStringRole(role), registerApp.getName(),
    100                             getStringChannelType(registerApp.getChannelType()));
    101                 }
    102 
    103                 if (path == null) {
    104                     callHealthApplicationStatusCallback(registerApp,
    105                             BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE);
    106                     mCallbacks.remove(registerApp);
    107                 } else {
    108                     mHealthAppConfigs.put(registerApp, path);
    109                     callHealthApplicationStatusCallback(registerApp,
    110                             BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS);
    111                 }
    112 
    113                 break;
    114             case MESSAGE_UNREGISTER_APPLICATION:
    115                 BluetoothHealthAppConfiguration unregisterApp =
    116                     (BluetoothHealthAppConfiguration) msg.obj;
    117 
    118                 // Disconnect all the channels
    119                 for (HealthChannel chan : mHealthChannels) {
    120                     if (chan.mConfig.equals(unregisterApp) &&
    121                             chan.mState != BluetoothHealth.STATE_CHANNEL_DISCONNECTED) {
    122                         disconnectChannel(chan.mDevice, unregisterApp, chan.mId);
    123                     }
    124                 }
    125 
    126                 boolean result = mBluetoothService.unregisterHealthApplicationNative(
    127                         mHealthAppConfigs.get(unregisterApp));
    128                 if (result) {
    129                     callHealthApplicationStatusCallback(unregisterApp,
    130                             BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS);
    131                     mCallbacks.remove(unregisterApp);
    132                     mHealthAppConfigs.remove(unregisterApp);
    133                 } else {
    134                     callHealthApplicationStatusCallback(unregisterApp,
    135                             BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE);
    136                 }
    137                 break;
    138             case MESSAGE_CONNECT_CHANNEL:
    139                 HealthChannel chan = (HealthChannel)msg.obj;
    140                 String deviceObjectPath =
    141                     mBluetoothService.getObjectPathFromAddress(chan.mDevice.getAddress());
    142                 String configPath = mHealthAppConfigs.get(chan.mConfig);
    143                 String channelType = getStringChannelType(chan.mChannelType);
    144 
    145                 if (!mBluetoothService.createChannelNative(deviceObjectPath, configPath,
    146                           channelType, chan.mId)) {
    147                     int prevState = chan.mState;
    148                     int state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
    149                     callHealthChannelCallback(chan.mConfig, chan.mDevice, prevState, state, null,
    150                             chan.mId);
    151                     mHealthChannels.remove(chan);
    152                 }
    153             }
    154         }
    155     };
    156 
    157     private BluetoothHealthProfileHandler(Context context, BluetoothService service) {
    158         mBluetoothService = service;
    159         mHealthAppConfigs = new HashMap<BluetoothHealthAppConfiguration, String>();
    160         mHealthChannels = new ArrayList<HealthChannel>();
    161         mHealthDevices = new HashMap<BluetoothDevice, Integer>();
    162         mCallbacks = new HashMap<BluetoothHealthAppConfiguration, IBluetoothHealthCallback>();
    163     }
    164 
    165     static synchronized BluetoothHealthProfileHandler getInstance(Context context,
    166             BluetoothService service) {
    167         if (sInstance == null) sInstance = new BluetoothHealthProfileHandler(context, service);
    168         return sInstance;
    169     }
    170 
    171     boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
    172                                      IBluetoothHealthCallback callback) {
    173         Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION);
    174         msg.obj = config;
    175         mHandler.sendMessage(msg);
    176         mCallbacks.put(config, callback);
    177         return true;
    178     }
    179 
    180     boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
    181         String path = mHealthAppConfigs.get(config);
    182         if (path == null) return false;
    183 
    184         Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION);
    185         msg.obj = config;
    186         mHandler.sendMessage(msg);
    187         return true;
    188     }
    189 
    190     boolean connectChannelToSource(BluetoothDevice device,
    191             BluetoothHealthAppConfiguration config) {
    192         return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
    193     }
    194 
    195     private HealthChannel getMainChannel(BluetoothDevice device,
    196             BluetoothHealthAppConfiguration config) {
    197         for (HealthChannel chan: mHealthChannels) {
    198             if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
    199                 if (chan.mMainChannel) return chan;
    200             }
    201         }
    202         return null;
    203     }
    204 
    205     boolean connectChannel(BluetoothDevice device,
    206             BluetoothHealthAppConfiguration config, int channelType) {
    207         String deviceObjectPath =
    208             mBluetoothService.getObjectPathFromAddress(device.getAddress());
    209         if (deviceObjectPath == null) return false;
    210 
    211         String configPath = mHealthAppConfigs.get(config);
    212         if (configPath == null) return false;
    213 
    214         HealthChannel chan = new HealthChannel(device, config, null, false, null);
    215         chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTING;
    216         chan.mChannelType = channelType;
    217         mHealthChannels.add(chan);
    218 
    219         int prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
    220         int state = BluetoothHealth.STATE_CHANNEL_CONNECTING;
    221         callHealthChannelCallback(config, device, prevState, state, null, chan.mId);
    222 
    223         Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
    224         msg.obj = chan;
    225         mHandler.sendMessage(msg);
    226 
    227         return true;
    228     }
    229 
    230     private String getStringChannelType(int type) {
    231         if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
    232             return "Reliable";
    233         } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
    234             return "Streaming";
    235         } else {
    236             return "Any";
    237         }
    238     }
    239 
    240     private String getStringRole(int role) {
    241         if (role == BluetoothHealth.SINK_ROLE) {
    242             return "Sink";
    243         } else if (role == BluetoothHealth.SOURCE_ROLE) {
    244             return "Streaming";
    245         } else {
    246             return null;
    247         }
    248     }
    249 
    250     private int getChannelId() {
    251         // The function doesn't need to be synchronized, as the health profile handler
    252         // will only allow one health channel object creation at a time.
    253         // In the worst case the while loop will have to break out at some point of
    254         // time, because only a limited number of L2CAP channels are possible.
    255         int id;
    256         boolean found;
    257         do {
    258             id = sChannelId.incrementAndGet();
    259             found = false;
    260             for (HealthChannel chan: mHealthChannels) {
    261                 if (chan.mId == id) found = true;
    262             }
    263         } while (found);
    264         return id;
    265     }
    266 
    267     boolean disconnectChannel(BluetoothDevice device,
    268             BluetoothHealthAppConfiguration config, int id) {
    269         HealthChannel chan = findChannelById(id);
    270         if (chan == null) {
    271           return false;
    272         }
    273 
    274         String deviceObjectPath =
    275                 mBluetoothService.getObjectPathFromAddress(device.getAddress());
    276 
    277         mBluetoothService.releaseChannelFdNative(chan.mChannelPath);
    278 
    279         int prevState = chan.mState;
    280         chan.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
    281         callHealthChannelCallback(config, device, prevState, chan.mState,
    282                 null, chan.mId);
    283 
    284         if (!mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath,
    285                                                     chan.mId)) {
    286             prevState = chan.mState;
    287             chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTED;
    288             callHealthChannelCallback(config, device, prevState, chan.mState,
    289                     chan.mChannelFd, chan.mId);
    290             return false;
    291         } else {
    292             return true;
    293         }
    294     }
    295 
    296     private HealthChannel findChannelById(int id) {
    297         for (HealthChannel chan : mHealthChannels) {
    298             if (chan.mId == id) return chan;
    299         }
    300         return null;
    301     }
    302 
    303     private HealthChannel findChannelByPath(BluetoothDevice device, String path) {
    304         for (HealthChannel chan : mHealthChannels) {
    305             if (path.equals(chan.mChannelPath) && device.equals(chan.mDevice)) return chan;
    306         }
    307         return null;
    308     }
    309 
    310     private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
    311         List<HealthChannel> channels = new ArrayList<HealthChannel>();
    312         for (HealthChannel chan: mHealthChannels) {
    313             if (chan.mDevice.equals(device)) {
    314                 for (int state : states) {
    315                     if (chan.mState == state) {
    316                         channels.add(chan);
    317                     }
    318                 }
    319             }
    320         }
    321         return channels;
    322     }
    323 
    324     private HealthChannel findConnectingChannel(BluetoothDevice device,
    325             BluetoothHealthAppConfiguration config) {
    326         for (HealthChannel chan : mHealthChannels) {
    327             if (chan.mDevice.equals(device) && chan.mConfig.equals(config) &&
    328                 chan.mState == BluetoothHealth.STATE_CHANNEL_CONNECTING) return chan;
    329         }
    330         return null;
    331     }
    332 
    333     ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
    334             BluetoothHealthAppConfiguration config) {
    335         HealthChannel chan = getMainChannel(device, config);
    336         if (chan != null) {
    337             ParcelFileDescriptor pfd =  null;
    338             try {
    339                 pfd = chan.mChannelFd.dup();
    340                 return pfd;
    341             } catch (IOException e) {
    342                 return null;
    343             }
    344         }
    345 
    346         String objectPath =
    347                 mBluetoothService.getObjectPathFromAddress(device.getAddress());
    348         if (objectPath == null) return null;
    349 
    350         String mainChannelPath = mBluetoothService.getMainChannelNative(objectPath);
    351         if (mainChannelPath == null) return null;
    352 
    353         // We had no record of the main channel but querying Bluez we got a
    354         // main channel. We might not have received the PropertyChanged yet for
    355         // the main channel creation so update our data structure here.
    356         chan = findChannelByPath(device, mainChannelPath);
    357         if (chan == null) {
    358             errorLog("Main Channel present but we don't have any account of it:" +
    359                     device +":" + config);
    360             return null;
    361         }
    362         chan.mMainChannel = true;
    363         try {
    364             return chan.mChannelFd.dup();
    365         } catch (IOException e) {
    366             return null;
    367         }
    368     }
    369 
    370     /*package*/ void onHealthDevicePropertyChanged(String devicePath,
    371             String channelPath) {
    372         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    373         String address = mBluetoothService.getAddressFromObjectPath(devicePath);
    374         if (address == null) return;
    375 
    376         //TODO: Fix this in Bluez
    377         if (channelPath.equals("/")) {
    378             // This means that the main channel is being destroyed.
    379             return;
    380         }
    381 
    382         BluetoothDevice device = adapter.getRemoteDevice(address);
    383         BluetoothHealthAppConfiguration config = findHealthApplication(device,
    384                 channelPath);
    385         if (config != null) {
    386             HealthChannel chan = findChannelByPath(device, channelPath);
    387             if (chan == null) {
    388                 errorLog("Health Channel is not present:" + channelPath);
    389             } else {
    390                 chan.mMainChannel = true;
    391             }
    392         }
    393     }
    394 
    395     /*package*/ void onHealthDeviceChannelConnectionError(int chanCode,
    396                                                           int state) {
    397         HealthChannel channel = findChannelById(chanCode);
    398         if (channel == null) errorLog("No record of this channel:" + chanCode);
    399 
    400         callHealthChannelCallback(channel.mConfig, channel.mDevice, channel.mState, state, null,
    401                 chanCode);
    402     }
    403 
    404     private BluetoothHealthAppConfiguration findHealthApplication(
    405             BluetoothDevice device, String channelPath) {
    406         BluetoothHealthAppConfiguration config = null;
    407         HealthChannel chan = findChannelByPath(device, channelPath);
    408 
    409         if (chan != null) {
    410             config = chan.mConfig;
    411         } else {
    412             String configPath = mBluetoothService.getChannelApplicationNative(channelPath);
    413             if (configPath == null) {
    414                 errorLog("Config path is null for application");
    415             } else {
    416                 for (Entry<BluetoothHealthAppConfiguration, String> e :
    417                         mHealthAppConfigs.entrySet()) {
    418                     if (e.getValue().equals(configPath)) {
    419                         config = e.getKey();
    420                     }
    421                 }
    422                 if (config == null) errorLog("No associated application for path:" + configPath);
    423             }
    424         }
    425         return config;
    426     }
    427 
    428     /*package*/ void onHealthDeviceChannelChanged(String devicePath,
    429             String channelPath, boolean exists) {
    430         debugLog("onHealthDeviceChannelChanged: devicePath: " + devicePath +
    431                 "ChannelPath: " + channelPath + "Exists: " + exists);
    432         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    433         String address = mBluetoothService.getAddressFromObjectPath(devicePath);
    434         if (address == null) return;
    435 
    436         BluetoothDevice device = adapter.getRemoteDevice(address);
    437         BluetoothHealthAppConfiguration config;
    438         int state, prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
    439         ParcelFileDescriptor fd;
    440         HealthChannel channel;
    441         config = findHealthApplication(device, channelPath);
    442 
    443         if (exists) {
    444             channel = findConnectingChannel(device, config);
    445             if (channel == null) {
    446                channel = new HealthChannel(device, config, null, false,
    447                        channelPath);
    448                channel.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
    449                mHealthChannels.add(channel);
    450             }
    451             channel.mChannelPath = channelPath;
    452 
    453             fd = mBluetoothService.getChannelFdNative(channelPath);
    454             if (fd == null) {
    455                 errorLog("Error obtaining fd for channel:" + channelPath);
    456                 disconnectChannel(device, config, channel.mId);
    457                 return;
    458             }
    459             boolean mainChannel =
    460                     getMainChannel(device, config) == null ? false : true;
    461             if (!mainChannel) {
    462                 String mainChannelPath =
    463                         mBluetoothService.getMainChannelNative(devicePath);
    464                 if (mainChannelPath == null) {
    465                     errorLog("Main Channel Path is null for devicePath:" + devicePath);
    466                     return;
    467                 }
    468                 if (mainChannelPath.equals(channelPath)) mainChannel = true;
    469             }
    470 
    471             channel.mChannelFd = fd;
    472             channel.mMainChannel = mainChannel;
    473             prevState = channel.mState;
    474             state = BluetoothHealth.STATE_CHANNEL_CONNECTED;
    475         } else {
    476             channel = findChannelByPath(device, channelPath);
    477             if (channel == null) {
    478                 errorLog("Channel not found:" + config + ":" + channelPath);
    479                 return;
    480             }
    481             mHealthChannels.remove(channel);
    482 
    483             channel.mChannelFd = null;
    484             prevState = channel.mState;
    485             state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
    486         }
    487         channel.mState = state;
    488         callHealthChannelCallback(config, device, prevState, state, channel.mChannelFd,
    489                 channel.mId);
    490     }
    491 
    492     private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
    493             BluetoothDevice device, int prevState, int state, ParcelFileDescriptor fd, int id) {
    494         broadcastHealthDeviceStateChange(device, prevState, state);
    495 
    496         debugLog("Health Device Callback: " + device + " State Change: "
    497                 + prevState + "->" + state);
    498 
    499         ParcelFileDescriptor dupedFd = null;
    500         if (fd != null) {
    501             try {
    502                 dupedFd = fd.dup();
    503             } catch (IOException e) {
    504                 dupedFd = null;
    505                 errorLog("Exception while duping: " + e);
    506             }
    507         }
    508 
    509         IBluetoothHealthCallback callback = mCallbacks.get(config);
    510         if (callback != null) {
    511             try {
    512                 callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id);
    513             } catch (RemoteException e) {
    514                 errorLog("Remote Exception:" + e);
    515             }
    516         }
    517     }
    518 
    519     private void callHealthApplicationStatusCallback(
    520             BluetoothHealthAppConfiguration config, int status) {
    521         debugLog("Health Device Application: " + config + " State Change: status:"
    522                 + status);
    523         IBluetoothHealthCallback callback = mCallbacks.get(config);
    524         if (callback != null) {
    525             try {
    526                 callback.onHealthAppConfigurationStatusChange(config, status);
    527             } catch (RemoteException e) {
    528                 errorLog("Remote Exception:" + e);
    529             }
    530         }
    531     }
    532 
    533     int getHealthDeviceConnectionState(BluetoothDevice device) {
    534         if (mHealthDevices.get(device) == null) {
    535             return BluetoothHealth.STATE_DISCONNECTED;
    536         }
    537         return mHealthDevices.get(device);
    538     }
    539 
    540     List<BluetoothDevice> getConnectedHealthDevices() {
    541         List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(
    542                     new int[] {BluetoothHealth.STATE_CONNECTED});
    543         return devices;
    544     }
    545 
    546     List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
    547         List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
    548         return devices;
    549     }
    550 
    551     List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
    552         List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
    553 
    554         for (BluetoothDevice device: mHealthDevices.keySet()) {
    555             int healthDeviceState = getHealthDeviceConnectionState(device);
    556             for (int state : states) {
    557                 if (state == healthDeviceState) {
    558                     healthDevices.add(device);
    559                     break;
    560                 }
    561             }
    562         }
    563         return healthDevices;
    564     }
    565 
    566     /**
    567      * This function sends the intent for the updates on the connection status to the remote device.
    568      * Note that multiple channels can be connected to the remote device by multiple applications.
    569      * This sends an intent for the update to the device connection status and not the channel
    570      * connection status. Only the following state transitions are possible:
    571      *
    572      * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
    573      * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
    574      * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
    575      * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
    576      * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
    577      * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
    578      * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
    579      *
    580      * @param device
    581      * @param prevChannelState
    582      * @param newChannelState
    583      * @hide
    584      */
    585     private void broadcastHealthDeviceStateChange(BluetoothDevice device, int prevChannelState,
    586             int newChannelState) {
    587         if (mHealthDevices.get(device) == null) {
    588             mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
    589         }
    590 
    591         int currDeviceState = mHealthDevices.get(device);
    592         int newDeviceState = convertState(newChannelState);
    593 
    594         if (currDeviceState != newDeviceState) {
    595             List<HealthChannel> chan;
    596             switch (currDeviceState) {
    597                 case BluetoothHealth.STATE_DISCONNECTED:
    598                     updateAndSendIntent(device, currDeviceState, newDeviceState);
    599                     break;
    600                 case BluetoothHealth.STATE_CONNECTING:
    601                     // Channel got connected.
    602                     if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
    603                         updateAndSendIntent(device, currDeviceState, newDeviceState);
    604                     } else {
    605                         // Channel got disconnected
    606                         chan = findChannelByStates(device, new int [] {
    607                                     BluetoothHealth.STATE_CHANNEL_CONNECTING,
    608                                     BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
    609                         if (chan.isEmpty()) {
    610                             updateAndSendIntent(device, currDeviceState, newDeviceState);
    611                         }
    612                     }
    613                     break;
    614                 case BluetoothHealth.STATE_CONNECTED:
    615                     // Channel got disconnected or is in disconnecting state.
    616                     chan = findChannelByStates(device, new int [] {
    617                                 BluetoothHealth.STATE_CHANNEL_CONNECTING,
    618                                 BluetoothHealth.STATE_CHANNEL_CONNECTED});
    619                     if (chan.isEmpty()) {
    620                         updateAndSendIntent(device, currDeviceState, newDeviceState);
    621                     }
    622                     break;
    623                 case BluetoothHealth.STATE_DISCONNECTING:
    624                     // Channel got disconnected.
    625                     chan = findChannelByStates(device, new int [] {
    626                                 BluetoothHealth.STATE_CHANNEL_CONNECTING,
    627                                 BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
    628                     if (chan.isEmpty()) {
    629                         updateAndSendIntent(device, currDeviceState, newDeviceState);
    630                     }
    631                     break;
    632             }
    633         }
    634     }
    635 
    636     private void updateAndSendIntent(BluetoothDevice device, int prevDeviceState,
    637             int newDeviceState) {
    638         mHealthDevices.put(device, newDeviceState);
    639         mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.HEALTH,
    640                                                     newDeviceState, prevDeviceState);
    641     }
    642 
    643     /**
    644      * This function converts the channel connection state to device connection state.
    645      *
    646      * @param state
    647      * @return
    648      */
    649     private int convertState(int state) {
    650         switch (state) {
    651             case BluetoothHealth.STATE_CHANNEL_CONNECTED:
    652                 return BluetoothHealth.STATE_CONNECTED;
    653             case BluetoothHealth.STATE_CHANNEL_CONNECTING:
    654                 return BluetoothHealth.STATE_CONNECTING;
    655             case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
    656                 return BluetoothHealth.STATE_DISCONNECTING;
    657             case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
    658                 return BluetoothHealth.STATE_DISCONNECTED;
    659         }
    660         errorLog("Mismatch in Channel and Health Device State");
    661         return -1;
    662     }
    663 
    664     private static void debugLog(String msg) {
    665         if (DBG) Log.d(TAG, msg);
    666     }
    667 
    668     private static void errorLog(String msg) {
    669         Log.e(TAG, msg);
    670     }
    671 }
    672