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