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