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