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                                     doBind();
    121                                 }
    122                             } catch (Exception re) {
    123                                 Log.e(TAG,"",re);
    124                             }
    125                         }
    126                     }
    127                 }
    128         };
    129 
    130 
    131     /**
    132      * Register an application configuration that acts as a Health SINK.
    133      * This is the configuration that will be used to communicate with health devices
    134      * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
    135      * the callback is used to notify success or failure if the function returns true.
    136      *
    137      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    138      *
    139      * @param name The friendly name associated with the application or configuration.
    140      * @param dataType The dataType of the Source role of Health Profile to which
    141      *                   the sink wants to connect to.
    142      * @param callback A callback to indicate success or failure of the registration and
    143      *               all operations done on this application configuration.
    144      * @return If true, callback will be called.
    145      */
    146     public boolean registerSinkAppConfiguration(String name, int dataType,
    147             BluetoothHealthCallback callback) {
    148         if (!isEnabled() || name == null) return false;
    149 
    150         if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
    151         return registerAppConfiguration(name, dataType, SINK_ROLE,
    152                 CHANNEL_TYPE_ANY, callback);
    153     }
    154 
    155     /**
    156      * Register an application configuration that acts as a Health SINK or in a Health
    157      * SOURCE role.This is an asynchronous call and so
    158      * the callback is used to notify success or failure if the function returns true.
    159      *
    160      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    161      *
    162      * @param name The friendly name associated with the application or configuration.
    163      * @param dataType The dataType of the Source role of Health Profile.
    164      * @param channelType The channel type. Will be one of
    165      *                              {@link #CHANNEL_TYPE_RELIABLE}  or
    166      *                              {@link #CHANNEL_TYPE_STREAMING}
    167      * @param callback - A callback to indicate success or failure.
    168      * @return If true, callback will be called.
    169      * @hide
    170      */
    171     public boolean registerAppConfiguration(String name, int dataType, int role,
    172             int channelType, BluetoothHealthCallback callback) {
    173         boolean result = false;
    174         if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
    175 
    176         if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
    177         BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
    178         BluetoothHealthAppConfiguration config =
    179                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
    180 
    181         if (mService != null) {
    182             try {
    183                 result = mService.registerAppConfiguration(config, wrapper);
    184             } catch (RemoteException e) {
    185                 Log.e(TAG, e.toString());
    186             }
    187         } else {
    188             Log.w(TAG, "Proxy not attached to service");
    189             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    190         }
    191         return result;
    192     }
    193 
    194     /**
    195      * Unregister an application configuration that has been registered using
    196      * {@link #registerSinkAppConfiguration}
    197      *
    198      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    199      *
    200      * @param config  The health app configuration
    201      * @return Success or failure.
    202      */
    203     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
    204         boolean result = false;
    205         if (mService != null && isEnabled() && config != null) {
    206             try {
    207                 result = mService.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
    228      *        {@link #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         if (mService != null && isEnabled() && isValidDevice(device) &&
    234                 config != null) {
    235             try {
    236                 return mService.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
    256      *        {@link #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         if (mService != null && isEnabled() && isValidDevice(device) &&
    263                 config != null) {
    264             try {
    265                 return mService.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
    285      *        {@link #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         if (mService != null && isEnabled() && isValidDevice(device) &&
    292                 config != null) {
    293             try {
    294                 return mService.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         if (mService != null && isEnabled() && isValidDevice(device) &&
    321                 config != null) {
    322             try {
    323                 return mService.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
    346      *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    347      *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
    348      */
    349     @Override
    350     public int getConnectionState(BluetoothDevice device) {
    351         if (mService != null && isEnabled() && isValidDevice(device)) {
    352             try {
    353                 return mService.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      * @return List of devices. The list will be empty on error.
    376      */
    377     @Override
    378     public List<BluetoothDevice> getConnectedDevices() {
    379         if (mService != null && isEnabled()) {
    380             try {
    381                 return mService.getConnectedHealthDevices();
    382             } catch (RemoteException e) {
    383                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    384                 return new ArrayList<BluetoothDevice>();
    385             }
    386         }
    387         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    388         return new ArrayList<BluetoothDevice>();
    389     }
    390 
    391     /**
    392      * Get a list of devices that match any of the given connection
    393      * states.
    394      *
    395      * <p> If none of the devices match any of the given states,
    396      * an empty list will be returned.
    397      *
    398      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    399      * This is not specific to any application configuration but represents the connection
    400      * state of the local Bluetooth adapter for this profile. This can be used
    401      * by applications like status bar which would just like to know the state of the
    402      * local adapter.
    403      *
    404      * @param states Array of states. States can be one of
    405      *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
    406      *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
    407      * @return List of devices. The list will be empty on error.
    408      */
    409     @Override
    410     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    411         if (mService != null && isEnabled()) {
    412             try {
    413                 return mService.getHealthDevicesMatchingConnectionStates(states);
    414             } catch (RemoteException e) {
    415                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    416                 return new ArrayList<BluetoothDevice>();
    417             }
    418         }
    419         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    420         return new ArrayList<BluetoothDevice>();
    421     }
    422 
    423     private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
    424         private BluetoothHealthCallback mCallback;
    425 
    426         public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
    427             mCallback = callback;
    428         }
    429 
    430         @Override
    431         public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
    432                                                          int status) {
    433            mCallback.onHealthAppConfigurationStatusChange(config, status);
    434         }
    435 
    436         @Override
    437         public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
    438                                        BluetoothDevice device, int prevState, int newState,
    439                                        ParcelFileDescriptor fd, int channelId) {
    440             mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
    441                                                  channelId);
    442         }
    443     }
    444 
    445      /** Health Channel Connection State - Disconnected */
    446     public static final int STATE_CHANNEL_DISCONNECTED  = 0;
    447     /** Health Channel Connection State - Connecting */
    448     public static final int STATE_CHANNEL_CONNECTING    = 1;
    449     /** Health Channel Connection State - Connected */
    450     public static final int STATE_CHANNEL_CONNECTED     = 2;
    451     /** Health Channel Connection State - Disconnecting */
    452     public static final int STATE_CHANNEL_DISCONNECTING = 3;
    453 
    454     /** Health App Configuration registration success */
    455     public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
    456     /** Health App Configuration registration failure */
    457     public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
    458     /** Health App Configuration un-registration success */
    459     public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
    460     /** Health App Configuration un-registration failure */
    461     public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
    462 
    463     private Context mContext;
    464     private ServiceListener mServiceListener;
    465     private IBluetoothHealth mService;
    466     BluetoothAdapter mAdapter;
    467 
    468     /**
    469      * Create a BluetoothHealth proxy object.
    470      */
    471     /*package*/ BluetoothHealth(Context context, ServiceListener l) {
    472         mContext = context;
    473         mServiceListener = l;
    474         mAdapter = BluetoothAdapter.getDefaultAdapter();
    475         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    476         if (mgr != null) {
    477             try {
    478                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    479             } catch (RemoteException e) {
    480                 Log.e(TAG,"",e);
    481             }
    482         }
    483 
    484         doBind();
    485     }
    486 
    487     boolean doBind() {
    488         Intent intent = new Intent(IBluetoothHealth.class.getName());
    489         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    490         intent.setComponent(comp);
    491         if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
    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