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