• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 android.bluetooth;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.Binder;
24 import android.os.IBinder;
25 import android.os.ParcelFileDescriptor;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * Public API for Bluetooth Health Profile.
34  *
35  * <p>BluetoothHealth is a proxy object for controlling the Bluetooth
36  * Service via IPC.
37  *
38  * <p> How to connect to a health device which is acting in the source role.
39  * <li> Use {@link BluetoothAdapter#getProfileProxy} to get
40  * the BluetoothHealth proxy object. </li>
41  * <li> Create an {@link BluetoothHealth} callback and call
42  * {@link #registerSinkAppConfiguration} to register an application
43  * configuration </li>
44  * <li> Pair with the remote device. This currently needs to be done manually
45  * from Bluetooth Settings </li>
46  * <li> Connect to a health device using {@link #connectChannelToSource}. Some
47  * devices will connect the channel automatically. The {@link BluetoothHealth}
48  * callback will inform the application of channel state change. </li>
49  * <li> Use the file descriptor provided with a connected channel to read and
50  * write data to the health channel. </li>
51  * <li> The received data needs to be interpreted using a health manager which
52  * implements the IEEE 11073-xxxxx specifications.
53  * <li> When done, close the health channel by calling {@link #disconnectChannel}
54  * and unregister the application configuration calling
55  * {@link #unregisterAppConfiguration}
56  */
57 public final class BluetoothHealth implements BluetoothProfile {
58     private static final String TAG = "BluetoothHealth";
59     private static final boolean DBG = true;
60     private static final boolean VDBG = false;
61 
62     /**
63      * Health Profile Source Role - the health device.
64      */
65     public static final int SOURCE_ROLE = 1 << 0;
66 
67     /**
68      * Health Profile Sink Role the device talking to the health device.
69      */
70     public static final int SINK_ROLE = 1 << 1;
71 
72     /**
73      * Health Profile - Channel Type used - Reliable
74      */
75     public static final int CHANNEL_TYPE_RELIABLE = 10;
76 
77     /**
78      * Health Profile - Channel Type used - Streaming
79      */
80     public static final int CHANNEL_TYPE_STREAMING = 11;
81 
82     /**
83      * @hide
84      */
85     public static final int CHANNEL_TYPE_ANY = 12;
86 
87     /** @hide */
88     public static final int HEALTH_OPERATION_SUCCESS = 6000;
89     /** @hide */
90     public static final int HEALTH_OPERATION_ERROR = 6001;
91     /** @hide */
92     public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
93     /** @hide */
94     public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
95     /** @hide */
96     public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
97     /** @hide */
98     public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
99 
100     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
101             new IBluetoothStateChangeCallback.Stub() {
102                 public void onBluetoothStateChange(boolean up) {
103                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
104                     if (!up) {
105                         if (VDBG) Log.d(TAG, "Unbinding service...");
106                         synchronized (mConnection) {
107                             try {
108                                 mService = null;
109                                 mContext.unbindService(mConnection);
110                             } catch (Exception re) {
111                                 Log.e(TAG, "", re);
112                             }
113                         }
114                     } else {
115                         synchronized (mConnection) {
116                             try {
117                                 if (mService == null) {
118                                     if (VDBG) Log.d(TAG, "Binding service...");
119                                     doBind();
120                                 }
121                             } catch (Exception re) {
122                                 Log.e(TAG, "", re);
123                             }
124                         }
125                     }
126                 }
127             };
128 
129 
130     /**
131      * Register an application configuration that acts as a Health SINK.
132      * This is the configuration that will be used to communicate with health devices
133      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
134      * the callback is used to notify success or failure if the function returns true.
135      *
136      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
137      *
138      * @param name The friendly name associated with the application or configuration.
139      * @param dataType The dataType of the Source role of Health Profile to which the sink wants to
140      * connect to.
141      * @param callback A callback to indicate success or failure of the registration and all
142      * operations done on this application configuration.
143      * @return If true, callback will be called.
144      */
registerSinkAppConfiguration(String name, int dataType, BluetoothHealthCallback callback)145     public boolean registerSinkAppConfiguration(String name, int dataType,
146             BluetoothHealthCallback callback) {
147         if (!isEnabled() || name == null) return false;
148 
149         if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
150         return registerAppConfiguration(name, dataType, SINK_ROLE,
151                 CHANNEL_TYPE_ANY, callback);
152     }
153 
154     /**
155      * Register an application configuration that acts as a Health SINK or in a Health
156      * SOURCE role.This is an asynchronous call and so
157      * the callback is used to notify success or failure if the function returns true.
158      *
159      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
160      *
161      * @param name The friendly name associated with the application or configuration.
162      * @param dataType The dataType of the Source role of Health Profile.
163      * @param channelType The channel type. Will be one of {@link #CHANNEL_TYPE_RELIABLE}  or {@link
164      * #CHANNEL_TYPE_STREAMING}
165      * @param callback - A callback to indicate success or failure.
166      * @return If true, callback will be called.
167      * @hide
168      */
registerAppConfiguration(String name, int dataType, int role, int channelType, BluetoothHealthCallback callback)169     public boolean registerAppConfiguration(String name, int dataType, int role,
170             int channelType, BluetoothHealthCallback callback) {
171         boolean result = false;
172         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
173 
174         if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
175         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
176         BluetoothHealthAppConfiguration config =
177                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
178 
179         final IBluetoothHealth service = mService;
180         if (service != null) {
181             try {
182                 result = service.registerAppConfiguration(config, wrapper);
183             } catch (RemoteException e) {
184                 Log.e(TAG, e.toString());
185             }
186         } else {
187             Log.w(TAG, "Proxy not attached to service");
188             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
189         }
190         return result;
191     }
192 
193     /**
194      * Unregister an application configuration that has been registered using
195      * {@link #registerSinkAppConfiguration}
196      *
197      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
198      *
199      * @param config The health app configuration
200      * @return Success or failure.
201      */
unregisterAppConfiguration(BluetoothHealthAppConfiguration config)202     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
203         boolean result = false;
204         final IBluetoothHealth service = mService;
205         if (service != null && isEnabled() && config != null) {
206             try {
207                 result = service.unregisterAppConfiguration(config);
208             } catch (RemoteException e) {
209                 Log.e(TAG, e.toString());
210             }
211         } else {
212             Log.w(TAG, "Proxy not attached to service");
213             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
214         }
215 
216         return result;
217     }
218 
219     /**
220      * Connect to a health device which has the {@link #SOURCE_ROLE}.
221      * This is an asynchronous call. If this function returns true, the callback
222      * associated with the application configuration will be called.
223      *
224      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
225      *
226      * @param device The remote Bluetooth device.
227      * @param config The application configuration which has been registered using {@link
228      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
229      * @return If true, the callback associated with the application config will be called.
230      */
connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config)231     public boolean connectChannelToSource(BluetoothDevice device,
232             BluetoothHealthAppConfiguration config) {
233         final IBluetoothHealth service = mService;
234         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
235             try {
236                 return service.connectChannelToSource(device, config);
237             } catch (RemoteException e) {
238                 Log.e(TAG, e.toString());
239             }
240         } else {
241             Log.w(TAG, "Proxy not attached to service");
242             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
243         }
244         return false;
245     }
246 
247     /**
248      * Connect to a health device which has the {@link #SINK_ROLE}.
249      * This is an asynchronous call. If this function returns true, the callback
250      * associated with the application configuration will be called.
251      *
252      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
253      *
254      * @param device The remote Bluetooth device.
255      * @param config The application configuration which has been registered using {@link
256      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
257      * @return If true, the callback associated with the application config will be called.
258      * @hide
259      */
connectChannelToSink(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelType)260     public boolean connectChannelToSink(BluetoothDevice device,
261             BluetoothHealthAppConfiguration config, int channelType) {
262         final IBluetoothHealth service = mService;
263         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
264             try {
265                 return service.connectChannelToSink(device, config, channelType);
266             } catch (RemoteException e) {
267                 Log.e(TAG, e.toString());
268             }
269         } else {
270             Log.w(TAG, "Proxy not attached to service");
271             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
272         }
273         return false;
274     }
275 
276     /**
277      * Disconnect a connected health channel.
278      * This is an asynchronous call. If this function returns true, the callback
279      * associated with the application configuration will be called.
280      *
281      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
282      *
283      * @param device The remote Bluetooth device.
284      * @param config The application configuration which has been registered using {@link
285      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
286      * @param channelId The channel id associated with the channel
287      * @return If true, the callback associated with the application config will be called.
288      */
disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelId)289     public boolean disconnectChannel(BluetoothDevice device,
290             BluetoothHealthAppConfiguration config, int channelId) {
291         final IBluetoothHealth service = mService;
292         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
293             try {
294                 return service.disconnectChannel(device, config, channelId);
295             } catch (RemoteException e) {
296                 Log.e(TAG, e.toString());
297             }
298         } else {
299             Log.w(TAG, "Proxy not attached to service");
300             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
301         }
302         return false;
303     }
304 
305     /**
306      * Get the file descriptor of the main channel associated with the remote device
307      * and application configuration.
308      *
309      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
310      *
311      * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
312      * when done.
313      *
314      * @param device The remote Bluetooth health device
315      * @param config The application configuration
316      * @return null on failure, ParcelFileDescriptor on success.
317      */
getMainChannelFd(BluetoothDevice device, BluetoothHealthAppConfiguration config)318     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
319             BluetoothHealthAppConfiguration config) {
320         final IBluetoothHealth service = mService;
321         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
322             try {
323                 return service.getMainChannelFd(device, config);
324             } catch (RemoteException e) {
325                 Log.e(TAG, e.toString());
326             }
327         } else {
328             Log.w(TAG, "Proxy not attached to service");
329             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
330         }
331         return null;
332     }
333 
334     /**
335      * Get the current connection state of the profile.
336      *
337      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
338      *
339      * This is not specific to any application configuration but represents the connection
340      * state of the local Bluetooth adapter with the remote device. This can be used
341      * by applications like status bar which would just like to know the state of the
342      * local adapter.
343      *
344      * @param device Remote bluetooth device.
345      * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link
346      * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
347      */
348     @Override
getConnectionState(BluetoothDevice device)349     public int getConnectionState(BluetoothDevice device) {
350         final IBluetoothHealth service = mService;
351         if (service != null && isEnabled() && isValidDevice(device)) {
352             try {
353                 return service.getHealthDeviceConnectionState(device);
354             } catch (RemoteException e) {
355                 Log.e(TAG, e.toString());
356             }
357         } else {
358             Log.w(TAG, "Proxy not attached to service");
359             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
360         }
361         return STATE_DISCONNECTED;
362     }
363 
364     /**
365      * Get connected devices for the health profile.
366      *
367      * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
368      *
369      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
370      *
371      * This is not specific to any application configuration but represents the connection
372      * state of the local Bluetooth adapter for this profile. This can be used
373      * by applications like status bar which would just like to know the state of the
374      * local adapter.
375      *
376      * @return List of devices. The list will be empty on error.
377      */
378     @Override
getConnectedDevices()379     public List<BluetoothDevice> getConnectedDevices() {
380         final IBluetoothHealth service = mService;
381         if (service != null && isEnabled()) {
382             try {
383                 return service.getConnectedHealthDevices();
384             } catch (RemoteException e) {
385                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
386                 return new ArrayList<BluetoothDevice>();
387             }
388         }
389         if (service == null) Log.w(TAG, "Proxy not attached to service");
390         return new ArrayList<BluetoothDevice>();
391     }
392 
393     /**
394      * Get a list of devices that match any of the given connection
395      * states.
396      *
397      * <p> If none of the devices match any of the given states,
398      * an empty list will be returned.
399      *
400      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
401      * This is not specific to any application configuration but represents the connection
402      * state of the local Bluetooth adapter for this profile. This can be used
403      * by applications like status bar which would just like to know the state of the
404      * local adapter.
405      *
406      * @param states Array of states. States can be one of {@link #STATE_CONNECTED}, {@link
407      * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
408      * @return List of devices. The list will be empty on error.
409      */
410     @Override
getDevicesMatchingConnectionStates(int[] states)411     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
412         final IBluetoothHealth service = mService;
413         if (service != null && isEnabled()) {
414             try {
415                 return service.getHealthDevicesMatchingConnectionStates(states);
416             } catch (RemoteException e) {
417                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
418                 return new ArrayList<BluetoothDevice>();
419             }
420         }
421         if (service == null) Log.w(TAG, "Proxy not attached to service");
422         return new ArrayList<BluetoothDevice>();
423     }
424 
425     private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
426         private BluetoothHealthCallback mCallback;
427 
BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback)428         public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
429             mCallback = callback;
430         }
431 
432         @Override
onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, int status)433         public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
434                 int status) {
435             mCallback.onHealthAppConfigurationStatusChange(config, status);
436         }
437 
438         @Override
onHealthChannelStateChange(BluetoothHealthAppConfiguration config, BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd, int channelId)439         public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
440                 BluetoothDevice device, int prevState, int newState,
441                 ParcelFileDescriptor fd, int channelId) {
442             mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
443                     channelId);
444         }
445     }
446 
447     /** Health Channel Connection State - Disconnected */
448     public static final int STATE_CHANNEL_DISCONNECTED = 0;
449     /** Health Channel Connection State - Connecting */
450     public static final int STATE_CHANNEL_CONNECTING = 1;
451     /** Health Channel Connection State - Connected */
452     public static final int STATE_CHANNEL_CONNECTED = 2;
453     /** Health Channel Connection State - Disconnecting */
454     public static final int STATE_CHANNEL_DISCONNECTING = 3;
455 
456     /** Health App Configuration registration success */
457     public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
458     /** Health App Configuration registration failure */
459     public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
460     /** Health App Configuration un-registration success */
461     public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
462     /** Health App Configuration un-registration failure */
463     public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
464 
465     private Context mContext;
466     private ServiceListener mServiceListener;
467     private volatile IBluetoothHealth mService;
468     BluetoothAdapter mAdapter;
469 
470     /**
471      * Create a BluetoothHealth proxy object.
472      */
BluetoothHealth(Context context, ServiceListener l)473     /*package*/ BluetoothHealth(Context context, ServiceListener l) {
474         mContext = context;
475         mServiceListener = l;
476         mAdapter = BluetoothAdapter.getDefaultAdapter();
477         IBluetoothManager mgr = mAdapter.getBluetoothManager();
478         if (mgr != null) {
479             try {
480                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
481             } catch (RemoteException e) {
482                 Log.e(TAG, "", e);
483             }
484         }
485 
486         doBind();
487     }
488 
doBind()489     boolean doBind() {
490         Intent intent = new Intent(IBluetoothHealth.class.getName());
491         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
492         intent.setComponent(comp);
493         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
494                 mContext.getUser())) {
495             Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
496             return false;
497         }
498         return true;
499     }
500 
close()501     /*package*/ void close() {
502         if (VDBG) log("close()");
503         IBluetoothManager mgr = mAdapter.getBluetoothManager();
504         if (mgr != null) {
505             try {
506                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
507             } catch (Exception e) {
508                 Log.e(TAG, "", e);
509             }
510         }
511 
512         synchronized (mConnection) {
513             if (mService != null) {
514                 try {
515                     mService = null;
516                     mContext.unbindService(mConnection);
517                 } catch (Exception re) {
518                     Log.e(TAG, "", re);
519                 }
520             }
521         }
522         mServiceListener = null;
523     }
524 
525     private final ServiceConnection mConnection = new ServiceConnection() {
526         public void onServiceConnected(ComponentName className, IBinder service) {
527             if (DBG) Log.d(TAG, "Proxy object connected");
528             mService = IBluetoothHealth.Stub.asInterface(Binder.allowBlocking(service));
529 
530             if (mServiceListener != null) {
531                 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
532             }
533         }
534 
535         public void onServiceDisconnected(ComponentName className) {
536             if (DBG) Log.d(TAG, "Proxy object disconnected");
537             mService = null;
538             if (mServiceListener != null) {
539                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
540             }
541         }
542     };
543 
isEnabled()544     private boolean isEnabled() {
545         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
546 
547         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
548         log("Bluetooth is Not enabled");
549         return false;
550     }
551 
isValidDevice(BluetoothDevice device)552     private static boolean isValidDevice(BluetoothDevice device) {
553         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
554     }
555 
checkAppParam(String name, int role, int channelType, BluetoothHealthCallback callback)556     private boolean checkAppParam(String name, int role, int channelType,
557             BluetoothHealthCallback callback) {
558         if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE)
559                 || (channelType != CHANNEL_TYPE_RELIABLE && channelType != CHANNEL_TYPE_STREAMING
560                     && channelType != CHANNEL_TYPE_ANY)
561                 || callback == null) {
562             return false;
563         }
564         if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
565         return true;
566     }
567 
log(String msg)568     private static void log(String msg) {
569         Log.d(TAG, msg);
570     }
571 }
572