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