Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.services.telephony;
     18 
     19 import android.os.Handler;
     20 import android.os.Message;
     21 
     22 import android.provider.Settings;
     23 import android.telephony.DisconnectCause;
     24 import android.telephony.PhoneNumberUtils;
     25 
     26 import com.android.internal.telephony.Call;
     27 import com.android.internal.telephony.CallStateException;
     28 import com.android.internal.telephony.Connection;
     29 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
     30 import com.android.internal.telephony.Phone;
     31 import com.android.phone.settings.SettingsConstants;
     32 
     33 import java.util.LinkedList;
     34 import java.util.Queue;
     35 
     36 /**
     37  * Manages a single phone call handled by CDMA.
     38  */
     39 final class CdmaConnection extends TelephonyConnection {
     40 
     41     private static final int MSG_CALL_WAITING_MISSED = 1;
     42     private static final int MSG_DTMF_SEND_CONFIRMATION = 2;
     43     private static final int TIMEOUT_CALL_WAITING_MILLIS = 20 * 1000;
     44 
     45     private final Handler mHandler = new Handler() {
     46 
     47         /** ${inheritDoc} */
     48         @Override
     49         public void handleMessage(Message msg) {
     50             switch (msg.what) {
     51                 case MSG_CALL_WAITING_MISSED:
     52                     hangupCallWaiting(DisconnectCause.INCOMING_MISSED);
     53                     break;
     54                 case MSG_DTMF_SEND_CONFIRMATION:
     55                     handleBurstDtmfConfirmation();
     56                     break;
     57                 default:
     58                     break;
     59             }
     60         }
     61 
     62     };
     63 
     64     /**
     65      * {@code True} if the CDMA connection should allow mute.
     66      */
     67     private boolean mAllowMute;
     68     // Queue of pending short-DTMF characters.
     69     private final Queue<Character> mDtmfQueue = new LinkedList<>();
     70     private final EmergencyTonePlayer mEmergencyTonePlayer;
     71 
     72     // Indicates that the DTMF confirmation from telephony is pending.
     73     private boolean mDtmfBurstConfirmationPending = false;
     74     private boolean mIsCallWaiting;
     75 
     76     CdmaConnection(
     77             Connection connection,
     78             EmergencyTonePlayer emergencyTonePlayer,
     79             boolean allowMute,
     80             boolean isOutgoing,
     81             String telecomCallId) {
     82         super(connection, telecomCallId, isOutgoing);
     83         mEmergencyTonePlayer = emergencyTonePlayer;
     84         mAllowMute = allowMute;
     85         mIsCallWaiting = connection != null && connection.getState() == Call.State.WAITING;
     86         boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
     87         // Start call waiting timer for CDMA waiting call.
     88         if (mIsCallWaiting && !isImsCall) {
     89             startCallWaitingTimer();
     90         }
     91     }
     92 
     93     /** {@inheritDoc} */
     94     @Override
     95     public void onPlayDtmfTone(char digit) {
     96         if (useBurstDtmf()) {
     97             Log.i(this, "sending dtmf digit as burst");
     98             sendShortDtmfToNetwork(digit);
     99         } else {
    100             Log.i(this, "sending dtmf digit directly");
    101             getPhone().startDtmf(digit);
    102         }
    103     }
    104 
    105     /** {@inheritDoc} */
    106     @Override
    107     public void onStopDtmfTone() {
    108         if (!useBurstDtmf()) {
    109             getPhone().stopDtmf();
    110         }
    111     }
    112 
    113     @Override
    114     public void onReject() {
    115         Connection connection = getOriginalConnection();
    116         if (connection != null) {
    117             switch (connection.getState()) {
    118                 case INCOMING:
    119                     // Normal ringing calls are handled the generic way.
    120                     super.onReject();
    121                     break;
    122                 case WAITING:
    123                     hangupCallWaiting(DisconnectCause.INCOMING_REJECTED);
    124                     break;
    125                 default:
    126                     Log.e(this, new Exception(), "Rejecting a non-ringing call");
    127                     // might as well hang this up, too.
    128                     super.onReject();
    129                     break;
    130             }
    131         }
    132     }
    133 
    134     @Override
    135     public void onAnswer() {
    136         mHandler.removeMessages(MSG_CALL_WAITING_MISSED);
    137         super.onAnswer();
    138     }
    139 
    140     /**
    141      * Clones the current {@link CdmaConnection}.
    142      * <p>
    143      * Listeners are not copied to the new instance.
    144      *
    145      * @return The cloned connection.
    146      */
    147     @Override
    148     public TelephonyConnection cloneConnection() {
    149         CdmaConnection cdmaConnection = new CdmaConnection(getOriginalConnection(),
    150                 mEmergencyTonePlayer, mAllowMute, mIsOutgoing, getTelecomCallId());
    151         return cdmaConnection;
    152     }
    153 
    154     @Override
    155     public void onStateChanged(int state) {
    156         Connection originalConnection = getOriginalConnection();
    157         mIsCallWaiting = originalConnection != null &&
    158                 originalConnection.getState() == Call.State.WAITING;
    159 
    160         if (mEmergencyTonePlayer != null) {
    161             if (state == android.telecom.Connection.STATE_DIALING) {
    162                 if (isEmergency()) {
    163                     mEmergencyTonePlayer.start();
    164                 }
    165             } else {
    166                 // No need to check if it is an emergency call, since it is a no-op if it
    167                 // isn't started.
    168                 mEmergencyTonePlayer.stop();
    169             }
    170         }
    171 
    172         super.onStateChanged(state);
    173     }
    174 
    175     @Override
    176     protected int buildConnectionCapabilities() {
    177         int capabilities = super.buildConnectionCapabilities();
    178         if (mAllowMute) {
    179             capabilities |= CAPABILITY_MUTE;
    180         }
    181         return capabilities;
    182     }
    183 
    184     @Override
    185     public void performConference(android.telecom.Connection otherConnection) {
    186         if (isImsConnection()) {
    187             super.performConference(otherConnection);
    188         } else {
    189             Log.w(this, "Non-IMS CDMA Connection attempted to call performConference.");
    190         }
    191     }
    192 
    193     void forceAsDialing(boolean isDialing) {
    194         if (isDialing) {
    195             setStateOverride(Call.State.DIALING);
    196         } else {
    197             resetStateOverride();
    198         }
    199     }
    200 
    201     boolean isOutgoing() {
    202         return mIsOutgoing;
    203     }
    204 
    205     boolean isCallWaiting() {
    206         return mIsCallWaiting;
    207     }
    208 
    209     /**
    210      * We do not get much in the way of confirmation for Cdma call waiting calls. There is no
    211      * indication that a rejected call succeeded, a call waiting call has stopped. Instead we
    212      * simulate this for the user. We allow TIMEOUT_CALL_WAITING_MILLIS milliseconds before we
    213      * assume that the call was missed and reject it ourselves. reject the call automatically.
    214      */
    215     private void startCallWaitingTimer() {
    216         mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS);
    217     }
    218 
    219     private void hangupCallWaiting(int telephonyDisconnectCause) {
    220         Connection originalConnection = getOriginalConnection();
    221         if (originalConnection != null) {
    222             try {
    223                 originalConnection.hangup();
    224             } catch (CallStateException e) {
    225                 Log.e(this, e, "Failed to hangup call waiting call");
    226             }
    227             setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause));
    228         }
    229     }
    230 
    231     /**
    232      * Read the settings to determine which type of DTMF method this CDMA phone calls.
    233      */
    234     private boolean useBurstDtmf() {
    235         if (isImsConnection()) {
    236             Log.d(this,"in ims call, return false");
    237             return false;
    238         }
    239         int dtmfTypeSetting = Settings.System.getInt(
    240                 getPhone().getContext().getContentResolver(),
    241                 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
    242                 SettingsConstants.DTMF_TONE_TYPE_NORMAL);
    243         return dtmfTypeSetting == SettingsConstants.DTMF_TONE_TYPE_NORMAL;
    244     }
    245 
    246     private void sendShortDtmfToNetwork(char digit) {
    247         synchronized(mDtmfQueue) {
    248             if (mDtmfBurstConfirmationPending) {
    249                 mDtmfQueue.add(new Character(digit));
    250             } else {
    251                 sendBurstDtmfStringLocked(Character.toString(digit));
    252             }
    253         }
    254     }
    255 
    256     private void sendBurstDtmfStringLocked(String dtmfString) {
    257         getPhone().sendBurstDtmf(
    258                 dtmfString, 0, 0, mHandler.obtainMessage(MSG_DTMF_SEND_CONFIRMATION));
    259         mDtmfBurstConfirmationPending = true;
    260     }
    261 
    262     private void handleBurstDtmfConfirmation() {
    263         String dtmfDigits = null;
    264         synchronized(mDtmfQueue) {
    265             mDtmfBurstConfirmationPending = false;
    266             if (!mDtmfQueue.isEmpty()) {
    267                 StringBuilder builder = new StringBuilder(mDtmfQueue.size());
    268                 while (!mDtmfQueue.isEmpty()) {
    269                     builder.append(mDtmfQueue.poll());
    270                 }
    271                 dtmfDigits = builder.toString();
    272 
    273                 // It would be nice to log the digit, but since DTMF digits can be passwords
    274                 // to things, or other secure account numbers, we want to keep it away from
    275                 // the logs.
    276                 Log.i(this, "%d dtmf character[s] removed from the queue", dtmfDigits.length());
    277             }
    278             if (dtmfDigits != null) {
    279                 sendBurstDtmfStringLocked(dtmfDigits);
    280             }
    281         }
    282     }
    283 
    284     private boolean isEmergency() {
    285         Phone phone = getPhone();
    286         return phone != null &&
    287                 PhoneNumberUtils.isLocalEmergencyNumber(
    288                     phone.getContext(), getAddress().getSchemeSpecificPart());
    289     }
    290 
    291     /**
    292      * Called when ECM mode is exited; set the connection to allow mute and update the connection
    293      * capabilities.
    294      */
    295     @Override
    296     protected void handleExitedEcmMode() {
    297         // We allow mute upon existing ECM mode and rebuild the capabilities.
    298         mAllowMute = true;
    299         super.handleExitedEcmMode();
    300     }
    301 }
    302