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