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