Home | History | Annotate | Download | only in bluetooth
      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 android.bluetooth;
     18 
     19 import android.content.Context;
     20 import android.os.IBinder;
     21 import android.os.ParcelFileDescriptor;
     22 import android.os.RemoteException;
     23 import android.os.ServiceManager;
     24 import android.util.Log;
     25 
     26 import java.util.ArrayList;
     27 import 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  *
     35  * <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  *
     54  */
     55 public 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 
     84     /** @hide */
     85     public static final int HEALTH_OPERATION_SUCCESS = 6000;
     86     /** @hide */
     87     public static final int HEALTH_OPERATION_ERROR = 6001;
     88     /** @hide */
     89     public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
     90     /** @hide */
     91     public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
     92     /** @hide */
     93     public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
     94     /** @hide */
     95     public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
     96 
     97 
     98     /**
     99      * Register an application configuration that acts as a Health SINK.
    100      * This is the configuration that will be used to communicate with health devices
    101      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
    102      * the callback is used to notify success or failure if the function returns true.
    103      *
    104      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    105      *
    106      * @param name The friendly name associated with the application or configuration.
    107      * @param dataType The dataType of the Source role of Health Profile to which
    108      *                   the sink wants to connect to.
    109      * @param callback A callback to indicate success or failure of the registration and
    110      *               all operations done on this application configuration.
    111      * @return If true, callback will be called.
    112      */
    113     public boolean registerSinkAppConfiguration(String name, int dataType,
    114             BluetoothHealthCallback callback) {
    115         if (!isEnabled() || name == null) return false;
    116 
    117         if (DBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
    118         return registerAppConfiguration(name, dataType, SINK_ROLE,
    119                 CHANNEL_TYPE_ANY, callback);
    120     }
    121 
    122     /**
    123      * Register an application configuration that acts as a Health SINK or in a Health
    124      * SOURCE role.This is an asynchronous call and so
    125      * the callback is used to notify success or failure if the function returns true.
    126      *
    127      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    128      *
    129      * @param name The friendly name associated with the application or configuration.
    130      * @param dataType The dataType of the Source role of Health Profile.
    131      * @param channelType The channel type. Will be one of
    132      *                              {@link #CHANNEL_TYPE_RELIABLE}  or
    133      *                              {@link #CHANNEL_TYPE_STREAMING}
    134      * @param callback - A callback to indicate success or failure.
    135      * @return If true, callback will be called.
    136      * @hide
    137      */
    138     public boolean registerAppConfiguration(String name, int dataType, int role,
    139             int channelType, BluetoothHealthCallback callback) {
    140         boolean result = false;
    141         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
    142 
    143         if (DBG) log("registerApplication(" + name + ":" + dataType + ")");
    144         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
    145         BluetoothHealthAppConfiguration config =
    146                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
    147 
    148         if (mService != null) {
    149             try {
    150                 result = mService.registerAppConfiguration(config, wrapper);
    151             } catch (RemoteException e) {
    152                 Log.e(TAG, e.toString());
    153             }
    154         } else {
    155             Log.w(TAG, "Proxy not attached to service");
    156             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    157         }
    158         return result;
    159     }
    160 
    161     /**
    162      * Unregister an application configuration that has been registered using
    163      * {@link #registerSinkAppConfiguration}
    164      *
    165      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    166      *
    167      * @param config  The health app configuration
    168      * @return Success or failure.
    169      */
    170     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
    171         boolean result = false;
    172         if (mService != null && isEnabled() && config != null) {
    173             try {
    174                 result = mService.unregisterAppConfiguration(config);
    175             } catch (RemoteException e) {
    176                 Log.e(TAG, e.toString());
    177             }
    178         } else {
    179             Log.w(TAG, "Proxy not attached to service");
    180             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    181         }
    182 
    183         return result;
    184     }
    185 
    186     /**
    187      * Connect to a health device which has the {@link #SOURCE_ROLE}.
    188      * This is an asynchronous call. If this function returns true, the callback
    189      * associated with the application configuration will be called.
    190      *
    191      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    192      *
    193      * @param device The remote Bluetooth device.
    194      * @param config The application configuration which has been registered using
    195      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    196      * @return If true, the callback associated with the application config will be called.
    197      */
    198     public boolean connectChannelToSource(BluetoothDevice device,
    199             BluetoothHealthAppConfiguration config) {
    200         if (mService != null && isEnabled() && isValidDevice(device) &&
    201                 config != null) {
    202             try {
    203                 return mService.connectChannelToSource(device, config);
    204             } catch (RemoteException e) {
    205                 Log.e(TAG, e.toString());
    206             }
    207         } else {
    208             Log.w(TAG, "Proxy not attached to service");
    209             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    210         }
    211         return false;
    212     }
    213 
    214     /**
    215      * Connect to a health device which has the {@link #SINK_ROLE}.
    216      * This is an asynchronous call. If this function returns true, the callback
    217      * associated with the application configuration will be called.
    218      *
    219      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    220      *
    221      * @param device The remote Bluetooth device.
    222      * @param config The application configuration which has been registered using
    223      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    224      * @return If true, the callback associated with the application config will be called.
    225      * @hide
    226      */
    227     public boolean connectChannelToSink(BluetoothDevice device,
    228             BluetoothHealthAppConfiguration config, int channelType) {
    229         if (mService != null && isEnabled() && isValidDevice(device) &&
    230                 config != null) {
    231             try {
    232                 return mService.connectChannelToSink(device, config, channelType);
    233             } catch (RemoteException e) {
    234                 Log.e(TAG, e.toString());
    235             }
    236         } else {
    237             Log.w(TAG, "Proxy not attached to service");
    238             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    239         }
    240         return false;
    241     }
    242 
    243     /**
    244      * Disconnect a connected health channel.
    245      * This is an asynchronous call. If this function returns true, the callback
    246      * associated with the application configuration will be called.
    247      *
    248      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    249      *
    250      * @param device The remote Bluetooth device.
    251      * @param config The application configuration which has been registered using
    252      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    253      * @param channelId The channel id associated with the channel
    254      * @return If true, the callback associated with the application config will be called.
    255      */
    256     public boolean disconnectChannel(BluetoothDevice device,
    257             BluetoothHealthAppConfiguration config, int channelId) {
    258         if (mService != null && isEnabled() && isValidDevice(device) &&
    259                 config != null) {
    260             try {
    261                 return mService.disconnectChannel(device, config, channelId);
    262             } catch (RemoteException e) {
    263                 Log.e(TAG, e.toString());
    264             }
    265         } else {
    266             Log.w(TAG, "Proxy not attached to service");
    267             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    268         }
    269         return false;
    270     }
    271 
    272     /**
    273      * Get the file descriptor of the main channel associated with the remote device
    274      * and application configuration.
    275      *
    276      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    277      *
    278      * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
    279      * when done.
    280      *
    281      * @param device The remote Bluetooth health device
    282      * @param config The application configuration
    283      * @return null on failure, ParcelFileDescriptor on success.
    284      */
    285     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
    286             BluetoothHealthAppConfiguration config) {
    287         if (mService != null && isEnabled() && isValidDevice(device) &&
    288                 config != null) {
    289             try {
    290                 return mService.getMainChannelFd(device, config);
    291             } catch (RemoteException e) {
    292                 Log.e(TAG, e.toString());
    293             }
    294         } else {
    295             Log.w(TAG, "Proxy not attached to service");
    296             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    297         }
    298         return null;
    299     }
    300 
    301     /**
    302      * Get the current connection state of the profile.
    303      *
    304      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    305      *
    306      * This is not specific to any application configuration but represents the connection
    307      * state of the local Bluetooth adapter with the remote device. This can be used
    308      * by applications like status bar which would just like to know the state of the
    309      * local adapter.
    310      *
    311      * @param device Remote bluetooth device.
    312      * @return State of the profile connection. One of
    313      *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    314      *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
    315      */
    316     @Override
    317     public int getConnectionState(BluetoothDevice device) {
    318         if (mService != null && isEnabled() && isValidDevice(device)) {
    319             try {
    320                 return mService.getHealthDeviceConnectionState(device);
    321             } catch (RemoteException e) {
    322                 Log.e(TAG, e.toString());
    323             }
    324         } else {
    325             Log.w(TAG, "Proxy not attached to service");
    326             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    327         }
    328         return STATE_DISCONNECTED;
    329     }
    330 
    331     /**
    332      * Get connected devices for the health profile.
    333      *
    334      * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
    335      *
    336      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    337      *
    338      * This is not specific to any application configuration but represents the connection
    339      * state of the local Bluetooth adapter for this profile. This can be used
    340      * by applications like status bar which would just like to know the state of the
    341      * local adapter.
    342      * @return List of devices. The list will be empty on error.
    343      */
    344     @Override
    345     public List<BluetoothDevice> getConnectedDevices() {
    346         if (mService != null && isEnabled()) {
    347             try {
    348                 return mService.getConnectedHealthDevices();
    349             } catch (RemoteException e) {
    350                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    351                 return new ArrayList<BluetoothDevice>();
    352             }
    353         }
    354         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    355         return new ArrayList<BluetoothDevice>();
    356     }
    357 
    358     /**
    359      * Get a list of devices that match any of the given connection
    360      * states.
    361      *
    362      * <p> If none of the devices match any of the given states,
    363      * an empty list will be returned.
    364      *
    365      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    366      * This is not specific to any application configuration but represents the connection
    367      * state of the local Bluetooth adapter for this profile. This can be used
    368      * by applications like status bar which would just like to know the state of the
    369      * local adapter.
    370      *
    371      * @param states Array of states. States can be one of
    372      *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    373      *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
    374      * @return List of devices. The list will be empty on error.
    375      */
    376     @Override
    377     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    378         if (mService != null && isEnabled()) {
    379             try {
    380                 return mService.getHealthDevicesMatchingConnectionStates(states);
    381             } catch (RemoteException e) {
    382                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    383                 return new ArrayList<BluetoothDevice>();
    384             }
    385         }
    386         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    387         return new ArrayList<BluetoothDevice>();
    388     }
    389 
    390     private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
    391         private BluetoothHealthCallback mCallback;
    392 
    393         public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
    394             mCallback = callback;
    395         }
    396 
    397         @Override
    398         public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
    399                                                          int status) {
    400            mCallback.onHealthAppConfigurationStatusChange(config, status);
    401         }
    402 
    403         @Override
    404         public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
    405                                        BluetoothDevice device, int prevState, int newState,
    406                                        ParcelFileDescriptor fd, int channelId) {
    407             mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
    408                                                  channelId);
    409         }
    410     }
    411 
    412      /** Health Channel Connection State - Disconnected */
    413     public static final int STATE_CHANNEL_DISCONNECTED  = 0;
    414     /** Health Channel Connection State - Connecting */
    415     public static final int STATE_CHANNEL_CONNECTING    = 1;
    416     /** Health Channel Connection State - Connected */
    417     public static final int STATE_CHANNEL_CONNECTED     = 2;
    418     /** Health Channel Connection State - Disconnecting */
    419     public static final int STATE_CHANNEL_DISCONNECTING = 3;
    420 
    421     /** Health App Configuration registration success */
    422     public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
    423     /** Health App Configuration registration failure */
    424     public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
    425     /** Health App Configuration un-registration success */
    426     public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
    427     /** Health App Configuration un-registration failure */
    428     public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
    429 
    430     private ServiceListener mServiceListener;
    431     private IBluetooth mService;
    432     BluetoothAdapter mAdapter;
    433 
    434     /**
    435      * Create a BluetoothHealth proxy object.
    436      */
    437     /*package*/ BluetoothHealth(Context mContext, ServiceListener l) {
    438         IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
    439         mServiceListener = l;
    440         mAdapter = BluetoothAdapter.getDefaultAdapter();
    441         if (b != null) {
    442             mService = IBluetooth.Stub.asInterface(b);
    443             if (mServiceListener != null) {
    444                 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, this);
    445             }
    446         } else {
    447             Log.w(TAG, "Bluetooth Service not available!");
    448 
    449             // Instead of throwing an exception which prevents people from going
    450             // into Wireless settings in the emulator. Let it crash later when it is actually used.
    451             mService = null;
    452         }
    453     }
    454 
    455     /*package*/ void close() {
    456         mServiceListener = null;
    457     }
    458 
    459     private boolean isEnabled() {
    460         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    461 
    462         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
    463         log("Bluetooth is Not enabled");
    464         return false;
    465     }
    466 
    467     private boolean isValidDevice(BluetoothDevice device) {
    468         if (device == null) return false;
    469 
    470         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    471         return false;
    472     }
    473 
    474     private boolean checkAppParam(String name, int role, int channelType,
    475             BluetoothHealthCallback callback) {
    476         if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
    477                 (channelType != CHANNEL_TYPE_RELIABLE &&
    478                 channelType != CHANNEL_TYPE_STREAMING &&
    479                 channelType != CHANNEL_TYPE_ANY) || callback == null) {
    480             return false;
    481         }
    482         if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
    483         return true;
    484     }
    485 
    486     private static void log(String msg) {
    487         Log.d(TAG, msg);
    488     }
    489 }
    490