blob: c165d9231d62e66013e7f713d4bdfc8441a4cb02 [file] [log] [blame]
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -07001/*
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
17package android.bluetooth;
18
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -070019import android.content.Context;
20import android.os.IBinder;
21import android.os.ParcelFileDescriptor;
22import android.os.RemoteException;
23import android.os.ServiceManager;
24import android.util.Log;
25
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * Public API for Bluetooth Health Profile.
31 *
32 * <p>BluetoothHealth is a proxy object for controlling the Bluetooth
33 * Service via IPC.
34 *
Jaikumar Ganesh090847e2011-08-31 15:36:05 -070035 * <p> How to connect to a health device which is acting in the source role.
36 * <li> Use {@link BluetoothAdapter#getProfileProxy} to get
37 * the BluetoothHealth proxy object. </li>
38 * <li> Create an {@link BluetoothHealth} callback and call
39 * {@link #registerSinkAppConfiguration} to register an application
40 * configuration </li>
41 * <li> Pair with the remote device. This currently needs to be done manually
42 * from Bluetooth Settings </li>
43 * <li> Connect to a health device using {@link #connectChannelToSource}. Some
44 * devices will connect the channel automatically. The {@link BluetoothHealth}
45 * callback will inform the application of channel state change. </li>
46 * <li> Use the file descriptor provided with a connected channel to read and
47 * write data to the health channel. </li>
48 * <li> The received data needs to be interpreted using a health manager which
49 * implements the IEEE 11073-xxxxx specifications.
50 * <li> When done, close the health channel by calling {@link #disconnectChannel}
51 * and unregister the application configuration calling
52 * {@link #unregisterAppConfiguration}
53 *
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -070054 */
55public final class BluetoothHealth implements BluetoothProfile {
56 private static final String TAG = "BluetoothHealth";
57 private static final boolean DBG = false;
58
59 /**
60 * Health Profile Source Role - the health device.
61 */
62 public static final int SOURCE_ROLE = 1 << 0;
63
64 /**
65 * Health Profile Sink Role the device talking to the health device.
66 */
67 public static final int SINK_ROLE = 1 << 1;
68
69 /**
70 * Health Profile - Channel Type used - Reliable
71 */
72 public static final int CHANNEL_TYPE_RELIABLE = 10;
73
74 /**
75 * Health Profile - Channel Type used - Streaming
76 */
77 public static final int CHANNEL_TYPE_STREAMING = 11;
78
79 /**
80 * @hide
81 */
82 public static final int CHANNEL_TYPE_ANY = 12;
83
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -070084 /**
85 * Register an application configuration that acts as a Health SINK.
86 * This is the configuration that will be used to communicate with health devices
87 * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
88 * the callback is used to notify success or failure if the function returns true.
89 *
90 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
91 *
92 * @param name The friendly name associated with the application or configuration.
93 * @param dataType The dataType of the Source role of Health Profile to which
94 * the sink wants to connect to.
95 * @param callback A callback to indicate success or failure of the registration and
96 * all operations done on this application configuration.
97 * @return If true, callback will be called.
98 */
99 public boolean registerSinkAppConfiguration(String name, int dataType,
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700100 BluetoothHealthCallback callback) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700101 if (!isEnabled() || name == null) return false;
102
103 if (DBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
104 return registerAppConfiguration(name, dataType, SINK_ROLE,
105 CHANNEL_TYPE_ANY, callback);
106 }
107
108 /**
109 * Register an application configuration that acts as a Health SINK or in a Health
110 * SOURCE role.This is an asynchronous call and so
111 * the callback is used to notify success or failure if the function returns true.
112 *
113 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
114 *
115 * @param name The friendly name associated with the application or configuration.
116 * @param dataType The dataType of the Source role of Health Profile.
117 * @param channelType The channel type. Will be one of
118 * {@link #CHANNEL_TYPE_RELIABLE} or
119 * {@link #CHANNEL_TYPE_STREAMING}
120 * @param callback - A callback to indicate success or failure.
121 * @return If true, callback will be called.
122 * @hide
123 */
124 public boolean registerAppConfiguration(String name, int dataType, int role,
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700125 int channelType, BluetoothHealthCallback callback) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700126 boolean result = false;
127 if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
128
129 if (DBG) log("registerApplication(" + name + ":" + dataType + ")");
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700130 BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700131 BluetoothHealthAppConfiguration config =
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700132 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700133
134 if (mService != null) {
135 try {
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700136 result = mService.registerAppConfiguration(config, wrapper);
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700137 } catch (RemoteException e) {
138 Log.e(TAG, e.toString());
139 }
140 } else {
141 Log.w(TAG, "Proxy not attached to service");
142 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
143 }
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700144 return result;
145 }
146
147 /**
148 * Unregister an application configuration that has been registered using
149 * {@link #registerSinkAppConfiguration}
150 *
151 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
152 *
153 * @param config The health app configuration
154 * @return Success or failure.
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700155 */
156 public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
157 boolean result = false;
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700158 if (mService != null && isEnabled() && config != null) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700159 try {
160 result = mService.unregisterAppConfiguration(config);
161 } catch (RemoteException e) {
162 Log.e(TAG, e.toString());
163 }
164 } else {
165 Log.w(TAG, "Proxy not attached to service");
166 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
167 }
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700168
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700169 return result;
170 }
171
172 /**
173 * Connect to a health device which has the {@link #SOURCE_ROLE}.
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700174 * This is an asynchronous call. If this function returns true, the callback
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700175 * associated with the application configuration will be called.
176 *
177 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
178 *
179 * @param device The remote Bluetooth device.
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700180 * @param config The application configuration which has been registered using
181 * {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700182 * @return If true, the callback associated with the application config will be called.
183 */
184 public boolean connectChannelToSource(BluetoothDevice device,
185 BluetoothHealthAppConfiguration config) {
186 if (mService != null && isEnabled() && isValidDevice(device) &&
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700187 config != null) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700188 try {
189 return mService.connectChannelToSource(device, config);
190 } catch (RemoteException e) {
191 Log.e(TAG, e.toString());
192 }
193 } else {
194 Log.w(TAG, "Proxy not attached to service");
195 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
196 }
197 return false;
198 }
199
200 /**
201 * Connect to a health device which has the {@link #SINK_ROLE}.
202 * This is an asynchronous call. If this function returns true, the callback
203 * associated with the application configuration will be called.
204 *
205 *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
206 *
207 * @param device The remote Bluetooth device.
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700208 * @param config The application configuration which has been registered using
209 * {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700210 * @return If true, the callback associated with the application config will be called.
211 * @hide
212 */
213 public boolean connectChannelToSink(BluetoothDevice device,
214 BluetoothHealthAppConfiguration config, int channelType) {
215 if (mService != null && isEnabled() && isValidDevice(device) &&
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700216 config != null) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700217 try {
218 return mService.connectChannelToSink(device, config, channelType);
219 } catch (RemoteException e) {
220 Log.e(TAG, e.toString());
221 }
222 } else {
223 Log.w(TAG, "Proxy not attached to service");
224 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
225 }
226 return false;
227 }
228
229 /**
230 * Disconnect a connected health channel.
231 * This is an asynchronous call. If this function returns true, the callback
232 * associated with the application configuration will be called.
233 *
234 *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
235 *
236 * @param device The remote Bluetooth device.
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700237 * @param config The application configuration which has been registered using
238 * {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700239 * @param channelId The channel id associated with the channel
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700240 * @return If true, the callback associated with the application config will be called.
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700241 */
242 public boolean disconnectChannel(BluetoothDevice device,
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700243 BluetoothHealthAppConfiguration config, int channelId) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700244 if (mService != null && isEnabled() && isValidDevice(device) &&
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700245 config != null) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700246 try {
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700247 return mService.disconnectChannel(device, config, channelId);
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700248 } catch (RemoteException e) {
249 Log.e(TAG, e.toString());
250 }
251 } else {
252 Log.w(TAG, "Proxy not attached to service");
253 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
254 }
255 return false;
256 }
257
258 /**
259 * Get the file descriptor of the main channel associated with the remote device
260 * and application configuration.
261 *
262 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
263 *
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700264 * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
265 * when done.
266 *
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700267 * @param device The remote Bluetooth health device
268 * @param config The application configuration
269 * @return null on failure, ParcelFileDescriptor on success.
270 */
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700271 public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
272 BluetoothHealthAppConfiguration config) {
273 if (mService != null && isEnabled() && isValidDevice(device) &&
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700274 config != null) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700275 try {
276 return mService.getMainChannelFd(device, config);
277 } catch (RemoteException e) {
278 Log.e(TAG, e.toString());
279 }
280 } else {
281 Log.w(TAG, "Proxy not attached to service");
282 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
283 }
284 return null;
285 }
286
287 /**
288 * Get the current connection state of the profile.
289 *
290 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
291 *
292 * This is not specific to any application configuration but represents the connection
293 * state of the local Bluetooth adapter with the remote device. This can be used
294 * by applications like status bar which would just like to know the state of the
295 * local adapter.
296 *
297 * @param device Remote bluetooth device.
298 * @return State of the profile connection. One of
299 * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
300 * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
301 */
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700302 @Override
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700303 public int getConnectionState(BluetoothDevice device) {
304 if (mService != null && isEnabled() && isValidDevice(device)) {
305 try {
306 return mService.getHealthDeviceConnectionState(device);
307 } catch (RemoteException e) {
308 Log.e(TAG, e.toString());
309 }
310 } else {
311 Log.w(TAG, "Proxy not attached to service");
312 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
313 }
314 return STATE_DISCONNECTED;
315 }
316
317 /**
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700318 * Get connected devices for the health profile.
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700319 *
320 * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
321 *
322 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
323 *
324 * This is not specific to any application configuration but represents the connection
325 * state of the local Bluetooth adapter for this profile. This can be used
326 * by applications like status bar which would just like to know the state of the
327 * local adapter.
328 * @return List of devices. The list will be empty on error.
329 */
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700330 @Override
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700331 public List<BluetoothDevice> getConnectedDevices() {
332 if (mService != null && isEnabled()) {
333 try {
334 return mService.getConnectedHealthDevices();
335 } catch (RemoteException e) {
336 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
337 return new ArrayList<BluetoothDevice>();
338 }
339 }
340 if (mService == null) Log.w(TAG, "Proxy not attached to service");
341 return new ArrayList<BluetoothDevice>();
342 }
343
344 /**
345 * Get a list of devices that match any of the given connection
346 * states.
347 *
348 * <p> If none of the devices match any of the given states,
349 * an empty list will be returned.
350 *
351 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
352 * This is not specific to any application configuration but represents the connection
353 * state of the local Bluetooth adapter for this profile. This can be used
354 * by applications like status bar which would just like to know the state of the
355 * local adapter.
356 *
357 * @param states Array of states. States can be one of
358 * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
359 * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
360 * @return List of devices. The list will be empty on error.
361 */
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700362 @Override
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700363 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
364 if (mService != null && isEnabled()) {
365 try {
366 return mService.getHealthDevicesMatchingConnectionStates(states);
367 } catch (RemoteException e) {
368 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
369 return new ArrayList<BluetoothDevice>();
370 }
371 }
372 if (mService == null) Log.w(TAG, "Proxy not attached to service");
373 return new ArrayList<BluetoothDevice>();
374 }
375
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700376 private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
377 private BluetoothHealthCallback mCallback;
378
379 public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
380 mCallback = callback;
381 }
382
383 @Override
384 public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
385 int status) {
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700386 mCallback.onHealthAppConfigurationStatusChange(config, status);
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700387 }
388
389 @Override
390 public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
391 BluetoothDevice device, int prevState, int newState,
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700392 ParcelFileDescriptor fd, int channelId) {
393 mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
394 channelId);
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700395 }
396 }
397
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700398 /** Health Channel Connection State - Disconnected */
399 public static final int STATE_CHANNEL_DISCONNECTED = 0;
400 /** Health Channel Connection State - Connecting */
401 public static final int STATE_CHANNEL_CONNECTING = 1;
402 /** Health Channel Connection State - Connected */
403 public static final int STATE_CHANNEL_CONNECTED = 2;
404 /** Health Channel Connection State - Disconnecting */
405 public static final int STATE_CHANNEL_DISCONNECTING = 3;
406
407 /** Health App Configuration registration success */
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700408 public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700409 /** Health App Configuration registration failure */
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700410 public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700411 /** Health App Configuration un-registration success */
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700412 public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700413 /** Health App Configuration un-registration failure */
Jaikumar Ganesh090847e2011-08-31 15:36:05 -0700414 public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700415
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700416 private ServiceListener mServiceListener;
417 private IBluetooth mService;
418 BluetoothAdapter mAdapter;
419
420 /**
421 * Create a BluetoothHealth proxy object.
422 */
423 /*package*/ BluetoothHealth(Context mContext, ServiceListener l) {
424 IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
425 mServiceListener = l;
426 mAdapter = BluetoothAdapter.getDefaultAdapter();
427 if (b != null) {
428 mService = IBluetooth.Stub.asInterface(b);
429 if (mServiceListener != null) {
430 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, this);
431 }
432 } else {
433 Log.w(TAG, "Bluetooth Service not available!");
434
435 // Instead of throwing an exception which prevents people from going
436 // into Wireless settings in the emulator. Let it crash later when it is actually used.
437 mService = null;
438 }
439 }
440
441 private boolean isEnabled() {
442 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
443
444 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
445 log("Bluetooth is Not enabled");
446 return false;
447 }
448
449 private boolean isValidDevice(BluetoothDevice device) {
450 if (device == null) return false;
451
452 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
453 return false;
454 }
455
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700456 private boolean checkAppParam(String name, int role, int channelType,
Jaikumar Ganeshd3b7d1d2011-07-06 17:37:02 -0700457 BluetoothHealthCallback callback) {
Jaikumar Ganeshbf981ca2011-04-01 16:33:09 -0700458 if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
459 (channelType != CHANNEL_TYPE_RELIABLE &&
460 channelType != CHANNEL_TYPE_STREAMING &&
461 channelType != CHANNEL_TYPE_ANY) || callback == null) {
462 return false;
463 }
464 if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
465 return true;
466 }
467
468 private static void log(String msg) {
469 Log.d(TAG, msg);
470 }
471}