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.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.os.IBinder;
     24 import android.os.ParcelFileDescriptor;
     25 import android.os.RemoteException;
     26 import android.os.ServiceManager;
     27 import android.util.Log;
     28 
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 
     32 /**
     33  * Public API for Bluetooth Health Profile.
     34  *
     35  * <p>BluetoothHealth is a proxy object for controlling the Bluetooth
     36  * Service via IPC.
     37  *
     38  * <p> How to connect to a health device which is acting in the source role.
     39  *  <li> Use {@link BluetoothAdapter#getProfileProxy} to get
     40  *  the BluetoothHealth proxy object. </li>
     41  *  <li> Create an {@link BluetoothHealth} callback and call
     42  *  {@link #registerSinkAppConfiguration} to register an application
     43  *  configuration </li>
     44  *  <li> Pair with the remote device. This currently needs to be done manually
     45  *  from Bluetooth Settings </li>
     46  *  <li> Connect to a health device using {@link #connectChannelToSource}. Some
     47  *  devices will connect the channel automatically. The {@link BluetoothHealth}
     48  *  callback will inform the application of channel state change. </li>
     49  *  <li> Use the file descriptor provided with a connected channel to read and
     50  *  write data to the health channel. </li>
     51  *  <li> The received data needs to be interpreted using a health manager which
     52  *  implements the IEEE 11073-xxxxx specifications.
     53  *  <li> When done, close the health channel by calling {@link #disconnectChannel}
     54  *  and unregister the application configuration calling
     55  *  {@link #unregisterAppConfiguration}
     56  *
     57  */
     58 public final class BluetoothHealth implements BluetoothProfile {
     59     private static final String TAG = "BluetoothHealth";
     60     private static final boolean DBG = true;
     61     private static final boolean VDBG = false;
     62 
     63     /**
     64      * Health Profile Source Role - the health device.
     65      */
     66     public static final int SOURCE_ROLE = 1 << 0;
     67 
     68     /**
     69      * Health Profile Sink Role the device talking to the health device.
     70      */
     71     public static final int SINK_ROLE = 1 << 1;
     72 
     73     /**
     74      * Health Profile - Channel Type used - Reliable
     75      */
     76     public static final int CHANNEL_TYPE_RELIABLE = 10;
     77 
     78     /**
     79      * Health Profile - Channel Type used - Streaming
     80      */
     81     public static final int CHANNEL_TYPE_STREAMING = 11;
     82 
     83     /**
     84      * @hide
     85      */
     86     public static final int CHANNEL_TYPE_ANY = 12;
     87 
     88     /** @hide */
     89     public static final int HEALTH_OPERATION_SUCCESS = 6000;
     90     /** @hide */
     91     public static final int HEALTH_OPERATION_ERROR = 6001;
     92     /** @hide */
     93     public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
     94     /** @hide */
     95     public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
     96     /** @hide */
     97     public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
     98     /** @hide */
     99     public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
    100 
    101     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    102             new IBluetoothStateChangeCallback.Stub() {
    103                 public void onBluetoothStateChange(boolean up) {
    104                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    105                     if (!up) {
    106                         if (VDBG) Log.d(TAG,"Unbinding service...");
    107                         synchronized (mConnection) {
    108                             try {
    109                                 mService = null;
    110                                 mContext.unbindService(mConnection);
    111                             } catch (Exception re) {
    112                                 Log.e(TAG,"",re);
    113                             }
    114                         }
    115                     } else {
    116                         synchronized (mConnection) {
    117                             try {
    118                                 if (mService == null) {
    119                                     if (VDBG) Log.d(TAG,"Binding service...");
    120                                     if (!mContext.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) {
    121                                         Log.e(TAG, "Could not bind to Bluetooth Health Service");
    122                                     }
    123                                 }
    124                             } catch (Exception re) {
    125                                 Log.e(TAG,"",re);
    126                             }
    127                         }
    128                     }
    129                 }
    130         };
    131 
    132 
    133     /**
    134      * Register an application configuration that acts as a Health SINK.
    135      * This is the configuration that will be used to communicate with health devices
    136      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
    137      * the callback is used to notify success or failure if the function returns true.
    138      *
    139      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    140      *
    141      * @param name The friendly name associated with the application or configuration.
    142      * @param dataType The dataType of the Source role of Health Profile to which
    143      *                   the sink wants to connect to.
    144      * @param callback A callback to indicate success or failure of the registration and
    145      *               all operations done on this application configuration.
    146      * @return If true, callback will be called.
    147      */
    148     public boolean registerSinkAppConfiguration(String name, int dataType,
    149             BluetoothHealthCallback callback) {
    150         if (!isEnabled() || name == null) return false;
    151 
    152         if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
    153         return registerAppConfiguration(name, dataType, SINK_ROLE,
    154                 CHANNEL_TYPE_ANY, callback);
    155     }
    156 
    157     /**
    158      * Register an application configuration that acts as a Health SINK or in a Health
    159      * SOURCE role.This is an asynchronous call and so
    160      * the callback is used to notify success or failure if the function returns true.
    161      *
    162      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    163      *
    164      * @param name The friendly name associated with the application or configuration.
    165      * @param dataType The dataType of the Source role of Health Profile.
    166      * @param channelType The channel type. Will be one of
    167      *                              {@link #CHANNEL_TYPE_RELIABLE}  or
    168      *                              {@link #CHANNEL_TYPE_STREAMING}
    169      * @param callback - A callback to indicate success or failure.
    170      * @return If true, callback will be called.
    171      * @hide
    172      */
    173     public boolean registerAppConfiguration(String name, int dataType, int role,
    174             int channelType, BluetoothHealthCallback callback) {
    175         boolean result = false;
    176         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
    177 
    178         if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
    179         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
    180         BluetoothHealthAppConfiguration config =
    181                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
    182 
    183         if (mService != null) {
    184             try {
    185                 result = mService.registerAppConfiguration(config, wrapper);
    186             } catch (RemoteException e) {
    187                 Log.e(TAG, e.toString());
    188             }
    189         } else {
    190             Log.w(TAG, "Proxy not attached to service");
    191             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    192         }
    193         return result;
    194     }
    195 
    196     /**
    197      * Unregister an application configuration that has been registered using
    198      * {@link #registerSinkAppConfiguration}
    199      *
    200      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    201      *
    202      * @param config  The health app configuration
    203      * @return Success or failure.
    204      */
    205     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
    206         boolean result = false;
    207         if (mService != null && isEnabled() && config != null) {
    208             try {
    209                 result = mService.unregisterAppConfiguration(config);
    210             } catch (RemoteException e) {
    211                 Log.e(TAG, e.toString());
    212             }
    213         } else {
    214             Log.w(TAG, "Proxy not attached to service");
    215             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    216         }
    217 
    218         return result;
    219     }
    220 
    221     /**
    222      * Connect to a health device which has the {@link #SOURCE_ROLE}.
    223      * This is an asynchronous call. If this function returns true, the callback
    224      * associated with the application configuration will be called.
    225      *
    226      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    227      *
    228      * @param device The remote Bluetooth device.
    229      * @param config The application configuration which has been registered using
    230      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    231      * @return If true, the callback associated with the application config will be called.
    232      */
    233     public boolean connectChannelToSource(BluetoothDevice device,
    234             BluetoothHealthAppConfiguration config) {
    235         if (mService != null && isEnabled() && isValidDevice(device) &&
    236                 config != null) {
    237             try {
    238                 return mService.connectChannelToSource(device, config);
    239             } catch (RemoteException e) {
    240                 Log.e(TAG, e.toString());
    241             }
    242         } else {
    243             Log.w(TAG, "Proxy not attached to service");
    244             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    245         }
    246         return false;
    247     }
    248 
    249     /**
    250      * Connect to a health device which has the {@link #SINK_ROLE}.
    251      * This is an asynchronous call. If this function returns true, the callback
    252      * associated with the application configuration will be called.
    253      *
    254      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    255      *
    256      * @param device The remote Bluetooth device.
    257      * @param config The application configuration which has been registered using
    258      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    259      * @return If true, the callback associated with the application config will be called.
    260      * @hide
    261      */
    262     public boolean connectChannelToSink(BluetoothDevice device,
    263             BluetoothHealthAppConfiguration config, int channelType) {
    264         if (mService != null && isEnabled() && isValidDevice(device) &&
    265                 config != null) {
    266             try {
    267                 return mService.connectChannelToSink(device, config, channelType);
    268             } catch (RemoteException e) {
    269                 Log.e(TAG, e.toString());
    270             }
    271         } else {
    272             Log.w(TAG, "Proxy not attached to service");
    273             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    274         }
    275         return false;
    276     }
    277 
    278     /**
    279      * Disconnect a connected health channel.
    280      * This is an asynchronous call. If this function returns true, the callback
    281      * associated with the application configuration will be called.
    282      *
    283      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    284      *
    285      * @param device The remote Bluetooth device.
    286      * @param config The application configuration which has been registered using
    287      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    288      * @param channelId The channel id associated with the channel
    289      * @return If true, the callback associated with the application config will be called.
    290      */
    291     public boolean disconnectChannel(BluetoothDevice device,
    292             BluetoothHealthAppConfiguration config, int channelId) {
    293         if (mService != null && isEnabled() && isValidDevice(device) &&
    294                 config != null) {
    295             try {
    296                 return mService.disconnectChannel(device, config, channelId);
    297             } catch (RemoteException e) {
    298                 Log.e(TAG, e.toString());
    299             }
    300         } else {
    301             Log.w(TAG, "Proxy not attached to service");
    302             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    303         }
    304         return false;
    305     }
    306 
    307     /**
    308      * Get the file descriptor of the main channel associated with the remote device
    309      * and application configuration.
    310      *
    311      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    312      *
    313      * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
    314      * when done.
    315      *
    316      * @param device The remote Bluetooth health device
    317      * @param config The application configuration
    318      * @return null on failure, ParcelFileDescriptor on success.
    319      */
    320     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
    321             BluetoothHealthAppConfiguration config) {
    322         if (mService != null && isEnabled() && isValidDevice(device) &&
    323                 config != null) {
    324             try {
    325                 return mService.getMainChannelFd(device, config);
    326             } catch (RemoteException e) {
    327                 Log.e(TAG, e.toString());
    328             }
    329         } else {
    330             Log.w(TAG, "Proxy not attached to service");
    331             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    332         }
    333         return null;
    334     }
    335 
    336     /**
    337      * Get the current connection state of the profile.
    338      *
    339      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    340      *
    341      * This is not specific to any application configuration but represents the connection
    342      * state of the local Bluetooth adapter with the remote device. This can be used
    343      * by applications like status bar which would just like to know the state of the
    344      * local adapter.
    345      *
    346      * @param device Remote bluetooth device.
    347      * @return State of the profile connection. One of
    348      *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    349      *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
    350      */
    351     @Override
    352     public int getConnectionState(BluetoothDevice device) {
    353         if (mService != null && isEnabled() && isValidDevice(device)) {
    354             try {
    355                 return mService.getHealthDeviceConnectionState(device);
    356             } catch (RemoteException e) {
    357                 Log.e(TAG, e.toString());
    358             }
    359         } else {
    360             Log.w(TAG, "Proxy not attached to service");
    361             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    362         }
    363         return STATE_DISCONNECTED;
    364     }
    365 
    366     /**
    367      * Get connected devices for the health profile.
    368      *
    369      * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
    370      *
    371      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    372      *
    373      * This is not specific to any application configuration but represents the connection
    374      * state of the local Bluetooth adapter for this profile. This can be used
    375      * by applications like status bar which would just like to know the state of the
    376      * local adapter.
    377      * @return List of devices. The list will be empty on error.
    378      */
    379     @Override
    380     public List<BluetoothDevice> getConnectedDevices() {
    381         if (mService != null && isEnabled()) {
    382             try {
    383                 return mService.getConnectedHealthDevices();
    384             } catch (RemoteException e) {
    385                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    386                 return new ArrayList<BluetoothDevice>();
    387             }
    388         }
    389         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    390         return new ArrayList<BluetoothDevice>();
    391     }
    392 
    393     /**
    394      * Get a list of devices that match any of the given connection
    395      * states.
    396      *
    397      * <p> If none of the devices match any of the given states,
    398      * an empty list will be returned.
    399      *
    400      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    401      * This is not specific to any application configuration but represents the connection
    402      * state of the local Bluetooth adapter for this profile. This can be used
    403      * by applications like status bar which would just like to know the state of the
    404      * local adapter.
    405      *
    406      * @param states Array of states. States can be one of
    407      *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    408      *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
    409      * @return List of devices. The list will be empty on error.
    410      */
    411     @Override
    412     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    413         if (mService != null && isEnabled()) {
    414             try {
    415                 return mService.getHealthDevicesMatchingConnectionStates(states);
    416             } catch (RemoteException e) {
    417                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    418                 return new ArrayList<BluetoothDevice>();
    419             }
    420         }
    421         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    422         return new ArrayList<BluetoothDevice>();
    423     }
    424 
    425     private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
    426         private BluetoothHealthCallback mCallback;
    427 
    428         public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
    429             mCallback = callback;
    430         }
    431 
    432         @Override
    433         public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
    434                                                          int status) {
    435            mCallback.onHealthAppConfigurationStatusChange(config, status);
    436         }
    437 
    438         @Override
    439         public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
    440                                        BluetoothDevice device, int prevState, int newState,
    441                                        ParcelFileDescriptor fd, int channelId) {
    442             mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
    443                                                  channelId);
    444         }
    445     }
    446 
    447      /** Health Channel Connection State - Disconnected */
    448     public static final int STATE_CHANNEL_DISCONNECTED  = 0;
    449     /** Health Channel Connection State - Connecting */
    450     public static final int STATE_CHANNEL_CONNECTING    = 1;
    451     /** Health Channel Connection State - Connected */
    452     public static final int STATE_CHANNEL_CONNECTED     = 2;
    453     /** Health Channel Connection State - Disconnecting */
    454     public static final int STATE_CHANNEL_DISCONNECTING = 3;
    455 
    456     /** Health App Configuration registration success */
    457     public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
    458     /** Health App Configuration registration failure */
    459     public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
    460     /** Health App Configuration un-registration success */
    461     public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
    462     /** Health App Configuration un-registration failure */
    463     public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
    464 
    465     private Context mContext;
    466     private ServiceListener mServiceListener;
    467     private IBluetoothHealth mService;
    468     BluetoothAdapter mAdapter;
    469 
    470     /**
    471      * Create a BluetoothHealth proxy object.
    472      */
    473     /*package*/ BluetoothHealth(Context context, ServiceListener l) {
    474         mContext = context;
    475         mServiceListener = l;
    476         mAdapter = BluetoothAdapter.getDefaultAdapter();
    477         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    478         if (mgr != null) {
    479             try {
    480                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    481             } catch (RemoteException e) {
    482                 Log.e(TAG,"",e);
    483             }
    484         }
    485 
    486         if (!context.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) {
    487             Log.e(TAG, "Could not bind to Bluetooth Health Service");
    488         }
    489     }
    490 
    491     /*package*/ void close() {
    492         if (VDBG) log("close()");
    493         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    494         if (mgr != null) {
    495             try {
    496                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    497             } catch (Exception e) {
    498                 Log.e(TAG,"",e);
    499             }
    500         }
    501 
    502         synchronized (mConnection) {
    503             if (mService != null) {
    504                 try {
    505                     mService = null;
    506                     mContext.unbindService(mConnection);
    507                 } catch (Exception re) {
    508                     Log.e(TAG,"",re);
    509                 }
    510             }
    511         }
    512         mServiceListener = null;
    513     }
    514 
    515     private ServiceConnection mConnection = new ServiceConnection() {
    516         public void onServiceConnected(ComponentName className, IBinder service) {
    517             if (DBG) Log.d(TAG, "Proxy object connected");
    518             mService = IBluetoothHealth.Stub.asInterface(service);
    519 
    520             if (mServiceListener != null) {
    521                 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
    522             }
    523         }
    524         public void onServiceDisconnected(ComponentName className) {
    525             if (DBG) Log.d(TAG, "Proxy object disconnected");
    526             mService = null;
    527             if (mServiceListener != null) {
    528                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
    529             }
    530         }
    531     };
    532 
    533     private boolean isEnabled() {
    534         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    535 
    536         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
    537         log("Bluetooth is Not enabled");
    538         return false;
    539     }
    540 
    541     private boolean isValidDevice(BluetoothDevice device) {
    542         if (device == null) return false;
    543 
    544         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    545         return false;
    546     }
    547 
    548     private boolean checkAppParam(String name, int role, int channelType,
    549             BluetoothHealthCallback callback) {
    550         if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
    551                 (channelType != CHANNEL_TYPE_RELIABLE &&
    552                 channelType != CHANNEL_TYPE_STREAMING &&
    553                 channelType != CHANNEL_TYPE_ANY) || callback == null) {
    554             return false;
    555         }
    556         if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
    557         return true;
    558     }
    559 
    560     private static void log(String msg) {
    561         Log.d(TAG, msg);
    562     }
    563 }
    564