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