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        Iterator <Map.Entry<BluetoothHealthAppConfiguration,AppInfo>>it
114                    = mApps.entrySet().iterator();
115        while (it.hasNext())
116        {
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    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        if (mApps.get(config) != null) {
405            if (DBG) Log.d(TAG, "Config has already been registered");
406            return false;
407        }
408        mApps.put(config, new AppInfo(callback));
409        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION,config);
410        mHandler.sendMessage(msg);
411        return true;
412    }
413
414    boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
415        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
416        if (mApps.get(config) == null) {
417            if (DBG) Log.d(TAG,"unregisterAppConfiguration: no app found");
418            return false;
419        }
420        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION,config);
421        mHandler.sendMessage(msg);
422        return true;
423    }
424
425    boolean connectChannelToSource(BluetoothDevice device,
426                                          BluetoothHealthAppConfiguration config) {
427        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
428        return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
429    }
430
431    boolean connectChannelToSink(BluetoothDevice device,
432                       BluetoothHealthAppConfiguration config, int channelType) {
433        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
434        return connectChannel(device, config, channelType);
435    }
436
437    boolean disconnectChannel(BluetoothDevice device,
438                                     BluetoothHealthAppConfiguration config, int channelId) {
439        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
440        HealthChannel chan = findChannelById(channelId);
441        if (chan == null) {
442            if (DBG) Log.d(TAG,"disconnectChannel: no channel found");
443            return false;
444        }
445        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL,chan);
446        mHandler.sendMessage(msg);
447        return true;
448    }
449
450    ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
451                                                 BluetoothHealthAppConfiguration config) {
452        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
453        HealthChannel healthChan = null;
454        for (HealthChannel chan: mHealthChannels) {
455            if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
456                healthChan = chan;
457            }
458        }
459        if (healthChan == null) {
460            Log.e(TAG, "No channel found for device: " + device + " config: " + config);
461            return null;
462        }
463        return healthChan.mChannelFd;
464    }
465
466    int getHealthDeviceConnectionState(BluetoothDevice device) {
467        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
468        return getConnectionState(device);
469    }
470
471    List<BluetoothDevice> getConnectedHealthDevices() {
472        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
473        List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(
474                new int[] {BluetoothHealth.STATE_CONNECTED});
475        return devices;
476    }
477
478    List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
479        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
480        List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
481        return devices;
482    }
483
484    private void onAppRegistrationState(int appId, int state) {
485        Message msg = mHandler.obtainMessage(MESSAGE_APP_REGISTRATION_CALLBACK);
486        msg.arg1 = appId;
487        msg.arg2 = state;
488        mHandler.sendMessage(msg);
489    }
490
491    private void onChannelStateChanged(int appId, byte[] addr, int cfgIndex,
492                                       int channelId, int state, FileDescriptor pfd) {
493        Message msg = mHandler.obtainMessage(MESSAGE_CHANNEL_STATE_CALLBACK);
494        ChannelStateEvent channelStateEvent = new ChannelStateEvent(appId, addr, cfgIndex,
495                                                                    channelId, state, pfd);
496        msg.obj = channelStateEvent;
497        mHandler.sendMessage(msg);
498    }
499
500    private String getStringChannelType(int type) {
501        if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
502            return "Reliable";
503        } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
504            return "Streaming";
505        } else {
506            return "Any";
507        }
508    }
509
510    private void callStatusCallback(BluetoothHealthAppConfiguration config, int status) {
511        if (VDBG) log ("Health Device Application: " + config + " State Change: status:" + status);
512        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
513        if (callback == null) {
514            Log.e(TAG, "Callback object null");
515        }
516
517        try {
518            callback.onHealthAppConfigurationStatusChange(config, status);
519        } catch (RemoteException e) {
520            Log.e(TAG, "Remote Exception:" + e);
521        }
522    }
523
524    private BluetoothHealthAppConfiguration findAppConfigByAppId(int appId) {
525        BluetoothHealthAppConfiguration appConfig = null;
526        for (Entry<BluetoothHealthAppConfiguration, AppInfo> e : mApps.entrySet()) {
527            if (appId == (e.getValue()).mAppId) {
528                appConfig = e.getKey();
529                break;
530            }
531        }
532        if (appConfig == null) {
533            Log.e(TAG, "No appConfig found for " + appId);
534        }
535        return appConfig;
536    }
537
538    private int convertHalRegStatus(int halRegStatus) {
539        switch (halRegStatus) {
540            case APP_REG_STATE_REG_SUCCESS:
541                return BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS;
542            case APP_REG_STATE_REG_FAILED:
543                return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
544            case APP_REG_STATE_DEREG_SUCCESS:
545                return BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS;
546            case APP_REG_STATE_DEREG_FAILED:
547                return BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE;
548        }
549        Log.e(TAG, "Unexpected App Registration state: " + halRegStatus);
550        return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE;
551    }
552
553    private int convertHalChannelState(int halChannelState) {
554        switch (halChannelState) {
555            case CONN_STATE_CONNECTED:
556                return BluetoothHealth.STATE_CHANNEL_CONNECTED;
557            case CONN_STATE_CONNECTING:
558                return BluetoothHealth.STATE_CHANNEL_CONNECTING;
559            case CONN_STATE_DISCONNECTING:
560                return BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
561            case CONN_STATE_DISCONNECTED:
562                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
563            case CONN_STATE_DESTROYED:
564                // TODO(BT) add BluetoothHealth.STATE_CHANNEL_DESTROYED;
565                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
566            default:
567                Log.e(TAG, "Unexpected channel state: " + halChannelState);
568                return BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
569        }
570    }
571
572    private boolean connectChannel(BluetoothDevice device,
573                                   BluetoothHealthAppConfiguration config, int channelType) {
574        if (mApps.get(config) == null) {
575            Log.e(TAG, "connectChannel fail to get a app id from config");
576            return false;
577        }
578
579        HealthChannel chan = new HealthChannel(device, config, channelType);
580
581        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
582        msg.obj = chan;
583        mHandler.sendMessage(msg);
584
585        return true;
586    }
587
588    private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
589            BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id) {
590        broadcastHealthDeviceStateChange(device, state);
591
592        log("Health Device Callback: " + device + " State Change: " + prevState + "->" +
593                     state);
594
595        ParcelFileDescriptor dupedFd = null;
596        if (fd != null) {
597            try {
598                dupedFd = fd.dup();
599            } catch (IOException e) {
600                dupedFd = null;
601                Log.e(TAG, "Exception while duping: " + e);
602            }
603        }
604
605        IBluetoothHealthCallback callback = (mApps.get(config)).mCallback;
606        if (callback == null) {
607            Log.e(TAG, "No callback found for config: " + config);
608            return;
609        }
610
611        try {
612            callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id);
613        } catch (RemoteException e) {
614            Log.e(TAG, "Remote Exception:" + e);
615        }
616    }
617
618    /**
619     * This function sends the intent for the updates on the connection status to the remote device.
620     * Note that multiple channels can be connected to the remote device by multiple applications.
621     * This sends an intent for the update to the device connection status and not the channel
622     * connection status. Only the following state transitions are possible:
623     *
624     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
625     * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
626     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
627     * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
628     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
629     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
630     * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
631     *
632     * @param device
633     * @param prevChannelState
634     * @param newChannelState
635     * @hide
636     */
637    private void broadcastHealthDeviceStateChange(BluetoothDevice device, int newChannelState) {
638        if (mHealthDevices.get(device) == null) {
639            mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
640        }
641
642        int currDeviceState = mHealthDevices.get(device);
643        int newDeviceState = convertState(newChannelState);
644
645        if (currDeviceState == newDeviceState) return;
646
647        boolean sendIntent = false;
648        List<HealthChannel> chan;
649        switch (currDeviceState) {
650            case BluetoothHealth.STATE_DISCONNECTED:
651                // there was no connection or connect/disconnect attemp with the remote device
652                sendIntent = true;
653                break;
654            case BluetoothHealth.STATE_CONNECTING:
655                // there was no connection, there was a connecting attempt going on
656
657                // Channel got connected.
658                if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
659                    sendIntent = true;
660                } else {
661                    // Channel got disconnected
662                    chan = findChannelByStates(device, new int [] {
663                            BluetoothHealth.STATE_CHANNEL_CONNECTING,
664                            BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
665                    if (chan.isEmpty()) {
666                        sendIntent = true;
667                    }
668                }
669                break;
670            case BluetoothHealth.STATE_CONNECTED:
671                // there was at least one connection
672
673                // Channel got disconnected or is in disconnecting state.
674                chan = findChannelByStates(device, new int [] {
675                        BluetoothHealth.STATE_CHANNEL_CONNECTING,
676                        BluetoothHealth.STATE_CHANNEL_CONNECTED});
677                if (chan.isEmpty()) {
678                    sendIntent = true;
679                }
680                break;
681            case BluetoothHealth.STATE_DISCONNECTING:
682                // there was no connected channel with the remote device
683                // We were disconnecting all the channels with the remote device
684
685                // Channel got disconnected.
686                chan = findChannelByStates(device, new int [] {
687                        BluetoothHealth.STATE_CHANNEL_CONNECTING,
688                        BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
689                if (chan.isEmpty()) {
690                    updateAndSendIntent(device, newDeviceState, currDeviceState);
691                }
692                break;
693        }
694        if (sendIntent)
695            updateAndSendIntent(device, newDeviceState, currDeviceState);
696    }
697
698    private void updateAndSendIntent(BluetoothDevice device, int newDeviceState,
699            int prevDeviceState) {
700        if (newDeviceState == BluetoothHealth.STATE_DISCONNECTED) {
701            mHealthDevices.remove(device);
702        } else {
703            mHealthDevices.put(device, newDeviceState);
704        }
705        notifyProfileConnectionStateChanged(device, BluetoothProfile.HEALTH, newDeviceState, prevDeviceState);
706    }
707
708    /**
709     * This function converts the channel connection state to device connection state.
710     *
711     * @param state
712     * @return
713     */
714    private int convertState(int state) {
715        switch (state) {
716            case BluetoothHealth.STATE_CHANNEL_CONNECTED:
717                return BluetoothHealth.STATE_CONNECTED;
718            case BluetoothHealth.STATE_CHANNEL_CONNECTING:
719                return BluetoothHealth.STATE_CONNECTING;
720            case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
721                return BluetoothHealth.STATE_DISCONNECTING;
722            case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
723                return BluetoothHealth.STATE_DISCONNECTED;
724        }
725        Log.e(TAG, "Mismatch in Channel and Health Device State: " + state);
726        return BluetoothHealth.STATE_DISCONNECTED;
727    }
728
729    private int convertRoleToHal(int role) {
730        if (role == BluetoothHealth.SOURCE_ROLE) return MDEP_ROLE_SOURCE;
731        if (role == BluetoothHealth.SINK_ROLE) return MDEP_ROLE_SINK;
732        Log.e(TAG, "unkonw role: " + role);
733        return MDEP_ROLE_SINK;
734    }
735
736    private int convertChannelTypeToHal(int channelType) {
737        if (channelType == BluetoothHealth.CHANNEL_TYPE_RELIABLE) return CHANNEL_TYPE_RELIABLE;
738        if (channelType == BluetoothHealth.CHANNEL_TYPE_STREAMING) return CHANNEL_TYPE_STREAMING;
739        if (channelType == BluetoothHealth.CHANNEL_TYPE_ANY) return CHANNEL_TYPE_ANY;
740        Log.e(TAG, "unkonw channel type: " + channelType);
741        return CHANNEL_TYPE_ANY;
742    }
743
744    private HealthChannel findChannelById(int id) {
745        for (HealthChannel chan : mHealthChannels) {
746            if (chan.mChannelId == id) return chan;
747        }
748        Log.e(TAG, "No channel found by id: " + id);
749        return null;
750    }
751
752    private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
753        List<HealthChannel> channels = new ArrayList<HealthChannel>();
754        for (HealthChannel chan: mHealthChannels) {
755            if (chan.mDevice.equals(device)) {
756                for (int state : states) {
757                    if (chan.mState == state) {
758                        channels.add(chan);
759                    }
760                }
761            }
762        }
763        return channels;
764    }
765
766    private int getConnectionState(BluetoothDevice device) {
767        if (mHealthDevices.get(device) == null) {
768            return BluetoothHealth.STATE_DISCONNECTED;
769        }
770        return mHealthDevices.get(device);
771    }
772
773    List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
774        List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
775
776        for (BluetoothDevice device: mHealthDevices.keySet()) {
777            int healthDeviceState = getConnectionState(device);
778            for (int state : states) {
779                if (state == healthDeviceState) {
780                    healthDevices.add(device);
781                    break;
782                }
783            }
784        }
785        return healthDevices;
786    }
787
788    private static class AppInfo {
789        private IBluetoothHealthCallback mCallback;
790        private BluetoothHealthDeathRecipient mRcpObj;
791        private int mAppId;
792
793        private AppInfo(IBluetoothHealthCallback callback) {
794            mCallback = callback;
795            mRcpObj = null;
796            mAppId = -1;
797        }
798
799        private void cleanup(){
800            if(mCallback != null){
801                if(mRcpObj != null){
802                    IBinder binder = mCallback.asBinder();
803                    try{
804                        binder.unlinkToDeath(mRcpObj,0);
805                    }catch(NoSuchElementException e){
806                        Log.e(TAG,"No death recipient registered"+e);
807                    }
808                    mRcpObj.cleanup();
809                    mRcpObj = null;
810                }
811                mCallback = null;
812            }
813            else if(mRcpObj != null){
814                    mRcpObj.cleanup();
815                    mRcpObj = null;
816            }
817       }
818    }
819
820    private class HealthChannel {
821        private ParcelFileDescriptor mChannelFd;
822        private BluetoothDevice mDevice;
823        private BluetoothHealthAppConfiguration mConfig;
824        // BluetoothHealth channel state
825        private int mState;
826        private int mChannelType;
827        private int mChannelId;
828
829        private HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
830                      int channelType) {
831             mChannelFd = null;
832             mDevice = device;
833             mConfig = config;
834             mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
835             mChannelType = channelType;
836             mChannelId = -1;
837        }
838    }
839
840    // Channel state event from Hal
841    private class ChannelStateEvent {
842        int mAppId;
843        byte[] mAddr;
844        int mCfgIndex;
845        int mChannelId;
846        int mState;
847        FileDescriptor mFd;
848
849        private ChannelStateEvent(int appId, byte[] addr, int cfgIndex,
850                                  int channelId, int state, FileDescriptor fileDescriptor) {
851            mAppId = appId;
852            mAddr = addr;
853            mCfgIndex = cfgIndex;
854            mState = state;
855            mChannelId = channelId;
856            mFd = fileDescriptor;
857        }
858    }
859
860    // Constants matching Hal header file bt_hl.h
861    // bthl_app_reg_state_t
862    private static final int APP_REG_STATE_REG_SUCCESS = 0;
863    private static final int APP_REG_STATE_REG_FAILED = 1;
864    private static final int APP_REG_STATE_DEREG_SUCCESS = 2;
865    private static final int APP_REG_STATE_DEREG_FAILED = 3;
866
867    // bthl_channel_state_t
868    private static final int CONN_STATE_CONNECTING = 0;
869    private static final int CONN_STATE_CONNECTED = 1;
870    private static final int CONN_STATE_DISCONNECTING = 2;
871    private static final int CONN_STATE_DISCONNECTED = 3;
872    private static final int CONN_STATE_DESTROYED = 4;
873
874    // bthl_mdep_role_t
875    private static final int MDEP_ROLE_SOURCE = 0;
876    private static final int MDEP_ROLE_SINK = 1;
877
878    // bthl_channel_type_t
879    private static final int CHANNEL_TYPE_RELIABLE = 0;
880    private static final int CHANNEL_TYPE_STREAMING = 1;
881    private static final int CHANNEL_TYPE_ANY =2;
882
883    private native static void classInitNative();
884    private native void initializeNative();
885    private native void cleanupNative();
886    private native int registerHealthAppNative(int dataType, int role, String name, int channelType);
887    private native boolean unregisterHealthAppNative(int appId);
888    private native int connectChannelNative(byte[] btAddress, int appId);
889    private native boolean disconnectChannelNative(int channelId);
890
891}
892