| /* |
| * 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(); |
| } |
| } |