• 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                                     if (!mContext.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) {
121                                         Log.e(TAG, "Could not bind to Bluetooth Health Service");
122                                     }
123                                 }
124                             } catch (Exception re) {
125                                 Log.e(TAG,"",re);
126                             }
127                         }
128                     }
129                 }
130         };
131 
132 
133     /**
134      * Register an application configuration that acts as a Health SINK.
135      * This is the configuration that will be used to communicate with health devices
136      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
137      * the callback is used to notify success or failure if the function returns true.
138      *
139      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
140      *
141      * @param name The friendly name associated with the application or configuration.
142      * @param dataType The dataType of the Source role of Health Profile to which
143      *                   the sink wants to connect to.
144      * @param callback A callback to indicate success or failure of the registration and
145      *               all operations done on this application configuration.
146      * @return If true, callback will be called.
147      */
registerSinkAppConfiguration(String name, int dataType, BluetoothHealthCallback callback)148     public boolean registerSinkAppConfiguration(String name, int dataType,
149             BluetoothHealthCallback callback) {
150         if (!isEnabled() || name == null) return false;
151 
152         if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
153         return registerAppConfiguration(name, dataType, SINK_ROLE,
154                 CHANNEL_TYPE_ANY, callback);
155     }
156 
157     /**
158      * Register an application configuration that acts as a Health SINK or in a Health
159      * SOURCE role.This is an asynchronous call and so
160      * the callback is used to notify success or failure if the function returns true.
161      *
162      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
163      *
164      * @param name The friendly name associated with the application or configuration.
165      * @param dataType The dataType of the Source role of Health Profile.
166      * @param channelType The channel type. Will be one of
167      *                              {@link #CHANNEL_TYPE_RELIABLE}  or
168      *                              {@link #CHANNEL_TYPE_STREAMING}
169      * @param callback - A callback to indicate success or failure.
170      * @return If true, callback will be called.
171      * @hide
172      */
registerAppConfiguration(String name, int dataType, int role, int channelType, BluetoothHealthCallback callback)173     public boolean registerAppConfiguration(String name, int dataType, int role,
174             int channelType, BluetoothHealthCallback callback) {
175         boolean result = false;
176         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
177 
178         if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
179         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
180         BluetoothHealthAppConfiguration config =
181                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
182 
183         if (mService != null) {
184             try {
185                 result = mService.registerAppConfiguration(config, wrapper);
186             } catch (RemoteException e) {
187                 Log.e(TAG, e.toString());
188             }
189         } else {
190             Log.w(TAG, "Proxy not attached to service");
191             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
192         }
193         return result;
194     }
195 
196     /**
197      * Unregister an application configuration that has been registered using
198      * {@link #registerSinkAppConfiguration}
199      *
200      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
201      *
202      * @param config  The health app configuration
203      * @return Success or failure.
204      */
unregisterAppConfiguration(BluetoothHealthAppConfiguration config)205     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
206         boolean result = false;
207         if (mService != null && isEnabled() && config != null) {
208             try {
209                 result = mService.unregisterAppConfiguration(config);
210             } catch (RemoteException e) {
211                 Log.e(TAG, e.toString());
212             }
213         } else {
214             Log.w(TAG, "Proxy not attached to service");
215             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
216         }
217 
218         return result;
219     }
220 
221     /**
222      * Connect to a health device which has the {@link #SOURCE_ROLE}.
223      * This is an asynchronous call. If this function returns true, the callback
224      * associated with the application configuration will be called.
225      *
226      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
227      *
228      * @param device The remote Bluetooth device.
229      * @param config The application configuration which has been registered using
230      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
231      * @return If true, the callback associated with the application config will be called.
232      */
connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config)233     public boolean connectChannelToSource(BluetoothDevice device,
234             BluetoothHealthAppConfiguration config) {
235         if (mService != null && isEnabled() && isValidDevice(device) &&
236                 config != null) {
237             try {
238                 return mService.connectChannelToSource(device, config);
239             } catch (RemoteException e) {
240                 Log.e(TAG, e.toString());
241             }
242         } else {
243             Log.w(TAG, "Proxy not attached to service");
244             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
245         }
246         return false;
247     }
248 
249     /**
250      * Connect to a health device which has the {@link #SINK_ROLE}.
251      * This is an asynchronous call. If this function returns true, the callback
252      * associated with the application configuration will be called.
253      *
254      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
255      *
256      * @param device The remote Bluetooth device.
257      * @param config The application configuration which has been registered using
258      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
259      * @return If true, the callback associated with the application config will be called.
260      * @hide
261      */
connectChannelToSink(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelType)262     public boolean connectChannelToSink(BluetoothDevice device,
263             BluetoothHealthAppConfiguration config, int channelType) {
264         if (mService != null && isEnabled() && isValidDevice(device) &&
265                 config != null) {
266             try {
267                 return mService.connectChannelToSink(device, config, channelType);
268             } catch (RemoteException e) {
269                 Log.e(TAG, e.toString());
270             }
271         } else {
272             Log.w(TAG, "Proxy not attached to service");
273             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
274         }
275         return false;
276     }
277 
278     /**
279      * Disconnect a connected health channel.
280      * This is an asynchronous call. If this function returns true, the callback
281      * associated with the application configuration will be called.
282      *
283      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
284      *
285      * @param device The remote Bluetooth device.
286      * @param config The application configuration which has been registered using
287      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
288      * @param channelId The channel id associated with the channel
289      * @return If true, the callback associated with the application config will be called.
290      */
disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelId)291     public boolean disconnectChannel(BluetoothDevice device,
292             BluetoothHealthAppConfiguration config, int channelId) {
293         if (mService != null && isEnabled() && isValidDevice(device) &&
294                 config != null) {
295             try {
296                 return mService.disconnectChannel(device, config, channelId);
297             } catch (RemoteException e) {
298                 Log.e(TAG, e.toString());
299             }
300         } else {
301             Log.w(TAG, "Proxy not attached to service");
302             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
303         }
304         return false;
305     }
306 
307     /**
308      * Get the file descriptor of the main channel associated with the remote device
309      * and application configuration.
310      *
311      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
312      *
313      * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
314      * when done.
315      *
316      * @param device The remote Bluetooth health device
317      * @param config The application configuration
318      * @return null on failure, ParcelFileDescriptor on success.
319      */
getMainChannelFd(BluetoothDevice device, BluetoothHealthAppConfiguration config)320     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
321             BluetoothHealthAppConfiguration config) {
322         if (mService != null && isEnabled() && isValidDevice(device) &&
323                 config != null) {
324             try {
325                 return mService.getMainChannelFd(device, config);
326             } catch (RemoteException e) {
327                 Log.e(TAG, e.toString());
328             }
329         } else {
330             Log.w(TAG, "Proxy not attached to service");
331             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
332         }
333         return null;
334     }
335 
336     /**
337      * Get the current connection state of the profile.
338      *
339      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
340      *
341      * This is not specific to any application configuration but represents the connection
342      * state of the local Bluetooth adapter with the remote device. This can be used
343      * by applications like status bar which would just like to know the state of the
344      * local adapter.
345      *
346      * @param device Remote bluetooth device.
347      * @return State of the profile connection. One of
348      *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
349      *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
350      */
351     @Override
getConnectionState(BluetoothDevice device)352     public int getConnectionState(BluetoothDevice device) {
353         if (mService != null && isEnabled() && isValidDevice(device)) {
354             try {
355                 return mService.getHealthDeviceConnectionState(device);
356             } catch (RemoteException e) {
357                 Log.e(TAG, e.toString());
358             }
359         } else {
360             Log.w(TAG, "Proxy not attached to service");
361             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
362         }
363         return STATE_DISCONNECTED;
364     }
365 
366     /**
367      * Get connected devices for the health profile.
368      *
369      * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
370      *
371      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
372      *
373      * This is not specific to any application configuration but represents the connection
374      * state of the local Bluetooth adapter for this profile. This can be used
375      * by applications like status bar which would just like to know the state of the
376      * local adapter.
377      * @return List of devices. The list will be empty on error.
378      */
379     @Override
getConnectedDevices()380     public List<BluetoothDevice> getConnectedDevices() {
381         if (mService != null && isEnabled()) {
382             try {
383                 return mService.getConnectedHealthDevices();
384             } catch (RemoteException e) {
385                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
386                 return new ArrayList<BluetoothDevice>();
387             }
388         }
389         if (mService == 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
407      *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
408      *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
409      * @return List of devices. The list will be empty on error.
410      */
411     @Override
getDevicesMatchingConnectionStates(int[] states)412     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
413         if (mService != null && isEnabled()) {
414             try {
415                 return mService.getHealthDevicesMatchingConnectionStates(states);
416             } catch (RemoteException e) {
417                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
418                 return new ArrayList<BluetoothDevice>();
419             }
420         }
421         if (mService == 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 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         if (!context.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) {
487             Log.e(TAG, "Could not bind to Bluetooth Health Service");
488         }
489     }
490 
close()491     /*package*/ void close() {
492         if (VDBG) log("close()");
493         IBluetoothManager mgr = mAdapter.getBluetoothManager();
494         if (mgr != null) {
495             try {
496                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
497             } catch (Exception e) {
498                 Log.e(TAG,"",e);
499             }
500         }
501 
502         synchronized (mConnection) {
503             if (mService != null) {
504                 try {
505                     mService = null;
506                     mContext.unbindService(mConnection);
507                 } catch (Exception re) {
508                     Log.e(TAG,"",re);
509                 }
510             }
511         }
512         mServiceListener = null;
513     }
514 
515     private ServiceConnection mConnection = new ServiceConnection() {
516         public void onServiceConnected(ComponentName className, IBinder service) {
517             if (DBG) Log.d(TAG, "Proxy object connected");
518             mService = IBluetoothHealth.Stub.asInterface(service);
519 
520             if (mServiceListener != null) {
521                 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
522             }
523         }
524         public void onServiceDisconnected(ComponentName className) {
525             if (DBG) Log.d(TAG, "Proxy object disconnected");
526             mService = null;
527             if (mServiceListener != null) {
528                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
529             }
530         }
531     };
532 
isEnabled()533     private boolean isEnabled() {
534         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
535 
536         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
537         log("Bluetooth is Not enabled");
538         return false;
539     }
540 
isValidDevice(BluetoothDevice device)541     private boolean isValidDevice(BluetoothDevice device) {
542         if (device == null) return false;
543 
544         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
545         return false;
546     }
547 
checkAppParam(String name, int role, int channelType, BluetoothHealthCallback callback)548     private boolean checkAppParam(String name, int role, int channelType,
549             BluetoothHealthCallback callback) {
550         if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
551                 (channelType != CHANNEL_TYPE_RELIABLE &&
552                 channelType != CHANNEL_TYPE_STREAMING &&
553                 channelType != CHANNEL_TYPE_ANY) || callback == null) {
554             return false;
555         }
556         if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
557         return true;
558     }
559 
log(String msg)560     private static void log(String msg) {
561         Log.d(TAG, msg);
562     }
563 }
564