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