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