ImsPhoneConnection.java revision 0455ac9f63bc28372efc270396305ea3f3a3c805
1/*
2 * Copyright (C) 2013 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.internal.telephony.imsphone;
18
19import android.content.Context;
20import android.net.Uri;
21import android.os.AsyncResult;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.os.PersistableBundle;
27import android.os.PowerManager;
28import android.os.Registrant;
29import android.os.SystemClock;
30import android.telecom.VideoProfile;
31import android.telephony.CarrierConfigManager;
32import android.telephony.DisconnectCause;
33import android.telephony.PhoneNumberUtils;
34import android.telephony.Rlog;
35import android.telephony.ServiceState;
36import android.text.TextUtils;
37
38import com.android.ims.ImsException;
39import com.android.ims.ImsStreamMediaProfile;
40import com.android.ims.internal.ImsVideoCallProviderWrapper;
41import com.android.internal.telephony.CallStateException;
42import com.android.internal.telephony.Connection;
43import com.android.internal.telephony.Phone;
44import com.android.internal.telephony.PhoneConstants;
45import com.android.internal.telephony.UUSInfo;
46
47import com.android.ims.ImsCall;
48import com.android.ims.ImsCallProfile;
49
50import java.util.Objects;
51
52/**
53 * {@hide}
54 */
55public class ImsPhoneConnection extends Connection implements
56        ImsVideoCallProviderWrapper.ImsVideoProviderWrapperCallback {
57
58    private static final String LOG_TAG = "ImsPhoneConnection";
59    private static final boolean DBG = true;
60
61    //***** Instance Variables
62
63    private ImsPhoneCallTracker mOwner;
64    private ImsPhoneCall mParent;
65    private ImsCall mImsCall;
66    private Bundle mExtras = new Bundle();
67
68    private boolean mDisconnected;
69
70    /*
71    int mIndex;          // index in ImsPhoneCallTracker.connections[], -1 if unassigned
72                        // The GSM index is 1 + this
73    */
74
75    /*
76     * These time/timespan values are based on System.currentTimeMillis(),
77     * i.e., "wall clock" time.
78     */
79    private long mDisconnectTime;
80
81    private UUSInfo mUusInfo;
82    private Handler mHandler;
83
84    private PowerManager.WakeLock mPartialWakeLock;
85
86    // The cached connect time of the connection when it turns into a conference.
87    private long mConferenceConnectTime = 0;
88
89    // The cached delay to be used between DTMF tones fetched from carrier config.
90    private int mDtmfToneDelay = 0;
91
92    private boolean mIsEmergency = false;
93
94    /**
95     * Used to indicate that video state changes detected by
96     * {@link #updateMediaCapabilities(ImsCall)} should be ignored.  When a video state change from
97     * unpaused to paused occurs, we set this flag and then update the existing video state when
98     * new {@link #onReceiveSessionModifyResponse(int, VideoProfile, VideoProfile)} callbacks come
99     * in.  When the video un-pauses we continue receiving the video state updates.
100     */
101    private boolean mShouldIgnoreVideoStateChanges = false;
102
103    private ImsVideoCallProviderWrapper mImsVideoCallProviderWrapper;
104
105    //***** Event Constants
106    private static final int EVENT_DTMF_DONE = 1;
107    private static final int EVENT_PAUSE_DONE = 2;
108    private static final int EVENT_NEXT_POST_DIAL = 3;
109    private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
110    private static final int EVENT_DTMF_DELAY_DONE = 5;
111
112    //***** Constants
113    private static final int PAUSE_DELAY_MILLIS = 3 * 1000;
114    private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
115
116    //***** Inner Classes
117
118    class MyHandler extends Handler {
119        MyHandler(Looper l) {super(l);}
120
121        @Override
122        public void
123        handleMessage(Message msg) {
124
125            switch (msg.what) {
126                case EVENT_NEXT_POST_DIAL:
127                case EVENT_DTMF_DELAY_DONE:
128                case EVENT_PAUSE_DONE:
129                    processNextPostDialChar();
130                    break;
131                case EVENT_WAKE_LOCK_TIMEOUT:
132                    releaseWakeLock();
133                    break;
134                case EVENT_DTMF_DONE:
135                    // We may need to add a delay specified by carrier between DTMF tones that are
136                    // sent out.
137                    mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE),
138                            mDtmfToneDelay);
139                    break;
140            }
141        }
142    }
143
144    //***** Constructors
145
146    /** This is probably an MT call */
147    public ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct,
148           ImsPhoneCall parent, boolean isUnknown) {
149        super(PhoneConstants.PHONE_TYPE_IMS);
150        createWakeLock(phone.getContext());
151        acquireWakeLock();
152
153        mOwner = ct;
154        mHandler = new MyHandler(mOwner.getLooper());
155        mImsCall = imsCall;
156
157        if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
158            mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
159            mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA);
160            mNumberPresentation = ImsCallProfile.OIRToPresentation(
161                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR));
162            mCnapNamePresentation = ImsCallProfile.OIRToPresentation(
163                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
164            updateMediaCapabilities(imsCall);
165        } else {
166            mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
167            mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
168        }
169
170        mIsIncoming = !isUnknown;
171        mCreateTime = System.currentTimeMillis();
172        mUusInfo = null;
173
174        // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally
175        // in the ImsPhoneConnection.  This isn't going to inform any listeners (since the original
176        // connection is not likely to be associated with a TelephonyConnection yet).
177        updateExtras(imsCall);
178
179        mParent = parent;
180        mParent.attach(this,
181                (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING));
182
183        fetchDtmfToneDelay(phone);
184
185        if (phone.getContext().getResources().getBoolean(
186                com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
187            setAudioModeIsVoip(true);
188        }
189    }
190
191    /** This is an MO call, created when dialing */
192    public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct,
193            ImsPhoneCall parent, boolean isEmergency) {
194        super(PhoneConstants.PHONE_TYPE_IMS);
195        createWakeLock(phone.getContext());
196        acquireWakeLock();
197
198        mOwner = ct;
199        mHandler = new MyHandler(mOwner.getLooper());
200
201        mDialString = dialString;
202
203        mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
204        mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
205
206        //mIndex = -1;
207
208        mIsIncoming = false;
209        mCnapName = null;
210        mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
211        mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
212        mCreateTime = System.currentTimeMillis();
213
214        mParent = parent;
215        parent.attachFake(this, ImsPhoneCall.State.DIALING);
216
217        mIsEmergency = isEmergency;
218
219        fetchDtmfToneDelay(phone);
220
221        if (phone.getContext().getResources().getBoolean(
222                com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
223            setAudioModeIsVoip(true);
224        }
225    }
226
227    public void dispose() {
228    }
229
230    static boolean
231    equalsHandlesNulls (Object a, Object b) {
232        return (a == null) ? (b == null) : a.equals (b);
233    }
234
235    private static int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) {
236        Rlog.w(LOG_TAG, "applyLocalCallCapabilities - localProfile = "+localProfile);
237        capabilities = removeCapability(capabilities,
238                Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
239
240        switch (localProfile.mCallType) {
241            case ImsCallProfile.CALL_TYPE_VT:
242                // Fall-through
243            case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE:
244                capabilities = addCapability(capabilities,
245                        Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
246                break;
247        }
248        return capabilities;
249    }
250
251    private static int applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities) {
252        Rlog.w(LOG_TAG, "applyRemoteCallCapabilities - remoteProfile = "+remoteProfile);
253        capabilities = removeCapability(capabilities,
254                Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
255
256        switch (remoteProfile.mCallType) {
257            case ImsCallProfile.CALL_TYPE_VT:
258                // fall-through
259            case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE:
260                capabilities = addCapability(capabilities,
261                        Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
262                break;
263        }
264        return capabilities;
265    }
266
267    @Override
268    public String getOrigDialString(){
269        return mDialString;
270    }
271
272    @Override
273    public ImsPhoneCall getCall() {
274        return mParent;
275    }
276
277    @Override
278    public long getDisconnectTime() {
279        return mDisconnectTime;
280    }
281
282    @Override
283    public long getHoldingStartTime() {
284        return mHoldingStartTime;
285    }
286
287    @Override
288    public long getHoldDurationMillis() {
289        if (getState() != ImsPhoneCall.State.HOLDING) {
290            // If not holding, return 0
291            return 0;
292        } else {
293            return SystemClock.elapsedRealtime() - mHoldingStartTime;
294        }
295    }
296
297    public void setDisconnectCause(int cause) {
298        mCause = cause;
299    }
300
301    @Override
302    public String getVendorDisconnectCause() {
303      return null;
304    }
305
306    public ImsPhoneCallTracker getOwner () {
307        return mOwner;
308    }
309
310    @Override
311    public ImsPhoneCall.State getState() {
312        if (mDisconnected) {
313            return ImsPhoneCall.State.DISCONNECTED;
314        } else {
315            return super.getState();
316        }
317    }
318
319    @Override
320    public void hangup() throws CallStateException {
321        if (!mDisconnected) {
322            mOwner.hangup(this);
323        } else {
324            throw new CallStateException ("disconnected");
325        }
326    }
327
328    @Override
329    public void separate() throws CallStateException {
330        throw new CallStateException ("not supported");
331    }
332
333    @Override
334    public void proceedAfterWaitChar() {
335        if (mPostDialState != PostDialState.WAIT) {
336            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
337                    + "getPostDialState() to be WAIT but was " + mPostDialState);
338            return;
339        }
340
341        setPostDialState(PostDialState.STARTED);
342
343        processNextPostDialChar();
344    }
345
346    @Override
347    public void proceedAfterWildChar(String str) {
348        if (mPostDialState != PostDialState.WILD) {
349            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
350                    + "getPostDialState() to be WILD but was " + mPostDialState);
351            return;
352        }
353
354        setPostDialState(PostDialState.STARTED);
355
356        // make a new postDialString, with the wild char replacement string
357        // at the beginning, followed by the remaining postDialString.
358
359        StringBuilder buf = new StringBuilder(str);
360        buf.append(mPostDialString.substring(mNextPostDialChar));
361        mPostDialString = buf.toString();
362        mNextPostDialChar = 0;
363        if (Phone.DEBUG_PHONE) {
364            Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " +
365                    mPostDialString);
366        }
367
368        processNextPostDialChar();
369    }
370
371    @Override
372    public void cancelPostDial() {
373        setPostDialState(PostDialState.CANCELLED);
374    }
375
376    /**
377     * Called when this Connection is being hung up locally (eg, user pressed "end")
378     */
379    void
380    onHangupLocal() {
381        mCause = DisconnectCause.LOCAL;
382    }
383
384    /** Called when the connection has been disconnected */
385    @Override
386    public boolean onDisconnect(int cause) {
387        Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
388        if (mCause != DisconnectCause.LOCAL || cause == DisconnectCause.INCOMING_REJECTED) {
389            mCause = cause;
390        }
391        return onDisconnect();
392    }
393
394    public boolean onDisconnect() {
395        boolean changed = false;
396
397        if (!mDisconnected) {
398            //mIndex = -1;
399
400            mDisconnectTime = System.currentTimeMillis();
401            mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
402            mDisconnected = true;
403
404            mOwner.mPhone.notifyDisconnect(this);
405
406            if (mParent != null) {
407                changed = mParent.connectionDisconnected(this);
408            } else {
409                Rlog.d(LOG_TAG, "onDisconnect: no parent");
410            }
411            if (mImsCall != null) mImsCall.close();
412            mImsCall = null;
413        }
414        releaseWakeLock();
415        return changed;
416    }
417
418    /**
419     * An incoming or outgoing call has connected
420     */
421    void
422    onConnectedInOrOut() {
423        mConnectTime = System.currentTimeMillis();
424        mConnectTimeReal = SystemClock.elapsedRealtime();
425        mDuration = 0;
426
427        if (Phone.DEBUG_PHONE) {
428            Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime);
429        }
430
431        if (!mIsIncoming) {
432            // outgoing calls only
433            processNextPostDialChar();
434        }
435        releaseWakeLock();
436    }
437
438    /*package*/ void
439    onStartedHolding() {
440        mHoldingStartTime = SystemClock.elapsedRealtime();
441    }
442    /**
443     * Performs the appropriate action for a post-dial char, but does not
444     * notify application. returns false if the character is invalid and
445     * should be ignored
446     */
447    private boolean
448    processPostDialChar(char c) {
449        if (PhoneNumberUtils.is12Key(c)) {
450            mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
451        } else if (c == PhoneNumberUtils.PAUSE) {
452            // From TS 22.101:
453            // It continues...
454            // Upon the called party answering the UE shall send the DTMF digits
455            // automatically to the network after a delay of 3 seconds( 20 ).
456            // The digits shall be sent according to the procedures and timing
457            // specified in 3GPP TS 24.008 [13]. The first occurrence of the
458            // "DTMF Control Digits Separator" shall be used by the ME to
459            // distinguish between the addressing digits (i.e. the phone number)
460            // and the DTMF digits. Upon subsequent occurrences of the
461            // separator,
462            // the UE shall pause again for 3 seconds ( 20 ) before sending
463            // any further DTMF digits.
464            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
465                    PAUSE_DELAY_MILLIS);
466        } else if (c == PhoneNumberUtils.WAIT) {
467            setPostDialState(PostDialState.WAIT);
468        } else if (c == PhoneNumberUtils.WILD) {
469            setPostDialState(PostDialState.WILD);
470        } else {
471            return false;
472        }
473
474        return true;
475    }
476
477    @Override
478    protected void finalize() {
479        releaseWakeLock();
480    }
481
482    private void
483    processNextPostDialChar() {
484        char c = 0;
485        Registrant postDialHandler;
486
487        if (mPostDialState == PostDialState.CANCELLED) {
488            //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail");
489            return;
490        }
491
492        if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) {
493            setPostDialState(PostDialState.COMPLETE);
494
495            // notifyMessage.arg1 is 0 on complete
496            c = 0;
497        } else {
498            boolean isValid;
499
500            setPostDialState(PostDialState.STARTED);
501
502            c = mPostDialString.charAt(mNextPostDialChar++);
503
504            isValid = processPostDialChar(c);
505
506            if (!isValid) {
507                // Will call processNextPostDialChar
508                mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
509                // Don't notify application
510                Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
511                return;
512            }
513        }
514
515        notifyPostDialListenersNextChar(c);
516
517        // TODO: remove the following code since the handler no longer executes anything.
518        postDialHandler = mOwner.mPhone.getPostDialHandler();
519
520        Message notifyMessage;
521
522        if (postDialHandler != null
523                && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
524            // The AsyncResult.result is the Connection object
525            PostDialState state = mPostDialState;
526            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
527            ar.result = this;
528            ar.userObj = state;
529
530            // arg1 is the character that was/is being processed
531            notifyMessage.arg1 = c;
532
533            //Rlog.v(LOG_TAG,
534            //      "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
535            notifyMessage.sendToTarget();
536        }
537    }
538
539    /**
540     * Set post dial state and acquire wake lock while switching to "started"
541     * state, the wake lock will be released if state switches out of "started"
542     * state or after WAKE_LOCK_TIMEOUT_MILLIS.
543     * @param s new PostDialState
544     */
545    private void setPostDialState(PostDialState s) {
546        if (mPostDialState != PostDialState.STARTED
547                && s == PostDialState.STARTED) {
548            acquireWakeLock();
549            Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
550            mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
551        } else if (mPostDialState == PostDialState.STARTED
552                && s != PostDialState.STARTED) {
553            mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
554            releaseWakeLock();
555        }
556        mPostDialState = s;
557        notifyPostDialListeners();
558    }
559
560    private void
561    createWakeLock(Context context) {
562        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
563        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
564    }
565
566    private void
567    acquireWakeLock() {
568        Rlog.d(LOG_TAG, "acquireWakeLock");
569        mPartialWakeLock.acquire();
570    }
571
572    void
573    releaseWakeLock() {
574        synchronized(mPartialWakeLock) {
575            if (mPartialWakeLock.isHeld()) {
576                Rlog.d(LOG_TAG, "releaseWakeLock");
577                mPartialWakeLock.release();
578            }
579        }
580    }
581
582    private void fetchDtmfToneDelay(Phone phone) {
583        CarrierConfigManager configMgr = (CarrierConfigManager)
584                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
585        PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId());
586        if (b != null) {
587            mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT);
588        }
589    }
590
591    @Override
592    public int getNumberPresentation() {
593        return mNumberPresentation;
594    }
595
596    @Override
597    public UUSInfo getUUSInfo() {
598        return mUusInfo;
599    }
600
601    @Override
602    public Connection getOrigConnection() {
603        return null;
604    }
605
606    @Override
607    public boolean isMultiparty() {
608        return mImsCall != null && mImsCall.isMultiparty();
609    }
610
611    /**
612     * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
613     * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
614     * {@link ImsCall} is a member of a conference hosted on another device.
615     *
616     * @return {@code true} if this call is the origin of the conference call it is a member of,
617     *      {@code false} otherwise.
618     */
619    @Override
620    public boolean isConferenceHost() {
621        if (mImsCall == null) {
622            return false;
623        }
624        return mImsCall.isConferenceHost();
625    }
626
627    @Override
628    public boolean isMemberOfPeerConference() {
629        return !isConferenceHost();
630    }
631
632    public ImsCall getImsCall() {
633        return mImsCall;
634    }
635
636    public void setImsCall(ImsCall imsCall) {
637        mImsCall = imsCall;
638    }
639
640    public void changeParent(ImsPhoneCall parent) {
641        mParent = parent;
642    }
643
644    /**
645     * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been
646     *     changed, and {@code false} otherwise.
647     */
648    public boolean update(ImsCall imsCall, ImsPhoneCall.State state) {
649        if (state == ImsPhoneCall.State.ACTIVE) {
650            // If the state of the call is active, but there is a pending request to the RIL to hold
651            // the call, we will skip this update.  This is really a signalling delay or failure
652            // from the RIL, but we will prevent it from going through as we will end up erroneously
653            // making this call active when really it should be on hold.
654            if (imsCall.isPendingHold()) {
655                Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping");
656                return false;
657            }
658
659            if (mParent.getState().isRinging() || mParent.getState().isDialing()) {
660                onConnectedInOrOut();
661            }
662
663            if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) {
664                //mForegroundCall should be IDLE
665                //when accepting WAITING call
666                //before accept WAITING call,
667                //the ACTIVE call should be held ahead
668                mParent.detach(this);
669                mParent = mOwner.mForegroundCall;
670                mParent.attach(this);
671            }
672        } else if (state == ImsPhoneCall.State.HOLDING) {
673            onStartedHolding();
674        }
675
676        boolean updateParent = mParent.update(this, imsCall, state);
677        boolean updateAddressDisplay = updateAddressDisplay(imsCall);
678        boolean updateMediaCapabilities = updateMediaCapabilities(imsCall);
679        boolean updateExtras = updateExtras(imsCall);
680
681        return updateParent || updateAddressDisplay || updateMediaCapabilities || updateExtras;
682    }
683
684    @Override
685    public int getPreciseDisconnectCause() {
686        return 0;
687    }
688
689    /**
690     * Notifies this Connection of a request to disconnect a participant of the conference managed
691     * by the connection.
692     *
693     * @param endpoint the {@link android.net.Uri} of the participant to disconnect.
694     */
695    @Override
696    public void onDisconnectConferenceParticipant(Uri endpoint) {
697        ImsCall imsCall = getImsCall();
698        if (imsCall == null) {
699            return;
700        }
701        try {
702            imsCall.removeParticipants(new String[]{endpoint.toString()});
703        } catch (ImsException e) {
704            // No session in place -- no change
705            Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+
706                    "Failed to disconnect endpoint = " + endpoint);
707        }
708    }
709
710    /**
711     * Sets the conference connect time.  Used when an {@code ImsConference} is created to out of
712     * this phone connection.
713     *
714     * @param conferenceConnectTime The conference connect time.
715     */
716    public void setConferenceConnectTime(long conferenceConnectTime) {
717        mConferenceConnectTime = conferenceConnectTime;
718    }
719
720    /**
721     * @return The conference connect time.
722     */
723    public long getConferenceConnectTime() {
724        return mConferenceConnectTime;
725    }
726
727    /**
728     * Check for a change in the address display related fields for the {@link ImsCall}, and
729     * update the {@link ImsPhoneConnection} with this information.
730     *
731     * @param imsCall The call to check for changes in address display fields.
732     * @return Whether the address display fields have been changed.
733     */
734    public boolean updateAddressDisplay(ImsCall imsCall) {
735        if (imsCall == null) {
736            return false;
737        }
738
739        boolean changed = false;
740        ImsCallProfile callProfile = imsCall.getCallProfile();
741        if (callProfile != null) {
742            String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI);
743            String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA);
744            int nump = ImsCallProfile.OIRToPresentation(
745                    callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR));
746            int namep = ImsCallProfile.OIRToPresentation(
747                    callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
748            if (Phone.DEBUG_PHONE) {
749                Rlog.d(LOG_TAG, "address = " + Rlog.pii(LOG_TAG, address) + " name = " + name +
750                        " nump = " + nump + " namep = " + namep);
751            }
752            if(equalsHandlesNulls(mAddress, address)) {
753                mAddress = address;
754                changed = true;
755            }
756            if (TextUtils.isEmpty(name)) {
757                if (!TextUtils.isEmpty(mCnapName)) {
758                    mCnapName = "";
759                    changed = true;
760                }
761            } else if (!name.equals(mCnapName)) {
762                mCnapName = name;
763                changed = true;
764            }
765            if (mNumberPresentation != nump) {
766                mNumberPresentation = nump;
767                changed = true;
768            }
769            if (mCnapNamePresentation != namep) {
770                mCnapNamePresentation = namep;
771                changed = true;
772            }
773        }
774        return changed;
775    }
776
777    /**
778     * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and
779     * update the {@link ImsPhoneConnection} with this information.
780     *
781     * @param imsCall The call to check for changes in media capabilities.
782     * @return Whether the media capabilities have been changed.
783     */
784    public boolean updateMediaCapabilities(ImsCall imsCall) {
785        if (imsCall == null) {
786            return false;
787        }
788
789        boolean changed = false;
790
791        try {
792            // The actual call profile (negotiated between local and peer).
793            ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile();
794
795            if (negotiatedCallProfile != null) {
796                int oldVideoState = getVideoState();
797                int newVideoState = ImsCallProfile
798                        .getVideoStateFromImsCallProfile(negotiatedCallProfile);
799
800                if (oldVideoState != newVideoState) {
801                    // The video state has changed.  See also code in onReceiveSessionModifyResponse
802                    // below.  When the video enters a paused state, subsequent changes to the video
803                    // state will not be reported by the modem.  In onReceiveSessionModifyResponse
804                    // we will be updating the current video state while paused to include any
805                    // changes the modem reports via the video provider.  When the video enters an
806                    // unpaused state, we will resume passing the video states from the modem as is.
807                    if (VideoProfile.isPaused(oldVideoState) &&
808                            !VideoProfile.isPaused(newVideoState)) {
809                        // Video entered un-paused state; recognize updates from now on; we want to
810                        // ensure that the new un-paused state is propagated to Telecom, so change
811                        // this now.
812                        mShouldIgnoreVideoStateChanges = false;
813                    }
814
815                    if (!mShouldIgnoreVideoStateChanges) {
816                        setVideoState(newVideoState);
817                        changed = true;
818                    } else {
819                        Rlog.d(LOG_TAG, "updateMediaCapabilities - ignoring video state change " +
820                                "due to paused state.");
821                    }
822
823                    if (!VideoProfile.isPaused(oldVideoState) &&
824                            VideoProfile.isPaused(newVideoState)) {
825                        // Video entered pause state; ignore updates until un-paused.  We do this
826                        // after setVideoState is called above to ensure Telecom is notified that
827                        // the device has entered paused state.
828                        mShouldIgnoreVideoStateChanges = true;
829                    }
830                }
831            }
832
833            // Check for a change in the capabilities for the call and update
834            // {@link ImsPhoneConnection} with this information.
835            int capabilities = getConnectionCapabilities();
836
837            // Use carrier config to determine if downgrading directly to audio-only is supported.
838            if (mOwner.isCarrierDowngradeOfVtCallSupported()) {
839                capabilities = addCapability(capabilities,
840                        Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE |
841                                Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL);
842            } else {
843                capabilities = removeCapability(capabilities,
844                        Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE |
845                                Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL);
846            }
847
848            // Get the current local call capabilities which might be voice or video or both.
849            ImsCallProfile localCallProfile = imsCall.getLocalCallProfile();
850            Rlog.v(LOG_TAG, "update localCallProfile=" + localCallProfile);
851            if (localCallProfile != null) {
852                capabilities = applyLocalCallCapabilities(localCallProfile, capabilities);
853            }
854
855            // Get the current remote call capabilities which might be voice or video or both.
856            ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile();
857            Rlog.v(LOG_TAG, "update remoteCallProfile=" + remoteCallProfile);
858            if (remoteCallProfile != null) {
859                capabilities = applyRemoteCallCapabilities(remoteCallProfile, capabilities);
860            }
861            if (getConnectionCapabilities() != capabilities) {
862                setConnectionCapabilities(capabilities);
863                changed = true;
864            }
865
866            int newAudioQuality =
867                    getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile);
868            if (getAudioQuality() != newAudioQuality) {
869                setAudioQuality(newAudioQuality);
870                changed = true;
871            }
872        } catch (ImsException e) {
873            // No session in place -- no change
874        }
875
876        return changed;
877    }
878
879    /**
880     * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}.
881     * The call is considered to be a WIFI call if the extra value is
882     * {@link ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}.
883     *
884     * @param extras The ImsCallProfile extras.
885     */
886    private void updateWifiStateFromExtras(Bundle extras) {
887        if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE) ||
888                extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) {
889
890            ImsCall call = getImsCall();
891            boolean isWifi = false;
892            if (call != null) {
893                isWifi = call.isWifiCall();
894            }
895
896            // Report any changes
897            if (isWifi() != isWifi) {
898                setWifi(isWifi);
899            }
900        }
901    }
902
903    /**
904     * Check for a change in call extras of {@link ImsCall}, and
905     * update the {@link ImsPhoneConnection} accordingly.
906     *
907     * @param imsCall The call to check for changes in extras.
908     * @return Whether the extras fields have been changed.
909     */
910     boolean updateExtras(ImsCall imsCall) {
911        if (imsCall == null) {
912            return false;
913        }
914
915        final ImsCallProfile callProfile = imsCall.getCallProfile();
916        final Bundle extras = callProfile != null ? callProfile.mCallExtras : null;
917        if (extras == null && DBG) {
918            Rlog.d(LOG_TAG, "Call profile extras are null.");
919        }
920
921        final boolean changed = !areBundlesEqual(extras, mExtras);
922        if (changed) {
923            updateWifiStateFromExtras(extras);
924
925            mExtras.clear();
926            mExtras.putAll(extras);
927            setConnectionExtras(mExtras);
928        }
929        return changed;
930    }
931
932    private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
933        if (extras == null || newExtras == null) {
934            return extras == newExtras;
935        }
936
937        if (extras.size() != newExtras.size()) {
938            return false;
939        }
940
941        for(String key : extras.keySet()) {
942            if (key != null) {
943                final Object value = extras.get(key);
944                final Object newValue = newExtras.get(key);
945                if (!Objects.equals(value, newValue)) {
946                    return false;
947                }
948            }
949        }
950        return true;
951    }
952
953    /**
954     * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote
955     * {@link ImsCallProfile}. Indicate a HD audio call if the local stream profile
956     * is AMR_WB, EVRC_WB, EVS_WB, EVS_SWB, EVS_FB and
957     * there is no remote restrict cause.
958     *
959     * @param localCallProfile The local call profile.
960     * @param remoteCallProfile The remote call profile.
961     * @return The audio quality.
962     */
963    private int getAudioQualityFromCallProfile(
964            ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) {
965        if (localCallProfile == null || remoteCallProfile == null
966                || localCallProfile.mMediaProfile == null) {
967            return AUDIO_QUALITY_STANDARD;
968        }
969
970        final boolean isEvsCodecHighDef = (localCallProfile.mMediaProfile.mAudioQuality
971                        == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB
972                || localCallProfile.mMediaProfile.mAudioQuality
973                        == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB
974                || localCallProfile.mMediaProfile.mAudioQuality
975                        == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB);
976
977        final boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality
978                        == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB
979                || localCallProfile.mMediaProfile.mAudioQuality
980                        == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB
981                || isEvsCodecHighDef)
982                && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE;
983        return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD;
984    }
985
986    /**
987     * Provides a string representation of the {@link ImsPhoneConnection}.  Primarily intended for
988     * use in log statements.
989     *
990     * @return String representation of call.
991     */
992    @Override
993    public String toString() {
994        StringBuilder sb = new StringBuilder();
995        sb.append("[ImsPhoneConnection objId: ");
996        sb.append(System.identityHashCode(this));
997        sb.append(" telecomCallID: ");
998        sb.append(getTelecomCallId());
999        sb.append(" address: ");
1000        sb.append(Rlog.pii(LOG_TAG, getAddress()));
1001        sb.append(" ImsCall: ");
1002        if (mImsCall == null) {
1003            sb.append("null");
1004        } else {
1005            sb.append(mImsCall);
1006        }
1007        sb.append("]");
1008        return sb.toString();
1009    }
1010
1011    @Override
1012    public void setVideoProvider(android.telecom.Connection.VideoProvider videoProvider) {
1013        super.setVideoProvider(videoProvider);
1014
1015        if (videoProvider instanceof ImsVideoCallProviderWrapper) {
1016            mImsVideoCallProviderWrapper = (ImsVideoCallProviderWrapper) videoProvider;
1017        }
1018    }
1019
1020    /**
1021     * Indicates whether current phone connection is emergency or not
1022     * @return boolean: true if emergency, false otherwise
1023     */
1024    protected boolean isEmergency() {
1025        return mIsEmergency;
1026    }
1027
1028    /**
1029     * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification
1030     * responses received.
1031     *
1032     * @param status The status of the original request.
1033     * @param requestProfile The requested video profile.
1034     * @param responseProfile The response upon video profile.
1035     */
1036    @Override
1037    public void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile,
1038            VideoProfile responseProfile) {
1039        if (status == android.telecom.Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS &&
1040                mShouldIgnoreVideoStateChanges) {
1041            int currentVideoState = getVideoState();
1042            int newVideoState = responseProfile.getVideoState();
1043
1044            // If the current video state is paused, the modem will not send us any changes to
1045            // the TX and RX bits of the video state.  Until the video is un-paused we will
1046            // "fake out" the video state by applying the changes that the modem reports via a
1047            // response.
1048
1049            // First, find out whether there was a change to the TX or RX bits:
1050            int changedBits = currentVideoState ^ newVideoState;
1051            changedBits &= VideoProfile.STATE_BIDIRECTIONAL;
1052            if (changedBits == 0) {
1053                // No applicable change, bail out.
1054                return;
1055            }
1056
1057            // Turn off any existing bits that changed.
1058            currentVideoState &= ~(changedBits & currentVideoState);
1059            // Turn on any new bits that turned on.
1060            currentVideoState |= changedBits & newVideoState;
1061
1062            Rlog.d(LOG_TAG, "onReceiveSessionModifyResponse : received " +
1063                    VideoProfile.videoStateToString(requestProfile.getVideoState()) +
1064                    " / " +
1065                    VideoProfile.videoStateToString(responseProfile.getVideoState()) +
1066                    " while paused ; sending new videoState = " +
1067                    VideoProfile.videoStateToString(currentVideoState));
1068            setVideoState(currentVideoState);
1069        }
1070    }
1071
1072    /**
1073     * Issues a request to pause the video using {@link VideoProfile#STATE_PAUSED} from a source
1074     * other than the InCall UI.
1075     *
1076     * @param source The source of the pause request.
1077     */
1078    public void pauseVideo(int source) {
1079        if (mImsVideoCallProviderWrapper == null) {
1080            return;
1081        }
1082
1083        mImsVideoCallProviderWrapper.pauseVideo(getVideoState(), source);
1084    }
1085
1086    /**
1087     * Issues a request to resume the video using {@link VideoProfile#STATE_PAUSED} from a source
1088     * other than the InCall UI.
1089     *
1090     * @param source The source of the resume request.
1091     */
1092    public void resumeVideo(int source) {
1093        if (mImsVideoCallProviderWrapper == null) {
1094            return;
1095        }
1096
1097        mImsVideoCallProviderWrapper.resumeVideo(getVideoState(), source);
1098    }
1099
1100    /**
1101     * Determines if a specified source has issued a pause request.
1102     *
1103     * @param source The source.
1104     * @return {@code true} if the source issued a pause request, {@code false} otherwise.
1105     */
1106    public boolean wasVideoPausedFromSource(int source) {
1107        if (mImsVideoCallProviderWrapper == null) {
1108            return false;
1109        }
1110
1111        return mImsVideoCallProviderWrapper.wasVideoPausedFromSource(source);
1112    }
1113}
1114