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