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