TelephonyConnection.java revision 0892903aa0d17a0e3d8ed59244c1199568382752
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
681        if (isImsConnection()) {
682            mWasImsConnection = true;
683        }
684        mIsMultiParty = mOriginalConnection.isMultiparty();
685
686        // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
687        // should be executed *after* the above setters have run.
688        updateState();
689        if (mOriginalConnection == null) {
690            Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " +
691                    originalConnection);
692        }
693
694        fireOnOriginalConnectionConfigured();
695    }
696
697    /**
698     * Un-sets the underlying radio connection.
699     */
700    void clearOriginalConnection() {
701        if (mOriginalConnection != null) {
702            if (getPhone() != null) {
703                getPhone().unregisterForPreciseCallStateChanged(mHandler);
704                getPhone().unregisterForRingbackTone(mHandler);
705                getPhone().unregisterForHandoverStateChanged(mHandler);
706                getPhone().unregisterForDisconnect(mHandler);
707                getPhone().unregisterForSuppServiceNotification(mHandler);
708            }
709            mOriginalConnection.removePostDialListener(mPostDialListener);
710            mOriginalConnection.removeListener(mOriginalConnectionListener);
711            mOriginalConnection = null;
712        }
713    }
714
715    protected void hangup(int telephonyDisconnectCode) {
716        if (mOriginalConnection != null) {
717            try {
718                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
719                // connection.hangup(). Without this change, the party originating the call will not
720                // get sent to voicemail if the user opts to reject the call.
721                if (isValidRingingCall()) {
722                    Call call = getCall();
723                    if (call != null) {
724                        call.hangup();
725                    } else {
726                        Log.w(this, "Attempting to hangup a connection without backing call.");
727                    }
728                } else {
729                    // We still prefer to call connection.hangup() for non-ringing calls in order
730                    // to support hanging-up specific calls within a conference call. If we invoked
731                    // call.hangup() while in a conference, we would end up hanging up the entire
732                    // conference call instead of the specific connection.
733                    mOriginalConnection.hangup();
734                }
735            } catch (CallStateException e) {
736                Log.e(this, e, "Call to Connection.hangup failed with exception");
737            }
738        }
739    }
740
741    com.android.internal.telephony.Connection getOriginalConnection() {
742        return mOriginalConnection;
743    }
744
745    protected Call getCall() {
746        if (mOriginalConnection != null) {
747            return mOriginalConnection.getCall();
748        }
749        return null;
750    }
751
752    Phone getPhone() {
753        Call call = getCall();
754        if (call != null) {
755            return call.getPhone();
756        }
757        return null;
758    }
759
760    private boolean hasMultipleTopLevelCalls() {
761        int numCalls = 0;
762        Phone phone = getPhone();
763        if (phone != null) {
764            if (!phone.getRingingCall().isIdle()) {
765                numCalls++;
766            }
767            if (!phone.getForegroundCall().isIdle()) {
768                numCalls++;
769            }
770            if (!phone.getBackgroundCall().isIdle()) {
771                numCalls++;
772            }
773        }
774        return numCalls > 1;
775    }
776
777    private com.android.internal.telephony.Connection getForegroundConnection() {
778        if (getPhone() != null) {
779            return getPhone().getForegroundCall().getEarliestConnection();
780        }
781        return null;
782    }
783
784     /**
785     * Checks for and returns the list of conference participants
786     * associated with this connection.
787     */
788    public List<ConferenceParticipant> getConferenceParticipants() {
789        if (mOriginalConnection == null) {
790            Log.v(this, "Null mOriginalConnection, cannot get conf participants.");
791            return null;
792        }
793        return mOriginalConnection.getConferenceParticipants();
794    }
795
796    /**
797     * Checks to see the original connection corresponds to an active incoming call. Returns false
798     * if there is no such actual call, or if the associated call is not incoming (See
799     * {@link Call.State#isRinging}).
800     */
801    private boolean isValidRingingCall() {
802        if (getPhone() == null) {
803            Log.v(this, "isValidRingingCall, phone is null");
804            return false;
805        }
806
807        Call ringingCall = getPhone().getRingingCall();
808        if (!ringingCall.getState().isRinging()) {
809            Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
810            return false;
811        }
812
813        if (ringingCall.getEarliestConnection() != mOriginalConnection) {
814            Log.v(this, "isValidRingingCall, ringing call connection does not match");
815            return false;
816        }
817
818        Log.v(this, "isValidRingingCall, returning true");
819        return true;
820    }
821
822    protected void updateExtras(Bundle extras) {
823        if (mOriginalConnection != null) {
824            if (extras != null) {
825                // Check if extras have changed and need updating.
826                if (!areBundlesEqual(mOriginalConnectionExtras, extras)) {
827                    if (Log.DEBUG) {
828                        Log.d(TelephonyConnection.this, "Updating extras:");
829                        for (String key : extras.keySet()) {
830                            Object value = extras.get(key);
831                            if (value instanceof String) {
832                                Log.d(this, "updateExtras Key=" + Log.pii(key) +
833                                             " value=" + Log.pii((String)value));
834                            }
835                        }
836                    }
837                    mOriginalConnectionExtras.clear();
838
839                    mOriginalConnectionExtras.putAll(extras);
840
841                    // Remap any string extras that have a remapping defined.
842                    for (String key : mOriginalConnectionExtras.keySet()) {
843                        if (sExtrasMap.containsKey(key)) {
844                            String newKey = sExtrasMap.get(key);
845                            mOriginalConnectionExtras.putString(newKey, extras.getString(key));
846                            mOriginalConnectionExtras.remove(key);
847                        }
848                    }
849
850                    // Ensure extras are propagated to Telecom.
851                    Bundle connectionExtras = getExtras();
852                    if (connectionExtras == null) {
853                        connectionExtras = new Bundle();
854                    }
855                    connectionExtras.putAll(mOriginalConnectionExtras);
856                    setExtras(connectionExtras);
857                } else {
858                    Log.d(this, "Extras update not required");
859                }
860            } else {
861                Log.d(this, "updateExtras extras: " + Log.pii(extras));
862            }
863        }
864    }
865
866    private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
867        if (extras == null || newExtras == null) {
868            return extras == newExtras;
869        }
870
871        if (extras.size() != newExtras.size()) {
872            return false;
873        }
874
875        for(String key : extras.keySet()) {
876            if (key != null) {
877                final Object value = extras.get(key);
878                final Object newValue = newExtras.get(key);
879                if (!Objects.equals(value, newValue)) {
880                    return false;
881                }
882            }
883        }
884        return true;
885    }
886
887    void setStateOverride(Call.State state) {
888        mIsStateOverridden = true;
889        mConnectionOverriddenState = state;
890        // Need to keep track of the original connection's state before override.
891        mOriginalConnectionState = mOriginalConnection.getState();
892        updateStateInternal();
893    }
894
895    void resetStateOverride() {
896        mIsStateOverridden = false;
897        updateStateInternal();
898    }
899
900    void updateStateInternal() {
901        Call.State newState;
902        // If the state is overridden and the state of the original connection hasn't changed since,
903        // then we continue in the overridden state, else we go to the original connection's state.
904        if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) {
905            newState = mConnectionOverriddenState;
906        } else {
907            newState = mOriginalConnection.getState();
908        }
909        Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this);
910
911        if (mConnectionState != newState) {
912            mConnectionState = newState;
913            switch (newState) {
914                case IDLE:
915                    break;
916                case ACTIVE:
917                    setActiveInternal();
918                    break;
919                case HOLDING:
920                    setOnHold();
921                    break;
922                case DIALING:
923                case ALERTING:
924                    setDialing();
925                    break;
926                case INCOMING:
927                case WAITING:
928                    setRinging();
929                    break;
930                case DISCONNECTED:
931                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
932                            mOriginalConnection.getDisconnectCause(),
933                            mOriginalConnection.getVendorDisconnectCause()));
934                    close();
935                    break;
936                case DISCONNECTING:
937                    break;
938            }
939        }
940    }
941
942    void updateState() {
943        if (mOriginalConnection == null) {
944            return;
945        }
946
947        updateStateInternal();
948        updateStatusHints();
949        updateConnectionCapabilities();
950        updateAddress();
951        updateMultiparty();
952    }
953
954    /**
955     * Checks for changes to the multiparty bit.  If a conference has started, informs listeners.
956     */
957    private void updateMultiparty() {
958        if (mOriginalConnection == null) {
959            return;
960        }
961
962        if (mIsMultiParty != mOriginalConnection.isMultiparty()) {
963            mIsMultiParty = mOriginalConnection.isMultiparty();
964
965            if (mIsMultiParty) {
966                notifyConferenceStarted();
967            }
968        }
969    }
970
971    /**
972     * Handles a failure when merging calls into a conference.
973     * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()}
974     * listener.
975     */
976    private void handleConferenceMergeFailed(){
977        mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget();
978    }
979
980    /**
981     * Handles requests to update the multiparty state received via the
982     * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
983     * listener.
984     * <p>
985     * Note: We post this to the mHandler to ensure that if a conference must be created as a
986     * result of the multiparty state change, the conference creation happens on the correct
987     * thread.  This ensures that the thread check in
988     * {@link com.android.internal.telephony.PhoneBase#checkCorrectThread(android.os.Handler)}
989     * does not fire.
990     *
991     * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
992     */
993    private void handleMultipartyStateChange(boolean isMultiParty) {
994        Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
995        mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
996    }
997
998    private void setActiveInternal() {
999        if (getState() == STATE_ACTIVE) {
1000            Log.w(this, "Should not be called if this is already ACTIVE");
1001            return;
1002        }
1003
1004        // When we set a call to active, we need to make sure that there are no other active
1005        // calls. However, the ordering of state updates to connections can be non-deterministic
1006        // since all connections register for state changes on the phone independently.
1007        // To "optimize", we check here to see if there already exists any active calls.  If so,
1008        // we issue an update for those calls first to make sure we only have one top-level
1009        // active call.
1010        if (getConnectionService() != null) {
1011            for (Connection current : getConnectionService().getAllConnections()) {
1012                if (current != this && current instanceof TelephonyConnection) {
1013                    TelephonyConnection other = (TelephonyConnection) current;
1014                    if (other.getState() == STATE_ACTIVE) {
1015                        other.updateState();
1016                    }
1017                }
1018            }
1019        }
1020        setActive();
1021    }
1022
1023    private void close() {
1024        Log.v(this, "close");
1025        clearOriginalConnection();
1026        destroy();
1027    }
1028
1029    /**
1030     * Applies capabilities specific to conferences termination to the
1031     * {@code CallCapabilities} bit-mask.
1032     *
1033     * @param capabilities The {@code CallCapabilities} bit-mask.
1034     * @return The capabilities with the IMS conference capabilities applied.
1035     */
1036    private int applyConferenceTerminationCapabilities(int capabilities) {
1037        int currentCapabilities = capabilities;
1038
1039        // An IMS call cannot be individually disconnected or separated from its parent conference.
1040        // If the call was IMS, even if it hands over to GMS, these capabilities are not supported.
1041        if (!mWasImsConnection) {
1042            currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE;
1043            currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE;
1044        }
1045
1046        return currentCapabilities;
1047    }
1048
1049    /**
1050     * Returns the local video capability state for the connection.
1051     *
1052     * @return {@code True} if the connection has local video capabilities.
1053     */
1054    public boolean isLocalVideoCapable() {
1055        return mLocalVideoCapable;
1056    }
1057
1058    /**
1059     * Returns the remote video capability state for the connection.
1060     *
1061     * @return {@code True} if the connection has remote video capabilities.
1062     */
1063    public boolean isRemoteVideoCapable() {
1064        return mRemoteVideoCapable;
1065    }
1066
1067    /**
1068     * Sets whether video capability is present locally.  Used during rebuild of the
1069     * capabilities to set the video call capabilities.
1070     *
1071     * @param capable {@code True} if video capable.
1072     */
1073    public void setLocalVideoCapable(boolean capable) {
1074        mLocalVideoCapable = capable;
1075        updateConnectionCapabilities();
1076    }
1077
1078    /**
1079     * Sets whether video capability is present remotely.  Used during rebuild of the
1080     * capabilities to set the video call capabilities.
1081     *
1082     * @param capable {@code True} if video capable.
1083     */
1084    public void setRemoteVideoCapable(boolean capable) {
1085        mRemoteVideoCapable = capable;
1086        updateConnectionCapabilities();
1087    }
1088
1089    /**
1090     * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
1091     * the {@link Connection#CAPABILITY_WIFI} capability.
1092     */
1093    public void setWifi(boolean isWifi) {
1094        mIsWifi = isWifi;
1095        updateConnectionCapabilities();
1096        updateStatusHints();
1097    }
1098
1099    /**
1100     * Whether the call is using wifi.
1101     */
1102    boolean isWifi() {
1103        return mIsWifi;
1104    }
1105
1106    /**
1107     * Sets the current call audio quality. Used during rebuild of the capabilities
1108     * to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
1109     *
1110     * @param audioQuality The audio quality.
1111     */
1112    public void setAudioQuality(int audioQuality) {
1113        mHasHighDefAudio = audioQuality ==
1114                com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION;
1115        updateConnectionCapabilities();
1116    }
1117
1118    void resetStateForConference() {
1119        if (getState() == Connection.STATE_HOLDING) {
1120            resetStateOverride();
1121        }
1122    }
1123
1124    boolean setHoldingForConference() {
1125        if (getState() == Connection.STATE_ACTIVE) {
1126            setStateOverride(Call.State.HOLDING);
1127            return true;
1128        }
1129        return false;
1130    }
1131
1132    /**
1133     * For video calls, sets whether this connection supports pausing the outgoing video for the
1134     * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState.
1135     *
1136     * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise.
1137     */
1138    public void setVideoPauseSupported(boolean isVideoPauseSupported) {
1139        mIsVideoPauseSupported = isVideoPauseSupported;
1140    }
1141
1142    /**
1143     * Sets whether this connection supports conference calling.
1144     * @param isConferenceSupported {@code true} if conference calling is supported by this
1145     *                                         connection, {@code false} otherwise.
1146     */
1147    public void setConferenceSupported(boolean isConferenceSupported) {
1148        mIsConferenceSupported = isConferenceSupported;
1149    }
1150
1151    /**
1152     * @return {@code true} if this connection supports merging calls into a conference.
1153     */
1154    public boolean isConferenceSupported() {
1155        return mIsConferenceSupported;
1156    }
1157
1158    /**
1159     * Whether the original connection is an IMS connection.
1160     * @return {@code True} if the original connection is an IMS connection, {@code false}
1161     *     otherwise.
1162     */
1163    protected boolean isImsConnection() {
1164        return getOriginalConnection() instanceof ImsPhoneConnection;
1165    }
1166
1167    /**
1168     * Whether the original connection was ever an IMS connection, either before or now.
1169     * @return {@code True} if the original connection was ever an IMS connection, {@code false}
1170     *     otherwise.
1171     */
1172    public boolean wasImsConnection() {
1173        return mWasImsConnection;
1174    }
1175
1176    private static Uri getAddressFromNumber(String number) {
1177        // Address can be null for blocked calls.
1178        if (number == null) {
1179            number = "";
1180        }
1181        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
1182    }
1183
1184    /**
1185     * Changes a capabilities bit-mask to add or remove a capability.
1186     *
1187     * @param capabilities The capabilities bit-mask.
1188     * @param capability The capability to change.
1189     * @param enabled Whether the capability should be set or removed.
1190     * @return The capabilities bit-mask with the capability changed.
1191     */
1192    private int changeCapability(int capabilities, int capability, boolean enabled) {
1193        if (enabled) {
1194            return capabilities | capability;
1195        } else {
1196            return capabilities & ~capability;
1197        }
1198    }
1199
1200    private void updateStatusHints() {
1201        boolean isIncoming = isValidRingingCall();
1202        if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) {
1203            int labelId = isIncoming
1204                    ? R.string.status_hint_label_incoming_wifi_call
1205                    : R.string.status_hint_label_wifi_call;
1206
1207            Context context = getPhone().getContext();
1208            setStatusHints(new StatusHints(
1209                    context.getString(labelId),
1210                    Icon.createWithResource(
1211                            context.getResources(),
1212                            R.drawable.ic_signal_wifi_4_bar_24dp),
1213                    null /* extras */));
1214        } else {
1215            setStatusHints(null);
1216        }
1217    }
1218
1219    /**
1220     * Register a listener for {@link TelephonyConnection} specific triggers.
1221     * @param l The instance of the listener to add
1222     * @return The connection being listened to
1223     */
1224    public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) {
1225        mTelephonyListeners.add(l);
1226        // If we already have an original connection, let's call back immediately.
1227        // This would be the case for incoming calls.
1228        if (mOriginalConnection != null) {
1229            fireOnOriginalConnectionConfigured();
1230        }
1231        return this;
1232    }
1233
1234    /**
1235     * Remove a listener for {@link TelephonyConnection} specific triggers.
1236     * @param l The instance of the listener to remove
1237     * @return The connection being listened to
1238     */
1239    public final TelephonyConnection removeTelephonyConnectionListener(
1240            TelephonyConnectionListener l) {
1241        if (l != null) {
1242            mTelephonyListeners.remove(l);
1243        }
1244        return this;
1245    }
1246
1247    /**
1248     * Fire a callback to the various listeners for when the original connection is
1249     * set in this {@link TelephonyConnection}
1250     */
1251    private final void fireOnOriginalConnectionConfigured() {
1252        for (TelephonyConnectionListener l : mTelephonyListeners) {
1253            l.onOriginalConnectionConfigured(this);
1254        }
1255    }
1256
1257    /**
1258     * Handles exiting ECM mode.
1259     */
1260    protected void handleExitedEcmMode() {
1261        updateConnectionCapabilities();
1262    }
1263
1264    /**
1265     * Provides a mapping from extras keys which may be found in the
1266     * {@link com.android.internal.telephony.Connection} to their equivalents defined in
1267     * {@link android.telecom.Connection}.
1268     *
1269     * @return Map containing key mappings.
1270     */
1271    private static Map<String, String> createExtrasMap() {
1272        Map<String, String> result = new HashMap<String, String>();
1273        result.put(ImsCallProfile.EXTRA_CHILD_NUMBER,
1274                android.telecom.Connection.EXTRA_CHILD_ADDRESS);
1275        result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT,
1276                android.telecom.Connection.EXTRA_CALL_SUBJECT);
1277        return Collections.unmodifiableMap(result);
1278    }
1279
1280    /**
1281     * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
1282     * use in log statements.
1283     *
1284     * @return String representation of the connection.
1285     */
1286    @Override
1287    public String toString() {
1288        StringBuilder sb = new StringBuilder();
1289        sb.append("[TelephonyConnection objId:");
1290        sb.append(System.identityHashCode(this));
1291        sb.append(" telecomCallID:");
1292        sb.append(getTelecomCallId());
1293        sb.append(" type:");
1294        if (isImsConnection()) {
1295            sb.append("ims");
1296        } else if (this instanceof com.android.services.telephony.GsmConnection) {
1297            sb.append("gsm");
1298        } else if (this instanceof CdmaConnection) {
1299            sb.append("cdma");
1300        }
1301        sb.append(" state:");
1302        sb.append(Connection.stateToString(getState()));
1303        sb.append(" capabilities:");
1304        sb.append(capabilitiesToString(getConnectionCapabilities()));
1305        sb.append(" address:");
1306        sb.append(Log.pii(getAddress()));
1307        sb.append(" originalConnection:");
1308        sb.append(mOriginalConnection);
1309        sb.append(" partOfConf:");
1310        if (getConference() == null) {
1311            sb.append("N");
1312        } else {
1313            sb.append("Y");
1314        }
1315        sb.append("]");
1316        return sb.toString();
1317    }
1318}
1319