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