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.Phone;
     30 import com.android.phone.settings.SettingsConstants;
     31 
     32 import java.util.LinkedList;
     33 import java.util.Queue;
     34 
     35 /**
     36  * Manages a single phone call handled by CDMA.
     37  */
     38 final class CdmaConnection extends TelephonyConnection {
     39 
     40     private static final int MSG_CALL_WAITING_MISSED = 1;
     41     private static final int MSG_DTMF_SEND_CONFIRMATION = 2;
     42     private static final int TIMEOUT_CALL_WAITING_MILLIS = 20 * 1000;
     43 
     44     private final Handler mHandler = new Handler() {
     45 
     46         /** ${inheritDoc} */
     47         @Override
     48         public void handleMessage(Message msg) {
     49             switch (msg.what) {
     50                 case MSG_CALL_WAITING_MISSED:
     51                     hangupCallWaiting(DisconnectCause.INCOMING_MISSED);
     52                     break;
     53                 case MSG_DTMF_SEND_CONFIRMATION:
     54                     handleBurstDtmfConfirmation();
     55                     break;
     56                 default:
     57                     break;
     58             }
     59         }
     60 
     61     };
     62 
     63     /**
     64      * {@code True} if the CDMA connection should allow mute.
     65      */
     66     private final boolean mAllowMute;
     67     private final boolean mIsOutgoing;
     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         super(connection);
     82         mEmergencyTonePlayer = emergencyTonePlayer;
     83         mAllowMute = allowMute;
     84         mIsOutgoing = isOutgoing;
     85         mIsCallWaiting = connection != null && connection.getState() == Call.State.WAITING;
     86         if (mIsCallWaiting) {
     87             startCallWaitingTimer();
     88         }
     89     }
     90 
     91     /** {@inheritDoc} */
     92     @Override
     93     public void onPlayDtmfTone(char digit) {
     94         if (useBurstDtmf()) {
     95             Log.i(this, "sending dtmf digit as burst");
     96             sendShortDtmfToNetwork(digit);
     97         } else {
     98             Log.i(this, "sending dtmf digit directly");
     99             getPhone().startDtmf(digit);
    100         }
    101     }
    102 
    103     /** {@inheritDoc} */
    104     @Override
    105     public void onStopDtmfTone() {
    106         if (!useBurstDtmf()) {
    107             getPhone().stopDtmf();
    108         }
    109     }
    110 
    111     @Override
    112     public void onReject() {
    113         Connection connection = getOriginalConnection();
    114         if (connection != null) {
    115             switch (connection.getState()) {
    116                 case INCOMING:
    117                     // Normal ringing calls are handled the generic way.
    118                     super.onReject();
    119                     break;
    120                 case WAITING:
    121                     hangupCallWaiting(DisconnectCause.INCOMING_REJECTED);
    122                     break;
    123                 default:
    124                     Log.e(this, new Exception(), "Rejecting a non-ringing call");
    125                     // might as well hang this up, too.
    126                     super.onReject();
    127                     break;
    128             }
    129         }
    130     }
    131 
    132     @Override
    133     public void onAnswer() {
    134         mHandler.removeMessages(MSG_CALL_WAITING_MISSED);
    135         super.onAnswer();
    136     }
    137 
    138     /**
    139      * Clones the current {@link CdmaConnection}.
    140      * <p>
    141      * Listeners are not copied to the new instance.
    142      *
    143      * @return The cloned connection.
    144      */
    145     @Override
    146     public TelephonyConnection cloneConnection() {
    147         CdmaConnection cdmaConnection = new CdmaConnection(getOriginalConnection(),
    148                 mEmergencyTonePlayer, mAllowMute, mIsOutgoing);
    149         return cdmaConnection;
    150     }
    151 
    152     @Override
    153     public void onStateChanged(int state) {
    154         Connection originalConnection = getOriginalConnection();
    155         mIsCallWaiting = originalConnection != null &&
    156                 originalConnection.getState() == Call.State.WAITING;
    157 
    158         if (mEmergencyTonePlayer != null) {
    159             if (state == android.telecom.Connection.STATE_DIALING) {
    160                 if (isEmergency()) {
    161                     mEmergencyTonePlayer.start();
    162                 }
    163             } else {
    164                 // No need to check if it is an emergency call, since it is a no-op if it
    165                 // isn't started.
    166                 mEmergencyTonePlayer.stop();
    167             }
    168         }
    169 
    170         super.onStateChanged(state);
    171     }
    172 
    173     @Override
    174     protected int buildConnectionCapabilities() {
    175         int capabilities = super.buildConnectionCapabilities();
    176         if (mAllowMute) {
    177             capabilities |= CAPABILITY_MUTE;
    178         }
    179         return capabilities;
    180     }
    181 
    182     @Override
    183     public void performConference(TelephonyConnection otherConnection) {
    184         if (isImsConnection()) {
    185             super.performConference(otherConnection);
    186         } else {
    187             Log.w(this, "Non-IMS CDMA Connection attempted to call performConference.");
    188         }
    189     }
    190 
    191     void forceAsDialing(boolean isDialing) {
    192         if (isDialing) {
    193             setDialing();
    194         } else {
    195             updateState(true);
    196         }
    197     }
    198 
    199     boolean isOutgoing() {
    200         return mIsOutgoing;
    201     }
    202 
    203     boolean isCallWaiting() {
    204         return mIsCallWaiting;
    205     }
    206 
    207     /**
    208      * We do not get much in the way of confirmation for Cdma call waiting calls. There is no
    209      * indication that a rejected call succeeded, a call waiting call has stopped. Instead we
    210      * simulate this for the user. We allow TIMEOUT_CALL_WAITING_MILLIS milliseconds before we
    211      * assume that the call was missed and reject it ourselves. reject the call automatically.
    212      */
    213     private void startCallWaitingTimer() {
    214         mHandler.sendEmptyMessageDelayed(MSG_CALL_WAITING_MISSED, TIMEOUT_CALL_WAITING_MILLIS);
    215     }
    216 
    217     private void hangupCallWaiting(int telephonyDisconnectCause) {
    218         Connection originalConnection = getOriginalConnection();
    219         if (originalConnection != null) {
    220             try {
    221                 originalConnection.hangup();
    222             } catch (CallStateException e) {
    223                 Log.e(this, e, "Failed to hangup call waiting call");
    224             }
    225             setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause));
    226         }
    227     }
    228 
    229     /**
    230      * Read the settings to determine which type of DTMF method this CDMA phone calls.
    231      */
    232     private boolean useBurstDtmf() {
    233         if (isImsConnection()) {
    234             Log.d(this,"in ims call, return false");
    235             return false;
    236         }
    237         int dtmfTypeSetting = Settings.System.getInt(
    238                 getPhone().getContext().getContentResolver(),
    239                 Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
    240                 SettingsConstants.DTMF_TONE_TYPE_NORMAL);
    241         return dtmfTypeSetting == SettingsConstants.DTMF_TONE_TYPE_NORMAL;
    242     }
    243 
    244     private void sendShortDtmfToNetwork(char digit) {
    245         synchronized(mDtmfQueue) {
    246             if (mDtmfBurstConfirmationPending) {
    247                 mDtmfQueue.add(new Character(digit));
    248             } else {
    249                 sendBurstDtmfStringLocked(Character.toString(digit));
    250             }
    251         }
    252     }
    253 
    254     private void sendBurstDtmfStringLocked(String dtmfString) {
    255         getPhone().sendBurstDtmf(
    256                 dtmfString, 0, 0, mHandler.obtainMessage(MSG_DTMF_SEND_CONFIRMATION));
    257         mDtmfBurstConfirmationPending = true;
    258     }
    259 
    260     private void handleBurstDtmfConfirmation() {
    261         String dtmfDigits = null;
    262         synchronized(mDtmfQueue) {
    263             mDtmfBurstConfirmationPending = false;
    264             if (!mDtmfQueue.isEmpty()) {
    265                 StringBuilder builder = new StringBuilder(mDtmfQueue.size());
    266                 while (!mDtmfQueue.isEmpty()) {
    267                     builder.append(mDtmfQueue.poll());
    268                 }
    269                 dtmfDigits = builder.toString();
    270 
    271                 // It would be nice to log the digit, but since DTMF digits can be passwords
    272                 // to things, or other secure account numbers, we want to keep it away from
    273                 // the logs.
    274                 Log.i(this, "%d dtmf character[s] removed from the queue", dtmfDigits.length());
    275             }
    276             if (dtmfDigits != null) {
    277                 sendBurstDtmfStringLocked(dtmfDigits);
    278             }
    279         }
    280     }
    281 
    282     private boolean isEmergency() {
    283         Phone phone = getPhone();
    284         return phone != null &&
    285                 PhoneNumberUtils.isLocalEmergencyNumber(
    286                     phone.getContext(), getAddress().getSchemeSpecificPart());
    287     }
    288 }
    289