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