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