• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         mHandler.removeCallbacksAndMessages(null);
104         Looper looper = mHandler.getLooper();
105         if (looper != null) {
106             looper.quit();
107         }
108         cleanupApps();
109         return true;
110     }
111 
cleanupApps()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     }
cleanup()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 {
HealthServiceMessageHandler(Looper looper)144         private HealthServiceMessageHandler(Looper looper) {
145             super(looper);
146         }
147 
148         @Override
handleMessage(Message msg)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 
BluetoothHealthDeathRecipient(HealthService service, BluetoothHealthAppConfiguration config)297         public BluetoothHealthDeathRecipient(HealthService service, BluetoothHealthAppConfiguration config) {
298             mService = service;
299             mConfig = config;
300         }
301 
binderDied()302         public void binderDied() {
303             if (DBG) Log.d(TAG,"Binder is dead.");
304             mService.unregisterAppConfiguration(mConfig);
305         }
306 
cleanup()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 
BluetoothHealthBinder(HealthService svc)319         public BluetoothHealthBinder(HealthService svc) {
320             mService = svc;
321         }
322 
cleanup()323         public boolean cleanup()  {
324             mService = null;
325             return true;
326         }
327 
getService()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 
registerAppConfiguration(BluetoothHealthAppConfiguration config, IBluetoothHealthCallback callback)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 
unregisterAppConfiguration(BluetoothHealthAppConfiguration config)347         public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
348             HealthService service = getService();
349             if (service == null) return false;
350             return service.unregisterAppConfiguration(config);
351         }
352 
connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config)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 
connectChannelToSink(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelType)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 
disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelId)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 
getMainChannelFd(BluetoothDevice device, BluetoothHealthAppConfiguration config)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 
getHealthDeviceConnectionState(BluetoothDevice device)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 
getConnectedHealthDevices()387         public List<BluetoothDevice> getConnectedHealthDevices() {
388             HealthService service = getService();
389             if (service == null) return new ArrayList<BluetoothDevice> (0);
390             return service.getConnectedHealthDevices();
391         }
392 
getHealthDevicesMatchingConnectionStates(int[] states)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 
registerAppConfiguration(BluetoothHealthAppConfiguration config, IBluetoothHealthCallback callback)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 
unregisterAppConfiguration(BluetoothHealthAppConfiguration config)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 
connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config)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 
connectChannelToSink(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelType)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 
disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelId)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 
getMainChannelFd(BluetoothDevice device, BluetoothHealthAppConfiguration config)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 
getHealthDeviceConnectionState(BluetoothDevice device)466     int getHealthDeviceConnectionState(BluetoothDevice device) {
467         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
468         return getConnectionState(device);
469     }
470 
getConnectedHealthDevices()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 
getHealthDevicesMatchingConnectionStates(int[] states)478     List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
479         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
480         List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
481         return devices;
482     }
483 
onAppRegistrationState(int appId, int state)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 
onChannelStateChanged(int appId, byte[] addr, int cfgIndex, int channelId, int state, FileDescriptor pfd)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 
getStringChannelType(int type)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 
callStatusCallback(BluetoothHealthAppConfiguration config, int status)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 
findAppConfigByAppId(int appId)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 
convertHalRegStatus(int halRegStatus)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 
convertHalChannelState(int halChannelState)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 
connectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelType)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 
callHealthChannelCallback(BluetoothHealthAppConfiguration config, BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id)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      */
broadcastHealthDeviceStateChange(BluetoothDevice device, int newChannelState)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 
updateAndSendIntent(BluetoothDevice device, int newDeviceState, int prevDeviceState)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      */
convertState(int state)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 
convertRoleToHal(int role)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 
convertChannelTypeToHal(int channelType)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 
findChannelById(int id)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 
findChannelByStates(BluetoothDevice device, int[] states)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 
getConnectionState(BluetoothDevice device)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 
lookupHealthDevicesMatchingStates(int[] states)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 
AppInfo(IBluetoothHealthCallback callback)793         private AppInfo(IBluetoothHealthCallback callback) {
794             mCallback = callback;
795             mRcpObj = null;
796             mAppId = -1;
797         }
798 
cleanup()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 
HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelType)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 
ChannelStateEvent(int appId, byte[] addr, int cfgIndex, int channelId, int state, FileDescriptor fileDescriptor)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 
classInitNative()883     private native static void classInitNative();
initializeNative()884     private native void initializeNative();
cleanupNative()885     private native void cleanupNative();
registerHealthAppNative(int dataType, int role, String name, int channelType)886     private native int registerHealthAppNative(int dataType, int role, String name, int channelType);
unregisterHealthAppNative(int appId)887     private native boolean unregisterHealthAppNative(int appId);
connectChannelNative(byte[] btAddress, int appId)888     private native int connectChannelNative(byte[] btAddress, int appId);
disconnectChannelNative(int channelId)889     private native boolean disconnectChannelNative(int channelId);
890 
891 }
892