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.util.Log;
     27 
     28 import java.util.ArrayList;
     29 import java.util.List;
     30 
     31 /**
     32  * Public API for Bluetooth Health Profile.
     33  *
     34  * <p>BluetoothHealth is a proxy object for controlling the Bluetooth
     35  * Service via IPC.
     36  *
     37  * <p> How to connect to a health device which is acting in the source role.
     38  *  <li> Use {@link BluetoothAdapter#getProfileProxy} to get
     39  *  the BluetoothHealth proxy object. </li>
     40  *  <li> Create an {@link BluetoothHealth} callback and call
     41  *  {@link #registerSinkAppConfiguration} to register an application
     42  *  configuration </li>
     43  *  <li> Pair with the remote device. This currently needs to be done manually
     44  *  from Bluetooth Settings </li>
     45  *  <li> Connect to a health device using {@link #connectChannelToSource}. Some
     46  *  devices will connect the channel automatically. The {@link BluetoothHealth}
     47  *  callback will inform the application of channel state change. </li>
     48  *  <li> Use the file descriptor provided with a connected channel to read and
     49  *  write data to the health channel. </li>
     50  *  <li> The received data needs to be interpreted using a health manager which
     51  *  implements the IEEE 11073-xxxxx specifications.
     52  *  <li> When done, close the health channel by calling {@link #disconnectChannel}
     53  *  and unregister the application configuration calling
     54  *  {@link #unregisterAppConfiguration}
     55  *
     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     final private 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
    140      *                   the sink wants to connect to.
    141      * @param callback A callback to indicate success or failure of the registration and
    142      *               all 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
    164      *                              {@link #CHANNEL_TYPE_RELIABLE}  or
    165      *                              {@link #CHANNEL_TYPE_STREAMING}
    166      * @param callback - A callback to indicate success or failure.
    167      * @return If true, callback will be called.
    168      * @hide
    169      */
    170     public boolean registerAppConfiguration(String name, int dataType, int role,
    171             int channelType, BluetoothHealthCallback callback) {
    172         boolean result = false;
    173         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
    174 
    175         if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
    176         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
    177         BluetoothHealthAppConfiguration config =
    178                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
    179 
    180         if (mService != null) {
    181             try {
    182                 result = mService.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         if (mService != null && isEnabled() && config != null) {
    205             try {
    206                 result = mService.unregisterAppConfiguration(config);
    207             } catch (RemoteException e) {
    208                 Log.e(TAG, e.toString());
    209             }
    210         } else {
    211             Log.w(TAG, "Proxy not attached to service");
    212             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    213         }
    214 
    215         return result;
    216     }
    217 
    218     /**
    219      * Connect to a health device which has the {@link #SOURCE_ROLE}.
    220      * This is an asynchronous call. If this function returns true, the callback
    221      * associated with the application configuration will be called.
    222      *
    223      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    224      *
    225      * @param device The remote Bluetooth device.
    226      * @param config The application configuration which has been registered using
    227      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    228      * @return If true, the callback associated with the application config will be called.
    229      */
    230     public boolean connectChannelToSource(BluetoothDevice device,
    231             BluetoothHealthAppConfiguration config) {
    232         if (mService != null && isEnabled() && isValidDevice(device) &&
    233                 config != null) {
    234             try {
    235                 return mService.connectChannelToSource(device, config);
    236             } catch (RemoteException e) {
    237                 Log.e(TAG, e.toString());
    238             }
    239         } else {
    240             Log.w(TAG, "Proxy not attached to service");
    241             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    242         }
    243         return false;
    244     }
    245 
    246     /**
    247      * Connect to a health device which has the {@link #SINK_ROLE}.
    248      * This is an asynchronous call. If this function returns true, the callback
    249      * associated with the application configuration will be called.
    250      *
    251      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    252      *
    253      * @param device The remote Bluetooth device.
    254      * @param config The application configuration which has been registered using
    255      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    256      * @return If true, the callback associated with the application config will be called.
    257      * @hide
    258      */
    259     public boolean connectChannelToSink(BluetoothDevice device,
    260             BluetoothHealthAppConfiguration config, int channelType) {
    261         if (mService != null && isEnabled() && isValidDevice(device) &&
    262                 config != null) {
    263             try {
    264                 return mService.connectChannelToSink(device, config, channelType);
    265             } catch (RemoteException e) {
    266                 Log.e(TAG, e.toString());
    267             }
    268         } else {
    269             Log.w(TAG, "Proxy not attached to service");
    270             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    271         }
    272         return false;
    273     }
    274 
    275     /**
    276      * Disconnect a connected health channel.
    277      * This is an asynchronous call. If this function returns true, the callback
    278      * associated with the application configuration will be called.
    279      *
    280      *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    281      *
    282      * @param device The remote Bluetooth device.
    283      * @param config The application configuration which has been registered using
    284      *        {@link #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
    285      * @param channelId The channel id associated with the channel
    286      * @return If true, the callback associated with the application config will be called.
    287      */
    288     public boolean disconnectChannel(BluetoothDevice device,
    289             BluetoothHealthAppConfiguration config, int channelId) {
    290         if (mService != null && isEnabled() && isValidDevice(device) &&
    291                 config != null) {
    292             try {
    293                 return mService.disconnectChannel(device, config, channelId);
    294             } catch (RemoteException e) {
    295                 Log.e(TAG, e.toString());
    296             }
    297         } else {
    298             Log.w(TAG, "Proxy not attached to service");
    299             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    300         }
    301         return false;
    302     }
    303 
    304     /**
    305      * Get the file descriptor of the main channel associated with the remote device
    306      * and application configuration.
    307      *
    308      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    309      *
    310      * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
    311      * when done.
    312      *
    313      * @param device The remote Bluetooth health device
    314      * @param config The application configuration
    315      * @return null on failure, ParcelFileDescriptor on success.
    316      */
    317     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
    318             BluetoothHealthAppConfiguration config) {
    319         if (mService != null && isEnabled() && isValidDevice(device) &&
    320                 config != null) {
    321             try {
    322                 return mService.getMainChannelFd(device, config);
    323             } catch (RemoteException e) {
    324                 Log.e(TAG, e.toString());
    325             }
    326         } else {
    327             Log.w(TAG, "Proxy not attached to service");
    328             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    329         }
    330         return null;
    331     }
    332 
    333     /**
    334      * Get the current connection state of the profile.
    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 with the remote device. This can be used
    340      * by applications like status bar which would just like to know the state of the
    341      * local adapter.
    342      *
    343      * @param device Remote bluetooth device.
    344      * @return State of the profile connection. One of
    345      *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    346      *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
    347      */
    348     @Override
    349     public int getConnectionState(BluetoothDevice device) {
    350         if (mService != null && isEnabled() && isValidDevice(device)) {
    351             try {
    352                 return mService.getHealthDeviceConnectionState(device);
    353             } catch (RemoteException e) {
    354                 Log.e(TAG, e.toString());
    355             }
    356         } else {
    357             Log.w(TAG, "Proxy not attached to service");
    358             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    359         }
    360         return STATE_DISCONNECTED;
    361     }
    362 
    363     /**
    364      * Get connected devices for the health profile.
    365      *
    366      * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
    367      *
    368      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    369      *
    370      * This is not specific to any application configuration but represents the connection
    371      * state of the local Bluetooth adapter for this profile. This can be used
    372      * by applications like status bar which would just like to know the state of the
    373      * local adapter.
    374      * @return List of devices. The list will be empty on error.
    375      */
    376     @Override
    377     public List<BluetoothDevice> getConnectedDevices() {
    378         if (mService != null && isEnabled()) {
    379             try {
    380                 return mService.getConnectedHealthDevices();
    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     /**
    391      * Get a list of devices that match any of the given connection
    392      * states.
    393      *
    394      * <p> If none of the devices match any of the given states,
    395      * an empty list will be returned.
    396      *
    397      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    398      * This is not specific to any application configuration but represents the connection
    399      * state of the local Bluetooth adapter for this profile. This can be used
    400      * by applications like status bar which would just like to know the state of the
    401      * local adapter.
    402      *
    403      * @param states Array of states. States can be one of
    404      *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    405      *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
    406      * @return List of devices. The list will be empty on error.
    407      */
    408     @Override
    409     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    410         if (mService != null && isEnabled()) {
    411             try {
    412                 return mService.getHealthDevicesMatchingConnectionStates(states);
    413             } catch (RemoteException e) {
    414                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    415                 return new ArrayList<BluetoothDevice>();
    416             }
    417         }
    418         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    419         return new ArrayList<BluetoothDevice>();
    420     }
    421 
    422     private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
    423         private BluetoothHealthCallback mCallback;
    424 
    425         public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
    426             mCallback = callback;
    427         }
    428 
    429         @Override
    430         public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
    431                                                          int status) {
    432            mCallback.onHealthAppConfigurationStatusChange(config, status);
    433         }
    434 
    435         @Override
    436         public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
    437                                        BluetoothDevice device, int prevState, int newState,
    438                                        ParcelFileDescriptor fd, int channelId) {
    439             mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
    440                                                  channelId);
    441         }
    442     }
    443 
    444      /** Health Channel Connection State - Disconnected */
    445     public static final int STATE_CHANNEL_DISCONNECTED  = 0;
    446     /** Health Channel Connection State - Connecting */
    447     public static final int STATE_CHANNEL_CONNECTING    = 1;
    448     /** Health Channel Connection State - Connected */
    449     public static final int STATE_CHANNEL_CONNECTED     = 2;
    450     /** Health Channel Connection State - Disconnecting */
    451     public static final int STATE_CHANNEL_DISCONNECTING = 3;
    452 
    453     /** Health App Configuration registration success */
    454     public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
    455     /** Health App Configuration registration failure */
    456     public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
    457     /** Health App Configuration un-registration success */
    458     public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
    459     /** Health App Configuration un-registration failure */
    460     public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
    461 
    462     private Context mContext;
    463     private ServiceListener mServiceListener;
    464     private IBluetoothHealth mService;
    465     BluetoothAdapter mAdapter;
    466 
    467     /**
    468      * Create a BluetoothHealth proxy object.
    469      */
    470     /*package*/ BluetoothHealth(Context context, ServiceListener l) {
    471         mContext = context;
    472         mServiceListener = l;
    473         mAdapter = BluetoothAdapter.getDefaultAdapter();
    474         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    475         if (mgr != null) {
    476             try {
    477                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    478             } catch (RemoteException e) {
    479                 Log.e(TAG,"",e);
    480             }
    481         }
    482 
    483         doBind();
    484     }
    485 
    486     boolean doBind() {
    487         Intent intent = new Intent(IBluetoothHealth.class.getName());
    488         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    489         intent.setComponent(comp);
    490         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    491                 android.os.Process.myUserHandle())) {
    492             Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
    493             return false;
    494         }
    495         return true;
    496     }
    497 
    498     /*package*/ void close() {
    499         if (VDBG) log("close()");
    500         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    501         if (mgr != null) {
    502             try {
    503                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    504             } catch (Exception e) {
    505                 Log.e(TAG,"",e);
    506             }
    507         }
    508 
    509         synchronized (mConnection) {
    510             if (mService != null) {
    511                 try {
    512                     mService = null;
    513                     mContext.unbindService(mConnection);
    514                 } catch (Exception re) {
    515                     Log.e(TAG,"",re);
    516                 }
    517             }
    518         }
    519         mServiceListener = null;
    520     }
    521 
    522     private final ServiceConnection mConnection = new ServiceConnection() {
    523         public void onServiceConnected(ComponentName className, IBinder service) {
    524             if (DBG) Log.d(TAG, "Proxy object connected");
    525             mService = IBluetoothHealth.Stub.asInterface(service);
    526 
    527             if (mServiceListener != null) {
    528                 mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
    529             }
    530         }
    531         public void onServiceDisconnected(ComponentName className) {
    532             if (DBG) Log.d(TAG, "Proxy object disconnected");
    533             mService = null;
    534             if (mServiceListener != null) {
    535                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
    536             }
    537         }
    538     };
    539 
    540     private boolean isEnabled() {
    541         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    542 
    543         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
    544         log("Bluetooth is Not enabled");
    545         return false;
    546     }
    547 
    548     private boolean isValidDevice(BluetoothDevice device) {
    549         if (device == null) return false;
    550 
    551         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    552         return false;
    553     }
    554 
    555     private boolean checkAppParam(String name, int role, int channelType,
    556             BluetoothHealthCallback callback) {
    557         if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
    558                 (channelType != CHANNEL_TYPE_RELIABLE &&
    559                 channelType != CHANNEL_TYPE_STREAMING &&
    560                 channelType != CHANNEL_TYPE_ANY) || callback == null) {
    561             return false;
    562         }
    563         if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
    564         return true;
    565     }
    566 
    567     private static void log(String msg) {
    568         Log.d(TAG, msg);
    569     }
    570 }
    571