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