Fields Summary |
---|
private static final String | TAG |
private static final boolean | DBG |
private static final boolean | VDBG |
public static final int | TYPE_UNKNOWN |
public static final int | TYPE_HEADSET |
public static final int | TYPE_HANDSFREE |
private final android.content.Context | mContext |
private final com.android.internal.telephony.Phone | mPhone |
private android.telephony.ServiceState | mServiceState |
private android.bluetooth.HeadsetBase | mHeadset |
private int | mHeadsetType |
private boolean | mAudioPossible |
private android.bluetooth.ScoSocket | mIncomingSco |
private android.bluetooth.ScoSocket | mOutgoingSco |
private android.bluetooth.ScoSocket | mConnectedSco |
private com.android.internal.telephony.Call | mForegroundCall |
private com.android.internal.telephony.Call | mBackgroundCall |
private com.android.internal.telephony.Call | mRingingCall |
private android.media.AudioManager | mAudioManager |
private android.os.PowerManager | mPowerManager |
private boolean | mUserWantsAudio |
private android.os.PowerManager.WakeLock | mStartCallWakeLock |
private android.os.PowerManager.WakeLock | mStartVoiceRecognitionWakeLock |
private static final int | MAX_CONNECTIONS |
private long | mBgndEarliestConnectionTime |
private boolean | mClip |
private boolean | mIndicatorsEnabled |
private boolean | mCmee |
private long[] | mClccTimestamps |
private boolean[] | mClccUsed |
private boolean | mWaitingForCallStart |
private boolean | mWaitingForVoiceRecognition |
private final BluetoothPhoneState | mPhoneState |
private final BluetoothAtPhonebook | mPhonebook |
private DebugThread | mDebugThread |
private int | mScoGain |
private static android.content.Intent | sVoiceCommandIntent |
private static final String | HEADSET_NREC |
private static final String | HEADSET_NAME |
private int | mRemoteBrsf |
private int | mLocalBrsf |
private static final int | BRSF_AG_THREE_WAY_CALLING |
private static final int | BRSF_AG_EC_NR |
private static final int | BRSF_AG_VOICE_RECOG |
private static final int | BRSF_AG_IN_BAND_RING |
private static final int | BRSF_AG_VOICE_TAG_NUMBE |
private static final int | BRSF_AG_REJECT_CALL |
private static final int | BRSF_AG_ENHANCED_CALL_STATUS |
private static final int | BRSF_AG_ENHANCED_CALL_CONTROL |
private static final int | BRSF_AG_ENHANCED_ERR_RESULT_CODES |
private static final int | BRSF_HF_EC_NR |
private static final int | BRSF_HF_CW_THREE_WAY_CALLING |
private static final int | BRSF_HF_CLIP |
private static final int | BRSF_HF_VOICE_REG_ACT |
private static final int | BRSF_HF_REMOTE_VOL_CONTROL |
private static final int | BRSF_HF_ENHANCED_CALL_STATUS |
private static final int | BRSF_HF_ENHANCED_CALL_CONTROL |
private static final int | SCO_ACCEPTED |
private static final int | SCO_CONNECTED |
private static final int | SCO_CLOSED |
private static final int | CHECK_CALL_STARTED |
private static final int | CHECK_VOICE_RECOGNITION_STARTED |
private final android.os.Handler | mHandler |
private static final int | START_CALL_TIMEOUT |
private static final int | START_VOICE_RECOGNITION_TIMEOUT |
Methods Summary |
---|
private boolean | allowAudioAnytime()
return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME,
false);
|
synchronized void | audioOff()Request to disconnect SCO (audio) connection to bluetooth
headset/handsfree, if one is connected. Does not block.
if (VDBG) log("audioOff()");
if (mConnectedSco != null) {
mAudioManager.setBluetoothScoOn(false);
broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
mConnectedSco.close();
mConnectedSco = null;
}
if (mOutgoingSco != null) {
mOutgoingSco.close();
mOutgoingSco = null;
}
|
synchronized boolean | audioOn()Request to establish SCO (audio) connection to bluetooth
headset/handsfree, if one is connected. Does not block.
Returns false if the user has requested audio off, or if there
is some other immediate problem that will prevent BT audio.
if (VDBG) log("audioOn()");
if (!isHeadsetConnected()) {
if (DBG) log("audioOn(): headset is not connected!");
return false;
}
if (mConnectedSco != null) {
if (DBG) log("audioOn(): audio is already connected");
return true;
}
if (!mUserWantsAudio) {
if (DBG) log("audioOn(): user requested no audio, ignoring");
return false;
}
if (mOutgoingSco != null) {
if (DBG) log("audioOn(): outgoing SCO already in progress");
return true;
}
mOutgoingSco = createScoSocket();
if (!mOutgoingSco.connect(mHeadset.getAddress())) {
mOutgoingSco = null;
}
return true;
|
private void | broadcastAudioStateIntent(int state)
if (VDBG) log("broadcastAudioStateIntent(" + state + ")");
Intent intent = new Intent(BluetoothIntent.HEADSET_AUDIO_STATE_CHANGED_ACTION);
intent.putExtra(BluetoothIntent.HEADSET_AUDIO_STATE, state);
mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
|
private synchronized void | callStarted()
if (mWaitingForCallStart) {
mWaitingForCallStart = false;
sendURC("OK");
if (mStartCallWakeLock.isHeld()) {
mStartCallWakeLock.release();
}
}
|
private void | configAudioParameters()
String name = mHeadset.getName();
if (name == null) {
name = "<unknown>";
}
mAudioManager.setParameter(HEADSET_NAME, name);
mAudioManager.setParameter(HEADSET_NREC, "on");
|
void | connectHeadset(android.bluetooth.HeadsetBase headset, int headsetType)
mHeadset = headset;
mHeadsetType = headsetType;
if (mHeadsetType == TYPE_HEADSET) {
initializeHeadsetAtParser();
} else {
initializeHandsfreeAtParser();
}
headset.startEventThread();
configAudioParameters();
if (inDebug()) {
startDebug();
}
if (isIncallAudio()) {
audioOn();
}
|
private java.lang.String | connectionToClccEntry(int index, com.android.internal.telephony.Connection c)Convert a Connection object into a single +CLCC result
int state;
switch (c.getState()) {
case ACTIVE:
state = 0;
break;
case HOLDING:
state = 1;
break;
case DIALING:
state = 2;
break;
case ALERTING:
state = 3;
break;
case INCOMING:
state = 4;
break;
case WAITING:
state = 5;
break;
default:
return null; // bad state
}
int mpty = 0;
Call call = c.getCall();
if (call != null) {
mpty = call.isMultiparty() ? 1 : 0;
}
int direction = c.isIncoming() ? 1 : 0;
String number = c.getAddress();
int type = -1;
if (number != null) {
type = PhoneNumberUtils.toaFromString(number);
}
String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
if (number != null) {
result += ",\"" + number + "\"," + type;
}
return result;
|
private android.bluetooth.ScoSocket | createScoSocket()
return new ScoSocket(mPowerManager, mHandler, SCO_ACCEPTED, SCO_CONNECTED, SCO_CLOSED);
|
void | disconnectHeadset()
mHeadset = null;
stopDebug();
resetAtState();
|
private synchronized void | expectCallStart() // ms
mWaitingForCallStart = true;
Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED);
mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT);
if (!mStartCallWakeLock.isHeld()) {
mStartCallWakeLock.acquire(START_CALL_TIMEOUT);
}
|
private synchronized void | expectVoiceRecognition() // ms
mWaitingForVoiceRecognition = true;
Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED);
mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT);
if (!mStartVoiceRecognitionWakeLock.isHeld()) {
mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT);
}
|
private synchronized android.bluetooth.AtCommandResult | getClccResult()Build the +CLCC result
The complexity arises from the fact that we need to maintain the same
CLCC index even as a call moves between states.
// Collect all known connections
Connection[] clccConnections = new Connection[MAX_CONNECTIONS]; // indexed by CLCC index
LinkedList<Connection> newConnections = new LinkedList<Connection>();
LinkedList<Connection> connections = new LinkedList<Connection>();
if (mRingingCall.getState().isAlive()) {
connections.addAll(mRingingCall.getConnections());
}
if (mForegroundCall.getState().isAlive()) {
connections.addAll(mForegroundCall.getConnections());
}
if (mBackgroundCall.getState().isAlive()) {
connections.addAll(mBackgroundCall.getConnections());
}
// Mark connections that we already known about
boolean clccUsed[] = new boolean[MAX_CONNECTIONS];
for (int i = 0; i < MAX_CONNECTIONS; i++) {
clccUsed[i] = mClccUsed[i];
mClccUsed[i] = false;
}
for (Connection c : connections) {
boolean found = false;
long timestamp = c.getCreateTime();
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
mClccUsed[i] = true;
found = true;
clccConnections[i] = c;
break;
}
}
if (!found) {
newConnections.add(c);
}
}
// Find a CLCC index for new connections
while (!newConnections.isEmpty()) {
// Find lowest empty index
int i = 0;
while (mClccUsed[i]) i++;
// Find earliest connection
long earliestTimestamp = newConnections.get(0).getCreateTime();
Connection earliestConnection = newConnections.get(0);
for (int j = 0; j < newConnections.size(); j++) {
long timestamp = newConnections.get(j).getCreateTime();
if (timestamp < earliestTimestamp) {
earliestTimestamp = timestamp;
earliestConnection = newConnections.get(j);
}
}
// update
mClccUsed[i] = true;
mClccTimestamps[i] = earliestTimestamp;
clccConnections[i] = earliestConnection;
newConnections.remove(earliestConnection);
}
// Build CLCC
AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
for (int i = 0; i < clccConnections.length; i++) {
if (mClccUsed[i]) {
String clccEntry = connectionToClccEntry(i, clccConnections[i]);
if (clccEntry != null) {
result.addResponse(clccEntry);
}
}
}
return result;
|
void | ignoreRing()
mPhoneState.ignoreRing();
|
private boolean | inDebug()
return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false);
|
private void | initializeHandsfreeAtParser()Register AT Command handlers to implement the Handsfree profile
if (DBG) log("Registering Handsfree AT commands");
AtParser parser = mHeadset.getAtParser();
// Answer
parser.register('A", new AtCommandHandler() {
@Override
public AtCommandResult handleBasicCommand(String args) {
PhoneUtils.answerCall(mPhone);
return new AtCommandResult(AtCommandResult.OK);
}
});
parser.register('D", new AtCommandHandler() {
@Override
public AtCommandResult handleBasicCommand(String args) {
if (args.length() > 0) {
if (args.charAt(0) == '>") {
// Yuck - memory dialling requested.
// Just dial last number for now
if (args.startsWith(">9999")) { // for PTS test
return new AtCommandResult(AtCommandResult.ERROR);
}
return redial();
} else {
// Remove trailing ';'
if (args.charAt(args.length() - 1) == ';") {
args = args.substring(0, args.length() - 1);
}
Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
Uri.fromParts("tel", args, null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
expectCallStart();
return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing
}
}
return new AtCommandResult(AtCommandResult.ERROR);
}
});
// Hang-up command
parser.register("+CHUP", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
if (!mForegroundCall.isIdle()) {
PhoneUtils.hangup(mForegroundCall);
} else if (!mRingingCall.isIdle()) {
PhoneUtils.hangup(mRingingCall);
} else if (!mBackgroundCall.isIdle()) {
PhoneUtils.hangup(mBackgroundCall);
}
return new AtCommandResult(AtCommandResult.OK);
}
});
// Bluetooth Retrieve Supported Features command
parser.register("+BRSF", new AtCommandHandler() {
private AtCommandResult sendBRSF() {
return new AtCommandResult("+BRSF: " + mLocalBrsf);
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// AT+BRSF=<handsfree supported features bitmap>
// Handsfree is telling us which features it supports. We
// send the features we support
if (args.length == 1 && (args[0] instanceof Integer)) {
mRemoteBrsf = (Integer) args[0];
} else {
Log.w(TAG, "HF didn't sent BRSF assuming 0");
}
return sendBRSF();
}
@Override
public AtCommandResult handleActionCommand() {
// This seems to be out of spec, but lets do the nice thing
return sendBRSF();
}
@Override
public AtCommandResult handleReadCommand() {
// This seems to be out of spec, but lets do the nice thing
return sendBRSF();
}
});
// Call waiting notification on/off
parser.register("+CCWA", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
// Seems to be out of spec, but lets return nicely
return new AtCommandResult(AtCommandResult.OK);
}
@Override
public AtCommandResult handleReadCommand() {
// Call waiting is always on
return new AtCommandResult("+CCWA: 1");
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// AT+CCWA=<n>
// Handsfree is trying to enable/disable call waiting. We
// cannot disable in the current implementation.
return new AtCommandResult(AtCommandResult.OK);
}
@Override
public AtCommandResult handleTestCommand() {
// Request for range of supported CCWA paramters
return new AtCommandResult("+CCWA: (\"n\",(1))");
}
});
// Mobile Equipment Event Reporting enable/disable command
// Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we
// only support paramter ind (disable/enable evert reporting using
// +CDEV)
parser.register("+CMER", new AtCommandHandler() {
@Override
public AtCommandResult handleReadCommand() {
return new AtCommandResult(
"+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0"));
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
if (args.length < 4) {
// This is a syntax error
return new AtCommandResult(AtCommandResult.ERROR);
} else if (args[0].equals(3) && args[1].equals(0) &&
args[2].equals(0)) {
if (args[3].equals(0)) {
mIndicatorsEnabled = false;
return new AtCommandResult(AtCommandResult.OK);
} else if (args[3].equals(1)) {
mIndicatorsEnabled = true;
return new AtCommandResult(AtCommandResult.OK);
}
return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
} else {
return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
}
}
@Override
public AtCommandResult handleTestCommand() {
return new AtCommandResult("+CMER: (3),(0),(0),(0-1)");
}
});
// Mobile Equipment Error Reporting enable/disable
parser.register("+CMEE", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
// out of spec, assume they want to enable
mCmee = true;
return new AtCommandResult(AtCommandResult.OK);
}
@Override
public AtCommandResult handleReadCommand() {
return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0"));
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// AT+CMEE=<n>
if (args.length == 0) {
// <n> ommitted - default to 0
mCmee = false;
return new AtCommandResult(AtCommandResult.OK);
} else if (!(args[0] instanceof Integer)) {
// Syntax error
return new AtCommandResult(AtCommandResult.ERROR);
} else {
mCmee = ((Integer)args[0] == 1);
return new AtCommandResult(AtCommandResult.OK);
}
}
@Override
public AtCommandResult handleTestCommand() {
// Probably not required but spec, but no harm done
return new AtCommandResult("+CMEE: (0-1)");
}
});
// Bluetooth Last Dialled Number
parser.register("+BLDN", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
return redial();
}
});
// Indicator Update command
parser.register("+CIND", new AtCommandHandler() {
@Override
public AtCommandResult handleReadCommand() {
return mPhoneState.toCindResult();
}
@Override
public AtCommandResult handleTestCommand() {
return mPhoneState.getCindTestResult();
}
});
// Query Signal Quality (legacy)
parser.register("+CSQ", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
return mPhoneState.toCsqResult();
}
});
// Query network registration state
parser.register("+CREG", new AtCommandHandler() {
@Override
public AtCommandResult handleReadCommand() {
return new AtCommandResult(mPhoneState.toCregString());
}
});
// Send DTMF. I don't know if we are also expected to play the DTMF tone
// locally, right now we don't
parser.register("+VTS", new AtCommandHandler() {
@Override
public AtCommandResult handleSetCommand(Object[] args) {
if (args.length >= 1) {
char c;
if (args[0] instanceof Integer) {
c = ((Integer) args[0]).toString().charAt(0);
} else {
c = ((String) args[0]).charAt(0);
}
if (isValidDtmf(c)) {
mPhone.sendDtmf(c);
return new AtCommandResult(AtCommandResult.OK);
}
}
return new AtCommandResult(AtCommandResult.ERROR);
}
private boolean isValidDtmf(char c) {
switch (c) {
case '#":
case '*":
return true;
default:
if (Character.digit(c, 14) != -1) {
return true; // 0-9 and A-D
}
return false;
}
}
});
// List calls
parser.register("+CLCC", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
return getClccResult();
}
});
// Call Hold and Multiparty Handling command
parser.register("+CHLD", new AtCommandHandler() {
@Override
public AtCommandResult handleSetCommand(Object[] args) {
if (args.length >= 1) {
if (args[0].equals(0)) {
boolean result;
if (mRingingCall.isRinging()) {
result = PhoneUtils.hangupRingingCall(mPhone);
} else {
result = PhoneUtils.hangupHoldingCall(mPhone);
}
if (result) {
return new AtCommandResult(AtCommandResult.OK);
} else {
return new AtCommandResult(AtCommandResult.ERROR);
}
} else if (args[0].equals(1)) {
// Hangup active call, answer held call
if (PhoneUtils.answerAndEndActive(mPhone)) {
return new AtCommandResult(AtCommandResult.OK);
} else {
return new AtCommandResult(AtCommandResult.ERROR);
}
} else if (args[0].equals(2)) {
PhoneUtils.switchHoldingAndActive(mPhone);
return new AtCommandResult(AtCommandResult.OK);
} else if (args[0].equals(3)) {
if (mForegroundCall.getState().isAlive() &&
mBackgroundCall.getState().isAlive()) {
PhoneUtils.mergeCalls(mPhone);
}
return new AtCommandResult(AtCommandResult.OK);
}
}
return new AtCommandResult(AtCommandResult.ERROR);
}
@Override
public AtCommandResult handleTestCommand() {
return new AtCommandResult("+CHLD: (0,1,2,3)");
}
});
// Get Network operator name
parser.register("+COPS", new AtCommandHandler() {
@Override
public AtCommandResult handleReadCommand() {
String operatorName = mPhone.getServiceState().getOperatorAlphaLong();
if (operatorName != null) {
if (operatorName.length() > 16) {
operatorName = operatorName.substring(0, 16);
}
return new AtCommandResult(
"+COPS: 0,0,\"" + operatorName + "\"");
} else {
return new AtCommandResult(
"+COPS: 0,0,\"UNKNOWN\",0");
}
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// Handsfree only supports AT+COPS=3,0
if (args.length != 2 || !(args[0] instanceof Integer)
|| !(args[1] instanceof Integer)) {
// syntax error
return new AtCommandResult(AtCommandResult.ERROR);
} else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) {
return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
} else {
return new AtCommandResult(AtCommandResult.OK);
}
}
@Override
public AtCommandResult handleTestCommand() {
// Out of spec, but lets be friendly
return new AtCommandResult("+COPS: (3),(0)");
}
});
// Mobile PIN
// AT+CPIN is not in the handsfree spec (although it is in 3GPP)
parser.register("+CPIN", new AtCommandHandler() {
@Override
public AtCommandResult handleReadCommand() {
return new AtCommandResult("+CPIN: READY");
}
});
// Bluetooth Response and Hold
// Only supported on PDC (Japan) and CDMA networks.
parser.register("+BTRH", new AtCommandHandler() {
@Override
public AtCommandResult handleReadCommand() {
// Replying with just OK indicates no response and hold
// features in use now
return new AtCommandResult(AtCommandResult.OK);
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// Neeed PDC or CDMA
return new AtCommandResult(AtCommandResult.ERROR);
}
});
// Request International Mobile Subscriber Identity (IMSI)
// Not in bluetooth handset spec
parser.register("+CIMI", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
// AT+CIMI
String imsi = mPhone.getSubscriberId();
if (imsi == null || imsi.length() == 0) {
return reportCmeError(BluetoothCmeError.SIM_FAILURE);
} else {
return new AtCommandResult(imsi);
}
}
});
// Calling Line Identification Presentation
parser.register("+CLIP", new AtCommandHandler() {
@Override
public AtCommandResult handleReadCommand() {
// Currently assumes the network is provisioned for CLIP
return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1");
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// AT+CLIP=<n>
if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) {
mClip = args[0].equals(1);
return new AtCommandResult(AtCommandResult.OK);
} else {
return new AtCommandResult(AtCommandResult.ERROR);
}
}
@Override
public AtCommandResult handleTestCommand() {
return new AtCommandResult("+CLIP: (0-1)");
}
});
// AT+CGSN - Returns the device IMEI number.
parser.register("+CGSN", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
// Get the IMEI of the device.
// mPhone will not be NULL at this point.
return new AtCommandResult("+CGSN: " + mPhone.getDeviceId());
}
});
// AT+CGMM - Query Model Information
parser.register("+CGMM", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
// Return the Model Information.
String model = SystemProperties.get("ro.product.model");
if (model != null) {
return new AtCommandResult("+CGMM: " + model);
} else {
return new AtCommandResult(AtCommandResult.ERROR);
}
}
});
// AT+CGMI - Query Manufacturer Information
parser.register("+CGMI", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
// Return the Model Information.
String manuf = SystemProperties.get("ro.product.manufacturer");
if (manuf != null) {
return new AtCommandResult("+CGMI: " + manuf);
} else {
return new AtCommandResult(AtCommandResult.ERROR);
}
}
});
// Noise Reduction and Echo Cancellation control
parser.register("+NREC", new AtCommandHandler() {
@Override
public AtCommandResult handleSetCommand(Object[] args) {
if (args[0].equals(0)) {
mAudioManager.setParameter(HEADSET_NREC, "off");
return new AtCommandResult(AtCommandResult.OK);
} else if (args[0].equals(1)) {
mAudioManager.setParameter(HEADSET_NREC, "on");
return new AtCommandResult(AtCommandResult.OK);
}
return new AtCommandResult(AtCommandResult.ERROR);
}
});
// Voice recognition (dialing)
parser.register("+BVRA", new AtCommandHandler() {
@Override
public AtCommandResult handleSetCommand(Object[] args) {
if (BluetoothHeadset.DISABLE_BT_VOICE_DIALING) {
return new AtCommandResult(AtCommandResult.ERROR);
}
if (args.length >= 1 && args[0].equals(1)) {
synchronized (BluetoothHandsfree.this) {
if (!mWaitingForVoiceRecognition) {
try {
mContext.startActivity(sVoiceCommandIntent);
} catch (ActivityNotFoundException e) {
return new AtCommandResult(AtCommandResult.ERROR);
}
expectVoiceRecognition();
}
}
return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing yet
} else if (args.length >= 1 && args[0].equals(0)) {
audioOff();
return new AtCommandResult(AtCommandResult.OK);
}
return new AtCommandResult(AtCommandResult.ERROR);
}
@Override
public AtCommandResult handleTestCommand() {
return new AtCommandResult("+BVRA: (0-1)");
}
});
// Retrieve Subscriber Number
parser.register("+CNUM", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
String number = mPhone.getLine1Number();
if (number == null) {
return new AtCommandResult(AtCommandResult.OK);
}
return new AtCommandResult("+CNUM: ,\"" + number + "\"," +
PhoneNumberUtils.toaFromString(number) + ",,4");
}
});
// Microphone Gain
parser.register("+VGM", new AtCommandHandler() {
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// AT+VGM=<gain> in range [0,15]
// Headset/Handsfree is reporting its current gain setting
return new AtCommandResult(AtCommandResult.OK);
}
});
// Speaker Gain
parser.register("+VGS", new AtCommandHandler() {
@Override
public AtCommandResult handleSetCommand(Object[] args) {
// AT+VGS=<gain> in range [0,15]
if (args.length != 1 || !(args[0] instanceof Integer)) {
return new AtCommandResult(AtCommandResult.ERROR);
}
mScoGain = (Integer) args[0];
int flag = mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0;
mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag);
return new AtCommandResult(AtCommandResult.OK);
}
});
// Phone activity status
parser.register("+CPAS", new AtCommandHandler() {
@Override
public AtCommandResult handleActionCommand() {
int status = 0;
switch (mPhone.getState()) {
case IDLE:
status = 0;
break;
case RINGING:
status = 3;
break;
case OFFHOOK:
status = 4;
break;
}
return new AtCommandResult("+CPAS: " + status);
}
});
mPhonebook.register(parser);
|
private void | initializeHeadsetAtParser()Register AT Command handlers to implement the Headset profile
if (DBG) log("Registering Headset AT commands");
AtParser parser = mHeadset.getAtParser();
// Headset's usually only have one button, which is meant to cause the
// HS to send us AT+CKPD=200 or AT+CKPD.
parser.register("+CKPD", new AtCommandHandler() {
private AtCommandResult headsetButtonPress() {
if (mRingingCall.isRinging()) {
// Answer the call
PhoneUtils.answerCall(mPhone);
// If in-band ring tone is supported, SCO connection will already
// be up and the following call will just return.
audioOn();
} else if (mForegroundCall.getState().isAlive()) {
if (!isAudioOn()) {
// Transfer audio from AG to HS
audioOn();
} else {
if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING &&
(System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) {
// Headset made a recent ACL connection to us - and
// made a mandatory AT+CKPD request to connect
// audio which races with our automatic audio
// setup. ignore
} else {
// Hang up the call
audioOff();
PhoneUtils.hangup(mPhone);
}
}
} else {
// No current call - redial last number
return redial();
}
return new AtCommandResult(AtCommandResult.OK);
}
@Override
public AtCommandResult handleActionCommand() {
return headsetButtonPress();
}
@Override
public AtCommandResult handleSetCommand(Object[] args) {
return headsetButtonPress();
}
});
|
boolean | isAudioOn()
return (mConnectedSco != null);
|
private boolean | isHeadsetConnected()
if (mHeadset == null) {
return false;
}
return mHeadset.isConnected();
|
private boolean | isIncallAudio()
Call.State state = mForegroundCall.getState();
return (state == Call.State.ACTIVE || state == Call.State.ALERTING);
|
private static void | log(java.lang.String msg)
Log.d(TAG, msg);
|
synchronized void | onBluetoothDisabled()
if (mConnectedSco != null) {
mAudioManager.setBluetoothScoOn(false);
broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
mConnectedSco.close();
mConnectedSco = null;
}
if (mOutgoingSco != null) {
mOutgoingSco.close();
mOutgoingSco = null;
}
if (mIncomingSco != null) {
mIncomingSco.close();
mIncomingSco = null;
}
|
synchronized void | onBluetoothEnabled()
/* Bluez has a bug where it will always accept and then orphan
* incoming SCO connections, regardless of whether we have a listening
* SCO socket. So the best thing to do is always run a listening socket
* while bluetooth is on so that at least we can diconnect it
* immediately when we don't want it.
*/
if (mIncomingSco == null) {
mIncomingSco = createScoSocket();
mIncomingSco.accept();
}
|
private android.bluetooth.AtCommandResult | redial()helper to redial last dialled number
String number = mPhonebook.getLastDialledNumber();
if (number == null) {
// spec seems to suggest sending ERROR if we dont have a
// number to redial
if (DBG) log("Bluetooth redial requested (+BLDN), but no previous " +
"outgoing calls found. Ignoring");
return new AtCommandResult(AtCommandResult.ERROR);
}
Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
Uri.fromParts("tel", number, null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
// We do not immediately respond OK, wait until we get a phone state
// update. If we return OK now and the handsfree immeidately requests
// our phone state it will say we are not in call yet which confuses
// some devices
expectCallStart();
return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing
|
public android.bluetooth.AtCommandResult | reportCmeError(int error)
if (mCmee) {
AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
result.addResponse("+CME ERROR: " + error);
return result;
} else {
return new AtCommandResult(AtCommandResult.ERROR);
}
|
private void | resetAtState()
mClip = false;
mIndicatorsEnabled = false;
mCmee = false;
mClccTimestamps = new long[MAX_CONNECTIONS];
mClccUsed = new boolean[MAX_CONNECTIONS];
for (int i = 0; i < MAX_CONNECTIONS; i++) {
mClccUsed[i] = false;
}
mRemoteBrsf = 0;
|
public void | sendScoGainUpdate(int gain)
if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) {
sendURC("+VGS:" + gain);
mScoGain = gain;
}
|
private void | sendURC(java.lang.String urc)
if (isHeadsetConnected()) {
mHeadset.sendURC(urc);
}
|
private void | startDebug()
if (DBG && mDebugThread == null) {
mDebugThread = new DebugThread();
mDebugThread.start();
}
|
synchronized boolean | startVoiceRecognition()
if (mWaitingForVoiceRecognition) {
// HF initiated
mWaitingForVoiceRecognition = false;
sendURC("OK");
} else {
// AG initiated
sendURC("+BVRA: 1");
}
boolean ret = audioOn();
if (mStartVoiceRecognitionWakeLock.isHeld()) {
mStartVoiceRecognitionWakeLock.release();
}
return ret;
|
private void | stopDebug()
if (mDebugThread != null) {
mDebugThread.interrupt();
mDebugThread = null;
}
|
synchronized boolean | stopVoiceRecognition()
sendURC("+BVRA: 0");
audioOff();
return true;
|
public static java.lang.String | typeToString(int type)
switch (type) {
case TYPE_UNKNOWN:
return "unknown";
case TYPE_HEADSET:
return "headset";
case TYPE_HANDSFREE:
return "handsfree";
}
return null;
|
synchronized void | userWantsAudioOff()Used to indicate the user requested BT audio off.
This will prevent us from establishing BT audio again during this call
if audioOn() is called.
mUserWantsAudio = false;
audioOff();
|
synchronized void | userWantsAudioOn()Used to indicate the user requested BT audio on.
This will establish SCO (BT audio), even if the user requested it off
previously on this call.
mUserWantsAudio = true;
audioOn();
|