Home | History | Annotate | Download | only in health
      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