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