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