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