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