| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.bluetooth.health; |
| |
| import android.app.Service; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothHealth; |
| import android.bluetooth.BluetoothHealthAppConfiguration; |
| import android.bluetooth.BluetoothHealthCallback; |
| import android.bluetooth.BluetoothProfile; |
| import android.content.Intent; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| |
| /** |
| * This Service encapsulates Bluetooth Health API to establish, manage, and disconnect |
| * communication between the Android device and a Bluetooth HDP-enabled device. Possible HDP |
| * device type includes blood pressure monitor, glucose meter, thermometer, etc. |
| * |
| * As outlined in the |
| * <a href="http://developer.android.com/reference/android/bluetooth/BluetoothHealth.html">BluetoothHealth</a> |
| * documentation, the steps involve: |
| * 1. Get a reference to the BluetoothHealth proxy object. |
| * 2. Create a BluetoothHealth callback and register an application configuration that acts as a |
| * Health SINK. |
| * 3. Establish connection to a health device. Some devices will initiate the connection. It is |
| * unnecessary to carry out this step for those devices. |
| * 4. When connected successfully, read / write to the health device using the file descriptor. |
| * The received data needs to be interpreted using a health manager which implements the |
| * IEEE 11073-xxxxx specifications. |
| * 5. When done, close the health channel and unregister the application. The channel will |
| * also close when there is extended inactivity. |
| */ |
| public class BluetoothHDPService extends Service { |
| private static final String TAG = "BluetoothHDPService"; |
| |
| public static final int RESULT_OK = 0; |
| public static final int RESULT_FAIL = -1; |
| |
| // Status codes sent back to the UI client. |
| // Application registration complete. |
| public static final int STATUS_HEALTH_APP_REG = 100; |
| // Application unregistration complete. |
| public static final int STATUS_HEALTH_APP_UNREG = 101; |
| // Channel creation complete. |
| public static final int STATUS_CREATE_CHANNEL = 102; |
| // Channel destroy complete. |
| public static final int STATUS_DESTROY_CHANNEL = 103; |
| // Reading data from Bluetooth HDP device. |
| public static final int STATUS_READ_DATA = 104; |
| // Done with reading data. |
| public static final int STATUS_READ_DATA_DONE = 105; |
| |
| // Message codes received from the UI client. |
| // Register client with this service. |
| public static final int MSG_REG_CLIENT = 200; |
| // Unregister client from this service. |
| public static final int MSG_UNREG_CLIENT = 201; |
| // Register health application. |
| public static final int MSG_REG_HEALTH_APP = 300; |
| // Unregister health application. |
| public static final int MSG_UNREG_HEALTH_APP = 301; |
| // Connect channel. |
| public static final int MSG_CONNECT_CHANNEL = 400; |
| // Disconnect channel. |
| public static final int MSG_DISCONNECT_CHANNEL = 401; |
| |
| private BluetoothHealthAppConfiguration mHealthAppConfig; |
| private BluetoothAdapter mBluetoothAdapter; |
| private BluetoothHealth mBluetoothHealth; |
| private BluetoothDevice mDevice; |
| private int mChannelId; |
| |
| private Messenger mClient; |
| |
| // Handles events sent by {@link HealthHDPActivity}. |
| private class IncomingHandler extends Handler { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| // Register UI client to this service so the client can receive messages. |
| case MSG_REG_CLIENT: |
| Log.d(TAG, "Activity client registered"); |
| mClient = msg.replyTo; |
| break; |
| // Unregister UI client from this service. |
| case MSG_UNREG_CLIENT: |
| mClient = null; |
| break; |
| // Register health application. |
| case MSG_REG_HEALTH_APP: |
| registerApp(msg.arg1); |
| break; |
| // Unregister health application. |
| case MSG_UNREG_HEALTH_APP: |
| unregisterApp(); |
| break; |
| // Connect channel. |
| case MSG_CONNECT_CHANNEL: |
| mDevice = (BluetoothDevice) msg.obj; |
| connectChannel(); |
| break; |
| // Disconnect channel. |
| case MSG_DISCONNECT_CHANNEL: |
| mDevice = (BluetoothDevice) msg.obj; |
| disconnectChannel(); |
| break; |
| default: |
| super.handleMessage(msg); |
| } |
| } |
| } |
| |
| final Messenger mMessenger = new Messenger(new IncomingHandler()); |
| |
| /** |
| * Make sure Bluetooth and health profile are available on the Android device. Stop service |
| * if they are not available. |
| */ |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); |
| if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { |
| // Bluetooth adapter isn't available. The client of the service is supposed to |
| // verify that it is available and activate before invoking this service. |
| stopSelf(); |
| return; |
| } |
| if (!mBluetoothAdapter.getProfileProxy(this, mBluetoothServiceListener, |
| BluetoothProfile.HEALTH)) { |
| Toast.makeText(this, R.string.bluetooth_health_profile_not_available, |
| Toast.LENGTH_LONG); |
| stopSelf(); |
| return; |
| } |
| } |
| |
| @Override |
| public int onStartCommand(Intent intent, int flags, int startId) { |
| Log.d(TAG, "BluetoothHDPService is running."); |
| return START_STICKY; |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return mMessenger.getBinder(); |
| }; |
| |
| // Register health application through the Bluetooth Health API. |
| private void registerApp(int dataType) { |
| mBluetoothHealth.registerSinkAppConfiguration(TAG, dataType, mHealthCallback); |
| } |
| |
| // Unregister health application through the Bluetooth Health API. |
| private void unregisterApp() { |
| mBluetoothHealth.unregisterAppConfiguration(mHealthAppConfig); |
| } |
| |
| // Connect channel through the Bluetooth Health API. |
| private void connectChannel() { |
| Log.i(TAG, "connectChannel()"); |
| mBluetoothHealth.connectChannelToSource(mDevice, mHealthAppConfig); |
| } |
| |
| // Disconnect channel through the Bluetooth Health API. |
| private void disconnectChannel() { |
| Log.i(TAG, "disconnectChannel()"); |
| mBluetoothHealth.disconnectChannel(mDevice, mHealthAppConfig, mChannelId); |
| } |
| |
| // Callbacks to handle connection set up and disconnection clean up. |
| private final BluetoothProfile.ServiceListener mBluetoothServiceListener = |
| new BluetoothProfile.ServiceListener() { |
| public void onServiceConnected(int profile, BluetoothProfile proxy) { |
| if (profile == BluetoothProfile.HEALTH) { |
| mBluetoothHealth = (BluetoothHealth) proxy; |
| if (Log.isLoggable(TAG, Log.DEBUG)) |
| Log.d(TAG, "onServiceConnected to profile: " + profile); |
| } |
| } |
| |
| public void onServiceDisconnected(int profile) { |
| if (profile == BluetoothProfile.HEALTH) { |
| mBluetoothHealth = null; |
| } |
| } |
| }; |
| |
| private final BluetoothHealthCallback mHealthCallback = new BluetoothHealthCallback() { |
| // Callback to handle application registration and unregistration events. The service |
| // passes the status back to the UI client. |
| public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, |
| int status) { |
| if (status == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE) { |
| mHealthAppConfig = null; |
| sendMessage(STATUS_HEALTH_APP_REG, RESULT_FAIL); |
| } else if (status == BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS) { |
| mHealthAppConfig = config; |
| sendMessage(STATUS_HEALTH_APP_REG, RESULT_OK); |
| } else if (status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE || |
| status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) { |
| sendMessage(STATUS_HEALTH_APP_UNREG, |
| status == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS ? |
| RESULT_OK : RESULT_FAIL); |
| } |
| } |
| |
| // Callback to handle channel connection state changes. |
| // Note that the logic of the state machine may need to be modified based on the HDP device. |
| // When the HDP device is connected, the received file descriptor is passed to the |
| // ReadThread to read the content. |
| public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config, |
| BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd, |
| int channelId) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) |
| Log.d(TAG, String.format("prevState\t%d ----------> newState\t%d", |
| prevState, newState)); |
| if (prevState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED && |
| newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) { |
| if (config.equals(mHealthAppConfig)) { |
| mChannelId = channelId; |
| sendMessage(STATUS_CREATE_CHANNEL, RESULT_OK); |
| (new ReadThread(fd)).start(); |
| } else { |
| sendMessage(STATUS_CREATE_CHANNEL, RESULT_FAIL); |
| } |
| } else if (prevState == BluetoothHealth.STATE_CHANNEL_CONNECTING && |
| newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED) { |
| sendMessage(STATUS_CREATE_CHANNEL, RESULT_FAIL); |
| } else if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED) { |
| if (config.equals(mHealthAppConfig)) { |
| sendMessage(STATUS_DESTROY_CHANNEL, RESULT_OK); |
| } else { |
| sendMessage(STATUS_DESTROY_CHANNEL, RESULT_FAIL); |
| } |
| } |
| } |
| }; |
| |
| // Sends an update message to registered UI client. |
| private void sendMessage(int what, int value) { |
| if (mClient == null) { |
| Log.d(TAG, "No clients registered."); |
| return; |
| } |
| |
| try { |
| mClient.send(Message.obtain(null, what, value, 0)); |
| } catch (RemoteException e) { |
| // Unable to reach client. |
| e.printStackTrace(); |
| } |
| } |
| |
| // Thread to read incoming data received from the HDP device. This sample application merely |
| // reads the raw byte from the incoming file descriptor. The data should be interpreted using |
| // a health manager which implements the IEEE 11073-xxxxx specifications. |
| private class ReadThread extends Thread { |
| private ParcelFileDescriptor mFd; |
| |
| public ReadThread(ParcelFileDescriptor fd) { |
| super(); |
| mFd = fd; |
| } |
| |
| @Override |
| public void run() { |
| FileInputStream fis = new FileInputStream(mFd.getFileDescriptor()); |
| final byte data[] = new byte[8192]; |
| try { |
| while(fis.read(data) > -1) { |
| // At this point, the application can pass the raw data to a parser that |
| // has implemented the IEEE 11073-xxxxx specifications. Instead, this sample |
| // simply indicates that some data has been received. |
| sendMessage(STATUS_READ_DATA, 0); |
| } |
| } catch(IOException ioe) {} |
| if (mFd != null) { |
| try { |
| mFd.close(); |
| } catch (IOException e) { /* Do nothing. */ } |
| } |
| sendMessage(STATUS_READ_DATA_DONE, 0); |
| } |
| } |
| } |