blob: c7b324d4ef1fd25a1ca5cc51bf428732c6b83f39 [file] [log] [blame]
/*
* 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 com.android.services.telephony;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.telephony.DisconnectCause;
import android.telephony.TelephonyManager;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.settings.SettingsConstants;
import java.util.LinkedList;
import java.util.Queue;
/**
* Manages a single phone call handled by CDMA.
*/
final class CdmaConnection extends TelephonyConnection {
private static final int MSG_CALL_WAITING_MISSED = 1;
private static final int MSG_DTMF_SEND_CONFIRMATION = 2;
private static final int MSG_CDMA_LINE_CONTROL_INFO_REC = 3;
private static final int TIMEOUT_CALL_WAITING_MILLIS = 20 * 1000;
private final Handler mHandler = new Handler() {
/** ${inheritDoc} */
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CALL_WAITING_MISSED:
hangupCallWaiting(DisconnectCause.INCOMING_MISSED);
break;
case MSG_DTMF_SEND_CONFIRMATION:
handleBurstDtmfConfirmation();
break;
case MSG_CDMA_LINE_CONTROL_INFO_REC:
handleCdmaConnectionTimeReset();
break;
default:
break;
}
}
};
/**
* {@code True} if the CDMA connection should allow mute.
*/
private boolean mAllowMute;
// Queue of pending short-DTMF characters.
private final Queue<Character> mDtmfQueue = new LinkedList<>();
private final EmergencyTonePlayer mEmergencyTonePlayer;
// Indicates that the DTMF confirmation from telephony is pending.
private boolean mDtmfBurstConfirmationPending = false;
private boolean mIsCallWaiting;
private boolean mIsConnectionTimeReset = false;
CdmaConnection(
Connection connection,
EmergencyTonePlayer emergencyTonePlayer,
boolean allowMute,
int callDirection,
String telecomCallId) {
super(connection, telecomCallId, callDirection);
mEmergencyTonePlayer = emergencyTonePlayer;
mAllowMute = allowMute;
mIsCallWaiting = connection != null && connection.getState() == Call.State.WAITING;
boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
// Start call waiting timer for CDMA waiting call.
if (mIsCallWaiting && !isImsCall) {
startCallWaitingTimer();
}
}
/** {@inheritDoc} */
@Override
public void onPlayDtmfTone(char digit) {
if (useBurstDtmf()) {
Log.i(this, "sending dtmf digit as burst");
sendShortDtmfToNetwork(digit);
} else {
Log.i(this, "sending dtmf digit directly");
getPhone().startDtmf(digit);
}
}
/** {@inheritDoc} */
@Override
public void onStopDtmfTone() {
if (!useBurstDtmf()) {
getPhone().stopDtmf();
}
}
@Override
public void onReject() {
Connection connection = getOriginalConnection();
if (connection != null) {
switch (connection.getState()) {
case INCOMING:
// Normal ringing calls are handled the generic way.
super.onReject();
break;
case WAITING:
hangupCallWaiting(DisconnectCause.INCOMING_REJECTED);
break;
default:
Log.e(this, new Exception(), "Rejecting a non-ringing call");
// might as well hang this up, too.
super.onReject();
break;
}
}
}
@Override
public void onAnswer() {
mHandler.removeMessages(MSG_CALL_WAITING_MISSED);
super.onAnswer();
}
/**
* Clones the current {@link CdmaConnection}.
* <p>
* Listeners are not copied to the new instance.
*
* @return The cloned connection.
*/
@Override
public TelephonyConnection cloneConnection() {
CdmaConnection cdmaConnection = new CdmaConnection(getOriginalConnection(),
mEmergencyTonePlayer, mAllowMute, getCallDirection(), getTelecomCallId());
return cdmaConnection;
}
@Override
public void onStateChanged(int state) {
Connection originalConnection = getOriginalConnection();
mIsCallWaiting = originalConnection != null &&
originalConnection.getState() == Call.State.WAITING;
if (mEmergencyTonePlayer != null) {
if (state == android.telecom.Connection.STATE_DIALING) {
if (isEmergency()) {
mEmergencyTonePlayer.start();
}
} else {
// No need to check if it is an emergency call, since it is a no-op if it
// isn't started.
mEmergencyTonePlayer.stop();
}
}
super.onStateChanged(state);
}
@Override
protected int buildConnectionCapabilities() {
int capabilities = super.buildConnectionCapabilities();
if (mAllowMute) {
capabilities |= CAPABILITY_MUTE;
}
return capabilities;
}
@Override
public void performConference(android.telecom.Connection otherConnection) {
if (isImsConnection()) {
super.performConference(otherConnection);
} else {
Log.w(this, "Non-IMS CDMA Connection attempted to call performConference.");
}
}
void forceAsDialing(boolean isDialing) {
if (isDialing) {
setStateOverride(Call.State.DIALING);
} else {
resetStateOverride();
}
}
boolean isCallWaiting() {
return mIsCallWaiting;
}
/**
* We do not get much in the way of confirmation for Cdma call waiting calls. There is no
* indication that a rejected call succeeded, a call waiting call has stopped. Instead we
* simulate this for the user. We allow TIMEOUT_CALL_WAITING_MILLIS milliseconds before we
* assume that the call was missed and reject it ourselves. reject the call automatically.
*/
private void startCallWaitingTimer() {
mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS);
}
private void hangupCallWaiting(int telephonyDisconnectCause) {
Connection originalConnection = getOriginalConnection();
if (originalConnection != null) {
try {
originalConnection.hangup();
} catch (CallStateException e) {
Log.e(this, e, "Failed to hangup call waiting call");
}
setTelephonyConnectionDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
telephonyDisconnectCause, null, getPhone().getPhoneId()));
}
}
/**
* Read the settings to determine which type of DTMF method this CDMA phone calls.
*/
private boolean useBurstDtmf() {
if (isImsConnection()) {
Log.d(this,"in ims call, return false");
return false;
}
int dtmfTypeSetting = Settings.System.getInt(
getPhone().getContext().getContentResolver(),
Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
SettingsConstants.DTMF_TONE_TYPE_NORMAL);
return dtmfTypeSetting == SettingsConstants.DTMF_TONE_TYPE_NORMAL;
}
private void sendShortDtmfToNetwork(char digit) {
synchronized(mDtmfQueue) {
if (mDtmfBurstConfirmationPending) {
mDtmfQueue.add(new Character(digit));
} else {
sendBurstDtmfStringLocked(Character.toString(digit));
}
}
}
private void sendBurstDtmfStringLocked(String dtmfString) {
getPhone().sendBurstDtmf(
dtmfString, 0, 0, mHandler.obtainMessage(MSG_DTMF_SEND_CONFIRMATION));
mDtmfBurstConfirmationPending = true;
}
private void handleBurstDtmfConfirmation() {
String dtmfDigits = null;
synchronized(mDtmfQueue) {
mDtmfBurstConfirmationPending = false;
if (!mDtmfQueue.isEmpty()) {
StringBuilder builder = new StringBuilder(mDtmfQueue.size());
while (!mDtmfQueue.isEmpty()) {
builder.append(mDtmfQueue.poll());
}
dtmfDigits = builder.toString();
// It would be nice to log the digit, but since DTMF digits can be passwords
// to things, or other secure account numbers, we want to keep it away from
// the logs.
Log.i(this, "%d dtmf character[s] removed from the queue", dtmfDigits.length());
}
if (dtmfDigits != null) {
sendBurstDtmfStringLocked(dtmfDigits);
}
}
}
private boolean isEmergency() {
Phone phone = getPhone();
if (phone != null && getAddress() != null) {
TelephonyManager tm = (TelephonyManager) phone.getContext()
.getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
return tm.isEmergencyNumber(getAddress().getSchemeSpecificPart());
}
}
return false;
}
/**
* Called when ECM mode is exited; set the connection to allow mute and update the connection
* capabilities.
*/
@Override
protected void handleExitedEcmMode() {
// We allow mute upon existing ECM mode and rebuild the capabilities.
mAllowMute = true;
super.handleExitedEcmMode();
}
private void handleCdmaConnectionTimeReset() {
boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
if (!isImsCall && !mIsConnectionTimeReset && isOutgoingCall()
&& getOriginalConnection() != null
&& getOriginalConnection().getState() == Call.State.ACTIVE
&& getOriginalConnection().getDurationMillis() > 0) {
mIsConnectionTimeReset = true;
getOriginalConnection().resetConnectionTime();
resetConnectionTime();
}
}
@Override
void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
super.setOriginalConnection(originalConnection);
if (getPhone() != null) {
getPhone().registerForLineControlInfo(mHandler, MSG_CDMA_LINE_CONTROL_INFO_REC, null);
}
}
@Override
public void close() {
mIsConnectionTimeReset = false;
if (getPhone() != null) {
getPhone().unregisterForLineControlInfo(mHandler);
}
super.close();
}
}