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 com.example.bluetooth.health; 18 19 import android.app.Service; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothHealth; 23 import android.bluetooth.BluetoothHealthAppConfiguration; 24 import android.bluetooth.BluetoothHealthCallback; 25 import android.bluetooth.BluetoothProfile; 26 import android.content.Intent; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Message; 30 import android.os.Messenger; 31 import android.os.ParcelFileDescriptor; 32 import android.os.RemoteException; 33 import android.util.Log; 34 import android.widget.Toast; 35 36 import java.io.FileInputStream; 37 import java.io.IOException; 38 39 /** 40 * This Service encapsulates Bluetooth Health API to establish, manage, and disconnect 41 * communication between the Android device and a Bluetooth HDP-enabled device. Possible HDP 42 * device type includes blood pressure monitor, glucose meter, thermometer, etc. 43 * 44 * As outlined in the 45 * <a href="http://developer.android.com/reference/android/bluetooth/BluetoothHealth.html">BluetoothHealth</a> 46 * documentation, the steps involve: 47 * 1. Get a reference to the BluetoothHealth proxy object. 48 * 2. Create a BluetoothHealth callback and register an application configuration that acts as a 49 * Health SINK. 50 * 3. Establish connection to a health device. Some devices will initiate the connection. It is 51 * unnecessary to carry out this step for those devices. 52 * 4. When connected successfully, read / write to the health device using the file descriptor. 53 * The received data needs to be interpreted using a health manager which implements the 54 * IEEE 11073-xxxxx specifications. 55 * 5. When done, close the health channel and unregister the application. The channel will 56 * also close when there is extended inactivity. 57 */ 58 public class BluetoothHDPService extends Service { 59 private static final String TAG = "BluetoothHDPService"; 60 61 public static final int RESULT_OK = 0; 62 public static final int RESULT_FAIL = -1; 63 64 // Status codes sent back to the UI client. 65 // Application registration complete. 66 public static final int STATUS_HEALTH_APP_REG = 100; 67 // Application unregistration complete. 68 public static final int STATUS_HEALTH_APP_UNREG = 101; 69 // Channel creation complete. 70 public static final int STATUS_CREATE_CHANNEL = 102; 71 // Channel destroy complete. 72 public static final int STATUS_DESTROY_CHANNEL = 103; 73 // Reading data from Bluetooth HDP device. 74 public static final int STATUS_READ_DATA = 104; 75 // Done with reading data. 76 public static final int STATUS_READ_DATA_DONE = 105; 77 78 // Message codes received from the UI client. 79 // Register client with this service. 80 public static final int MSG_REG_CLIENT = 200; 81 // Unregister client from this service. 82 public static final int MSG_UNREG_CLIENT = 201; 83 // Register health application. 84 public static final int MSG_REG_HEALTH_APP = 300; 85 // Unregister health application. 86 public static final int MSG_UNREG_HEALTH_APP = 301; 87 // Connect channel. 88 public static final int MSG_CONNECT_CHANNEL = 400; 89 // Disconnect channel. 90 public static final int MSG_DISCONNECT_CHANNEL = 401; 91 92 private BluetoothHealthAppConfiguration mHealthAppConfig; 93 private BluetoothAdapter mBluetoothAdapter; 94 private BluetoothHealth mBluetoothHealth; 95 private BluetoothDevice mDevice; 96 private int mChannelId; 97 98 private Messenger mClient; 99 100 // Handles events sent by {@link HealthHDPActivity}. 101 private class IncomingHandler extends Handler { 102 @Override 103 public void handleMessage(Message msg) { 104 switch (msg.what) { 105 // Register UI client to this service so the client can receive messages. 106 case MSG_REG_CLIENT: 107 Log.d(TAG, "Activity client registered"); 108 mClient = msg.replyTo; 109 break; 110 // Unregister UI client from this service. 111 case MSG_UNREG_CLIENT: 112 mClient = null; 113 break; 114 // Register health application. 115 case MSG_REG_HEALTH_APP: 116 registerApp(msg.arg1); 117 break; 118 // Unregister health application. 119 case MSG_UNREG_HEALTH_APP: 120 unregisterApp(); 121 break; 122 // Connect channel. 123 case MSG_CONNECT_CHANNEL: 124 mDevice = (BluetoothDevice) msg.obj; 125 connectChannel(); 126 break; 127 // Disconnect channel. 128 case MSG_DISCONNECT_CHANNEL: 129 mDevice = (BluetoothDevice) msg.obj; 130 disconnectChannel(); 131 break; 132 default: 133 super.handleMessage(msg); 134 } 135 } 136 } 137 138 final Messenger mMessenger = new Messenger(new IncomingHandler()); 139 140 /** 141 * Make sure Bluetooth and health profile are available on the Android device. Stop service 142 * if they are not available. 143 */ 144 @Override 145 public void onCreate() { 146 super.onCreate(); 147 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 148 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { 149 // Bluetooth adapter isn't available. The client of the service is supposed to 150 // verify that it is available and activate before invoking this service. 151 stopSelf(); 152 return; 153 } 154 if (!mBluetoothAdapter.getProfileProxy(this, mBluetoothServiceListener, 155 BluetoothProfile.HEALTH)) { 156 Toast.makeText(this, R.string.bluetooth_health_profile_not_available, 157 Toast.LENGTH_LONG); 158 stopSelf(); 159 return; 160 } 161 } 162 163 @Override 164 public int onStartCommand(Intent intent, int flags, int startId) { 165 Log.d(TAG, "BluetoothHDPService is running."); 166 return START_STICKY; 167 } 168 169 @Override 170 public IBinder onBind(Intent intent) { 171 return mMessenger.getBinder(); 172 }; 173 174 // Register health application through the Bluetooth Health API. 175 private void registerApp(int dataType) { 176 mBluetoothHealth.registerSinkAppConfiguration(TAG, dataType, mHealthCallback); 177 } 178 179 // Unregister health application through the Bluetooth Health API. 180 private void unregisterApp() { 181 mBluetoothHealth.unregisterAppConfiguration(mHealthAppConfig); 182 } 183 184 // Connect channel through the Bluetooth Health API. 185 private void connectChannel() { 186 Log.i(TAG, "connectChannel()"); 187 mBluetoothHealth.connectChannelToSource(mDevice, mHealthAppConfig); 188 } 189 190 // Disconnect channel through the Bluetooth Health API. 191 private void disconnectChannel() { 192 Log.i(TAG, "disconnectChannel()"); 193 mBluetoothHealth.disconnectChannel(mDevice, mHealthAppConfig, mChannelId); 194 } 195 196 // Callbacks to handle connection set up and disconnection clean up. 197 private final BluetoothProfile.ServiceListener mBluetoothServiceListener = 198 new BluetoothProfile.ServiceListener() { 199 public void onServiceConnected(int profile, BluetoothProfile proxy) { 200 if (profile == BluetoothProfile.HEALTH) { 201 mBluetoothHealth = (BluetoothHealth) proxy; 202 if (Log.isLoggable(TAG, Log.DEBUG)) 203 Log.d(TAG, "onServiceConnected to profile: " + profile); 204 } 205 } 206 207 public void onServiceDisconnected(int profile) { 208 if (profile == BluetoothProfile.HEALTH) { 209 mBluetoothHealth = null; 210 } 211 } 212 }; 213 214 private final BluetoothHealthCallback mHealthCallback = new BluetoothHealthCallback() { 215 // Callback to handle application registration and unregistration events. The service 216 // passes the status back to the UI client. 217 public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, 218 int status) { 219 if (status == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE) { 220 mHealthAppConfig = null; 221 sendMessage(STATUS_HEALTH_APP_REG, RESULT_FAIL); 222 } else if (status == BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS) { 223 mHealthAppConfig = config; 224 sendMessage(STATUS_HEALTH_APP_REG, RESULT_OK); 225 } else if (status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE || 226 status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) { 227 sendMessage(STATUS_HEALTH_APP_UNREG, 228 status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS ? 229 RESULT_OK : RESULT_FAIL); 230 } 231 } 232 233 // Callback to handle channel connection state changes. 234 // Note that the logic of the state machine may need to be modified based on the HDP device. 235 // When the HDP device is connected, the received file descriptor is passed to the 236 // ReadThread to read the content. 237 public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config, 238 BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd, 239 int channelId) { 240 if (Log.isLoggable(TAG, Log.DEBUG)) 241 Log.d(TAG, String.format("prevState\t%d ----------> newState\t%d", 242 prevState, newState)); 243 if (prevState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED && 244 newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) { 245 if (config.equals(mHealthAppConfig)) { 246 mChannelId = channelId; 247 sendMessage(STATUS_CREATE_CHANNEL, RESULT_OK); 248 (new ReadThread(fd)).start(); 249 } else { 250 sendMessage(STATUS_CREATE_CHANNEL, RESULT_FAIL); 251 } 252 } else if (prevState == BluetoothHealth.STATE_CHANNEL_CONNECTING && 253 newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED) { 254 sendMessage(STATUS_CREATE_CHANNEL, RESULT_FAIL); 255 } else if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED) { 256 if (config.equals(mHealthAppConfig)) { 257 sendMessage(STATUS_DESTROY_CHANNEL, RESULT_OK); 258 } else { 259 sendMessage(STATUS_DESTROY_CHANNEL, RESULT_FAIL); 260 } 261 } 262 } 263 }; 264 265 // Sends an update message to registered UI client. 266 private void sendMessage(int what, int value) { 267 if (mClient == null) { 268 Log.d(TAG, "No clients registered."); 269 return; 270 } 271 272 try { 273 mClient.send(Message.obtain(null, what, value, 0)); 274 } catch (RemoteException e) { 275 // Unable to reach client. 276 e.printStackTrace(); 277 } 278 } 279 280 // Thread to read incoming data received from the HDP device. This sample application merely 281 // reads the raw byte from the incoming file descriptor. The data should be interpreted using 282 // a health manager which implements the IEEE 11073-xxxxx specifications. 283 private class ReadThread extends Thread { 284 private ParcelFileDescriptor mFd; 285 286 public ReadThread(ParcelFileDescriptor fd) { 287 super(); 288 mFd = fd; 289 } 290 291 @Override 292 public void run() { 293 FileInputStream fis = new FileInputStream(mFd.getFileDescriptor()); 294 final byte data[] = new byte[8192]; 295 try { 296 while(fis.read(data) > -1) { 297 // At this point, the application can pass the raw data to a parser that 298 // has implemented the IEEE 11073-xxxxx specifications. Instead, this sample 299 // simply indicates that some data has been received. 300 sendMessage(STATUS_READ_DATA, 0); 301 } 302 } catch(IOException ioe) {} 303 if (mFd != null) { 304 try { 305 mFd.close(); 306 } catch (IOException e) { /* Do nothing. */ } 307 } 308 sendMessage(STATUS_READ_DATA_DONE, 0); 309 } 310 } 311 } 312