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.net.Uri;
20 import android.os.AsyncResult;
21 import android.os.Handler;
22 import android.os.Message;
23 import android.telecom.AudioState;
24 import android.telecom.ConferenceParticipant;
25 import android.telecom.Connection;
26 import android.telecom.PhoneAccount;
27 
28 import com.android.internal.telephony.Call;
29 import com.android.internal.telephony.CallStateException;
30 import com.android.internal.telephony.Connection.PostDialListener;
31 import com.android.internal.telephony.Phone;
32 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
33 
34 import java.lang.Override;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Objects;
38 import java.util.Set;
39 import java.util.concurrent.ConcurrentHashMap;
40 
41 /**
42  * Base class for CDMA and GSM connections.
43  */
44 abstract class TelephonyConnection extends Connection {
45     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
46     private static final int MSG_RINGBACK_TONE = 2;
47     private static final int MSG_HANDOVER_STATE_CHANGED = 3;
48     private static final int MSG_DISCONNECT = 4;
49 
50     private final Handler mHandler = new Handler() {
51         @Override
52         public void handleMessage(Message msg) {
53             switch (msg.what) {
54                 case MSG_PRECISE_CALL_STATE_CHANGED:
55                     Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
56                     updateState();
57                     break;
58                 case MSG_HANDOVER_STATE_CHANGED:
59                     Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED");
60                     AsyncResult ar = (AsyncResult) msg.obj;
61                     com.android.internal.telephony.Connection connection =
62                          (com.android.internal.telephony.Connection) ar.result;
63                     if ((connection.getAddress() != null &&
64                                     mOriginalConnection.getAddress() != null &&
65                             mOriginalConnection.getAddress().contains(connection.getAddress())) ||
66                             connection.getStateBeforeHandover() == mOriginalConnection.getState()) {
67                         Log.d(TelephonyConnection.this, "SettingOriginalConnection " +
68                                 mOriginalConnection.toString() + " with " + connection.toString());
69                         setOriginalConnection(connection);
70                     }
71                     break;
72                 case MSG_RINGBACK_TONE:
73                     Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
74                     // TODO: This code assumes that there is only one connection in the foreground
75                     // call, in other words, it punts on network-mediated conference calling.
76                     if (getOriginalConnection() != getForegroundConnection()) {
77                         Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
78                                 "not foreground connection, skipping");
79                         return;
80                     }
81                     setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
82                     break;
83                 case MSG_DISCONNECT:
84                     updateState();
85                     break;
86             }
87         }
88     };
89 
90     /**
91      * A listener/callback mechanism that is specific communication from TelephonyConnections
92      * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
93      * because it is only exposed in Telephony.
94      */
95     public abstract static class TelephonyConnectionListener {
onOriginalConnectionConfigured(TelephonyConnection c)96         public void onOriginalConnectionConfigured(TelephonyConnection c) {}
97     }
98 
99     private final PostDialListener mPostDialListener = new PostDialListener() {
100         @Override
101         public void onPostDialWait() {
102             Log.v(TelephonyConnection.this, "onPostDialWait");
103             if (mOriginalConnection != null) {
104                 setPostDialWait(mOriginalConnection.getRemainingPostDialString());
105             }
106         }
107 
108         @Override
109         public void onPostDialChar(char c) {
110             Log.v(TelephonyConnection.this, "onPostDialChar: %s", c);
111             if (mOriginalConnection != null) {
112                 setNextPostDialWaitChar(c);
113             }
114         }
115     };
116 
117     /**
118      * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
119      */
120     private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
121             new com.android.internal.telephony.Connection.ListenerBase() {
122         @Override
123         public void onVideoStateChanged(int videoState) {
124             setVideoState(videoState);
125         }
126 
127         /**
128          * The {@link com.android.internal.telephony.Connection} has reported a change in local
129          * video capability.
130          *
131          * @param capable True if capable.
132          */
133         @Override
134         public void onLocalVideoCapabilityChanged(boolean capable) {
135             setLocalVideoCapable(capable);
136         }
137 
138         /**
139          * The {@link com.android.internal.telephony.Connection} has reported a change in remote
140          * video capability.
141          *
142          * @param capable True if capable.
143          */
144         @Override
145         public void onRemoteVideoCapabilityChanged(boolean capable) {
146             setRemoteVideoCapable(capable);
147         }
148 
149         /**
150          * The {@link com.android.internal.telephony.Connection} has reported a change in the
151          * video call provider.
152          *
153          * @param videoProvider The video call provider.
154          */
155         @Override
156         public void onVideoProviderChanged(VideoProvider videoProvider) {
157             setVideoProvider(videoProvider);
158         }
159 
160         /**
161          * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
162          * audio quality for the current call.
163          *
164          * @param audioQuality The audio quality.
165          */
166         @Override
167         public void onAudioQualityChanged(int audioQuality) {
168             setAudioQuality(audioQuality);
169         }
170 
171         /**
172          * Handles a change in the state of conference participant(s), as reported by the
173          * {@link com.android.internal.telephony.Connection}.
174          *
175          * @param participants The participant(s) which changed.
176          */
177         @Override
178         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
179             updateConferenceParticipants(participants);
180         }
181     };
182 
183     private com.android.internal.telephony.Connection mOriginalConnection;
184     private Call.State mOriginalConnectionState = Call.State.IDLE;
185 
186     private boolean mWasImsConnection;
187 
188     /**
189      * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
190      */
191     private boolean mIsMultiParty = false;
192 
193     /**
194      * Determines if the {@link TelephonyConnection} has local video capabilities.
195      * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
196      * ensuring the appropriate capabilities are set.  Since capabilities
197      * can be rebuilt at any time it is necessary to track the video capabilities between rebuild.
198      * The capabilities (including video capabilities) are communicated to the telecom
199      * layer.
200      */
201     private boolean mLocalVideoCapable;
202 
203     /**
204      * Determines if the {@link TelephonyConnection} has remote video capabilities.
205      * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
206      * ensuring the appropriate capabilities are set.  Since capabilities can be rebuilt at any time
207      * it is necessary to track the video capabilities between rebuild. The capabilities (including
208      * video capabilities) are communicated to the telecom layer.
209      */
210     private boolean mRemoteVideoCapable;
211 
212     /**
213      * Determines the current audio quality for the {@link TelephonyConnection}.
214      * This is used when {@link TelephonyConnection#updateConnectionCapabilities}} is called to
215      * indicate whether a call has the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
216      */
217     private int mAudioQuality;
218 
219     /**
220      * Listeners to our TelephonyConnection specific callbacks
221      */
222     private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
223             new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
224 
TelephonyConnection(com.android.internal.telephony.Connection originalConnection)225     protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
226         if (originalConnection != null) {
227             setOriginalConnection(originalConnection);
228         }
229     }
230 
231     /**
232      * Creates a clone of the current {@link TelephonyConnection}.
233      *
234      * @return The clone.
235      */
cloneConnection()236     public abstract TelephonyConnection cloneConnection();
237 
238     @Override
onAudioStateChanged(AudioState audioState)239     public void onAudioStateChanged(AudioState audioState) {
240         // TODO: update TTY mode.
241         if (getPhone() != null) {
242             getPhone().setEchoSuppressionEnabled();
243         }
244     }
245 
246     @Override
onStateChanged(int state)247     public void onStateChanged(int state) {
248         Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
249     }
250 
251     @Override
onDisconnect()252     public void onDisconnect() {
253         Log.v(this, "onDisconnect");
254         hangup(android.telephony.DisconnectCause.LOCAL);
255     }
256 
257     /**
258      * Notifies this Connection of a request to disconnect a participant of the conference managed
259      * by the connection.
260      *
261      * @param endpoint the {@link Uri} of the participant to disconnect.
262      */
263     @Override
onDisconnectConferenceParticipant(Uri endpoint)264     public void onDisconnectConferenceParticipant(Uri endpoint) {
265         Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
266 
267         if (mOriginalConnection == null) {
268             return;
269         }
270 
271         mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
272     }
273 
274     @Override
onSeparate()275     public void onSeparate() {
276         Log.v(this, "onSeparate");
277         if (mOriginalConnection != null) {
278             try {
279                 mOriginalConnection.separate();
280             } catch (CallStateException e) {
281                 Log.e(this, e, "Call to Connection.separate failed with exception");
282             }
283         }
284     }
285 
286     @Override
onAbort()287     public void onAbort() {
288         Log.v(this, "onAbort");
289         hangup(android.telephony.DisconnectCause.LOCAL);
290     }
291 
292     @Override
onHold()293     public void onHold() {
294         performHold();
295     }
296 
297     @Override
onUnhold()298     public void onUnhold() {
299         performUnhold();
300     }
301 
302     @Override
onAnswer(int videoState)303     public void onAnswer(int videoState) {
304         Log.v(this, "onAnswer");
305         if (isValidRingingCall() && getPhone() != null) {
306             try {
307                 getPhone().acceptCall(videoState);
308             } catch (CallStateException e) {
309                 Log.e(this, e, "Failed to accept call.");
310             }
311         }
312     }
313 
314     @Override
onReject()315     public void onReject() {
316         Log.v(this, "onReject");
317         if (isValidRingingCall()) {
318             hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
319         }
320         super.onReject();
321     }
322 
323     @Override
onPostDialContinue(boolean proceed)324     public void onPostDialContinue(boolean proceed) {
325         Log.v(this, "onPostDialContinue, proceed: " + proceed);
326         if (mOriginalConnection != null) {
327             if (proceed) {
328                 mOriginalConnection.proceedAfterWaitChar();
329             } else {
330                 mOriginalConnection.cancelPostDial();
331             }
332         }
333     }
334 
performHold()335     public void performHold() {
336         Log.v(this, "performHold");
337         // TODO: Can dialing calls be put on hold as well since they take up the
338         // foreground call slot?
339         if (Call.State.ACTIVE == mOriginalConnectionState) {
340             Log.v(this, "Holding active call");
341             try {
342                 Phone phone = mOriginalConnection.getCall().getPhone();
343                 Call ringingCall = phone.getRingingCall();
344 
345                 // Although the method says switchHoldingAndActive, it eventually calls a RIL method
346                 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
347                 // a call on hold while a call-waiting call exists, it'll end up accepting the
348                 // call-waiting call, which is bad if that was not the user's intention. We are
349                 // cheating here and simply skipping it because we know any attempt to hold a call
350                 // while a call-waiting call is happening is likely a request from Telecom prior to
351                 // accepting the call-waiting call.
352                 // TODO: Investigate a better solution. It would be great here if we
353                 // could "fake" hold by silencing the audio and microphone streams for this call
354                 // instead of actually putting it on hold.
355                 if (ringingCall.getState() != Call.State.WAITING) {
356                     phone.switchHoldingAndActive();
357                 }
358 
359                 // TODO: Cdma calls are slightly different.
360             } catch (CallStateException e) {
361                 Log.e(this, e, "Exception occurred while trying to put call on hold.");
362             }
363         } else {
364             Log.w(this, "Cannot put a call that is not currently active on hold.");
365         }
366     }
367 
performUnhold()368     public void performUnhold() {
369         Log.v(this, "performUnhold");
370         if (Call.State.HOLDING == mOriginalConnectionState) {
371             try {
372                 // Here's the deal--Telephony hold/unhold is weird because whenever there exists
373                 // more than one call, one of them must always be active. In other words, if you
374                 // have an active call and holding call, and you put the active call on hold, it
375                 // will automatically activate the holding call. This is weird with how Telecom
376                 // sends its commands. When a user opts to "unhold" a background call, telecom
377                 // issues hold commands to all active calls, and then the unhold command to the
378                 // background call. This means that we get two commands...each of which reduces to
379                 // switchHoldingAndActive(). The result is that they simply cancel each other out.
380                 // To fix this so that it works well with telecom we add a minor hack. If we
381                 // have one telephony call, everything works as normally expected. But if we have
382                 // two or more calls, we will ignore all requests to "unhold" knowing that the hold
383                 // requests already do what we want. If you've read up to this point, I'm very sorry
384                 // that we are doing this. I didn't think of a better solution that wouldn't also
385                 // make the Telecom APIs very ugly.
386 
387                 if (!hasMultipleTopLevelCalls()) {
388                     mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
389                 } else {
390                     Log.i(this, "Skipping unhold command for %s", this);
391                 }
392             } catch (CallStateException e) {
393                 Log.e(this, e, "Exception occurred while trying to release call from hold.");
394             }
395         } else {
396             Log.w(this, "Cannot release a call that is not already on hold from hold.");
397         }
398     }
399 
performConference(TelephonyConnection otherConnection)400     public void performConference(TelephonyConnection otherConnection) {
401         Log.d(this, "performConference - %s", this);
402         if (getPhone() != null) {
403             try {
404                 // We dont use the "other" connection because there is no concept of that in the
405                 // implementation of calls inside telephony. Basically, you can "conference" and it
406                 // will conference with the background call.  We know that otherConnection is the
407                 // background call because it would never have called setConferenceableConnections()
408                 // otherwise.
409                 getPhone().conference();
410             } catch (CallStateException e) {
411                 Log.e(this, e, "Failed to conference call.");
412             }
413         }
414     }
415 
416     /**
417      * Builds call capabilities common to all TelephonyConnections. Namely, apply IMS-based
418      * capabilities.
419      */
buildConnectionCapabilities()420     protected int buildConnectionCapabilities() {
421         int callCapabilities = 0;
422         if (isImsConnection()) {
423             callCapabilities |= CAPABILITY_SUPPORT_HOLD;
424             if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
425                 callCapabilities |= CAPABILITY_HOLD;
426             }
427         }
428         return callCapabilities;
429     }
430 
updateConnectionCapabilities()431     protected final void updateConnectionCapabilities() {
432         int newCapabilities = buildConnectionCapabilities();
433         newCapabilities = applyVideoCapabilities(newCapabilities);
434         newCapabilities = applyAudioQualityCapabilities(newCapabilities);
435         newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
436 
437         if (getConnectionCapabilities() != newCapabilities) {
438             setConnectionCapabilities(newCapabilities);
439         }
440     }
441 
updateAddress()442     protected final void updateAddress() {
443         updateConnectionCapabilities();
444         if (mOriginalConnection != null) {
445             Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
446             int presentation = mOriginalConnection.getNumberPresentation();
447             if (!Objects.equals(address, getAddress()) ||
448                     presentation != getAddressPresentation()) {
449                 Log.v(this, "updateAddress, address changed");
450                 setAddress(address, presentation);
451             }
452 
453             String name = mOriginalConnection.getCnapName();
454             int namePresentation = mOriginalConnection.getCnapNamePresentation();
455             if (!Objects.equals(name, getCallerDisplayName()) ||
456                     namePresentation != getCallerDisplayNamePresentation()) {
457                 Log.v(this, "updateAddress, caller display name changed");
458                 setCallerDisplayName(name, namePresentation);
459             }
460         }
461     }
462 
onRemovedFromCallService()463     void onRemovedFromCallService() {
464         // Subclass can override this to do cleanup.
465     }
466 
setOriginalConnection(com.android.internal.telephony.Connection originalConnection)467     void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
468         Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
469         clearOriginalConnection();
470 
471         mOriginalConnection = originalConnection;
472         getPhone().registerForPreciseCallStateChanged(
473                 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
474         getPhone().registerForHandoverStateChanged(
475                 mHandler, MSG_HANDOVER_STATE_CHANGED, null);
476         getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
477         getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
478         mOriginalConnection.addPostDialListener(mPostDialListener);
479         mOriginalConnection.addListener(mOriginalConnectionListener);
480 
481         // Set video state and capabilities
482         setVideoState(mOriginalConnection.getVideoState());
483         setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
484         setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
485         setVideoProvider(mOriginalConnection.getVideoProvider());
486         setAudioQuality(mOriginalConnection.getAudioQuality());
487 
488         if (isImsConnection()) {
489             mWasImsConnection = true;
490         }
491         mIsMultiParty = mOriginalConnection.isMultiparty();
492 
493         fireOnOriginalConnectionConfigured();
494         updateAddress();
495     }
496 
497     /**
498      * Un-sets the underlying radio connection.
499      */
clearOriginalConnection()500     void clearOriginalConnection() {
501         if (mOriginalConnection != null) {
502             getPhone().unregisterForPreciseCallStateChanged(mHandler);
503             getPhone().unregisterForRingbackTone(mHandler);
504             getPhone().unregisterForHandoverStateChanged(mHandler);
505             getPhone().unregisterForDisconnect(mHandler);
506             mOriginalConnection = null;
507         }
508     }
509 
hangup(int telephonyDisconnectCode)510     protected void hangup(int telephonyDisconnectCode) {
511         if (mOriginalConnection != null) {
512             try {
513                 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
514                 // connection.hangup(). Without this change, the party originating the call will not
515                 // get sent to voicemail if the user opts to reject the call.
516                 if (isValidRingingCall()) {
517                     Call call = getCall();
518                     if (call != null) {
519                         call.hangup();
520                     } else {
521                         Log.w(this, "Attempting to hangup a connection without backing call.");
522                     }
523                 } else {
524                     // We still prefer to call connection.hangup() for non-ringing calls in order
525                     // to support hanging-up specific calls within a conference call. If we invoked
526                     // call.hangup() while in a conference, we would end up hanging up the entire
527                     // conference call instead of the specific connection.
528                     mOriginalConnection.hangup();
529                 }
530             } catch (CallStateException e) {
531                 Log.e(this, e, "Call to Connection.hangup failed with exception");
532             }
533         }
534     }
535 
getOriginalConnection()536     com.android.internal.telephony.Connection getOriginalConnection() {
537         return mOriginalConnection;
538     }
539 
getCall()540     protected Call getCall() {
541         if (mOriginalConnection != null) {
542             return mOriginalConnection.getCall();
543         }
544         return null;
545     }
546 
getPhone()547     Phone getPhone() {
548         Call call = getCall();
549         if (call != null) {
550             return call.getPhone();
551         }
552         return null;
553     }
554 
hasMultipleTopLevelCalls()555     private boolean hasMultipleTopLevelCalls() {
556         int numCalls = 0;
557         Phone phone = getPhone();
558         if (phone != null) {
559             if (!phone.getRingingCall().isIdle()) {
560                 numCalls++;
561             }
562             if (!phone.getForegroundCall().isIdle()) {
563                 numCalls++;
564             }
565             if (!phone.getBackgroundCall().isIdle()) {
566                 numCalls++;
567             }
568         }
569         return numCalls > 1;
570     }
571 
getForegroundConnection()572     private com.android.internal.telephony.Connection getForegroundConnection() {
573         if (getPhone() != null) {
574             return getPhone().getForegroundCall().getEarliestConnection();
575         }
576         return null;
577     }
578 
579     /**
580      * Checks to see the original connection corresponds to an active incoming call. Returns false
581      * if there is no such actual call, or if the associated call is not incoming (See
582      * {@link Call.State#isRinging}).
583      */
isValidRingingCall()584     private boolean isValidRingingCall() {
585         if (getPhone() == null) {
586             Log.v(this, "isValidRingingCall, phone is null");
587             return false;
588         }
589 
590         Call ringingCall = getPhone().getRingingCall();
591         if (!ringingCall.getState().isRinging()) {
592             Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
593             return false;
594         }
595 
596         if (ringingCall.getEarliestConnection() != mOriginalConnection) {
597             Log.v(this, "isValidRingingCall, ringing call connection does not match");
598             return false;
599         }
600 
601         Log.v(this, "isValidRingingCall, returning true");
602         return true;
603     }
604 
updateState()605     void updateState() {
606         if (mOriginalConnection == null) {
607             return;
608         }
609 
610         Call.State newState = mOriginalConnection.getState();
611         Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this);
612         if (mOriginalConnectionState != newState) {
613             mOriginalConnectionState = newState;
614             switch (newState) {
615                 case IDLE:
616                     break;
617                 case ACTIVE:
618                     setActiveInternal();
619                     break;
620                 case HOLDING:
621                     setOnHold();
622                     break;
623                 case DIALING:
624                 case ALERTING:
625                     setDialing();
626                     break;
627                 case INCOMING:
628                 case WAITING:
629                     setRinging();
630                     break;
631                 case DISCONNECTED:
632                     setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
633                             mOriginalConnection.getDisconnectCause()));
634                     close();
635                     break;
636                 case DISCONNECTING:
637                     break;
638             }
639         }
640         updateConnectionCapabilities();
641         updateAddress();
642         updateMultiparty();
643     }
644 
645     /**
646      * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
647      */
updateMultiparty()648     private void updateMultiparty() {
649         if (mOriginalConnection == null) {
650             return;
651         }
652 
653         if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
654             mIsMultiParty = mOriginalConnection.isMultiparty();
655 
656             if (mIsMultiParty) {
657                 notifyConferenceStarted();
658             }
659         }
660     }
661 
setActiveInternal()662     private void setActiveInternal() {
663         if (getState() == STATE_ACTIVE) {
664             Log.w(this, "Should not be called if this is already ACTIVE");
665             return;
666         }
667 
668         // When we set a call to active, we need to make sure that there are no other active
669         // calls. However, the ordering of state updates to connections can be non-deterministic
670         // since all connections register for state changes on the phone independently.
671         // To "optimize", we check here to see if there already exists any active calls.  If so,
672         // we issue an update for those calls first to make sure we only have one top-level
673         // active call.
674         if (getConnectionService() != null) {
675             for (Connection current : getConnectionService().getAllConnections()) {
676                 if (current != this && current instanceof TelephonyConnection) {
677                     TelephonyConnection other = (TelephonyConnection) current;
678                     if (other.getState() == STATE_ACTIVE) {
679                         other.updateState();
680                     }
681                 }
682             }
683         }
684         setActive();
685     }
686 
close()687     private void close() {
688         Log.v(this, "close");
689         if (getPhone() != null) {
690             getPhone().unregisterForPreciseCallStateChanged(mHandler);
691             getPhone().unregisterForRingbackTone(mHandler);
692             getPhone().unregisterForHandoverStateChanged(mHandler);
693         }
694         mOriginalConnection = null;
695         destroy();
696     }
697 
698     /**
699      * Applies the video capability states to the CallCapabilities bit-mask.
700      *
701      * @param capabilities The CallCapabilities bit-mask.
702      * @return The capabilities with video capabilities applied.
703      */
applyVideoCapabilities(int capabilities)704     private int applyVideoCapabilities(int capabilities) {
705         int currentCapabilities = capabilities;
706         if (mRemoteVideoCapable) {
707             currentCapabilities = applyCapability(currentCapabilities,
708                     CAPABILITY_SUPPORTS_VT_REMOTE);
709         } else {
710             currentCapabilities = removeCapability(currentCapabilities,
711                     CAPABILITY_SUPPORTS_VT_REMOTE);
712         }
713 
714         if (mLocalVideoCapable) {
715             currentCapabilities = applyCapability(currentCapabilities,
716                     CAPABILITY_SUPPORTS_VT_LOCAL);
717         } else {
718             currentCapabilities = removeCapability(currentCapabilities,
719                     CAPABILITY_SUPPORTS_VT_LOCAL);
720         }
721         return currentCapabilities;
722     }
723 
724     /**
725      * Applies the audio capabilities to the {@code CallCapabilities} bit-mask.  A call with high
726      * definition audio is considered to have the {@code HIGH_DEF_AUDIO} call capability.
727      *
728      * @param capabilities The {@code CallCapabilities} bit-mask.
729      * @return The capabilities with the audio capabilities applied.
730      */
applyAudioQualityCapabilities(int capabilities)731     private int applyAudioQualityCapabilities(int capabilities) {
732         int currentCapabilities = capabilities;
733 
734         if (mAudioQuality ==
735                 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) {
736             currentCapabilities = applyCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO);
737         } else {
738             currentCapabilities = removeCapability(currentCapabilities, CAPABILITY_HIGH_DEF_AUDIO);
739         }
740 
741         return currentCapabilities;
742     }
743 
744     /**
745      * Applies capabilities specific to conferences termination to the
746      * {@code CallCapabilities} bit-mask.
747      *
748      * @param capabilities The {@code CallCapabilities} bit-mask.
749      * @return The capabilities with the IMS conference capabilities applied.
750      */
applyConferenceTerminationCapabilities(int capabilities)751     private int applyConferenceTerminationCapabilities(int capabilities) {
752         int currentCapabilities = capabilities;
753 
754         // An IMS call cannot be individually disconnected or separated from its parent conference.
755         // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
756         if (!mWasImsConnection) {
757             currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
758             currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
759         }
760 
761         return currentCapabilities;
762     }
763 
764     /**
765      * Returns the local video capability state for the connection.
766      *
767      * @return {@code True} if the connection has local video capabilities.
768      */
isLocalVideoCapable()769     public boolean isLocalVideoCapable() {
770         return mLocalVideoCapable;
771     }
772 
773     /**
774      * Returns the remote video capability state for the connection.
775      *
776      * @return {@code True} if the connection has remote video capabilities.
777      */
isRemoteVideoCapable()778     public boolean isRemoteVideoCapable() {
779         return mRemoteVideoCapable;
780     }
781 
782     /**
783      * Sets whether video capability is present locally.  Used during rebuild of the
784      * capabilities to set the video call capabilities.
785      *
786      * @param capable {@code True} if video capable.
787      */
setLocalVideoCapable(boolean capable)788     public void setLocalVideoCapable(boolean capable) {
789         mLocalVideoCapable = capable;
790         updateConnectionCapabilities();
791     }
792 
793     /**
794      * Sets whether video capability is present remotely.  Used during rebuild of the
795      * capabilities to set the video call capabilities.
796      *
797      * @param capable {@code True} if video capable.
798      */
setRemoteVideoCapable(boolean capable)799     public void setRemoteVideoCapable(boolean capable) {
800         mRemoteVideoCapable = capable;
801         updateConnectionCapabilities();
802     }
803 
804     /**
805      * Sets the current call audio quality.  Used during rebuild of the capabilities
806      * to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
807      *
808      * @param audioQuality The audio quality.
809      */
setAudioQuality(int audioQuality)810     public void setAudioQuality(int audioQuality) {
811         mAudioQuality = audioQuality;
812         updateConnectionCapabilities();
813     }
814 
815     /**
816      * Obtains the current call audio quality.
817      */
getAudioQuality()818     public int getAudioQuality() {
819         return mAudioQuality;
820     }
821 
resetStateForConference()822     void resetStateForConference() {
823         if (getState() == Connection.STATE_HOLDING) {
824             if (mOriginalConnection.getState() == Call.State.ACTIVE) {
825                 setActive();
826             }
827         }
828     }
829 
setHoldingForConference()830     boolean setHoldingForConference() {
831         if (getState() == Connection.STATE_ACTIVE) {
832             setOnHold();
833             return true;
834         }
835         return false;
836     }
837 
838     /**
839      * Whether the original connection is an IMS connection.
840      * @return {@code True} if the original connection is an IMS connection, {@code false}
841      *     otherwise.
842      */
isImsConnection()843     protected boolean isImsConnection() {
844         return getOriginalConnection() instanceof ImsPhoneConnection;
845     }
846 
847     /**
848      * Whether the original connection was ever an IMS connection, either before or now.
849      * @return {@code True} if the original connection was ever an IMS connection, {@code false}
850      *     otherwise.
851      */
wasImsConnection()852     public boolean wasImsConnection() {
853         return mWasImsConnection;
854     }
855 
getAddressFromNumber(String number)856     private static Uri getAddressFromNumber(String number) {
857         // Address can be null for blocked calls.
858         if (number == null) {
859             number = "";
860         }
861         return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
862     }
863 
864     /**
865      * Applies a capability to a capabilities bit-mask.
866      *
867      * @param capabilities The capabilities bit-mask.
868      * @param capability The capability to apply.
869      * @return The capabilities bit-mask with the capability applied.
870      */
applyCapability(int capabilities, int capability)871     private int applyCapability(int capabilities, int capability) {
872         int newCapabilities = capabilities | capability;
873         return newCapabilities;
874     }
875 
876     /**
877      * Removes a capability from a capabilities bit-mask.
878      *
879      * @param capabilities The capabilities bit-mask.
880      * @param capability The capability to remove.
881      * @return The capabilities bit-mask with the capability removed.
882      */
removeCapability(int capabilities, int capability)883     private int removeCapability(int capabilities, int capability) {
884         int newCapabilities = capabilities & ~capability;
885         return newCapabilities;
886     }
887 
888     /**
889      * Register a listener for {@link TelephonyConnection} specific triggers.
890      * @param l The instance of the listener to add
891      * @return The connection being listened to
892      */
addTelephonyConnectionListener(TelephonyConnectionListener l)893     public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
894         mTelephonyListeners.add(l);
895         // If we already have an original connection, let's call back immediately.
896         // This would be the case for incoming calls.
897         if (mOriginalConnection != null) {
898             fireOnOriginalConnectionConfigured();
899         }
900         return this;
901     }
902 
903     /**
904      * Remove a listener for {@link TelephonyConnection} specific triggers.
905      * @param l The instance of the listener to remove
906      * @return The connection being listened to
907      */
removeTelephonyConnectionListener( TelephonyConnectionListener l)908     public final TelephonyConnection removeTelephonyConnectionListener(
909             TelephonyConnectionListener l) {
910         if (l != null) {
911             mTelephonyListeners.remove(l);
912         }
913         return this;
914     }
915 
916     /**
917      * Fire a callback to the various listeners for when the original connection is
918      * set in this {@link TelephonyConnection}
919      */
fireOnOriginalConnectionConfigured()920     private final void fireOnOriginalConnectionConfigured() {
921         for (TelephonyConnectionListener l : mTelephonyListeners) {
922             l.onOriginalConnectionConfigured(this);
923         }
924     }
925 
926     /**
927      * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
928      * use in log statements.
929      *
930      * @return String representation of the connection.
931      */
932     @Override
toString()933     public String toString() {
934         StringBuilder sb = new StringBuilder();
935         sb.append("[TelephonyConnection objId:");
936         sb.append(System.identityHashCode(this));
937         sb.append(" type:");
938         if (isImsConnection()) {
939             sb.append("ims");
940         } else if (this instanceof com.android.services.telephony.GsmConnection) {
941             sb.append("gsm");
942         } else if (this instanceof CdmaConnection) {
943             sb.append("cdma");
944         }
945         sb.append(" state:");
946         sb.append(Connection.stateToString(getState()));
947         sb.append(" capabilities:");
948         sb.append(capabilitiesToString(getConnectionCapabilities()));
949         sb.append(" address:");
950         sb.append(Log.pii(getAddress()));
951         sb.append(" originalConnection:");
952         sb.append(mOriginalConnection);
953         sb.append(" partOfConf:");
954         if (getConference() == null) {
955             sb.append("N");
956         } else {
957             sb.append("Y");
958         }
959         sb.append("]");
960         return sb.toString();
961     }
962 }
963