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.Binder;
     24 import android.os.IBinder;
     25 import android.os.ParcelFileDescriptor;
     26 import android.os.RemoteException;
     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 public final class BluetoothHealth implements BluetoothProfile {
     58     private static final String TAG = "BluetoothHealth";
     59     private static final boolean DBG = true;
     60     private static final boolean VDBG = false;
     61 
     62     /**
     63      * Health Profile Source Role - the health device.
     64      */
     65     public static final int SOURCE_ROLE = 1 << 0;
     66 
     67     /**
     68      * Health Profile Sink Role the device talking to the health device.
     69      */
     70     public static final int SINK_ROLE = 1 << 1;
     71 
     72     /**
     73      * Health Profile - Channel Type used - Reliable
     74      */
     75     public static final int CHANNEL_TYPE_RELIABLE = 10;
     76 
     77     /**
     78      * Health Profile - Channel Type used - Streaming
     79      */
     80     public static final int CHANNEL_TYPE_STREAMING = 11;
     81 
     82     /**
     83      * @hide
     84      */
     85     public static final int CHANNEL_TYPE_ANY = 12;
     86 
     87     /** @hide */
     88     public static final int HEALTH_OPERATION_SUCCESS = 6000;
     89     /** @hide */
     90     public static final int HEALTH_OPERATION_ERROR = 6001;
     91     /** @hide */
     92     public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
     93     /** @hide */
     94     public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
     95     /** @hide */
     96     public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
     97     /** @hide */
     98     public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
     99 
    100     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    101             new IBluetoothStateChangeCallback.Stub() {
    102                 public void onBluetoothStateChange(boolean up) {
    103                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    104                     if (!up) {
    105                         if (VDBG) Log.d(TAG, "Unbinding service...");
    106                         synchronized (mConnection) {
    107                             try {
    108                                 mService = null;
    109                                 mContext.unbindService(mConnection);
    110                             } catch (Exception re) {
    111                                 Log.e(TAG, "", re);
    112                             }
    113                         }
    114                     } else {
    115                         synchronized (mConnection) {
    116                             try {
    117                                 if (mService == null) {
    118                                     if (VDBG) Log.d(TAG, "Binding service...");
    119                                     doBind();
    120                                 }
    121                             } catch (Exception re) {
    122                                 Log.e(TAG, "", re);
    123                             }
    124                         }
    125                     }
    126                 }
    127             };
    128 
    129 
    130     /**
    131      * Register an application configuration that acts as a Health SINK.
    132      * This is the configuration that will be used to communicate with health devices
    133      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
    134      * the callback is used to notify success or failure if the function returns true.
    135      *
    136      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    137      *
    138      * @param name The friendly name associated with the application or configuration.
    139      * @param dataType The dataType of the Source role of Health Profile to which the sink wants to
    140      * connect to.
    141      * @param callback A callback to indicate success or failure of the registration and all
    142      * operations done on this application configuration.
    143      * @return If true, callback will be called.
    144      */
    145     public boolean registerSinkAppConfiguration(String name, int dataType,
    146             BluetoothHealthCallback callback) {
    147         if (!isEnabled() || name == null) return false;
    148 
    149         if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
    150         return registerAppConfiguration(name, dataType, SINK_ROLE,
    151                 CHANNEL_TYPE_ANY, callback);
    152     }
    153 
    154     /**
    155      * Register an application configuration that acts as a Health SINK or in a Health
    156      * SOURCE role.This is an asynchronous call and so
    157      * the callback is used to notify success or failure if the function returns true.
    158      *
    159      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    160      *
    161      * @param name The friendly name associated with the application or configuration.
    162      * @param dataType The dataType of the Source role of Health Profile.
    163      * @param channelType The channel type. Will be one of {@link #CHANNEL_TYPE_RELIABLE}  or {@link
    164      * #CHANNEL_TYPE_STREAMING}
    165      * @param callback - A callback to indicate success or failure.
    166      * @return If true, callback will be called.
    167      * @hide
    168      */
    169     public boolean registerAppConfiguration(String name, int dataType, int role,
    170             int channelType, BluetoothHealthCallback callback) {
    171         boolean result = false;
    172         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
    173 
    174         if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
    175         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
    176         BluetoothHealthAppConfiguration config =
    177                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
    178 
    179         final IBluetoothHealth service = mService;
    180         if (service != null) {
    181             try {
    182                 result = service.registerAppConfiguration(config, wrapper);
    183             } catch (RemoteException e) {
    184                 Log.e(TAG, e.toString());
    185             }
    186         } else {
    187             Log.w(TAG, "Proxy not attached to service");
    188             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    189         }
    190         return result;
    191     }
    192 
    193     /**
    194      * Unregister an application configuration that has been registered using
    195      * {@link #registerSinkAppConfiguration}
    196      *
    197      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    198      *
    199      * @param config The health app configuration
    200      * @return Success or failure.
    201      */
    202     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
    203         boolean result = false;
    204         final IBluetoothHealth service = mService;
    205         if (service != null && isEnabled() && config != null) {
    206             try {
    207                 result = service.unregisterAppConfiguration(config);
    208             } catch (RemoteException e) {
    209                 Log.e(TAG, e.toString());
    210             }
    211         } else {
    212             Log.w(TAG, "Proxy not attached to service");
    213             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    214         }
    215 
    216         return result;
    217     }
    218 
    219     /**
    220      * Connect to a health device which has the {@link #SOURCE_ROLE}.
    221      * This is an asynchronous call. If this function returns true, the callback
    222      * associated with the application configuration will be called.
    223      *
    224      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    225      *
    226      * @param device The remote Bluetooth device.
    227      * @param config The application configuration which has been registered using {@link
    228      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    229      * @return If true, the callback associated with the application config will be called.
    230      */
    231     public boolean connectChannelToSource(BluetoothDevice device,
    232             BluetoothHealthAppConfiguration config) {
    233         final IBluetoothHealth service = mService;
    234         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
    235             try {
    236                 return service.connectChannelToSource(device, config);
    237             } catch (RemoteException e) {
    238                 Log.e(TAG, e.toString());
    239             }
    240         } else {
    241             Log.w(TAG, "Proxy not attached to service");
    242             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    243         }
    244         return false;
    245     }
    246 
    247     /**
    248      * Connect to a health device which has the {@link #SINK_ROLE}.
    249      * This is an asynchronous call. If this function returns true, the callback
    250      * associated with the application configuration will be called.
    251      *
    252      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    253      *
    254      * @param device The remote Bluetooth device.
    255      * @param config The application configuration which has been registered using {@link
    256      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    257      * @return If true, the callback associated with the application config will be called.
    258      * @hide
    259      */
    260     public boolean connectChannelToSink(BluetoothDevice device,
    261             BluetoothHealthAppConfiguration config, int channelType) {
    262         final IBluetoothHealth service = mService;
    263         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
    264             try {
    265                 return service.connectChannelToSink(device, config, channelType);
    266             } catch (RemoteException e) {
    267                 Log.e(TAG, e.toString());
    268             }
    269         } else {
    270             Log.w(TAG, "Proxy not attached to service");
    271             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    272         }
    273         return false;
    274     }
    275 
    276     /**
    277      * Disconnect a connected health channel.
    278      * This is an asynchronous call. If this function returns true, the callback
    279      * associated with the application configuration will be called.
    280      *
    281      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    282      *
    283      * @param device The remote Bluetooth device.
    284      * @param config The application configuration which has been registered using {@link
    285      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    286      * @param channelId The channel id associated with the channel
    287      * @return If true, the callback associated with the application config will be called.
    288      */
    289     public boolean disconnectChannel(BluetoothDevice device,
    290             BluetoothHealthAppConfiguration config, int channelId) {
    291         final IBluetoothHealth service = mService;
    292         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
    293             try {
    294                 return service.disconnectChannel(device, config, channelId);
    295             } catch (RemoteException e) {
    296                 Log.e(TAG, e.toString());
    297             }
    298         } else {
    299             Log.w(TAG, "Proxy not attached to service");
    300             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    301         }
    302         return false;
    303     }
    304 
    305     /**
    306      * Get the file descriptor of the main channel associated with the remote device
    307      * and application configuration.
    308      *
    309      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    310      *
    311      * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
    312      * when done.
    313      *
    314      * @param device The remote Bluetooth health device
    315      * @param config The application configuration
    316      * @return null on failure, ParcelFileDescriptor on success.
    317      */
    318     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
    319             BluetoothHealthAppConfiguration config) {
    320         final IBluetoothHealth service = mService;
    321         if (service != null && isEnabled() && isValidDevice(device) && config != null) {
    322             try {
    323                 return service.getMainChannelFd(device, config);
    324             } catch (RemoteException e) {
    325                 Log.e(TAG, e.toString());
    326             }
    327         } else {
    328             Log.w(TAG, "Proxy not attached to service");
    329             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    330         }
    331         return null;
    332     }
    333 
    334     /**
    335      * Get the current connection state of the profile.
    336      *
    337      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    338      *
    339      * This is not specific to any application configuration but represents the connection
    340      * state of the local Bluetooth adapter with the remote device. This can be used
    341      * by applications like status bar which would just like to know the state of the
    342      * local adapter.
    343      *
    344      * @param device Remote bluetooth device.
    345      * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link
    346      * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
    347      */
    348     @Override
    349     public int getConnectionState(BluetoothDevice device) {
    350         final IBluetoothHealth service = mService;
    351         if (service != null && isEnabled() && isValidDevice(device)) {
    352             try {
    353                 return service.getHealthDeviceConnectionState(device);
    354             } catch (RemoteException e) {
    355                 Log.e(TAG, e.toString());
    356             }
    357         } else {
    358             Log.w(TAG, "Proxy not attached to service");
    359             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    360         }
    361         return STATE_DISCONNECTED;
    362     }
    363 
    364     /**
    365      * Get connected devices for the health profile.
    366      *
    367      * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
    368      *
    369      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    370      *
    371      * This is not specific to any application configuration but represents the connection
    372      * state of the local Bluetooth adapter for this profile. This can be used
    373      * by applications like status bar which would just like to know the state of the
    374      * local adapter.
    375      *
    376      * @return List of devices. The list will be empty on error.
    377      */
    378     @Override
    379     public List<BluetoothDevice> getConnectedDevices() {
    380         final IBluetoothHealth service = mService;
    381         if (service != null && isEnabled()) {
    382             try {
    383                 return service.getConnectedHealthDevices();
    384             } catch (RemoteException e) {
    385                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    386                 return new ArrayList<BluetoothDevice>();
    387             }
    388         }
    389         if (service == 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 {@link #STATE_CONNECTED}, {@link
    407      * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
    408      * @return List of devices. The list will be empty on error.
    409      */
    410     @Override
    411     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    412         final IBluetoothHealth service = mService;
    413         if (service != null && isEnabled()) {
    414             try {
    415                 return service.getHealthDevicesMatchingConnectionStates(states);
    416             } catch (RemoteException e) {
    417                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    418                 return new ArrayList<BluetoothDevice>();
    419             }
    420         }
    421         if (service == 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 volatile 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         doBind();
    487     }
    488 
    489     boolean doBind() {
    490         Intent intent = new Intent(IBluetoothHealth.class.getName());
    491         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    492         intent.setComponent(comp);
    493         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    494                 mContext.getUser())) {
    495             Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
    496             return false;
    497         }
    498         return true;
    499     }
    500 
    501     /*package*/ void close() {
    502         if (VDBG) log("close()");
    503         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    504         if (mgr != null) {
    505             try {
    506                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    507             } catch (Exception e) {
    508                 Log.e(TAG, "", e);
    509             }
    510         }
    511 
    512         synchronized (mConnection) {
    513             if (mService != null) {
    514                 try {
    515                     mService = null;
    516                     mContext.unbindService(mConnection);
    517                 } catch (Exception re) {
    518                     Log.e(TAG, "", re);
    519                 }
    520             }
    521         }
    522         mServiceListener = null;
    523     }
    524 
    525     private final ServiceConnection mConnection = new ServiceConnection() {
    526         public void onServiceConnected(ComponentName className, IBinder service) {
    527             if (DBG) Log.d(TAG, "Proxy object connected");
    528             mService = IBluetoothHealth.Stub.asInterface(Binder.allowBlocking(service));
    529 
    530             if (mServiceListener != null) {
    531                 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
    532             }
    533         }
    534 
    535         public void onServiceDisconnected(ComponentName className) {
    536             if (DBG) Log.d(TAG, "Proxy object disconnected");
    537             mService = null;
    538             if (mServiceListener != null) {
    539                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
    540             }
    541         }
    542     };
    543 
    544     private boolean isEnabled() {
    545         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    546 
    547         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
    548         log("Bluetooth is Not enabled");
    549         return false;
    550     }
    551 
    552     private static boolean isValidDevice(BluetoothDevice device) {
    553         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
    554     }
    555 
    556     private boolean checkAppParam(String name, int role, int channelType,
    557             BluetoothHealthCallback callback) {
    558         if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE)
    559                 || (channelType != CHANNEL_TYPE_RELIABLE && channelType != CHANNEL_TYPE_STREAMING
    560                     && channelType != CHANNEL_TYPE_ANY)
    561                 || callback == null) {
    562             return false;
    563         }
    564         if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
    565         return true;
    566     }
    567 
    568     private static void log(String msg) {
    569         Log.d(TAG, msg);
    570     }
    571 }
    572