Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2008 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.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.os.RemoteException;
     26 import android.os.IBinder;
     27 import android.util.Log;
     28 
     29 /**
     30  * The Android Bluetooth API is not finalized, and *will* change. Use at your
     31  * own risk.
     32  *
     33  * Public API for controlling the Bluetooth Headset Service. This includes both
     34  * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
     35  * attempt a handsfree connection first, and fall back to headset.
     36  *
     37  * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
     38  * Service via IPC.
     39  *
     40  * Creating a BluetoothHeadset object will create a binding with the
     41  * BluetoothHeadset service. Users of this object should call close() when they
     42  * are finished with the BluetoothHeadset, so that this proxy object can unbind
     43  * from the service.
     44  *
     45  * This BluetoothHeadset object is not immediately bound to the
     46  * BluetoothHeadset service. Use the ServiceListener interface to obtain a
     47  * notification when it is bound, this is especially important if you wish to
     48  * immediately call methods on BluetoothHeadset after construction.
     49  *
     50  * Android only supports one connected Bluetooth Headset at a time.
     51  *
     52  * @hide
     53  */
     54 public final class BluetoothHeadset {
     55 
     56     private static final String TAG = "BluetoothHeadset";
     57     private static final boolean DBG = false;
     58 
     59     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     60     public static final String ACTION_STATE_CHANGED =
     61             "android.bluetooth.headset.action.STATE_CHANGED";
     62     /**
     63      * TODO(API release): Consider incorporating as new state in
     64      * HEADSET_STATE_CHANGED
     65      */
     66     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     67     public static final String ACTION_AUDIO_STATE_CHANGED =
     68             "android.bluetooth.headset.action.AUDIO_STATE_CHANGED";
     69     public static final String EXTRA_STATE =
     70             "android.bluetooth.headset.extra.STATE";
     71     public static final String EXTRA_PREVIOUS_STATE =
     72             "android.bluetooth.headset.extra.PREVIOUS_STATE";
     73     public static final String EXTRA_AUDIO_STATE =
     74             "android.bluetooth.headset.extra.AUDIO_STATE";
     75 
     76     /** Extra to be used with the Headset State change intent.
     77      * This will be used only when Headset state changes to
     78      * {@link #STATE_DISCONNECTED} from any previous state.
     79      * This extra field is optional and will be used when
     80      * we have deterministic information regarding whether
     81      * the disconnect was initiated by the remote device or
     82      * by the local adapter.
     83      */
     84     public static final String EXTRA_DISCONNECT_INITIATOR =
     85             "android.bluetooth.headset.extra.DISCONNECT_INITIATOR";
     86 
     87     /**
     88      * TODO(API release): Consider incorporating as new state in
     89      * HEADSET_STATE_CHANGED
     90      */
     91     private IBluetoothHeadset mService;
     92     private final Context mContext;
     93     private final ServiceListener mServiceListener;
     94 
     95     /** There was an error trying to obtain the state */
     96     public static final int STATE_ERROR        = -1;
     97     /** No headset currently connected */
     98     public static final int STATE_DISCONNECTED = 0;
     99     /** Connection attempt in progress */
    100     public static final int STATE_CONNECTING   = 1;
    101     /** A headset is currently connected */
    102     public static final int STATE_CONNECTED    = 2;
    103 
    104     /** A SCO audio channel is not established */
    105     public static final int AUDIO_STATE_DISCONNECTED = 0;
    106     /** A SCO audio channel is established */
    107     public static final int AUDIO_STATE_CONNECTED = 1;
    108 
    109     public static final int RESULT_FAILURE = 0;
    110     public static final int RESULT_SUCCESS = 1;
    111     /** Connection canceled before completion. */
    112     public static final int RESULT_CANCELED = 2;
    113 
    114     /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */
    115     public static final int REMOTE_DISCONNECT = 0;
    116     public static final int LOCAL_DISCONNECT = 1;
    117 
    118 
    119     /** Default priority for headsets for which we will accept
    120      * incoming connections and auto-connect. */
    121     public static final int PRIORITY_AUTO_CONNECT = 1000;
    122     /** Default priority for headsets for which we will accept
    123      * incoming connections but not auto-connect. */
    124     public static final int PRIORITY_ON = 100;
    125     /** Default priority for headsets that should not be auto-connected
    126      * and not allow incoming connections. */
    127     public static final int PRIORITY_OFF = 0;
    128     /** Default priority when not set or when the device is unpaired */
    129     public static final int PRIORITY_UNDEFINED = -1;
    130 
    131     /**
    132      * An interface for notifying BluetoothHeadset IPC clients when they have
    133      * been connected to the BluetoothHeadset service.
    134      */
    135     public interface ServiceListener {
    136         /**
    137          * Called to notify the client when this proxy object has been
    138          * connected to the BluetoothHeadset service. Clients must wait for
    139          * this callback before making IPC calls on the BluetoothHeadset
    140          * service.
    141          */
    142         public void onServiceConnected();
    143 
    144         /**
    145          * Called to notify the client that this proxy object has been
    146          * disconnected from the BluetoothHeadset service. Clients must not
    147          * make IPC calls on the BluetoothHeadset service after this callback.
    148          * This callback will currently only occur if the application hosting
    149          * the BluetoothHeadset service, but may be called more often in future.
    150          */
    151         public void onServiceDisconnected();
    152     }
    153 
    154     /**
    155      * Create a BluetoothHeadset proxy object.
    156      */
    157     public BluetoothHeadset(Context context, ServiceListener l) {
    158         mContext = context;
    159         mServiceListener = l;
    160         if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
    161             Log.e(TAG, "Could not bind to Bluetooth Headset Service");
    162         }
    163     }
    164 
    165     protected void finalize() throws Throwable {
    166         try {
    167             close();
    168         } finally {
    169             super.finalize();
    170         }
    171     }
    172 
    173     /**
    174      * Close the connection to the backing service.
    175      * Other public functions of BluetoothHeadset will return default error
    176      * results once close() has been called. Multiple invocations of close()
    177      * are ok.
    178      */
    179     public synchronized void close() {
    180         if (DBG) log("close()");
    181         if (mConnection != null) {
    182             mContext.unbindService(mConnection);
    183             mConnection = null;
    184         }
    185     }
    186 
    187     /**
    188      * Get the current state of the Bluetooth Headset service.
    189      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
    190      *         object is currently not connected to the Headset service.
    191      */
    192     public int getState(BluetoothDevice device) {
    193         if (DBG) log("getState()");
    194         if (mService != null) {
    195             try {
    196                 return mService.getState(device);
    197             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    198         } else {
    199             Log.w(TAG, "Proxy not attached to service");
    200             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    201         }
    202         return BluetoothHeadset.STATE_ERROR;
    203     }
    204 
    205     /**
    206      * Get the BluetoothDevice for the current headset.
    207      * @return current headset, or null if not in connected or connecting
    208      *         state, or if this proxy object is not connected to the Headset
    209      *         service.
    210      */
    211     public BluetoothDevice getCurrentHeadset() {
    212         if (DBG) log("getCurrentHeadset()");
    213         if (mService != null) {
    214             try {
    215                 return mService.getCurrentHeadset();
    216             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    217         } else {
    218             Log.w(TAG, "Proxy not attached to service");
    219             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    220         }
    221         return null;
    222     }
    223 
    224     /**
    225      * Request to initiate a connection to a headset.
    226      * This call does not block. Fails if a headset is already connecting
    227      * or connected.
    228      * Initiates auto-connection if device is null. Tries to connect to all
    229      * devices with priority greater than PRIORITY_AUTO in descending order.
    230      * @param device device to connect to, or null to auto-connect last connected
    231      *               headset
    232      * @return       false if there was a problem initiating the connection
    233      *               procedure, and no further HEADSET_STATE_CHANGED intents
    234      *               will be expected.
    235      */
    236     public boolean connectHeadset(BluetoothDevice device) {
    237         if (DBG) log("connectHeadset(" + device + ")");
    238         if (mService != null) {
    239             try {
    240                 if (mService.connectHeadset(device)) {
    241                     return true;
    242                 }
    243             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    244         } else {
    245             Log.w(TAG, "Proxy not attached to service");
    246             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    247         }
    248         return false;
    249     }
    250 
    251     /**
    252      * Returns true if the specified headset is connected (does not include
    253      * connecting). Returns false if not connected, or if this proxy object
    254      * if not currently connected to the headset service.
    255      */
    256     public boolean isConnected(BluetoothDevice device) {
    257         if (DBG) log("isConnected(" + device + ")");
    258         if (mService != null) {
    259             try {
    260                 return mService.isConnected(device);
    261             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    262         } else {
    263             Log.w(TAG, "Proxy not attached to service");
    264             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    265         }
    266         return false;
    267     }
    268 
    269     /**
    270      * Disconnects the current headset. Currently this call blocks, it may soon
    271      * be made asynchronous. Returns false if this proxy object is
    272      * not currently connected to the Headset service.
    273      */
    274     public boolean disconnectHeadset(BluetoothDevice device) {
    275         if (DBG) log("disconnectHeadset()");
    276         if (mService != null) {
    277             try {
    278                 mService.disconnectHeadset(device);
    279                 return true;
    280             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    281         } else {
    282             Log.w(TAG, "Proxy not attached to service");
    283             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    284         }
    285         return false;
    286     }
    287 
    288     /**
    289      * Start BT Voice Recognition mode, and set up Bluetooth audio path.
    290      * Returns false if there is no headset connected, or if the
    291      * connected headset does not support voice recognition, or on
    292      * error.
    293      */
    294     public boolean startVoiceRecognition() {
    295         if (DBG) log("startVoiceRecognition()");
    296         if (mService != null) {
    297             try {
    298                 return mService.startVoiceRecognition();
    299             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    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      * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
    309      * Returns false if there is no headset connected, or the connected
    310      * headset is not in voice recognition mode, or on error.
    311      */
    312     public boolean stopVoiceRecognition() {
    313         if (DBG) log("stopVoiceRecognition()");
    314         if (mService != null) {
    315             try {
    316                 return mService.stopVoiceRecognition();
    317             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    318         } else {
    319             Log.w(TAG, "Proxy not attached to service");
    320             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    321         }
    322         return false;
    323     }
    324 
    325     /**
    326      * Set priority of headset.
    327      * Priority is a non-negative integer. By default paired headsets will have
    328      * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
    329      * Headsets with priority greater than zero will be auto-connected, and
    330      * incoming connections will be accepted (if no other headset is
    331      * connected).
    332      * Auto-connection occurs at the following events: boot, incoming phone
    333      * call, outgoing phone call.
    334      * Headsets with priority equal to zero, or that are unpaired, are not
    335      * auto-connected.
    336      * Incoming connections are ignored regardless of priority if there is
    337      * already a headset connected.
    338      * @param device paired headset
    339      * @param priority Integer priority, for example PRIORITY_AUTO or
    340      *                 PRIORITY_NONE
    341      * @return true if successful, false if there was some error
    342      */
    343     public boolean setPriority(BluetoothDevice device, int priority) {
    344         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    345         if (mService != null) {
    346             try {
    347                 return mService.setPriority(device, priority);
    348             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    349         } else {
    350             Log.w(TAG, "Proxy not attached to service");
    351             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    352         }
    353         return false;
    354     }
    355 
    356     /**
    357      * Get priority of headset.
    358      * @param device headset
    359      * @return non-negative priority, or negative error code on error
    360      */
    361     public int getPriority(BluetoothDevice device) {
    362         if (DBG) log("getPriority(" + device + ")");
    363         if (mService != null) {
    364             try {
    365                 return mService.getPriority(device);
    366             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    367         } else {
    368             Log.w(TAG, "Proxy not attached to service");
    369             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    370         }
    371         return -1;
    372     }
    373 
    374     /**
    375      * Get battery usage hint for Bluetooth Headset service.
    376      * This is a monotonically increasing integer. Wraps to 0 at
    377      * Integer.MAX_INT, and at boot.
    378      * Current implementation returns the number of AT commands handled since
    379      * boot. This is a good indicator for spammy headset/handsfree units that
    380      * can keep the device awake by polling for cellular status updates. As a
    381      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
    382      * @return monotonically increasing battery usage hint, or a negative error
    383      *         code on error
    384      * @hide
    385      */
    386     public int getBatteryUsageHint() {
    387         if (DBG) log("getBatteryUsageHint()");
    388         if (mService != null) {
    389             try {
    390                 return mService.getBatteryUsageHint();
    391             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    392         } else {
    393             Log.w(TAG, "Proxy not attached to service");
    394             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    395         }
    396         return -1;
    397     }
    398     /**
    399      * Indicates if current platform supports voice dialing over bluetooth SCO.
    400      * @return true if voice dialing over bluetooth is supported, false otherwise.
    401      * @hide
    402      */
    403     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
    404         return context.getResources().getBoolean(
    405                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
    406     }
    407 
    408     /**
    409      * Cancel the outgoing connection.
    410      * @hide
    411      */
    412     public boolean cancelConnectThread() {
    413         if (DBG) log("cancelConnectThread");
    414         if (mService != null) {
    415             try {
    416                 return mService.cancelConnectThread();
    417             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    418         } else {
    419             Log.w(TAG, "Proxy not attached to service");
    420             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    421         }
    422         return false;
    423     }
    424 
    425     /**
    426      * Accept the incoming connection.
    427      * @hide
    428      */
    429     public boolean acceptIncomingConnect(BluetoothDevice device) {
    430         if (DBG) log("acceptIncomingConnect");
    431         if (mService != null) {
    432             try {
    433                 return mService.acceptIncomingConnect(device);
    434             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    435         } else {
    436             Log.w(TAG, "Proxy not attached to service");
    437             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    438         }
    439         return false;
    440     }
    441 
    442     /**
    443      * Create the connect thread the incoming connection.
    444      * @hide
    445      */
    446     public boolean createIncomingConnect(BluetoothDevice device) {
    447         if (DBG) log("createIncomingConnect");
    448         if (mService != null) {
    449             try {
    450                 return mService.createIncomingConnect(device);
    451             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    452         } else {
    453             Log.w(TAG, "Proxy not attached to service");
    454             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    455         }
    456         return false;
    457     }
    458 
    459     /**
    460      * Reject the incoming connection.
    461      * @hide
    462      */
    463     public boolean rejectIncomingConnect(BluetoothDevice device) {
    464         if (DBG) log("rejectIncomingConnect");
    465         if (mService != null) {
    466             try {
    467                 return mService.rejectIncomingConnect(device);
    468             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    469         } else {
    470             Log.w(TAG, "Proxy not attached to service");
    471             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    472         }
    473         return false;
    474     }
    475 
    476     /**
    477      * Connect to a Bluetooth Headset.
    478      * Note: This is an internal function and shouldn't be exposed
    479      * @hide
    480      */
    481     public boolean connectHeadsetInternal(BluetoothDevice device) {
    482         if (DBG) log("connectHeadsetInternal");
    483         if (mService != null) {
    484             try {
    485                 return mService.connectHeadsetInternal(device);
    486             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    487         } else {
    488             Log.w(TAG, "Proxy not attached to service");
    489             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    490         }
    491         return false;
    492     }
    493 
    494     /**
    495      * Disconnect a Bluetooth Headset.
    496      * Note: This is an internal function and shouldn't be exposed
    497      * @hide
    498      */
    499     public boolean disconnectHeadsetInternal(BluetoothDevice device) {
    500         if (DBG) log("disconnectHeadsetInternal");
    501         if (mService != null) {
    502             try {
    503                  return mService.disconnectHeadsetInternal(device);
    504             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    505         } else {
    506             Log.w(TAG, "Proxy not attached to service");
    507             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    508         }
    509         return false;
    510     }
    511     private ServiceConnection mConnection = new ServiceConnection() {
    512         public void onServiceConnected(ComponentName className, IBinder service) {
    513             if (DBG) Log.d(TAG, "Proxy object connected");
    514             mService = IBluetoothHeadset.Stub.asInterface(service);
    515             if (mServiceListener != null) {
    516                 mServiceListener.onServiceConnected();
    517             }
    518         }
    519         public void onServiceDisconnected(ComponentName className) {
    520             if (DBG) Log.d(TAG, "Proxy object disconnected");
    521             mService = null;
    522             if (mServiceListener != null) {
    523                 mServiceListener.onServiceDisconnected();
    524             }
    525         }
    526     };
    527 
    528     private static void log(String msg) {
    529         Log.d(TAG, msg);
    530     }
    531 }
    532