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