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