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