• 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 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
handleMessage(Message msg)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
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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
onBind(Intent intent)170     public IBinder onBind(Intent intent) {
171         return mMessenger.getBinder();
172     };
173 
174     // Register health application through the Bluetooth Health API.
registerApp(int dataType)175     private void registerApp(int dataType) {
176         mBluetoothHealth.registerSinkAppConfiguration(TAG, dataType, mHealthCallback);
177     }
178 
179     // Unregister health application through the Bluetooth Health API.
unregisterApp()180     private void unregisterApp() {
181         mBluetoothHealth.unregisterAppConfiguration(mHealthAppConfig);
182     }
183 
184     // Connect channel through the Bluetooth Health API.
connectChannel()185     private void connectChannel() {
186         Log.i(TAG, "connectChannel()");
187         mBluetoothHealth.connectChannelToSource(mDevice, mHealthAppConfig);
188     }
189 
190     // Disconnect channel through the Bluetooth Health API.
disconnectChannel()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.
sendMessage(int what, int value)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 
ReadThread(ParcelFileDescriptor fd)286         public ReadThread(ParcelFileDescriptor fd) {
287             super();
288             mFd = fd;
289         }
290 
291         @Override
run()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