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