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