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