/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.bluetooth;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

Public API to control Hands Free Profile (HFP role only).

This class defines methods that shall be used by application to manage profile connection, calls states and calls actions.

@hide
/** * Public API to control Hands Free Profile (HFP role only). * <p> * This class defines methods that shall be used by application to manage profile * connection, calls states and calls actions. * <p> * * @hide */
public final class BluetoothHeadsetClient implements BluetoothProfile { private static final String TAG = "BluetoothHeadsetClient"; private static final boolean DBG = true; private static final boolean VDBG = false;
Intent sent whenever connection to remote changes.

It includes two extras: BluetoothProfile.EXTRA_PREVIOUS_STATE and BluetoothProfile.EXTRA_STATE, which are mandatory.

There are also non mandatory feature extras: EXTRA_AG_FEATURE_3WAY_CALLING, EXTRA_AG_FEATURE_VOICE_RECOGNITION, EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT, EXTRA_AG_FEATURE_REJECT_CALL, EXTRA_AG_FEATURE_ECC, EXTRA_AG_FEATURE_RESPONSE_AND_HOLD, EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL, EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, EXTRA_AG_FEATURE_MERGE, EXTRA_AG_FEATURE_MERGE_AND_DETACH, sent as boolean values only when EXTRA_STATE is set to STATE_CONNECTED.

Note that features supported by AG are being sent as booleans with value true, and not supported ones are not being sent at all.

/** * Intent sent whenever connection to remote changes. * * <p>It includes two extras: * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code> * and <code>BluetoothProfile.EXTRA_STATE</code>, which * are mandatory. * <p>There are also non mandatory feature extras: * {@link #EXTRA_AG_FEATURE_3WAY_CALLING}, * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}, * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}, * {@link #EXTRA_AG_FEATURE_REJECT_CALL}, * {@link #EXTRA_AG_FEATURE_ECC}, * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD}, * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL}, * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL}, * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT}, * {@link #EXTRA_AG_FEATURE_MERGE}, * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}, * sent as boolean values only when <code>EXTRA_STATE</code> * is set to <code>STATE_CONNECTED</code>.</p> * * <p>Note that features supported by AG are being sent as * booleans with value <code>true</code>, * and not supported ones are <strong>not</strong> being sent at all.</p> */
public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED";
Intent sent whenever audio state changes.

It includes two mandatory extras: BluetoothProfile.EXTRA_STATE, BluetoothProfile.EXTRA_PREVIOUS_STATE, with possible values: STATE_AUDIO_CONNECTING, STATE_AUDIO_CONNECTED, STATE_AUDIO_DISCONNECTED

When EXTRA_STATE is set to STATE_AUDIO_CONNECTED, it also includes EXTRA_AUDIO_WBS indicating wide band speech support.

/** * Intent sent whenever audio state changes. * * <p>It includes two mandatory extras: * {@link BluetoothProfile#EXTRA_STATE}, * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}, * with possible values: * {@link #STATE_AUDIO_CONNECTING}, * {@link #STATE_AUDIO_CONNECTED}, * {@link #STATE_AUDIO_DISCONNECTED}</p> * <p>When <code>EXTRA_STATE</code> is set * to </code>STATE_AUDIO_CONNECTED</code>, * it also includes {@link #EXTRA_AUDIO_WBS} * indicating wide band speech support.</p> */
public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
Intent sending updates of the Audio Gateway state. Each extra is being sent only when value it represents has been changed recently on AG.

It can contain one or more of the following extras: EXTRA_NETWORK_STATUS, EXTRA_NETWORK_SIGNAL_STRENGTH, EXTRA_NETWORK_ROAMING, EXTRA_BATTERY_LEVEL, EXTRA_OPERATOR_NAME, EXTRA_VOICE_RECOGNITION, EXTRA_IN_BAND_RING

/** * Intent sending updates of the Audio Gateway state. * Each extra is being sent only when value it * represents has been changed recently on AG. * <p>It can contain one or more of the following extras: * {@link #EXTRA_NETWORK_STATUS}, * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH}, * {@link #EXTRA_NETWORK_ROAMING}, * {@link #EXTRA_BATTERY_LEVEL}, * {@link #EXTRA_OPERATOR_NAME}, * {@link #EXTRA_VOICE_RECOGNITION}, * {@link #EXTRA_IN_BAND_RING}</p> */
public static final String ACTION_AG_EVENT = "android.bluetooth.headsetclient.profile.action.AG_EVENT";
Intent sent whenever state of a call changes.

It includes: EXTRA_CALL, with value of BluetoothHeadsetClientCall instance, representing actual call state.

/** * Intent sent whenever state of a call changes. * * <p>It includes: * {@link #EXTRA_CALL}, * with value of {@link BluetoothHeadsetClientCall} instance, * representing actual call state.</p> */
public static final String ACTION_CALL_CHANGED = "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";
Intent that notifies about the result of the last issued action. Please note that not every action results in explicit action result code being sent. Instead other notifications about new Audio Gateway state might be sent, like ACTION_AG_EVENT with EXTRA_VOICE_RECOGNITION value when for example user started voice recognition from HF unit.
/** * Intent that notifies about the result of the last issued action. * Please note that not every action results in explicit action result code being sent. * Instead other notifications about new Audio Gateway state might be sent, * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value * when for example user started voice recognition from HF unit. */
public static final String ACTION_RESULT = "android.bluetooth.headsetclient.profile.action.RESULT";
Intent that notifies about the number attached to the last voice tag recorded on AG.

It contains: EXTRA_NUMBER, with a String value representing phone number.

/** * Intent that notifies about the number attached to the last voice tag * recorded on AG. * * <p>It contains: * {@link #EXTRA_NUMBER}, * with a <code>String</code> value representing phone number.</p> */
public static final String ACTION_LAST_VTAG = "android.bluetooth.headsetclient.profile.action.LAST_VTAG"; public static final int STATE_AUDIO_DISCONNECTED = 0; public static final int STATE_AUDIO_CONNECTING = 1; public static final int STATE_AUDIO_CONNECTED = 2;
Extra with information if connected audio is WBS.

Possible values: true, false.

/** * Extra with information if connected audio is WBS. * <p>Possible values: <code>true</code>, * <code>false</code>.</p> */
public static final String EXTRA_AUDIO_WBS = "android.bluetooth.headsetclient.extra.AUDIO_WBS";
Extra for AG_EVENT indicates network status.

Value: 0 - network unavailable, 1 - network available

/** * Extra for AG_EVENT indicates network status. * <p>Value: 0 - network unavailable, * 1 - network available </p> */
public static final String EXTRA_NETWORK_STATUS = "android.bluetooth.headsetclient.extra.NETWORK_STATUS";
Extra for AG_EVENT intent indicates network signal strength.

Value: Integer representing signal strength.

/** * Extra for AG_EVENT intent indicates network signal strength. * <p>Value: <code>Integer</code> representing signal strength.</p> */
public static final String EXTRA_NETWORK_SIGNAL_STRENGTH = "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH";
Extra for AG_EVENT intent indicates roaming state.

Value: 0 - no roaming 1 - active roaming

/** * Extra for AG_EVENT intent indicates roaming state. * <p>Value: 0 - no roaming * 1 - active roaming</p> */
public static final String EXTRA_NETWORK_ROAMING = "android.bluetooth.headsetclient.extra.NETWORK_ROAMING";
Extra for AG_EVENT intent indicates the battery level.

Value: Integer representing signal strength.

/** * Extra for AG_EVENT intent indicates the battery level. * <p>Value: <code>Integer</code> representing signal strength.</p> */
public static final String EXTRA_BATTERY_LEVEL = "android.bluetooth.headsetclient.extra.BATTERY_LEVEL";
Extra for AG_EVENT intent indicates operator name.

Value: String representing operator name.

/** * Extra for AG_EVENT intent indicates operator name. * <p>Value: <code>String</code> representing operator name.</p> */
public static final String EXTRA_OPERATOR_NAME = "android.bluetooth.headsetclient.extra.OPERATOR_NAME";
Extra for AG_EVENT intent indicates voice recognition state.

Value: 0 - voice recognition stopped, 1 - voice recognition started.

/** * Extra for AG_EVENT intent indicates voice recognition state. * <p>Value: * 0 - voice recognition stopped, * 1 - voice recognition started.</p> */
public static final String EXTRA_VOICE_RECOGNITION = "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION";
Extra for AG_EVENT intent indicates in band ring state.

Value: 0 - in band ring tone not supported, or 1 - in band ring tone supported.

/** * Extra for AG_EVENT intent indicates in band ring state. * <p>Value: * 0 - in band ring tone not supported, or * 1 - in band ring tone supported.</p> */
public static final String EXTRA_IN_BAND_RING = "android.bluetooth.headsetclient.extra.IN_BAND_RING";
Extra for AG_EVENT intent indicates subscriber info.

Value: String containing subscriber information.

/** * Extra for AG_EVENT intent indicates subscriber info. * <p>Value: <code>String</code> containing subscriber information.</p> */
public static final String EXTRA_SUBSCRIBER_INFO = "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO";
Extra for AG_CALL_CHANGED intent indicates the BluetoothHeadsetClientCall object that has changed.
/** * Extra for AG_CALL_CHANGED intent indicates the * {@link BluetoothHeadsetClientCall} object that has changed. */
public static final String EXTRA_CALL = "android.bluetooth.headsetclient.extra.CALL";
Extra for ACTION_LAST_VTAG intent.

Value: String representing phone number corresponding to last voice tag recorded on AG

/** * Extra for ACTION_LAST_VTAG intent. * <p>Value: <code>String</code> representing phone number * corresponding to last voice tag recorded on AG</p> */
public static final String EXTRA_NUMBER = "android.bluetooth.headsetclient.extra.NUMBER"; /** * Extra for ACTION_RESULT intent that shows the result code of * last issued action. * <p>Possible results: * {@link #ACTION_RESULT_OK}, * {@link #ACTION_RESULT_ERROR}, * {@link #ACTION_RESULT_ERROR_NO_CARRIER}, * {@link #ACTION_RESULT_ERROR_BUSY}, * {@link #ACTION_RESULT_ERROR_NO_ANSWER}, * {@link #ACTION_RESULT_ERROR_DELAYED}, * {@link #ACTION_RESULT_ERROR_BLACKLISTED}, * {@link #ACTION_RESULT_ERROR_CME}</p> */ public static final String EXTRA_RESULT_CODE = "android.bluetooth.headsetclient.extra.RESULT_CODE";
Extra for ACTION_RESULT intent that shows the extended result code of last issued action.

Value: Integer - error code.

/** * Extra for ACTION_RESULT intent that shows the extended result code of * last issued action. * <p>Value: <code>Integer</code> - error code.</p> */
public static final String EXTRA_CME_CODE = "android.bluetooth.headsetclient.extra.CME_CODE"; /* Extras for AG_FEATURES, extras type is boolean */ // TODO verify if all of those are actually useful
AG feature: three way calling.
/** * AG feature: three way calling. */
public static final String EXTRA_AG_FEATURE_3WAY_CALLING = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING";
AG feature: voice recognition.
/** * AG feature: voice recognition. */
public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION";
AG feature: fetching phone number for voice tagging procedure.
/** * AG feature: fetching phone number for voice tagging procedure. */
public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT";
AG feature: ability to reject incoming call.
/** * AG feature: ability to reject incoming call. */
public static final String EXTRA_AG_FEATURE_REJECT_CALL = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL";
AG feature: enhanced call handling (terminate specific call, private consultation).
/** * AG feature: enhanced call handling (terminate specific call, private consultation). */
public static final String EXTRA_AG_FEATURE_ECC = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC";
AG feature: response and hold.
/** * AG feature: response and hold. */
public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD";
AG call handling feature: accept held or waiting call in three way calling scenarios.
/** * AG call handling feature: accept held or waiting call in three way calling scenarios. */
public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL";
AG call handling feature: release held or waiting call in three way calling scenarios.
/** * AG call handling feature: release held or waiting call in three way calling scenarios. */
public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL";
AG call handling feature: release active call and accept held or waiting call in three way calling scenarios.
/** * AG call handling feature: release active call and accept held or waiting call in three way * calling scenarios. */
public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT";
AG call handling feature: merge two calls, held and active - multi party conference mode.
/** * AG call handling feature: merge two calls, held and active - multi party conference mode. */
public static final String EXTRA_AG_FEATURE_MERGE = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE";
AG call handling feature: merge calls and disconnect from multi party conversation leaving peers connected to each other. Note that this feature needs to be supported by mobile network operator as it requires connection and billing transfer.
/** * AG call handling feature: merge calls and disconnect from multi party * conversation leaving peers connected to each other. * Note that this feature needs to be supported by mobile network operator * as it requires connection and billing transfer. */
public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH = "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH"; /* Action result codes */ public static final int ACTION_RESULT_OK = 0; public static final int ACTION_RESULT_ERROR = 1; public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2; public static final int ACTION_RESULT_ERROR_BUSY = 3; public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4; public static final int ACTION_RESULT_ERROR_DELAYED = 5; public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6; public static final int ACTION_RESULT_ERROR_CME = 7; /* Detailed CME error codes */ public static final int CME_PHONE_FAILURE = 0; public static final int CME_NO_CONNECTION_TO_PHONE = 1; public static final int CME_OPERATION_NOT_ALLOWED = 3; public static final int CME_OPERATION_NOT_SUPPORTED = 4; public static final int CME_PHSIM_PIN_REQUIRED = 5; public static final int CME_PHFSIM_PIN_REQUIRED = 6; public static final int CME_PHFSIM_PUK_REQUIRED = 7; public static final int CME_SIM_NOT_INSERTED = 10; public static final int CME_SIM_PIN_REQUIRED = 11; public static final int CME_SIM_PUK_REQUIRED = 12; public static final int CME_SIM_FAILURE = 13; public static final int CME_SIM_BUSY = 14; public static final int CME_SIM_WRONG = 15; public static final int CME_INCORRECT_PASSWORD = 16; public static final int CME_SIM_PIN2_REQUIRED = 17; public static final int CME_SIM_PUK2_REQUIRED = 18; public static final int CME_MEMORY_FULL = 20; public static final int CME_INVALID_INDEX = 21; public static final int CME_NOT_FOUND = 22; public static final int CME_MEMORY_FAILURE = 23; public static final int CME_TEXT_STRING_TOO_LONG = 24; public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; public static final int CME_DIAL_STRING_TOO_LONG = 26; public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; public static final int CME_NO_NETWORK_SERVICE = 30; public static final int CME_NETWORK_TIMEOUT = 31; public static final int CME_EMERGENCY_SERVICE_ONLY = 32; public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33; public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34; public static final int CME_SIP_RESPONSE_CODE = 35; public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; public static final int CME_HIDDEN_KEY_REQUIRED = 48; public static final int CME_EAP_NOT_SUPPORTED = 49; public static final int CME_INCORRECT_PARAMETERS = 50; /* Action policy for other calls when accepting call */ public static final int CALL_ACCEPT_NONE = 0; public static final int CALL_ACCEPT_HOLD = 1; public static final int CALL_ACCEPT_TERMINATE = 2; private Context mContext; private ServiceListener mServiceListener; private volatile IBluetoothHeadsetClient mService; private BluetoothAdapter mAdapter; private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { @Override public void onBluetoothStateChange(boolean up) { if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); if (!up) { if (VDBG) Log.d(TAG, "Unbinding service..."); synchronized (mConnection) { try { mService = null; mContext.unbindService(mConnection); } catch (Exception re) { Log.e(TAG, "", re); } } } else { synchronized (mConnection) { try { if (mService == null) { if (VDBG) Log.d(TAG, "Binding service..."); Intent intent = new Intent( IBluetoothHeadsetClient.class.getName()); doBind(); } } catch (Exception re) { Log.e(TAG, "", re); } } } } };
Create a BluetoothHeadsetClient proxy object.
/** * Create a BluetoothHeadsetClient proxy object. */
/*package*/ BluetoothHeadsetClient(Context context, ServiceListener l) { mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); IBluetoothManager mgr = mAdapter.getBluetoothManager(); if (mgr != null) { try { mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); } catch (RemoteException e) { Log.e(TAG, "", e); } } doBind(); } boolean doBind() { Intent intent = new Intent(IBluetoothHeadsetClient.class.getName()); ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, mContext.getUser())) { Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent); return false; } return true; }
Close the connection to the backing service. Other public functions of BluetoothHeadsetClient will return default error results once close() has been called. Multiple invocations of close() are ok.
/** * Close the connection to the backing service. * Other public functions of BluetoothHeadsetClient will return default error * results once close() has been called. Multiple invocations of close() * are ok. */
/*package*/ void close() { if (VDBG) log("close()"); IBluetoothManager mgr = mAdapter.getBluetoothManager(); if (mgr != null) { try { mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); } catch (Exception e) { Log.e(TAG, "", e); } } synchronized (mConnection) { if (mService != null) { try { mService = null; mContext.unbindService(mConnection); } catch (Exception re) { Log.e(TAG, "", re); } } } mServiceListener = null; }
Connects to remote device. Currently, the system supports only 1 connection. So, in case of the second connection, this implementation will disconnect already connected device automatically and will process the new one.
Params:
  • device – a remote device we want connect to
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CONNECTION_STATE_CHANGED intent.
/** * Connects to remote device. * * Currently, the system supports only 1 connection. So, in case of the * second connection, this implementation will disconnect already connected * device automatically and will process the new one. * * @param device a remote device we want connect to * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. */
public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.connect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Disconnects remote device
Params:
  • device – a remote device we want disconnect
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CONNECTION_STATE_CHANGED intent.
/** * Disconnects remote device * * @param device a remote device we want disconnect * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. */
public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Return the list of connected remote devices
Returns:list of connected devices; empty list if nothing is connected.
/** * Return the list of connected remote devices * * @return list of connected devices; empty list if nothing is connected. */
@Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); }
Returns list of remote devices in a particular state
Params:
  • states – collection of states
Returns:list of devices that state matches the states listed in states; empty list if nothing matches the states
/** * Returns list of remote devices in a particular state * * @param states collection of states * @return list of devices that state matches the states listed in <code>states</code>; empty * list if nothing matches the <code>states</code> */
@Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); }
Returns state of the device
Params:
  • device – a remote device
Returns:the state of connection of the device
/** * Returns state of the <code>device</code> * * @param device a remote device * @return the state of connection of the device */
@Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getConnectionState(" + device + ")"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; }
Set priority of the profile The device should already be paired.
/** * Set priority of the profile * * The device should already be paired. */
public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Get the priority of the profile.
/** * Get the priority of the profile. */
public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return PRIORITY_OFF; } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return PRIORITY_OFF; }
Starts voice recognition.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_AG_EVENT intent.

Feature required for successful execution is being reported by: EXTRA_AG_FEATURE_VOICE_RECOGNITION. This method invocation will fail silently when feature is not supported.

/** * Starts voice recognition. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. * * <p>Feature required for successful execution is being reported by: {@link * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature * is not supported.</p> */
public boolean startVoiceRecognition(BluetoothDevice device) { if (DBG) log("startVoiceRecognition()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.startVoiceRecognition(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Stops voice recognition.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_AG_EVENT intent.

Feature required for successful execution is being reported by: EXTRA_AG_FEATURE_VOICE_RECOGNITION. This method invocation will fail silently when feature is not supported.

/** * Stops voice recognition. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. * * <p>Feature required for successful execution is being reported by: {@link * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature * is not supported.</p> */
public boolean stopVoiceRecognition(BluetoothDevice device) { if (DBG) log("stopVoiceRecognition()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.stopVoiceRecognition(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Returns list of all calls in any state.
Params:
  • device – remote device
Returns:list of calls; empty list if none call exists
/** * Returns list of all calls in any state. * * @param device remote device * @return list of calls; empty list if none call exists */
public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { if (DBG) log("getCurrentCalls()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getCurrentCalls(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; }
Returns list of current values of AG indicators.
Params:
  • device – remote device
Returns:bundle of AG indicators; null if device is not in CONNECTED state
/** * Returns list of current values of AG indicators. * * @param device remote device * @return bundle of AG indicators; null if device is not in CONNECTED state */
public Bundle getCurrentAgEvents(BluetoothDevice device) { if (DBG) log("getCurrentCalls()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getCurrentAgEvents(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; }
Accepts a call
Params:
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CALL_CHANGED intent.
/** * Accepts a call * * @param device remote device * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE}, * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE} * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. */
public boolean acceptCall(BluetoothDevice device, int flag) { if (DBG) log("acceptCall()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.acceptCall(device, flag); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Holds a call.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CALL_CHANGED intent.
/** * Holds a call. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. */
public boolean holdCall(BluetoothDevice device) { if (DBG) log("holdCall()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.holdCall(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Rejects a call.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CALL_CHANGED intent.

Feature required for successful execution is being reported by: EXTRA_AG_FEATURE_REJECT_CALL. This method invocation will fail silently when feature is not supported.

/** * Rejects a call. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. * * <p>Feature required for successful execution is being reported by: {@link * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not * supported.</p> */
public boolean rejectCall(BluetoothDevice device) { if (DBG) log("rejectCall()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.rejectCall(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Terminates a specified call. Works only when Extended Call Control is supported by Audio Gateway.
Params:
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CALL_CHANGED intent.

Feature required for successful execution is being reported by: EXTRA_AG_FEATURE_ECC. This method invocation will fail silently when feature is not supported.

/** * Terminates a specified call. * * Works only when Extended Call Control is supported by Audio Gateway. * * @param device remote device * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active * calls. * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. * * <p>Feature required for successful execution is being reported by: {@link * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not * supported.</p> */
public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { if (DBG) log("terminateCall()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.terminateCall(device, call); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Enters private mode with a specified call. Works only when Extended Call Control is supported by Audio Gateway.
Params:
  • device – remote device
  • index – index of the call to connect in private mode
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CALL_CHANGED intent.

Feature required for successful execution is being reported by: EXTRA_AG_FEATURE_ECC. This method invocation will fail silently when feature is not supported.

/** * Enters private mode with a specified call. * * Works only when Extended Call Control is supported by Audio Gateway. * * @param device remote device * @param index index of the call to connect in private mode * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. * * <p>Feature required for successful execution is being reported by: {@link * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not * supported.</p> */
public boolean enterPrivateMode(BluetoothDevice device, int index) { if (DBG) log("enterPrivateMode()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.enterPrivateMode(device, index); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Performs explicit call transfer. That means connect other calls and disconnect.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_CALL_CHANGED intent.

Feature required for successful execution is being reported by: EXTRA_AG_FEATURE_MERGE_AND_DETACH. This method invocation will fail silently when feature is not supported.

/** * Performs explicit call transfer. * * That means connect other calls and disconnect. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. * * <p>Feature required for successful execution is being reported by: {@link * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature * is not supported.</p> */
public boolean explicitCallTransfer(BluetoothDevice device) { if (DBG) log("explicitCallTransfer()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.explicitCallTransfer(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Places a call with specified number.
Params:
  • device – remote device
  • number – valid phone number
Returns:BluetoothHeadsetClientCall call if command has been issued successfully; null otherwise; upon completion HFP sends ACTION_CALL_CHANGED intent in case of success; ACTION_RESULT is sent otherwise;
/** * Places a call with specified number. * * @param device remote device * @param number valid phone number * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise; */
public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { if (DBG) log("dial()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.dial(device, number); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; }
Sends DTMF code. Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,#
Params:
  • device – remote device
  • code – ASCII code
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_RESULT intent;
/** * Sends DTMF code. * * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# * * @param device remote device * @param code ASCII code * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent; */
public boolean sendDTMF(BluetoothDevice device, byte code) { if (DBG) log("sendDTMF()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.sendDTMF(device, code); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Get a number corresponding to last voice tag recorded on AG.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_LAST_VTAG or ACTION_RESULT intent;

Feature required for successful execution is being reported by: EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT. This method invocation will fail silently when feature is not supported.

/** * Get a number corresponding to last voice tag recorded on AG. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT} * intent; * * <p>Feature required for successful execution is being reported by: {@link * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when * feature is not supported.</p> */
public boolean getLastVoiceTagNumber(BluetoothDevice device) { if (DBG) log("getLastVoiceTagNumber()"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getLastVoiceTagNumber(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; }
Returns current audio state of Audio Gateway. Note: This is an internal function and shouldn't be exposed
/** * Returns current audio state of Audio Gateway. * * Note: This is an internal function and shouldn't be exposed */
public int getAudioState(BluetoothDevice device) { if (VDBG) log("getAudioState"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { return service.getAudioState(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; }
Sets whether audio routing is allowed.
Params:
  • device – remote device
  • allowed – if routing is allowed to the device Note: This is an internal function and shouldn't be exposed
/** * Sets whether audio routing is allowed. * * @param device remote device * @param allowed if routing is allowed to the device Note: This is an internal function and * shouldn't be exposed */
public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { if (VDBG) log("setAudioRouteAllowed"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { service.setAudioRouteAllowed(device, allowed); } catch (RemoteException e) { Log.e(TAG, e.toString()); } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } }
Returns whether audio routing is allowed.
Params:
  • device – remote device
Returns:whether the command succeeded Note: This is an internal function and shouldn't be exposed
/** * Returns whether audio routing is allowed. * * @param device remote device * @return whether the command succeeded Note: This is an internal function and shouldn't be * exposed */
public boolean getAudioRouteAllowed(BluetoothDevice device) { if (VDBG) log("getAudioRouteAllowed"); final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { return service.getAudioRouteAllowed(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } return false; }
Initiates a connection of audio channel. It setup SCO channel with remote connected Handsfree AG device.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_AUDIO_STATE_CHANGED intent;
/** * Initiates a connection of audio channel. * * It setup SCO channel with remote connected Handsfree AG device. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; */
public boolean connectAudio(BluetoothDevice device) { final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { return service.connectAudio(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } return false; }
Disconnects audio channel. It tears down the SCO channel from remote AG device.
Params:
  • device – remote device
Returns:true if command has been issued successfully; false otherwise; upon completion HFP sends ACTION_AUDIO_STATE_CHANGED intent;
/** * Disconnects audio channel. * * It tears down the SCO channel from remote AG device. * * @param device remote device * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; */
public boolean disconnectAudio(BluetoothDevice device) { final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { return service.disconnectAudio(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } return false; }
Get Audio Gateway features
Params:
  • device – remote device
Returns:bundle of AG features; null if no service or AG not connected
/** * Get Audio Gateway features * * @param device remote device * @return bundle of AG features; null if no service or AG not connected */
public Bundle getCurrentAgFeatures(BluetoothDevice device) { final IBluetoothHeadsetClient service = mService; if (service != null && isEnabled()) { try { return service.getCurrentAgFeatures(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } } else { Log.w(TAG, "Proxy not attached to service"); if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); } return null; } private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service)); if (mServiceListener != null) { mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT, BluetoothHeadsetClient.this); } } @Override public void onServiceDisconnected(ComponentName className) { if (DBG) Log.d(TAG, "Proxy object disconnected"); mService = null; if (mServiceListener != null) { mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET_CLIENT); } } }; private boolean isEnabled() { return mAdapter.getState() == BluetoothAdapter.STATE_ON; } private static boolean isValidDevice(BluetoothDevice device) { return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } private static void log(String msg) { Log.d(TAG, msg); } }