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