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