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.content.Context;
     20 import android.graphics.drawable.Icon;
     21 import android.net.Uri;
     22 import android.os.AsyncResult;
     23 import android.os.Bundle;
     24 import android.os.Handler;
     25 import android.os.Message;
     26 import android.os.PersistableBundle;
     27 import android.telecom.CallAudioState;
     28 import android.telecom.ConferenceParticipant;
     29 import android.telecom.Connection;
     30 import android.telecom.PhoneAccount;
     31 import android.telecom.PhoneAccountHandle;
     32 import android.telecom.StatusHints;
     33 import android.telecom.TelecomManager;
     34 import android.telecom.VideoProfile;
     35 import android.telephony.CarrierConfigManager;
     36 import android.telephony.PhoneNumberUtils;
     37 import android.telephony.TelephonyManager;
     38 import android.util.Pair;
     39 
     40 import com.android.ims.ImsCall;
     41 import com.android.ims.ImsCallProfile;
     42 import com.android.internal.telephony.Call;
     43 import com.android.internal.telephony.CallStateException;
     44 import com.android.internal.telephony.Connection.Capability;
     45 import com.android.internal.telephony.Connection.PostDialListener;
     46 import com.android.internal.telephony.PhoneConstants;
     47 import com.android.internal.telephony.gsm.SuppServiceNotification;
     48 
     49 import com.android.internal.telephony.Phone;
     50 import com.android.internal.telephony.imsphone.ImsPhone;
     51 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
     52 import com.android.phone.ImsUtil;
     53 import com.android.phone.PhoneGlobals;
     54 import com.android.phone.PhoneUtils;
     55 import com.android.phone.R;
     56 
     57 import java.lang.Override;
     58 import java.util.Arrays;
     59 import java.util.ArrayList;
     60 import java.util.Collections;
     61 import java.util.HashMap;
     62 import java.util.List;
     63 import java.util.Map;
     64 import java.util.Objects;
     65 import java.util.Set;
     66 import java.util.concurrent.ConcurrentHashMap;
     67 
     68 /**
     69  * Base class for CDMA and GSM connections.
     70  */
     71 abstract class TelephonyConnection extends Connection {
     72     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
     73     private static final int MSG_RINGBACK_TONE = 2;
     74     private static final int MSG_HANDOVER_STATE_CHANGED = 3;
     75     private static final int MSG_DISCONNECT = 4;
     76     private static final int MSG_MULTIPARTY_STATE_CHANGED = 5;
     77     private static final int MSG_CONFERENCE_MERGE_FAILED = 6;
     78     private static final int MSG_SUPP_SERVICE_NOTIFY = 7;
     79 
     80     /**
     81      * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their
     82      * equivalents defined in {@link android.telecom.Connection}.
     83      */
     84     private static final Map<String, String> sExtrasMap = createExtrasMap();
     85 
     86     private static final int MSG_SET_VIDEO_STATE = 8;
     87     private static final int MSG_SET_VIDEO_PROVIDER = 9;
     88     private static final int MSG_SET_AUDIO_QUALITY = 10;
     89     private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11;
     90     private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12;
     91     private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13;
     92     private static final int MSG_ON_HOLD_TONE = 14;
     93     private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15;
     94     private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16;
     95 
     96     private final Handler mHandler = new Handler() {
     97         @Override
     98         public void handleMessage(Message msg) {
     99             switch (msg.what) {
    100                 case MSG_PRECISE_CALL_STATE_CHANGED:
    101                     Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
    102                     updateState();
    103                     break;
    104                 case MSG_HANDOVER_STATE_CHANGED:
    105                     Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED");
    106                     AsyncResult ar = (AsyncResult) msg.obj;
    107                     com.android.internal.telephony.Connection connection =
    108                          (com.android.internal.telephony.Connection) ar.result;
    109                     if (mOriginalConnection != null) {
    110                         if (connection != null &&
    111                             ((connection.getAddress() != null &&
    112                             mOriginalConnection.getAddress() != null &&
    113                             mOriginalConnection.getAddress().contains(connection.getAddress())) ||
    114                             connection.getState() == mOriginalConnection.getStateBeforeHandover())) {
    115                             Log.d(TelephonyConnection.this,
    116                                     "SettingOriginalConnection " + mOriginalConnection.toString()
    117                                             + " with " + connection.toString());
    118                             setOriginalConnection(connection);
    119                             mWasImsConnection = false;
    120                         }
    121                     } else {
    122                         Log.w(TelephonyConnection.this,
    123                                 "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)");
    124                     }
    125                     break;
    126                 case MSG_RINGBACK_TONE:
    127                     Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
    128                     // TODO: This code assumes that there is only one connection in the foreground
    129                     // call, in other words, it punts on network-mediated conference calling.
    130                     if (getOriginalConnection() != getForegroundConnection()) {
    131                         Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
    132                                 "not foreground connection, skipping");
    133                         return;
    134                     }
    135                     setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
    136                     break;
    137                 case MSG_DISCONNECT:
    138                     updateState();
    139                     break;
    140                 case MSG_MULTIPARTY_STATE_CHANGED:
    141                     boolean isMultiParty = (Boolean) msg.obj;
    142                     Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
    143                     mIsMultiParty = isMultiParty;
    144                     if (isMultiParty) {
    145                         notifyConferenceStarted();
    146                     }
    147                     break;
    148                 case MSG_CONFERENCE_MERGE_FAILED:
    149                     notifyConferenceMergeFailed();
    150                     break;
    151                 case MSG_SUPP_SERVICE_NOTIFY:
    152                     Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : "
    153                             +getPhone().getPhoneId());
    154                     SuppServiceNotification mSsNotification = null;
    155                     if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
    156                         mSsNotification =
    157                                 (SuppServiceNotification)((AsyncResult) msg.obj).result;
    158                         if (mOriginalConnection != null && mSsNotification.history != null) {
    159                             Bundle lastForwardedNumber = new Bundle();
    160                             Log.v(TelephonyConnection.this,
    161                                     "Updating call history info in extras.");
    162                             lastForwardedNumber.putStringArrayList(
    163                                 Connection.EXTRA_LAST_FORWARDED_NUMBER,
    164                                 new ArrayList(Arrays.asList(mSsNotification.history)));
    165                             putExtras(lastForwardedNumber);
    166                         }
    167                     }
    168                     break;
    169 
    170                 case MSG_SET_VIDEO_STATE:
    171                     int videoState = (int) msg.obj;
    172                     setVideoState(videoState);
    173 
    174                     // A change to the video state of the call can influence whether or not it
    175                     // can be part of a conference, whether another call can be added, and
    176                     // whether the call should have the HD audio property set.
    177                     refreshConferenceSupported();
    178                     refreshDisableAddCall();
    179                     updateConnectionProperties();
    180                     break;
    181 
    182                 case MSG_SET_VIDEO_PROVIDER:
    183                     VideoProvider videoProvider = (VideoProvider) msg.obj;
    184                     setVideoProvider(videoProvider);
    185                     break;
    186 
    187                 case MSG_SET_AUDIO_QUALITY:
    188                     int audioQuality = (int) msg.obj;
    189                     setAudioQuality(audioQuality);
    190                     break;
    191 
    192                 case MSG_SET_CONFERENCE_PARTICIPANTS:
    193                     List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj;
    194                     updateConferenceParticipants(participants);
    195                     break;
    196 
    197                 case MSG_CONNECTION_EXTRAS_CHANGED:
    198                     final Bundle extras = (Bundle) msg.obj;
    199                     updateExtras(extras);
    200                     break;
    201 
    202                 case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES:
    203                     setOriginalConnectionCapabilities(msg.arg1);
    204                     break;
    205 
    206                 case MSG_ON_HOLD_TONE:
    207                     AsyncResult asyncResult = (AsyncResult) msg.obj;
    208                     Pair<com.android.internal.telephony.Connection, Boolean> heldInfo =
    209                             (Pair<com.android.internal.telephony.Connection, Boolean>)
    210                                     asyncResult.result;
    211 
    212                     // Determines if the hold tone is starting or stopping.
    213                     boolean playTone = ((Boolean) (heldInfo.second)).booleanValue();
    214 
    215                     // Determine which connection the hold tone is stopping or starting for
    216                     com.android.internal.telephony.Connection heldConnection = heldInfo.first;
    217 
    218                     // Only start or stop the hold tone if this is the connection which is starting
    219                     // or stopping the hold tone.
    220                     if (heldConnection == mOriginalConnection) {
    221                         // If starting the hold tone, send a connection event to Telecom which will
    222                         // cause it to play the on hold tone.
    223                         if (playTone) {
    224                             sendConnectionEvent(EVENT_ON_HOLD_TONE_START, null);
    225                         } else {
    226                             sendConnectionEvent(EVENT_ON_HOLD_TONE_END, null);
    227                         }
    228                     }
    229                     break;
    230 
    231                 case MSG_CDMA_VOICE_PRIVACY_ON:
    232                     Log.d(this, "MSG_CDMA_VOICE_PRIVACY_ON received");
    233                     setCdmaVoicePrivacy(true);
    234                     break;
    235                 case MSG_CDMA_VOICE_PRIVACY_OFF:
    236                     Log.d(this, "MSG_CDMA_VOICE_PRIVACY_OFF received");
    237                     setCdmaVoicePrivacy(false);
    238                     break;
    239             }
    240         }
    241     };
    242 
    243     /**
    244      * @return {@code true} if carrier video conferencing is supported, {@code false} otherwise.
    245      */
    246     public boolean isCarrierVideoConferencingSupported() {
    247         return mIsCarrierVideoConferencingSupported;
    248     }
    249 
    250     /**
    251      * A listener/callback mechanism that is specific communication from TelephonyConnections
    252      * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
    253      * because it is only exposed in Telephony.
    254      */
    255     public abstract static class TelephonyConnectionListener {
    256         public void onOriginalConnectionConfigured(TelephonyConnection c) {}
    257     }
    258 
    259     private final PostDialListener mPostDialListener = new PostDialListener() {
    260         @Override
    261         public void onPostDialWait() {
    262             Log.v(TelephonyConnection.this, "onPostDialWait");
    263             if (mOriginalConnection != null) {
    264                 setPostDialWait(mOriginalConnection.getRemainingPostDialString());
    265             }
    266         }
    267 
    268         @Override
    269         public void onPostDialChar(char c) {
    270             Log.v(TelephonyConnection.this, "onPostDialChar: %s", c);
    271             if (mOriginalConnection != null) {
    272                 setNextPostDialChar(c);
    273             }
    274         }
    275     };
    276 
    277     /**
    278      * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
    279      */
    280     private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
    281             new com.android.internal.telephony.Connection.ListenerBase() {
    282         @Override
    283         public void onVideoStateChanged(int videoState) {
    284             mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget();
    285         }
    286 
    287         /*
    288          * The {@link com.android.internal.telephony.Connection} has reported a change in
    289          * connection capability.
    290          * @param capabilities bit mask containing voice or video or both capabilities.
    291          */
    292         @Override
    293         public void onConnectionCapabilitiesChanged(int capabilities) {
    294             mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES,
    295                     capabilities, 0).sendToTarget();
    296         }
    297 
    298         /**
    299          * The {@link com.android.internal.telephony.Connection} has reported a change in the
    300          * video call provider.
    301          *
    302          * @param videoProvider The video call provider.
    303          */
    304         @Override
    305         public void onVideoProviderChanged(VideoProvider videoProvider) {
    306             mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget();
    307         }
    308 
    309         /**
    310          * Used by {@link com.android.internal.telephony.Connection} to report a change in whether
    311          * the call is being made over a wifi network.
    312          *
    313          * @param isWifi True if call is made over wifi.
    314          */
    315         @Override
    316         public void onWifiChanged(boolean isWifi) {
    317             setWifi(isWifi);
    318         }
    319 
    320         /**
    321          * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
    322          * audio quality for the current call.
    323          *
    324          * @param audioQuality The audio quality.
    325          */
    326         @Override
    327         public void onAudioQualityChanged(int audioQuality) {
    328             mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget();
    329         }
    330         /**
    331          * Handles a change in the state of conference participant(s), as reported by the
    332          * {@link com.android.internal.telephony.Connection}.
    333          *
    334          * @param participants The participant(s) which changed.
    335          */
    336         @Override
    337         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
    338             mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget();
    339         }
    340 
    341         /*
    342          * Handles a change to the multiparty state for this connection.
    343          *
    344          * @param isMultiParty {@code true} if the call became multiparty, {@code false}
    345          *      otherwise.
    346          */
    347         @Override
    348         public void onMultipartyStateChanged(boolean isMultiParty) {
    349             handleMultipartyStateChange(isMultiParty);
    350         }
    351 
    352         /**
    353          * Handles the event that the request to merge calls failed.
    354          */
    355         @Override
    356         public void onConferenceMergedFailed() {
    357             handleConferenceMergeFailed();
    358         }
    359 
    360         @Override
    361         public void onExtrasChanged(Bundle extras) {
    362             mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget();
    363         }
    364 
    365         /**
    366          * Handles the phone exiting ECM mode by updating the connection capabilities.  During an
    367          * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls.
    368          */
    369         @Override
    370         public void onExitedEcmMode() {
    371             handleExitedEcmMode();
    372         }
    373 
    374         /**
    375          * Called from {@link ImsPhoneCallTracker} when a request to pull an external call has
    376          * failed.
    377          * @param externalConnection
    378          */
    379         @Override
    380         public void onCallPullFailed(com.android.internal.telephony.Connection externalConnection) {
    381             if (externalConnection == null) {
    382                 return;
    383             }
    384 
    385             Log.i(this, "onCallPullFailed - pull failed; swapping back to call: %s",
    386                     externalConnection);
    387 
    388             // Inform the InCallService of the fact that the call pull failed (it may choose to
    389             // display a message informing the user of the pull failure).
    390             sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, null);
    391 
    392             // Swap the ImsPhoneConnection we used to do the pull for the ImsExternalConnection
    393             // which originally represented the call.
    394             setOriginalConnection(externalConnection);
    395 
    396             // Set our state to active again since we're no longer pulling.
    397             setActiveInternal();
    398         }
    399 
    400         /**
    401          * Called from {@link ImsPhoneCallTracker} when a handover to WIFI has failed.
    402          */
    403         @Override
    404         public void onHandoverToWifiFailed() {
    405             sendConnectionEvent(TelephonyManager.EVENT_HANDOVER_TO_WIFI_FAILED, null);
    406         }
    407 
    408         /**
    409          * Informs the {@link android.telecom.ConnectionService} of a connection event raised by the
    410          * original connection.
    411          * @param event The connection event.
    412          * @param extras The extras.
    413          */
    414         @Override
    415         public void onConnectionEvent(String event, Bundle extras) {
    416             sendConnectionEvent(event, extras);
    417         }
    418     };
    419 
    420     protected com.android.internal.telephony.Connection mOriginalConnection;
    421     private Call.State mConnectionState = Call.State.IDLE;
    422     private Bundle mOriginalConnectionExtras = new Bundle();
    423     private boolean mIsStateOverridden = false;
    424     private Call.State mOriginalConnectionState = Call.State.IDLE;
    425     private Call.State mConnectionOverriddenState = Call.State.IDLE;
    426 
    427     private boolean mWasImsConnection;
    428 
    429     /**
    430      * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
    431      */
    432     private boolean mIsMultiParty = false;
    433 
    434     /**
    435      * The {@link com.android.internal.telephony.Connection} capabilities associated with the
    436      * current {@link #mOriginalConnection}.
    437      */
    438     private int mOriginalConnectionCapabilities;
    439 
    440     /**
    441      * Determines if the {@link TelephonyConnection} is using wifi.
    442      * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to
    443      * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property.
    444      */
    445     private boolean mIsWifi;
    446 
    447     /**
    448      * Determines the audio quality is high for the {@link TelephonyConnection}.
    449      * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to
    450      * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
    451      */
    452     private boolean mHasHighDefAudio;
    453 
    454     /**
    455      * Indicates that the connection should be treated as an emergency call because the
    456      * number dialed matches an internal list of emergency numbers. Does not guarantee whether
    457      * the network will treat the call as an emergency call.
    458      */
    459     private boolean mTreatAsEmergencyCall;
    460 
    461     /**
    462      * For video calls, indicates whether the outgoing video for the call can be paused using
    463      * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
    464      */
    465     private boolean mIsVideoPauseSupported;
    466 
    467     /**
    468      * Indicates whether this connection supports being a part of a conference..
    469      */
    470     private boolean mIsConferenceSupported;
    471 
    472     /**
    473      * Indicates whether the carrier supports video conferencing; captures the current state of the
    474      * carrier config
    475      * {@link android.telephony.CarrierConfigManager#KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL}.
    476      */
    477     private boolean mIsCarrierVideoConferencingSupported;
    478 
    479     /**
    480      * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled.
    481      */
    482     private boolean mIsCdmaVoicePrivacyEnabled;
    483 
    484     /**
    485      * Listeners to our TelephonyConnection specific callbacks
    486      */
    487     private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
    488             new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
    489 
    490     protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
    491             String callId) {
    492         setTelecomCallId(callId);
    493         if (originalConnection != null) {
    494             setOriginalConnection(originalConnection);
    495         }
    496     }
    497 
    498     /**
    499      * Creates a clone of the current {@link TelephonyConnection}.
    500      *
    501      * @return The clone.
    502      */
    503     public abstract TelephonyConnection cloneConnection();
    504 
    505     @Override
    506     public void onCallAudioStateChanged(CallAudioState audioState) {
    507         // TODO: update TTY mode.
    508         if (getPhone() != null) {
    509             getPhone().setEchoSuppressionEnabled();
    510         }
    511     }
    512 
    513     @Override
    514     public void onStateChanged(int state) {
    515         Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
    516         updateStatusHints();
    517     }
    518 
    519     @Override
    520     public void onDisconnect() {
    521         Log.v(this, "onDisconnect");
    522         hangup(android.telephony.DisconnectCause.LOCAL);
    523     }
    524 
    525     /**
    526      * Notifies this Connection of a request to disconnect a participant of the conference managed
    527      * by the connection.
    528      *
    529      * @param endpoint the {@link Uri} of the participant to disconnect.
    530      */
    531     @Override
    532     public void onDisconnectConferenceParticipant(Uri endpoint) {
    533         Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
    534 
    535         if (mOriginalConnection == null) {
    536             return;
    537         }
    538 
    539         mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
    540     }
    541 
    542     @Override
    543     public void onSeparate() {
    544         Log.v(this, "onSeparate");
    545         if (mOriginalConnection != null) {
    546             try {
    547                 mOriginalConnection.separate();
    548             } catch (CallStateException e) {
    549                 Log.e(this, e, "Call to Connection.separate failed with exception");
    550             }
    551         }
    552     }
    553 
    554     @Override
    555     public void onAbort() {
    556         Log.v(this, "onAbort");
    557         hangup(android.telephony.DisconnectCause.LOCAL);
    558     }
    559 
    560     @Override
    561     public void onHold() {
    562         performHold();
    563     }
    564 
    565     @Override
    566     public void onUnhold() {
    567         performUnhold();
    568     }
    569 
    570     @Override
    571     public void onAnswer(int videoState) {
    572         Log.v(this, "onAnswer");
    573         if (isValidRingingCall() && getPhone() != null) {
    574             try {
    575                 getPhone().acceptCall(videoState);
    576             } catch (CallStateException e) {
    577                 Log.e(this, e, "Failed to accept call.");
    578             }
    579         }
    580     }
    581 
    582     @Override
    583     public void onReject() {
    584         Log.v(this, "onReject");
    585         if (isValidRingingCall()) {
    586             hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
    587         }
    588         super.onReject();
    589     }
    590 
    591     @Override
    592     public void onPostDialContinue(boolean proceed) {
    593         Log.v(this, "onPostDialContinue, proceed: " + proceed);
    594         if (mOriginalConnection != null) {
    595             if (proceed) {
    596                 mOriginalConnection.proceedAfterWaitChar();
    597             } else {
    598                 mOriginalConnection.cancelPostDial();
    599             }
    600         }
    601     }
    602 
    603     /**
    604      * Handles requests to pull an external call.
    605      */
    606     @Override
    607     public void onPullExternalCall() {
    608         if ((getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) !=
    609                 Connection.PROPERTY_IS_EXTERNAL_CALL) {
    610             Log.w(this, "onPullExternalCall - cannot pull non-external call");
    611             return;
    612         }
    613 
    614         if (mOriginalConnection != null) {
    615             mOriginalConnection.pullExternalCall();
    616         }
    617     }
    618 
    619     public void performHold() {
    620         Log.v(this, "performHold");
    621         // TODO: Can dialing calls be put on hold as well since they take up the
    622         // foreground call slot?
    623         if (Call.State.ACTIVE == mConnectionState) {
    624             Log.v(this, "Holding active call");
    625             try {
    626                 Phone phone = mOriginalConnection.getCall().getPhone();
    627                 Call ringingCall = phone.getRingingCall();
    628 
    629                 // Although the method says switchHoldingAndActive, it eventually calls a RIL method
    630                 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
    631                 // a call on hold while a call-waiting call exists, it'll end up accepting the
    632                 // call-waiting call, which is bad if that was not the user's intention. We are
    633                 // cheating here and simply skipping it because we know any attempt to hold a call
    634                 // while a call-waiting call is happening is likely a request from Telecom prior to
    635                 // accepting the call-waiting call.
    636                 // TODO: Investigate a better solution. It would be great here if we
    637                 // could "fake" hold by silencing the audio and microphone streams for this call
    638                 // instead of actually putting it on hold.
    639                 if (ringingCall.getState() != Call.State.WAITING) {
    640                     phone.switchHoldingAndActive();
    641                 }
    642 
    643                 // TODO: Cdma calls are slightly different.
    644             } catch (CallStateException e) {
    645                 Log.e(this, e, "Exception occurred while trying to put call on hold.");
    646             }
    647         } else {
    648             Log.w(this, "Cannot put a call that is not currently active on hold.");
    649         }
    650     }
    651 
    652     public void performUnhold() {
    653         Log.v(this, "performUnhold");
    654         if (Call.State.HOLDING == mConnectionState) {
    655             try {
    656                 // Here's the deal--Telephony hold/unhold is weird because whenever there exists
    657                 // more than one call, one of them must always be active. In other words, if you
    658                 // have an active call and holding call, and you put the active call on hold, it
    659                 // will automatically activate the holding call. This is weird with how Telecom
    660                 // sends its commands. When a user opts to "unhold" a background call, telecom
    661                 // issues hold commands to all active calls, and then the unhold command to the
    662                 // background call. This means that we get two commands...each of which reduces to
    663                 // switchHoldingAndActive(). The result is that they simply cancel each other out.
    664                 // To fix this so that it works well with telecom we add a minor hack. If we
    665                 // have one telephony call, everything works as normally expected. But if we have
    666                 // two or more calls, we will ignore all requests to "unhold" knowing that the hold
    667                 // requests already do what we want. If you've read up to this point, I'm very sorry
    668                 // that we are doing this. I didn't think of a better solution that wouldn't also
    669                 // make the Telecom APIs very ugly.
    670 
    671                 if (!hasMultipleTopLevelCalls()) {
    672                     mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
    673                 } else {
    674                     Log.i(this, "Skipping unhold command for %s", this);
    675                 }
    676             } catch (CallStateException e) {
    677                 Log.e(this, e, "Exception occurred while trying to release call from hold.");
    678             }
    679         } else {
    680             Log.w(this, "Cannot release a call that is not already on hold from hold.");
    681         }
    682     }
    683 
    684     public void performConference(TelephonyConnection otherConnection) {
    685         Log.d(this, "performConference - %s", this);
    686         if (getPhone() != null) {
    687             try {
    688                 // We dont use the "other" connection because there is no concept of that in the
    689                 // implementation of calls inside telephony. Basically, you can "conference" and it
    690                 // will conference with the background call.  We know that otherConnection is the
    691                 // background call because it would never have called setConferenceableConnections()
    692                 // otherwise.
    693                 getPhone().conference();
    694             } catch (CallStateException e) {
    695                 Log.e(this, e, "Failed to conference call.");
    696             }
    697         }
    698     }
    699 
    700     /**
    701      * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based
    702      * capabilities.
    703      */
    704     protected int buildConnectionCapabilities() {
    705         int callCapabilities = 0;
    706         if (mOriginalConnection != null && mOriginalConnection.isIncoming()) {
    707             callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO;
    708         }
    709         if (!shouldTreatAsEmergencyCall() && isImsConnection() && canHoldImsCalls()) {
    710             callCapabilities |= CAPABILITY_SUPPORT_HOLD;
    711             if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
    712                 callCapabilities |= CAPABILITY_HOLD;
    713             }
    714         }
    715 
    716         return callCapabilities;
    717     }
    718 
    719     protected final void updateConnectionCapabilities() {
    720         int newCapabilities = buildConnectionCapabilities();
    721 
    722         newCapabilities = applyOriginalConnectionCapabilities(newCapabilities);
    723         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
    724                 mIsVideoPauseSupported && isVideoCapable());
    725         newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL,
    726                 isExternalConnection() && isPullable());
    727         newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
    728 
    729         if (getConnectionCapabilities() != newCapabilities) {
    730             setConnectionCapabilities(newCapabilities);
    731         }
    732     }
    733 
    734     protected int buildConnectionProperties() {
    735         int connectionProperties = 0;
    736 
    737         // If the phone is in ECM mode, mark the call to indicate that the callback number should be
    738         // shown.
    739         Phone phone = getPhone();
    740         if (phone != null && phone.isInEcm()) {
    741             connectionProperties |= PROPERTY_SHOW_CALLBACK_NUMBER;
    742         }
    743 
    744         return connectionProperties;
    745     }
    746 
    747     /**
    748      * Updates the properties of the connection.
    749      */
    750     protected final void updateConnectionProperties() {
    751         int newProperties = buildConnectionProperties();
    752 
    753         newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO,
    754                 hasHighDefAudioProperty());
    755         newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi);
    756         newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
    757                 isExternalConnection());
    758         newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY,
    759                 mIsCdmaVoicePrivacyEnabled);
    760 
    761         if (getConnectionProperties() != newProperties) {
    762             setConnectionProperties(newProperties);
    763         }
    764     }
    765 
    766     protected final void updateAddress() {
    767         updateConnectionCapabilities();
    768         updateConnectionProperties();
    769         if (mOriginalConnection != null) {
    770             Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
    771             int presentation = mOriginalConnection.getNumberPresentation();
    772             if (!Objects.equals(address, getAddress()) ||
    773                     presentation != getAddressPresentation()) {
    774                 Log.v(this, "updateAddress, address changed");
    775                 if ((getConnectionProperties() & PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0) {
    776                     address = null;
    777                 }
    778                 setAddress(address, presentation);
    779             }
    780 
    781             String name = filterCnapName(mOriginalConnection.getCnapName());
    782             int namePresentation = mOriginalConnection.getCnapNamePresentation();
    783             if (!Objects.equals(name, getCallerDisplayName()) ||
    784                     namePresentation != getCallerDisplayNamePresentation()) {
    785                 Log.v(this, "updateAddress, caller display name changed");
    786                 setCallerDisplayName(name, namePresentation);
    787             }
    788 
    789             if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
    790                 mTreatAsEmergencyCall = true;
    791             }
    792 
    793             // Changing the address of the connection can change whether it is an emergency call or
    794             // not, which can impact whether it can be part of a conference.
    795             refreshConferenceSupported();
    796         }
    797     }
    798 
    799     void onRemovedFromCallService() {
    800         // Subclass can override this to do cleanup.
    801     }
    802 
    803     void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
    804         Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
    805         clearOriginalConnection();
    806         mOriginalConnectionExtras.clear();
    807         mOriginalConnection = originalConnection;
    808         mOriginalConnection.setTelecomCallId(getTelecomCallId());
    809         getPhone().registerForPreciseCallStateChanged(
    810                 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
    811         getPhone().registerForHandoverStateChanged(
    812                 mHandler, MSG_HANDOVER_STATE_CHANGED, null);
    813         getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
    814         getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
    815         getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
    816         getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
    817         getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null);
    818         getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null);
    819         mOriginalConnection.addPostDialListener(mPostDialListener);
    820         mOriginalConnection.addListener(mOriginalConnectionListener);
    821 
    822         // Set video state and capabilities
    823         setVideoState(mOriginalConnection.getVideoState());
    824         setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
    825         setWifi(mOriginalConnection.isWifi());
    826         setVideoProvider(mOriginalConnection.getVideoProvider());
    827         setAudioQuality(mOriginalConnection.getAudioQuality());
    828         setTechnologyTypeExtra();
    829 
    830         // Post update of extras to the handler; extras are updated via the handler to ensure thread
    831         // safety. The Extras Bundle is cloned in case the original extras are modified while they
    832         // are being added to mOriginalConnectionExtras in updateExtras.
    833         Bundle connExtras = mOriginalConnection.getConnectionExtras();
    834             mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null :
    835                     new Bundle(connExtras)).sendToTarget();
    836 
    837         if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
    838             mTreatAsEmergencyCall = true;
    839         }
    840 
    841         if (isImsConnection()) {
    842             mWasImsConnection = true;
    843         }
    844         mIsMultiParty = mOriginalConnection.isMultiparty();
    845 
    846         Bundle extrasToPut = new Bundle();
    847         List<String> extrasToRemove = new ArrayList<>();
    848         if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) {
    849             extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
    850         } else {
    851             extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
    852         }
    853 
    854         if (shouldSetDisableAddCallExtra()) {
    855             extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true);
    856         } else {
    857             extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL);
    858         }
    859         putExtras(extrasToPut);
    860         removeExtras(extrasToRemove);
    861 
    862         // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
    863         // should be executed *after* the above setters have run.
    864         updateState();
    865         if (mOriginalConnection == null) {
    866             Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " +
    867                     originalConnection);
    868         }
    869 
    870         fireOnOriginalConnectionConfigured();
    871     }
    872 
    873     /**
    874      * Filters the CNAP name to not include a list of names that are unhelpful to the user for
    875      * Caller ID purposes.
    876      */
    877     private String filterCnapName(final String cnapName) {
    878         if (cnapName == null) {
    879             return null;
    880         }
    881         PersistableBundle carrierConfig = getCarrierConfig();
    882         String[] filteredCnapNames = null;
    883         if (carrierConfig != null) {
    884             filteredCnapNames = carrierConfig.getStringArray(
    885                     CarrierConfigManager.FILTERED_CNAP_NAMES_STRING_ARRAY);
    886         }
    887         if (filteredCnapNames != null) {
    888             long cnapNameMatches = Arrays.asList(filteredCnapNames)
    889                     .stream()
    890                     .filter(filteredCnapName -> filteredCnapName.equals(cnapName.toUpperCase()))
    891                     .count();
    892             if (cnapNameMatches > 0) {
    893                 Log.i(this, "filterCnapName: Filtered CNAP Name: " + cnapName);
    894                 return "";
    895             }
    896         }
    897         return cnapName;
    898     }
    899 
    900     /**
    901      * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom.
    902      */
    903     private void setTechnologyTypeExtra() {
    904         if (getPhone() != null) {
    905             putExtra(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType());
    906         }
    907     }
    908 
    909     private void refreshDisableAddCall() {
    910         if (shouldSetDisableAddCallExtra()) {
    911             putExtra(Connection.EXTRA_DISABLE_ADD_CALL, true);
    912         } else {
    913             removeExtras(Connection.EXTRA_DISABLE_ADD_CALL);
    914         }
    915     }
    916 
    917     private boolean shouldSetDisableAddCallExtra() {
    918         boolean carrierShouldAllowAddCall = mOriginalConnection.shouldAllowAddCallDuringVideoCall();
    919         if (carrierShouldAllowAddCall) {
    920             return false;
    921         }
    922         Phone phone = getPhone();
    923         if (phone == null) {
    924             return false;
    925         }
    926         boolean isCurrentVideoCall = false;
    927         boolean wasVideoCall = false;
    928         boolean isVowifiEnabled = false;
    929         if (phone instanceof ImsPhone) {
    930             ImsPhone imsPhone = (ImsPhone) phone;
    931             if (imsPhone.getForegroundCall() != null
    932                     && imsPhone.getForegroundCall().getImsCall() != null) {
    933                 ImsCall call = imsPhone.getForegroundCall().getImsCall();
    934                 isCurrentVideoCall = call.isVideoCall();
    935                 wasVideoCall = call.wasVideoCall();
    936             }
    937 
    938             isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext());
    939         }
    940 
    941         if (isCurrentVideoCall) {
    942             return true;
    943         } else if (wasVideoCall && mIsWifi && !isVowifiEnabled) {
    944             return true;
    945         }
    946         return false;
    947     }
    948 
    949     private boolean hasHighDefAudioProperty() {
    950         if (!mHasHighDefAudio) {
    951             return false;
    952         }
    953 
    954         boolean isVideoCall = VideoProfile.isVideo(getVideoState());
    955 
    956         PersistableBundle b = getCarrierConfig();
    957         boolean canWifiCallsBeHdAudio =
    958                 b != null && b.getBoolean(CarrierConfigManager.KEY_WIFI_CALLS_CAN_BE_HD_AUDIO);
    959         boolean canVideoCallsBeHdAudio =
    960                 b != null && b.getBoolean(CarrierConfigManager.KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO);
    961 
    962         if (isVideoCall && !canVideoCallsBeHdAudio) {
    963             return false;
    964         }
    965 
    966         if (mIsWifi && !canWifiCallsBeHdAudio) {
    967             return false;
    968         }
    969 
    970         return true;
    971     }
    972 
    973     private boolean canHoldImsCalls() {
    974         PersistableBundle b = getCarrierConfig();
    975         // Return true if the CarrierConfig is unavailable
    976         return b == null || b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL);
    977     }
    978 
    979     private PersistableBundle getCarrierConfig() {
    980         Phone phone = getPhone();
    981         if (phone == null) {
    982             return null;
    983         }
    984         return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
    985     }
    986 
    987     /**
    988      * Whether the connection should be treated as an emergency.
    989      * @return {@code true} if the connection should be treated as an emergency call based
    990      * on the number dialed, {@code false} otherwise.
    991      */
    992     protected boolean shouldTreatAsEmergencyCall() {
    993         return mTreatAsEmergencyCall;
    994     }
    995 
    996     /**
    997      * Un-sets the underlying radio connection.
    998      */
    999     void clearOriginalConnection() {
   1000         if (mOriginalConnection != null) {
   1001             if (getPhone() != null) {
   1002                 getPhone().unregisterForPreciseCallStateChanged(mHandler);
   1003                 getPhone().unregisterForRingbackTone(mHandler);
   1004                 getPhone().unregisterForHandoverStateChanged(mHandler);
   1005                 getPhone().unregisterForDisconnect(mHandler);
   1006                 getPhone().unregisterForSuppServiceNotification(mHandler);
   1007                 getPhone().unregisterForOnHoldTone(mHandler);
   1008                 getPhone().unregisterForInCallVoicePrivacyOn(mHandler);
   1009                 getPhone().unregisterForInCallVoicePrivacyOff(mHandler);
   1010             }
   1011             mOriginalConnection.removePostDialListener(mPostDialListener);
   1012             mOriginalConnection.removeListener(mOriginalConnectionListener);
   1013             mOriginalConnection = null;
   1014         }
   1015     }
   1016 
   1017     protected void hangup(int telephonyDisconnectCode) {
   1018         if (mOriginalConnection != null) {
   1019             try {
   1020                 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
   1021                 // connection.hangup(). Without this change, the party originating the call will not
   1022                 // get sent to voicemail if the user opts to reject the call.
   1023                 if (isValidRingingCall()) {
   1024                     Call call = getCall();
   1025                     if (call != null) {
   1026                         call.hangup();
   1027                     } else {
   1028                         Log.w(this, "Attempting to hangup a connection without backing call.");
   1029                     }
   1030                 } else {
   1031                     // We still prefer to call connection.hangup() for non-ringing calls in order
   1032                     // to support hanging-up specific calls within a conference call. If we invoked
   1033                     // call.hangup() while in a conference, we would end up hanging up the entire
   1034                     // conference call instead of the specific connection.
   1035                     mOriginalConnection.hangup();
   1036                 }
   1037             } catch (CallStateException e) {
   1038                 Log.e(this, e, "Call to Connection.hangup failed with exception");
   1039             }
   1040         } else {
   1041             if (getState() == STATE_DISCONNECTED) {
   1042                 Log.i(this, "hangup called on an already disconnected call!");
   1043                 close();
   1044             } else {
   1045                 // There are a few cases where mOriginalConnection has not been set yet. For
   1046                 // example, when the radio has to be turned on to make an emergency call,
   1047                 // mOriginalConnection could not be set for many seconds.
   1048                 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
   1049                         android.telephony.DisconnectCause.LOCAL,
   1050                         "Local Disconnect before connection established."));
   1051                 close();
   1052             }
   1053         }
   1054     }
   1055 
   1056     com.android.internal.telephony.Connection getOriginalConnection() {
   1057         return mOriginalConnection;
   1058     }
   1059 
   1060     protected Call getCall() {
   1061         if (mOriginalConnection != null) {
   1062             return mOriginalConnection.getCall();
   1063         }
   1064         return null;
   1065     }
   1066 
   1067     Phone getPhone() {
   1068         Call call = getCall();
   1069         if (call != null) {
   1070             return call.getPhone();
   1071         }
   1072         return null;
   1073     }
   1074 
   1075     private boolean hasMultipleTopLevelCalls() {
   1076         int numCalls = 0;
   1077         Phone phone = getPhone();
   1078         if (phone != null) {
   1079             if (!phone.getRingingCall().isIdle()) {
   1080                 numCalls++;
   1081             }
   1082             if (!phone.getForegroundCall().isIdle()) {
   1083                 numCalls++;
   1084             }
   1085             if (!phone.getBackgroundCall().isIdle()) {
   1086                 numCalls++;
   1087             }
   1088         }
   1089         return numCalls > 1;
   1090     }
   1091 
   1092     private com.android.internal.telephony.Connection getForegroundConnection() {
   1093         if (getPhone() != null) {
   1094             return getPhone().getForegroundCall().getEarliestConnection();
   1095         }
   1096         return null;
   1097     }
   1098 
   1099      /**
   1100      * Checks for and returns the list of conference participants
   1101      * associated with this connection.
   1102      */
   1103     public List<ConferenceParticipant> getConferenceParticipants() {
   1104         if (mOriginalConnection == null) {
   1105             Log.v(this, "Null mOriginalConnection, cannot get conf participants.");
   1106             return null;
   1107         }
   1108         return mOriginalConnection.getConferenceParticipants();
   1109     }
   1110 
   1111     /**
   1112      * Checks to see the original connection corresponds to an active incoming call. Returns false
   1113      * if there is no such actual call, or if the associated call is not incoming (See
   1114      * {@link Call.State#isRinging}).
   1115      */
   1116     private boolean isValidRingingCall() {
   1117         if (getPhone() == null) {
   1118             Log.v(this, "isValidRingingCall, phone is null");
   1119             return false;
   1120         }
   1121 
   1122         Call ringingCall = getPhone().getRingingCall();
   1123         if (!ringingCall.getState().isRinging()) {
   1124             Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
   1125             return false;
   1126         }
   1127 
   1128         if (ringingCall.getEarliestConnection() != mOriginalConnection) {
   1129             Log.v(this, "isValidRingingCall, ringing call connection does not match");
   1130             return false;
   1131         }
   1132 
   1133         Log.v(this, "isValidRingingCall, returning true");
   1134         return true;
   1135     }
   1136 
   1137     // Make sure the extras being passed into this method is a COPY of the original extras Bundle.
   1138     // We do not want the extras to be cleared or modified during mOriginalConnectionExtras.putAll
   1139     // below.
   1140     protected void updateExtras(Bundle extras) {
   1141         if (mOriginalConnection != null) {
   1142             if (extras != null) {
   1143                 // Check if extras have changed and need updating.
   1144                 if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
   1145                     if (Log.DEBUG) {
   1146                         Log.d(TelephonyConnection.this, "Updating extras:");
   1147                         for (String key : extras.keySet()) {
   1148                             Object value = extras.get(key);
   1149                             if (value instanceof String) {
   1150                                 Log.d(this, "updateExtras Key=" + Log.pii(key) +
   1151                                              " value=" + Log.pii((String)value));
   1152                             }
   1153                         }
   1154                     }
   1155                     mOriginalConnectionExtras.clear();
   1156 
   1157                     mOriginalConnectionExtras.putAll(extras);
   1158 
   1159                     // Remap any string extras that have a remapping defined.
   1160                     for (String key : mOriginalConnectionExtras.keySet()) {
   1161                         if (sExtrasMap.containsKey(key)) {
   1162                             String newKey = sExtrasMap.get(key);
   1163                             mOriginalConnectionExtras.putString(newKey, extras.getString(key));
   1164                             mOriginalConnectionExtras.remove(key);
   1165                         }
   1166                     }
   1167 
   1168                     // Ensure extras are propagated to Telecom.
   1169                     putExtras(mOriginalConnectionExtras);
   1170                 } else {
   1171                     Log.d(this, "Extras update not required");
   1172                 }
   1173             } else {
   1174                 Log.d(this, "updateExtras extras: " + Log.pii(extras));
   1175             }
   1176         }
   1177     }
   1178 
   1179     private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
   1180         if (extras == null || newExtras == null) {
   1181             return extras == newExtras;
   1182         }
   1183 
   1184         if (extras.size() != newExtras.size()) {
   1185             return false;
   1186         }
   1187 
   1188         for(String key : extras.keySet()) {
   1189             if (key != null) {
   1190                 final Object value = extras.get(key);
   1191                 final Object newValue = newExtras.get(key);
   1192                 if (!Objects.equals(value, newValue)) {
   1193                     return false;
   1194                 }
   1195             }
   1196         }
   1197         return true;
   1198     }
   1199 
   1200     void setStateOverride(Call.State state) {
   1201         mIsStateOverridden = true;
   1202         mConnectionOverriddenState = state;
   1203         // Need to keep track of the original connection's state before override.
   1204         mOriginalConnectionState = mOriginalConnection.getState();
   1205         updateStateInternal();
   1206     }
   1207 
   1208     void resetStateOverride() {
   1209         mIsStateOverridden = false;
   1210         updateStateInternal();
   1211     }
   1212 
   1213     void updateStateInternal() {
   1214         if (mOriginalConnection == null) {
   1215             return;
   1216         }
   1217         Call.State newState;
   1218         // If the state is overridden and the state of the original connection hasn't changed since,
   1219         // then we continue in the overridden state, else we go to the original connection's state.
   1220         if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) {
   1221             newState = mConnectionOverriddenState;
   1222         } else {
   1223             newState = mOriginalConnection.getState();
   1224         }
   1225         Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this);
   1226 
   1227         if (mConnectionState != newState) {
   1228             mConnectionState = newState;
   1229             switch (newState) {
   1230                 case IDLE:
   1231                     break;
   1232                 case ACTIVE:
   1233                     setActiveInternal();
   1234                     break;
   1235                 case HOLDING:
   1236                     setOnHold();
   1237                     break;
   1238                 case DIALING:
   1239                 case ALERTING:
   1240                     if (mOriginalConnection != null && mOriginalConnection.isPulledCall()) {
   1241                         setPulling();
   1242                     } else {
   1243                         setDialing();
   1244                     }
   1245                     break;
   1246                 case INCOMING:
   1247                 case WAITING:
   1248                     setRinging();
   1249                     break;
   1250                 case DISCONNECTED:
   1251                     setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
   1252                             mOriginalConnection.getDisconnectCause(),
   1253                             mOriginalConnection.getVendorDisconnectCause()));
   1254                     close();
   1255                     break;
   1256                 case DISCONNECTING:
   1257                     break;
   1258             }
   1259         }
   1260     }
   1261 
   1262     void updateState() {
   1263         if (mOriginalConnection == null) {
   1264             return;
   1265         }
   1266 
   1267         updateStateInternal();
   1268         updateStatusHints();
   1269         updateConnectionCapabilities();
   1270         updateConnectionProperties();
   1271         updateAddress();
   1272         updateMultiparty();
   1273     }
   1274 
   1275     /**
   1276      * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
   1277      */
   1278     private void updateMultiparty() {
   1279         if (mOriginalConnection == null) {
   1280             return;
   1281         }
   1282 
   1283         if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
   1284             mIsMultiParty = mOriginalConnection.isMultiparty();
   1285 
   1286             if (mIsMultiParty) {
   1287                 notifyConferenceStarted();
   1288             }
   1289         }
   1290     }
   1291 
   1292     /**
   1293      * Handles a failure when merging calls into a conference.
   1294      * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()}
   1295      * listener.
   1296      */
   1297     private void handleConferenceMergeFailed(){
   1298         mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget();
   1299     }
   1300 
   1301     /**
   1302      * Handles requests to update the multiparty state received via the
   1303      * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
   1304      * listener.
   1305      * <p>
   1306      * Note: We post this to the mHandler to ensure that if a conference must be created as a
   1307      * result of the multiparty state change, the conference creation happens on the correct
   1308      * thread.  This ensures that the thread check in
   1309      * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)}
   1310      * does not fire.
   1311      *
   1312      * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
   1313      */
   1314     private void handleMultipartyStateChange(boolean isMultiParty) {
   1315         Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
   1316         mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
   1317     }
   1318 
   1319     private void setActiveInternal() {
   1320         if (getState() == STATE_ACTIVE) {
   1321             Log.w(this, "Should not be called if this is already ACTIVE");
   1322             return;
   1323         }
   1324 
   1325         // When we set a call to active, we need to make sure that there are no other active
   1326         // calls. However, the ordering of state updates to connections can be non-deterministic
   1327         // since all connections register for state changes on the phone independently.
   1328         // To "optimize", we check here to see if there already exists any active calls.  If so,
   1329         // we issue an update for those calls first to make sure we only have one top-level
   1330         // active call.
   1331         if (getConnectionService() != null) {
   1332             for (Connection current : getConnectionService().getAllConnections()) {
   1333                 if (current != this && current instanceof TelephonyConnection) {
   1334                     TelephonyConnection other = (TelephonyConnection) current;
   1335                     if (other.getState() == STATE_ACTIVE) {
   1336                         other.updateState();
   1337                     }
   1338                 }
   1339             }
   1340         }
   1341         setActive();
   1342     }
   1343 
   1344     private void close() {
   1345         Log.v(this, "close");
   1346         clearOriginalConnection();
   1347         destroy();
   1348     }
   1349 
   1350     /**
   1351      * Determines if the current connection is video capable.
   1352      *
   1353      * A connection is deemed to be video capable if the original connection capabilities state that
   1354      * both local and remote video is supported.
   1355      *
   1356      * @return {@code true} if the connection is video capable, {@code false} otherwise.
   1357      */
   1358     private boolean isVideoCapable() {
   1359         return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
   1360                 && can(mOriginalConnectionCapabilities,
   1361                 Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
   1362     }
   1363 
   1364     /**
   1365      * Determines if the current connection is an external connection.
   1366      *
   1367      * A connection is deemed to be external if the original connection capabilities state that it
   1368      * is.
   1369      *
   1370      * @return {@code true} if the connection is external, {@code false} otherwise.
   1371      */
   1372     private boolean isExternalConnection() {
   1373         return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
   1374                 && can(mOriginalConnectionCapabilities,
   1375                 Capability.IS_EXTERNAL_CONNECTION);
   1376     }
   1377 
   1378     /**
   1379      * Determines if the current connection is pullable.
   1380      *
   1381      * A connection is deemed to be pullable if the original connection capabilities state that it
   1382      * is.
   1383      *
   1384      * @return {@code true} if the connection is pullable, {@code false} otherwise.
   1385      */
   1386     private boolean isPullable() {
   1387         return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION)
   1388                 && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE);
   1389     }
   1390 
   1391     /**
   1392      * Sets whether or not CDMA enhanced call privacy is enabled for this connection.
   1393      */
   1394     private void setCdmaVoicePrivacy(boolean isEnabled) {
   1395         if(mIsCdmaVoicePrivacyEnabled != isEnabled) {
   1396             mIsCdmaVoicePrivacyEnabled = isEnabled;
   1397             updateConnectionProperties();
   1398         }
   1399     }
   1400 
   1401     /**
   1402      * Applies capabilities specific to conferences termination to the
   1403      * {@code ConnectionCapabilities} bit-mask.
   1404      *
   1405      * @param capabilities The {@code ConnectionCapabilities} bit-mask.
   1406      * @return The capabilities with the IMS conference capabilities applied.
   1407      */
   1408     private int applyConferenceTerminationCapabilities(int capabilities) {
   1409         int currentCapabilities = capabilities;
   1410 
   1411         // An IMS call cannot be individually disconnected or separated from its parent conference.
   1412         // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
   1413         if (!mWasImsConnection) {
   1414             currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
   1415             currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
   1416         }
   1417 
   1418         return currentCapabilities;
   1419     }
   1420 
   1421     /**
   1422      * Stores the new original connection capabilities, and applies them to the current connection,
   1423      * notifying any listeners as necessary.
   1424      *
   1425      * @param connectionCapabilities The original connection capabilties.
   1426      */
   1427     public void setOriginalConnectionCapabilities(int connectionCapabilities) {
   1428         mOriginalConnectionCapabilities = connectionCapabilities;
   1429         updateConnectionCapabilities();
   1430         updateConnectionProperties();
   1431     }
   1432 
   1433     /**
   1434      * Called to apply the capabilities present in the {@link #mOriginalConnection} to this
   1435      * {@link Connection}.  Provides a mapping between the capabilities present in the original
   1436      * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in
   1437      * this {@link Connection}.
   1438      *
   1439      * @param capabilities The capabilities bitmask from the {@link Connection}.
   1440      * @return the capabilities bitmask with the original connection capabilities remapped and
   1441      *      applied.
   1442      */
   1443     public int applyOriginalConnectionCapabilities(int capabilities) {
   1444         // We only support downgrading to audio if both the remote and local side support
   1445         // downgrading to audio.
   1446         boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities,
   1447                 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
   1448                         Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE);
   1449         capabilities = changeBitmask(capabilities,
   1450                 CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio);
   1451 
   1452         capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
   1453                 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
   1454 
   1455         capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
   1456                 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
   1457 
   1458         return capabilities;
   1459     }
   1460 
   1461     /**
   1462      * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
   1463      * the {@link Connection#PROPERTY_WIFI} property.
   1464      */
   1465     public void setWifi(boolean isWifi) {
   1466         mIsWifi = isWifi;
   1467         updateConnectionProperties();
   1468         updateStatusHints();
   1469         refreshDisableAddCall();
   1470     }
   1471 
   1472     /**
   1473      * Whether the call is using wifi.
   1474      */
   1475     boolean isWifi() {
   1476         return mIsWifi;
   1477     }
   1478 
   1479     /**
   1480      * Sets the current call audio quality. Used during rebuild of the properties
   1481      * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
   1482      *
   1483      * @param audioQuality The audio quality.
   1484      */
   1485     public void setAudioQuality(int audioQuality) {
   1486         mHasHighDefAudio = audioQuality ==
   1487                 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
   1488         updateConnectionProperties();
   1489     }
   1490 
   1491     void resetStateForConference() {
   1492         if (getState() == Connection.STATE_HOLDING) {
   1493             resetStateOverride();
   1494         }
   1495     }
   1496 
   1497     boolean setHoldingForConference() {
   1498         if (getState() == Connection.STATE_ACTIVE) {
   1499             setStateOverride(Call.State.HOLDING);
   1500             return true;
   1501         }
   1502         return false;
   1503     }
   1504 
   1505     /**
   1506      * For video calls, sets whether this connection supports pausing the outgoing video for the
   1507      * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
   1508      *
   1509      * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
   1510      */
   1511     public void setVideoPauseSupported(boolean isVideoPauseSupported) {
   1512         mIsVideoPauseSupported = isVideoPauseSupported;
   1513     }
   1514 
   1515     /**
   1516      * Sets whether this connection supports conference calling.
   1517      * @param isConferenceSupported {@code true} if conference calling is supported by this
   1518      *                                         connection, {@code false} otherwise.
   1519      */
   1520     public void setConferenceSupported(boolean isConferenceSupported) {
   1521         mIsConferenceSupported = isConferenceSupported;
   1522     }
   1523 
   1524     /**
   1525      * @return {@code true} if this connection supports merging calls into a conference.
   1526      */
   1527     public boolean isConferenceSupported() {
   1528         return mIsConferenceSupported;
   1529     }
   1530 
   1531     /**
   1532      * Whether the original connection is an IMS connection.
   1533      * @return {@code True} if the original connection is an IMS connection, {@code false}
   1534      *     otherwise.
   1535      */
   1536     protected boolean isImsConnection() {
   1537         com.android.internal.telephony.Connection originalConnection = getOriginalConnection();
   1538         return originalConnection != null &&
   1539                 originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
   1540     }
   1541 
   1542     /**
   1543      * Whether the original connection was ever an IMS connection, either before or now.
   1544      * @return {@code True} if the original connection was ever an IMS connection, {@code false}
   1545      *     otherwise.
   1546      */
   1547     public boolean wasImsConnection() {
   1548         return mWasImsConnection;
   1549     }
   1550 
   1551     private static Uri getAddressFromNumber(String number) {
   1552         // Address can be null for blocked calls.
   1553         if (number == null) {
   1554             number = "";
   1555         }
   1556         return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
   1557     }
   1558 
   1559     /**
   1560      * Changes a capabilities bit-mask to add or remove a capability.
   1561      *
   1562      * @param bitmask The bit-mask.
   1563      * @param bitfield The bit-field to change.
   1564      * @param enabled Whether the bit-field should be set or removed.
   1565      * @return The bit-mask with the bit-field changed.
   1566      */
   1567     private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
   1568         if (enabled) {
   1569             return bitmask | bitfield;
   1570         } else {
   1571             return bitmask & ~bitfield;
   1572         }
   1573     }
   1574 
   1575     private void updateStatusHints() {
   1576         boolean isIncoming = isValidRingingCall();
   1577         if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) {
   1578             int labelId = isIncoming
   1579                     ? R.string.status_hint_label_incoming_wifi_call
   1580                     : R.string.status_hint_label_wifi_call;
   1581 
   1582             Context context = getPhone().getContext();
   1583             setStatusHints(new StatusHints(
   1584                     context.getString(labelId),
   1585                     Icon.createWithResource(
   1586                             context.getResources(),
   1587                             R.drawable.ic_signal_wifi_4_bar_24dp),
   1588                     null /* extras */));
   1589         } else {
   1590             setStatusHints(null);
   1591         }
   1592     }
   1593 
   1594     /**
   1595      * Register a listener for {@link TelephonyConnection} specific triggers.
   1596      * @param l The instance of the listener to add
   1597      * @return The connection being listened to
   1598      */
   1599     public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
   1600         mTelephonyListeners.add(l);
   1601         // If we already have an original connection, let's call back immediately.
   1602         // This would be the case for incoming calls.
   1603         if (mOriginalConnection != null) {
   1604             fireOnOriginalConnectionConfigured();
   1605         }
   1606         return this;
   1607     }
   1608 
   1609     /**
   1610      * Remove a listener for {@link TelephonyConnection} specific triggers.
   1611      * @param l The instance of the listener to remove
   1612      * @return The connection being listened to
   1613      */
   1614     public final TelephonyConnection removeTelephonyConnectionListener(
   1615             TelephonyConnectionListener l) {
   1616         if (l != null) {
   1617             mTelephonyListeners.remove(l);
   1618         }
   1619         return this;
   1620     }
   1621 
   1622     /**
   1623      * Fire a callback to the various listeners for when the original connection is
   1624      * set in this {@link TelephonyConnection}
   1625      */
   1626     private final void fireOnOriginalConnectionConfigured() {
   1627         for (TelephonyConnectionListener l : mTelephonyListeners) {
   1628             l.onOriginalConnectionConfigured(this);
   1629         }
   1630     }
   1631 
   1632     /**
   1633      * Handles exiting ECM mode.
   1634      */
   1635     protected void handleExitedEcmMode() {
   1636         updateConnectionProperties();
   1637     }
   1638 
   1639     /**
   1640      * Determines whether the connection supports conference calling.  A connection supports
   1641      * conference calling if it:
   1642      * 1. Is not an emergency call.
   1643      * 2. Carrier supports conference calls.
   1644      * 3. If call is a video call, carrier supports video conference calls.
   1645      * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls.
   1646      */
   1647     private void refreshConferenceSupported() {
   1648         boolean isVideoCall = VideoProfile.isVideo(getVideoState());
   1649         Phone phone = getPhone();
   1650         boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
   1651         boolean isVoWifiEnabled = false;
   1652         if (isIms) {
   1653             ImsPhone imsPhone = (ImsPhone) phone;
   1654             isVoWifiEnabled = imsPhone.isWifiCallingEnabled();
   1655         }
   1656         PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
   1657                 .makePstnPhoneAccountHandle(phone.getDefaultPhone())
   1658                 : PhoneUtils.makePstnPhoneAccountHandle(phone);
   1659         TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry
   1660                 .getInstance(getPhone().getContext());
   1661         boolean isConferencingSupported = telecomAccountRegistry
   1662                 .isMergeCallSupported(phoneAccountHandle);
   1663         mIsCarrierVideoConferencingSupported = telecomAccountRegistry
   1664                 .isVideoConferencingSupported(phoneAccountHandle);
   1665         boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry
   1666                 .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle);
   1667 
   1668         Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isVidConfSupp=%b, " +
   1669                 "isMergeOfWifiAllowed=%b, isWifi=%b, isVoWifiEnabled=%b", isConferencingSupported,
   1670                 mIsCarrierVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff,
   1671                 isWifi(), isVoWifiEnabled);
   1672         boolean isConferenceSupported = true;
   1673         if (mTreatAsEmergencyCall) {
   1674             isConferenceSupported = false;
   1675             Log.d(this, "refreshConferenceSupported = false; emergency call");
   1676         } else if (!isConferencingSupported) {
   1677             isConferenceSupported = false;
   1678             Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf.");
   1679         } else if (isVideoCall && !mIsCarrierVideoConferencingSupported) {
   1680             isConferenceSupported = false;
   1681             Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
   1682         } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {
   1683             isConferenceSupported = false;
   1684             Log.d(this,
   1685                     "refreshConferenceSupported = false; can't merge wifi calls when voWifi off.");
   1686         } else {
   1687             Log.d(this, "refreshConferenceSupported = true.");
   1688         }
   1689 
   1690         if (isConferenceSupported != isConferenceSupported()) {
   1691             setConferenceSupported(isConferenceSupported);
   1692             notifyConferenceSupportedChanged(isConferenceSupported);
   1693         }
   1694     }
   1695     /**
   1696      * Provides a mapping from extras keys which may be found in the
   1697      * {@link com.android.internal.telephony.Connection} to their equivalents defined in
   1698      * {@link android.telecom.Connection}.
   1699      *
   1700      * @return Map containing key mappings.
   1701      */
   1702     private static Map<String, String> createExtrasMap() {
   1703         Map<String, String> result = new HashMap<String, String>();
   1704         result.put(ImsCallProfile.EXTRA_CHILD_NUMBER,
   1705                 android.telecom.Connection.EXTRA_CHILD_ADDRESS);
   1706         result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT,
   1707                 android.telecom.Connection.EXTRA_CALL_SUBJECT);
   1708         return Collections.unmodifiableMap(result);
   1709     }
   1710 
   1711     /**
   1712      * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
   1713      * use in log statements.
   1714      *
   1715      * @return String representation of the connection.
   1716      */
   1717     @Override
   1718     public String toString() {
   1719         StringBuilder sb = new StringBuilder();
   1720         sb.append("[TelephonyConnection objId:");
   1721         sb.append(System.identityHashCode(this));
   1722         sb.append(" telecomCallID:");
   1723         sb.append(getTelecomCallId());
   1724         sb.append(" type:");
   1725         if (isImsConnection()) {
   1726             sb.append("ims");
   1727         } else if (this instanceof com.android.services.telephony.GsmConnection) {
   1728             sb.append("gsm");
   1729         } else if (this instanceof CdmaConnection) {
   1730             sb.append("cdma");
   1731         }
   1732         sb.append(" state:");
   1733         sb.append(Connection.stateToString(getState()));
   1734         sb.append(" capabilities:");
   1735         sb.append(capabilitiesToString(getConnectionCapabilities()));
   1736         sb.append(" properties:");
   1737         sb.append(propertiesToString(getConnectionProperties()));
   1738         sb.append(" address:");
   1739         sb.append(Log.pii(getAddress()));
   1740         sb.append(" originalConnection:");
   1741         sb.append(mOriginalConnection);
   1742         sb.append(" partOfConf:");
   1743         if (getConference() == null) {
   1744             sb.append("N");
   1745         } else {
   1746             sb.append("Y");
   1747         }
   1748         sb.append(" confSupported:");
   1749         sb.append(mIsConferenceSupported ? "Y" : "N");
   1750         sb.append("]");
   1751         return sb.toString();
   1752     }
   1753 }
   1754