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