• 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.IBinder;
24 import android.os.ParcelFileDescriptor;
25 import android.os.RemoteException;
26 import android.os.ServiceManager;
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  */
58 public final class BluetoothHealth implements BluetoothProfile {
59     private static final String TAG = "BluetoothHealth";
60     private static final boolean DBG = true;
61     private static final boolean VDBG = false;
62 
63     /**
64      * Health Profile Source Role - the health device.
65      */
66     public static final int SOURCE_ROLE = 1 << 0;
67 
68     /**
69      * Health Profile Sink Role the device talking to the health device.
70      */
71     public static final int SINK_ROLE = 1 << 1;
72 
73     /**
74      * Health Profile - Channel Type used - Reliable
75      */
76     public static final int CHANNEL_TYPE_RELIABLE = 10;
77 
78     /**
79      * Health Profile - Channel Type used - Streaming
80      */
81     public static final int CHANNEL_TYPE_STREAMING = 11;
82 
83     /**
84      * @hide
85      */
86     public static final int CHANNEL_TYPE_ANY = 12;
87 
88     /** @hide */
89     public static final int HEALTH_OPERATION_SUCCESS = 6000;
90     /** @hide */
91     public static final int HEALTH_OPERATION_ERROR = 6001;
92     /** @hide */
93     public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
94     /** @hide */
95     public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
96     /** @hide */
97     public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
98     /** @hide */
99     public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
100 
101     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
102             new IBluetoothStateChangeCallback.Stub() {
103                 public void onBluetoothStateChange(boolean up) {
104                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
105                     if (!up) {
106                         if (VDBG) Log.d(TAG,"Unbinding service...");
107                         synchronized (mConnection) {
108                             try {
109                                 mService = null;
110                                 mContext.unbindService(mConnection);
111                             } catch (Exception re) {
112                                 Log.e(TAG,"",re);
113                             }
114                         }
115                     } else {
116                         synchronized (mConnection) {
117                             try {
118                                 if (mService == null) {
119                                     if (VDBG) Log.d(TAG,"Binding service...");
120                                     doBind();
121                                 }
122                             } catch (Exception re) {
123                                 Log.e(TAG,"",re);
124                             }
125                         }
126                     }
127                 }
128         };
129 
130 
131     /**
132      * Register an application configuration that acts as a Health SINK.
133      * This is the configuration that will be used to communicate with health devices
134      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
135      * the callback is used to notify success or failure if the function returns true.
136      *
137      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
138      *
139      * @param name The friendly name associated with the application or configuration.
140      * @param dataType The dataType of the Source role of Health Profile to which
141      *                   the sink wants to connect to.
142      * @param callback A callback to indicate success or failure of the registration and
143      *               all operations done on this application configuration.
144      * @return If true, callback will be called.
145      */
registerSinkAppConfiguration(String name, int dataType, BluetoothHealthCallback callback)146     public boolean registerSinkAppConfiguration(String name, int dataType,
147             BluetoothHealthCallback callback) {
148         if (!isEnabled() || name == null) return false;
149 
150         if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
151         return registerAppConfiguration(name, dataType, SINK_ROLE,
152                 CHANNEL_TYPE_ANY, callback);
153     }
154 
155     /**
156      * Register an application configuration that acts as a Health SINK or in a Health
157      * SOURCE role.This is an asynchronous call and so
158      * the callback is used to notify success or failure if the function returns true.
159      *
160      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
161      *
162      * @param name The friendly name associated with the application or configuration.
163      * @param dataType The dataType of the Source role of Health Profile.
164      * @param channelType The channel type. Will be one of
165      *                              {@link #CHANNEL_TYPE_RELIABLE}  or
166      *                              {@link #CHANNEL_TYPE_STREAMING}
167      * @param callback - A callback to indicate success or failure.
168      * @return If true, callback will be called.
169      * @hide
170      */
registerAppConfiguration(String name, int dataType, int role, int channelType, BluetoothHealthCallback callback)171     public boolean registerAppConfiguration(String name, int dataType, int role,
172             int channelType, BluetoothHealthCallback callback) {
173         boolean result = false;
174         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
175 
176         if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
177         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
178         BluetoothHealthAppConfiguration config =
179                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
180 
181         if (mService != null) {
182             try {
183                 result = mService.registerAppConfiguration(config, wrapper);
184             } catch (RemoteException e) {
185                 Log.e(TAG, e.toString());
186             }
187         } else {
188             Log.w(TAG, "Proxy not attached to service");
189             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
190         }
191         return result;
192     }
193 
194     /**
195      * Unregister an application configuration that has been registered using
196      * {@link #registerSinkAppConfiguration}
197      *
198      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
199      *
200      * @param config  The health app configuration
201      * @return Success or failure.
202      */
unregisterAppConfiguration(BluetoothHealthAppConfiguration config)203     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
204         boolean result = false;
205         if (mService != null && isEnabled() && config != null) {
206             try {
207                 result = mService.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
228      *        {@link #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         if (mService != null && isEnabled() && isValidDevice(device) &&
234                 config != null) {
235             try {
236                 return mService.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
256      *        {@link #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         if (mService != null && isEnabled() && isValidDevice(device) &&
263                 config != null) {
264             try {
265                 return mService.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
285      *        {@link #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         if (mService != null && isEnabled() && isValidDevice(device) &&
292                 config != null) {
293             try {
294                 return mService.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         if (mService != null && isEnabled() && isValidDevice(device) &&
321                 config != null) {
322             try {
323                 return mService.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
346      *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
347      *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
348      */
349     @Override
getConnectionState(BluetoothDevice device)350     public int getConnectionState(BluetoothDevice device) {
351         if (mService != null && isEnabled() && isValidDevice(device)) {
352             try {
353                 return mService.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      * @return List of devices. The list will be empty on error.
376      */
377     @Override
getConnectedDevices()378     public List<BluetoothDevice> getConnectedDevices() {
379         if (mService != null && isEnabled()) {
380             try {
381                 return mService.getConnectedHealthDevices();
382             } catch (RemoteException e) {
383                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
384                 return new ArrayList<BluetoothDevice>();
385             }
386         }
387         if (mService == null) Log.w(TAG, "Proxy not attached to service");
388         return new ArrayList<BluetoothDevice>();
389     }
390 
391     /**
392      * Get a list of devices that match any of the given connection
393      * states.
394      *
395      * <p> If none of the devices match any of the given states,
396      * an empty list will be returned.
397      *
398      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
399      * This is not specific to any application configuration but represents the connection
400      * state of the local Bluetooth adapter for this profile. This can be used
401      * by applications like status bar which would just like to know the state of the
402      * local adapter.
403      *
404      * @param states Array of states. States can be one of
405      *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
406      *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
407      * @return List of devices. The list will be empty on error.
408      */
409     @Override
getDevicesMatchingConnectionStates(int[] states)410     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
411         if (mService != null && isEnabled()) {
412             try {
413                 return mService.getHealthDevicesMatchingConnectionStates(states);
414             } catch (RemoteException e) {
415                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
416                 return new ArrayList<BluetoothDevice>();
417             }
418         }
419         if (mService == null) Log.w(TAG, "Proxy not attached to service");
420         return new ArrayList<BluetoothDevice>();
421     }
422 
423     private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
424         private BluetoothHealthCallback mCallback;
425 
BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback)426         public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
427             mCallback = callback;
428         }
429 
430         @Override
onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, int status)431         public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
432                                                          int status) {
433            mCallback.onHealthAppConfigurationStatusChange(config, status);
434         }
435 
436         @Override
onHealthChannelStateChange(BluetoothHealthAppConfiguration config, BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd, int channelId)437         public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
438                                        BluetoothDevice device, int prevState, int newState,
439                                        ParcelFileDescriptor fd, int channelId) {
440             mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
441                                                  channelId);
442         }
443     }
444 
445      /** Health Channel Connection State - Disconnected */
446     public static final int STATE_CHANNEL_DISCONNECTED  = 0;
447     /** Health Channel Connection State - Connecting */
448     public static final int STATE_CHANNEL_CONNECTING    = 1;
449     /** Health Channel Connection State - Connected */
450     public static final int STATE_CHANNEL_CONNECTED     = 2;
451     /** Health Channel Connection State - Disconnecting */
452     public static final int STATE_CHANNEL_DISCONNECTING = 3;
453 
454     /** Health App Configuration registration success */
455     public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
456     /** Health App Configuration registration failure */
457     public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
458     /** Health App Configuration un-registration success */
459     public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
460     /** Health App Configuration un-registration failure */
461     public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
462 
463     private Context mContext;
464     private ServiceListener mServiceListener;
465     private IBluetoothHealth mService;
466     BluetoothAdapter mAdapter;
467 
468     /**
469      * Create a BluetoothHealth proxy object.
470      */
BluetoothHealth(Context context, ServiceListener l)471     /*package*/ BluetoothHealth(Context context, ServiceListener l) {
472         mContext = context;
473         mServiceListener = l;
474         mAdapter = BluetoothAdapter.getDefaultAdapter();
475         IBluetoothManager mgr = mAdapter.getBluetoothManager();
476         if (mgr != null) {
477             try {
478                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
479             } catch (RemoteException e) {
480                 Log.e(TAG,"",e);
481             }
482         }
483 
484         doBind();
485     }
486 
doBind()487     boolean doBind() {
488         Intent intent = new Intent(IBluetoothHealth.class.getName());
489         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
490         intent.setComponent(comp);
491         if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
492             Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
493             return false;
494         }
495         return true;
496     }
497 
close()498     /*package*/ void close() {
499         if (VDBG) log("close()");
500         IBluetoothManager mgr = mAdapter.getBluetoothManager();
501         if (mgr != null) {
502             try {
503                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
504             } catch (Exception e) {
505                 Log.e(TAG,"",e);
506             }
507         }
508 
509         synchronized (mConnection) {
510             if (mService != null) {
511                 try {
512                     mService = null;
513                     mContext.unbindService(mConnection);
514                 } catch (Exception re) {
515                     Log.e(TAG,"",re);
516                 }
517             }
518         }
519         mServiceListener = null;
520     }
521 
522     private final ServiceConnection mConnection = new ServiceConnection() {
523         public void onServiceConnected(ComponentName className, IBinder service) {
524             if (DBG) Log.d(TAG, "Proxy object connected");
525             mService = IBluetoothHealth.Stub.asInterface(service);
526 
527             if (mServiceListener != null) {
528                 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
529             }
530         }
531         public void onServiceDisconnected(ComponentName className) {
532             if (DBG) Log.d(TAG, "Proxy object disconnected");
533             mService = null;
534             if (mServiceListener != null) {
535                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
536             }
537         }
538     };
539 
isEnabled()540     private boolean isEnabled() {
541         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
542 
543         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
544         log("Bluetooth is Not enabled");
545         return false;
546     }
547 
isValidDevice(BluetoothDevice device)548     private boolean isValidDevice(BluetoothDevice device) {
549         if (device == null) return false;
550 
551         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
552         return false;
553     }
554 
checkAppParam(String name, int role, int channelType, BluetoothHealthCallback callback)555     private boolean checkAppParam(String name, int role, int channelType,
556             BluetoothHealthCallback callback) {
557         if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
558                 (channelType != CHANNEL_TYPE_RELIABLE &&
559                 channelType != CHANNEL_TYPE_STREAMING &&
560                 channelType != CHANNEL_TYPE_ANY) || callback == null) {
561             return false;
562         }
563         if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
564         return true;
565     }
566 
log(String msg)567     private static void log(String msg) {
568         Log.d(TAG, msg);
569     }
570 }
571