ImsPhoneConnection.java revision 2e66c6325d321e41b8851da9a4596f7a745c7b1e
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.Log;
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.internal.telephony.CallStateException;
41import com.android.internal.telephony.Connection;
42import com.android.internal.telephony.Phone;
43import com.android.internal.telephony.PhoneConstants;
44import com.android.internal.telephony.UUSInfo;
45
46import com.android.ims.ImsCall;
47import com.android.ims.ImsCallProfile;
48
49import java.util.Objects;
50
51/**
52 * {@hide}
53 */
54public class ImsPhoneConnection extends Connection {
55    private static final String LOG_TAG = "ImsPhoneConnection";
56    private static final boolean DBG = true;
57
58    //***** Instance Variables
59
60    private ImsPhoneCallTracker mOwner;
61    private ImsPhoneCall mParent;
62    private ImsCall mImsCall;
63    private Bundle mExtras = new Bundle();
64
65    private String mPostDialString;      // outgoing calls only
66    private boolean mDisconnected;
67
68    /*
69    int mIndex;          // index in ImsPhoneCallTracker.connections[], -1 if unassigned
70                        // The GSM index is 1 + this
71    */
72
73    /*
74     * These time/timespan values are based on System.currentTimeMillis(),
75     * i.e., "wall clock" time.
76     */
77    private long mDisconnectTime;
78
79    private int mNextPostDialChar;       // index into postDialString
80
81    private int mCause = DisconnectCause.NOT_DISCONNECTED;
82    private PostDialState mPostDialState = PostDialState.NOT_STARTED;
83    private UUSInfo mUusInfo;
84    private Handler mHandler;
85
86    private PowerManager.WakeLock mPartialWakeLock;
87
88    // The cached connect time of the connection when it turns into a conference.
89    private long mConferenceConnectTime = 0;
90
91    // The cached delay to be used between DTMF tones fetched from carrier config.
92    private int mDtmfToneDelay = 0;
93
94    private boolean mIsEmergency = false;
95
96    /**
97     * Used to indicate whether the wifi state is based on
98     * {@link com.android.ims.ImsConnectionStateListener#
99     *      onFeatureCapabilityChanged(int, int[], int[])} callbacks, or values received via the
100     * {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra.  Util we receive a value via the extras,
101     * we will use the wifi state based on the {@code onFeatureCapabilityChanged}.  Once a value
102     * is received via the extras, we will prefer those values going forward.
103     */
104    private boolean mIsWifiStateFromExtras = false;
105
106    //***** Event Constants
107    private static final int EVENT_DTMF_DONE = 1;
108    private static final int EVENT_PAUSE_DONE = 2;
109    private static final int EVENT_NEXT_POST_DIAL = 3;
110    private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
111    private static final int EVENT_DTMF_DELAY_DONE = 5;
112
113    //***** Constants
114    private static final int PAUSE_DELAY_MILLIS = 3 * 1000;
115    private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
116
117    //***** Inner Classes
118
119    class MyHandler extends Handler {
120        MyHandler(Looper l) {super(l);}
121
122        @Override
123        public void
124        handleMessage(Message msg) {
125
126            switch (msg.what) {
127                case EVENT_NEXT_POST_DIAL:
128                case EVENT_DTMF_DELAY_DONE:
129                case EVENT_PAUSE_DONE:
130                    processNextPostDialChar();
131                    break;
132                case EVENT_WAKE_LOCK_TIMEOUT:
133                    releaseWakeLock();
134                    break;
135                case EVENT_DTMF_DONE:
136                    // We may need to add a delay specified by carrier between DTMF tones that are
137                    // sent out.
138                    mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE),
139                            mDtmfToneDelay);
140                    break;
141            }
142        }
143    }
144
145    //***** Constructors
146
147    /** This is probably an MT call */
148    /*package*/
149    ImsPhoneConnection(ImsPhone phone, ImsCall imsCall, ImsPhoneCallTracker ct,
150           ImsPhoneCall parent, boolean isUnknown) {
151        createWakeLock(phone.getContext());
152        acquireWakeLock();
153
154        mOwner = ct;
155        mHandler = new MyHandler(mOwner.getLooper());
156        mImsCall = imsCall;
157
158        if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
159            mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
160            mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA);
161            mNumberPresentation = ImsCallProfile.OIRToPresentation(
162                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR));
163            mCnapNamePresentation = ImsCallProfile.OIRToPresentation(
164                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
165            updateMediaCapabilities(imsCall);
166        } else {
167            mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
168            mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
169        }
170
171        mIsIncoming = !isUnknown;
172        mCreateTime = System.currentTimeMillis();
173        mUusInfo = null;
174
175        updateWifiState();
176
177        // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally
178        // in the ImsPhoneConnection.  This isn't going to inform any listeners (since the original
179        // connection is not likely to be associated with a TelephonyConnection yet).
180        updateExtras(imsCall);
181
182        mParent = parent;
183        mParent.attach(this,
184                (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING));
185
186        fetchDtmfToneDelay(phone);
187    }
188
189    /** This is an MO call, created when dialing */
190    /*package*/
191    ImsPhoneConnection(ImsPhone phone, String dialString, ImsPhoneCallTracker ct,
192            ImsPhoneCall parent, boolean isEmergency) {
193        createWakeLock(phone.getContext());
194        acquireWakeLock();
195
196        mOwner = ct;
197        mHandler = new MyHandler(mOwner.getLooper());
198
199        mDialString = dialString;
200
201        mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
202        mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
203
204        //mIndex = -1;
205
206        mIsIncoming = false;
207        mCnapName = null;
208        mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
209        mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
210        mCreateTime = System.currentTimeMillis();
211
212        mParent = parent;
213        parent.attachFake(this, ImsPhoneCall.State.DIALING);
214
215        mIsEmergency = isEmergency;
216
217        fetchDtmfToneDelay(phone);
218    }
219
220    public void dispose() {
221    }
222
223    static boolean
224    equalsHandlesNulls (Object a, Object b) {
225        return (a == null) ? (b == null) : a.equals (b);
226    }
227
228    @Override
229    public String getOrigDialString(){
230        return mDialString;
231    }
232
233    @Override
234    public ImsPhoneCall getCall() {
235        return mParent;
236    }
237
238    @Override
239    public long getDisconnectTime() {
240        return mDisconnectTime;
241    }
242
243    @Override
244    public long getHoldingStartTime() {
245        return mHoldingStartTime;
246    }
247
248    @Override
249    public long getHoldDurationMillis() {
250        if (getState() != ImsPhoneCall.State.HOLDING) {
251            // If not holding, return 0
252            return 0;
253        } else {
254            return SystemClock.elapsedRealtime() - mHoldingStartTime;
255        }
256    }
257
258    @Override
259    public int getDisconnectCause() {
260        return mCause;
261    }
262
263    public void setDisconnectCause(int cause) {
264        mCause = cause;
265    }
266
267    @Override
268    public String getVendorDisconnectCause() {
269      return null;
270    }
271
272    public ImsPhoneCallTracker getOwner () {
273        return mOwner;
274    }
275
276    @Override
277    public ImsPhoneCall.State getState() {
278        if (mDisconnected) {
279            return ImsPhoneCall.State.DISCONNECTED;
280        } else {
281            return super.getState();
282        }
283    }
284
285    @Override
286    public void hangup() throws CallStateException {
287        if (!mDisconnected) {
288            mOwner.hangup(this);
289        } else {
290            throw new CallStateException ("disconnected");
291        }
292    }
293
294    @Override
295    public void separate() throws CallStateException {
296        throw new CallStateException ("not supported");
297    }
298
299    @Override
300    public PostDialState getPostDialState() {
301        return mPostDialState;
302    }
303
304    @Override
305    public void proceedAfterWaitChar() {
306        if (mPostDialState != PostDialState.WAIT) {
307            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
308                    + "getPostDialState() to be WAIT but was " + mPostDialState);
309            return;
310        }
311
312        setPostDialState(PostDialState.STARTED);
313
314        processNextPostDialChar();
315    }
316
317    @Override
318    public void proceedAfterWildChar(String str) {
319        if (mPostDialState != PostDialState.WILD) {
320            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
321                    + "getPostDialState() to be WILD but was " + mPostDialState);
322            return;
323        }
324
325        setPostDialState(PostDialState.STARTED);
326
327        // make a new postDialString, with the wild char replacement string
328        // at the beginning, followed by the remaining postDialString.
329
330        StringBuilder buf = new StringBuilder(str);
331        buf.append(mPostDialString.substring(mNextPostDialChar));
332        mPostDialString = buf.toString();
333        mNextPostDialChar = 0;
334        if (Phone.DEBUG_PHONE) {
335            Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " +
336                    mPostDialString);
337        }
338
339        processNextPostDialChar();
340    }
341
342    @Override
343    public void cancelPostDial() {
344        setPostDialState(PostDialState.CANCELLED);
345    }
346
347    /**
348     * Called when this Connection is being hung up locally (eg, user pressed "end")
349     */
350    void
351    onHangupLocal() {
352        mCause = DisconnectCause.LOCAL;
353    }
354
355    /** Called when the connection has been disconnected */
356    public boolean
357    onDisconnect(int cause) {
358        Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
359        if (mCause != DisconnectCause.LOCAL) mCause = cause;
360        return onDisconnect();
361    }
362
363    /*package*/ boolean
364    onDisconnect() {
365        boolean changed = false;
366
367        if (!mDisconnected) {
368            //mIndex = -1;
369
370            mDisconnectTime = System.currentTimeMillis();
371            mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
372            mDisconnected = true;
373
374            mOwner.mPhone.notifyDisconnect(this);
375
376            if (mParent != null) {
377                changed = mParent.connectionDisconnected(this);
378            } else {
379                Rlog.d(LOG_TAG, "onDisconnect: no parent");
380            }
381            if (mImsCall != null) mImsCall.close();
382            mImsCall = null;
383        }
384        releaseWakeLock();
385        return changed;
386    }
387
388    /**
389     * An incoming or outgoing call has connected
390     */
391    void
392    onConnectedInOrOut() {
393        mConnectTime = System.currentTimeMillis();
394        mConnectTimeReal = SystemClock.elapsedRealtime();
395        mDuration = 0;
396
397        if (Phone.DEBUG_PHONE) {
398            Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime);
399        }
400
401        if (!mIsIncoming) {
402            // outgoing calls only
403            processNextPostDialChar();
404        }
405        releaseWakeLock();
406    }
407
408    /*package*/ void
409    onStartedHolding() {
410        mHoldingStartTime = SystemClock.elapsedRealtime();
411    }
412    /**
413     * Performs the appropriate action for a post-dial char, but does not
414     * notify application. returns false if the character is invalid and
415     * should be ignored
416     */
417    private boolean
418    processPostDialChar(char c) {
419        if (PhoneNumberUtils.is12Key(c)) {
420            mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
421        } else if (c == PhoneNumberUtils.PAUSE) {
422            // From TS 22.101:
423            // It continues...
424            // Upon the called party answering the UE shall send the DTMF digits
425            // automatically to the network after a delay of 3 seconds( 20 ).
426            // The digits shall be sent according to the procedures and timing
427            // specified in 3GPP TS 24.008 [13]. The first occurrence of the
428            // "DTMF Control Digits Separator" shall be used by the ME to
429            // distinguish between the addressing digits (i.e. the phone number)
430            // and the DTMF digits. Upon subsequent occurrences of the
431            // separator,
432            // the UE shall pause again for 3 seconds ( 20 ) before sending
433            // any further DTMF digits.
434            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
435                    PAUSE_DELAY_MILLIS);
436        } else if (c == PhoneNumberUtils.WAIT) {
437            setPostDialState(PostDialState.WAIT);
438        } else if (c == PhoneNumberUtils.WILD) {
439            setPostDialState(PostDialState.WILD);
440        } else {
441            return false;
442        }
443
444        return true;
445    }
446
447    @Override
448    public String
449    getRemainingPostDialString() {
450        if (mPostDialState == PostDialState.CANCELLED
451            || mPostDialState == PostDialState.COMPLETE
452            || mPostDialString == null
453            || mPostDialString.length() <= mNextPostDialChar
454        ) {
455            return "";
456        }
457
458        return mPostDialString.substring(mNextPostDialChar);
459    }
460
461    @Override
462    protected void finalize()
463    {
464        releaseWakeLock();
465    }
466
467    private void
468    processNextPostDialChar() {
469        char c = 0;
470        Registrant postDialHandler;
471
472        if (mPostDialState == PostDialState.CANCELLED) {
473            //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail");
474            return;
475        }
476
477        if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) {
478            setPostDialState(PostDialState.COMPLETE);
479
480            // notifyMessage.arg1 is 0 on complete
481            c = 0;
482        } else {
483            boolean isValid;
484
485            setPostDialState(PostDialState.STARTED);
486
487            c = mPostDialString.charAt(mNextPostDialChar++);
488
489            isValid = processPostDialChar(c);
490
491            if (!isValid) {
492                // Will call processNextPostDialChar
493                mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
494                // Don't notify application
495                Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
496                return;
497            }
498        }
499
500        notifyPostDialListenersNextChar(c);
501
502        // TODO: remove the following code since the handler no longer executes anything.
503        postDialHandler = mOwner.mPhone.mPostDialHandler;
504
505        Message notifyMessage;
506
507        if (postDialHandler != null
508                && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
509            // The AsyncResult.result is the Connection object
510            PostDialState state = mPostDialState;
511            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
512            ar.result = this;
513            ar.userObj = state;
514
515            // arg1 is the character that was/is being processed
516            notifyMessage.arg1 = c;
517
518            //Rlog.v(LOG_TAG,
519            //      "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
520            notifyMessage.sendToTarget();
521        }
522    }
523
524    /**
525     * Set post dial state and acquire wake lock while switching to "started"
526     * state, the wake lock will be released if state switches out of "started"
527     * state or after WAKE_LOCK_TIMEOUT_MILLIS.
528     * @param s new PostDialState
529     */
530    private void setPostDialState(PostDialState s) {
531        if (mPostDialState != PostDialState.STARTED
532                && s == PostDialState.STARTED) {
533            acquireWakeLock();
534            Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
535            mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
536        } else if (mPostDialState == PostDialState.STARTED
537                && s != PostDialState.STARTED) {
538            mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
539            releaseWakeLock();
540        }
541        mPostDialState = s;
542        notifyPostDialListeners();
543    }
544
545    private void
546    createWakeLock(Context context) {
547        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
548        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
549    }
550
551    private void
552    acquireWakeLock() {
553        Rlog.d(LOG_TAG, "acquireWakeLock");
554        mPartialWakeLock.acquire();
555    }
556
557    void
558    releaseWakeLock() {
559        synchronized(mPartialWakeLock) {
560            if (mPartialWakeLock.isHeld()) {
561                Rlog.d(LOG_TAG, "releaseWakeLock");
562                mPartialWakeLock.release();
563            }
564        }
565    }
566
567    private void fetchDtmfToneDelay(ImsPhone phone) {
568        CarrierConfigManager configMgr = (CarrierConfigManager)
569                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
570        PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId());
571        if (b != null) {
572            mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT);
573        }
574    }
575
576    @Override
577    public int getNumberPresentation() {
578        return mNumberPresentation;
579    }
580
581    @Override
582    public UUSInfo getUUSInfo() {
583        return mUusInfo;
584    }
585
586    @Override
587    public Connection getOrigConnection() {
588        return null;
589    }
590
591    @Override
592    public boolean isMultiparty() {
593        return mImsCall != null && mImsCall.isMultiparty();
594    }
595
596    /**
597     * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
598     * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
599     * {@link ImsCall} is a member of a conference hosted on another device.
600     *
601     * @return {@code true} if this call is the origin of the conference call it is a member of,
602     *      {@code false} otherwise.
603     */
604    public boolean isConferenceHost() {
605        if (mImsCall == null) {
606            return false;
607        }
608        return mImsCall.isConferenceHost();
609    }
610
611    /*package*/ ImsCall getImsCall() {
612        return mImsCall;
613    }
614
615    /*package*/ void setImsCall(ImsCall imsCall) {
616        mImsCall = imsCall;
617    }
618
619    /*package*/ void changeParent(ImsPhoneCall parent) {
620        mParent = parent;
621    }
622
623    /**
624     * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been
625     *     changed, and {@code false} otherwise.
626     */
627    /*package*/ boolean update(ImsCall imsCall, ImsPhoneCall.State state) {
628        if (state == ImsPhoneCall.State.ACTIVE) {
629            // If the state of the call is active, but there is a pending request to the RIL to hold
630            // the call, we will skip this update.  This is really a signalling delay or failure
631            // from the RIL, but we will prevent it from going through as we will end up erroneously
632            // making this call active when really it should be on hold.
633            if (imsCall.isPendingHold()) {
634                Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping");
635                return false;
636            }
637
638            if (mParent.getState().isRinging() || mParent.getState().isDialing()) {
639                onConnectedInOrOut();
640            }
641
642            if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) {
643                //mForegroundCall should be IDLE
644                //when accepting WAITING call
645                //before accept WAITING call,
646                //the ACTIVE call should be held ahead
647                mParent.detach(this);
648                mParent = mOwner.mForegroundCall;
649                mParent.attach(this);
650            }
651        } else if (state == ImsPhoneCall.State.HOLDING) {
652            onStartedHolding();
653        }
654
655        boolean updateParent = mParent.update(this, imsCall, state);
656        boolean updateWifiState = updateWifiState();
657        boolean updateAddressDisplay = updateAddressDisplay(imsCall);
658        boolean updateMediaCapabilities = updateMediaCapabilities(imsCall);
659        boolean updateExtras = updateExtras(imsCall);
660
661        return updateParent || updateWifiState || updateAddressDisplay || updateMediaCapabilities
662                || updateExtras;
663    }
664
665    @Override
666    public int getPreciseDisconnectCause() {
667        return 0;
668    }
669
670    /**
671     * Notifies this Connection of a request to disconnect a participant of the conference managed
672     * by the connection.
673     *
674     * @param endpoint the {@link android.net.Uri} of the participant to disconnect.
675     */
676    @Override
677    public void onDisconnectConferenceParticipant(Uri endpoint) {
678        ImsCall imsCall = getImsCall();
679        if (imsCall == null) {
680            return;
681        }
682        try {
683            imsCall.removeParticipants(new String[]{endpoint.toString()});
684        } catch (ImsException e) {
685            // No session in place -- no change
686            Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+
687                    "Failed to disconnect endpoint = " + endpoint);
688        }
689    }
690
691    /**
692     * Sets the conference connect time.  Used when an {@code ImsConference} is created to out of
693     * this phone connection.
694     *
695     * @param conferenceConnectTime The conference connect time.
696     */
697    public void setConferenceConnectTime(long conferenceConnectTime) {
698        mConferenceConnectTime = conferenceConnectTime;
699    }
700
701    /**
702     * @return The conference connect time.
703     */
704    public long getConferenceConnectTime() {
705        return mConferenceConnectTime;
706    }
707
708    /**
709     * Check for a change in the address display related fields for the {@link ImsCall}, and
710     * update the {@link ImsPhoneConnection} with this information.
711     *
712     * @param imsCall The call to check for changes in address display fields.
713     * @return Whether the address display fields have been changed.
714     */
715    private boolean updateAddressDisplay(ImsCall imsCall) {
716        if (imsCall == null) {
717            return false;
718        }
719
720        boolean changed = false;
721        ImsCallProfile callProfile = imsCall.getCallProfile();
722        if (callProfile != null) {
723            String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI);
724            String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA);
725            int nump = ImsCallProfile.OIRToPresentation(
726                    callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR));
727            int namep = ImsCallProfile.OIRToPresentation(
728                    callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
729            if (Phone.DEBUG_PHONE) {
730                Rlog.d(LOG_TAG, "address = " +  address + " name = " + name +
731                        " nump = " + nump + " namep = " + namep);
732            }
733            if(equalsHandlesNulls(mAddress, address)) {
734                mAddress = address;
735                changed = true;
736            }
737            if (TextUtils.isEmpty(name)) {
738                if (!TextUtils.isEmpty(mCnapName)) {
739                    mCnapName = "";
740                    changed = true;
741                }
742            } else if (!name.equals(mCnapName)) {
743                mCnapName = name;
744                changed = true;
745            }
746            if (mNumberPresentation != nump) {
747                mNumberPresentation = nump;
748                changed = true;
749            }
750            if (mCnapNamePresentation != namep) {
751                mCnapNamePresentation = namep;
752                changed = true;
753            }
754        }
755        return changed;
756    }
757
758    /**
759     * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and
760     * update the {@link ImsPhoneConnection} with this information.
761     *
762     * @param imsCall The call to check for changes in media capabilities.
763     * @return Whether the media capabilities have been changed.
764     */
765    public boolean updateMediaCapabilities(ImsCall imsCall) {
766        if (imsCall == null) {
767            return false;
768        }
769
770        boolean changed = false;
771
772        try {
773            // The actual call profile (negotiated between local and peer).
774            ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile();
775            // The capabilities of the local device.
776            ImsCallProfile localCallProfile = imsCall.getLocalCallProfile();
777            // The capabilities of the peer device.
778            ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile();
779
780            if (negotiatedCallProfile != null) {
781                int oldVideoState = getVideoState();
782                int newVideoState = ImsCallProfile
783                        .getVideoStateFromImsCallProfile(negotiatedCallProfile);
784
785                if (oldVideoState != newVideoState) {
786                    setVideoState(newVideoState);
787                    changed = true;
788                }
789            }
790
791            if (localCallProfile != null) {
792                int callType = localCallProfile.mCallType;
793
794                boolean newLocalVideoCapable = callType == ImsCallProfile.CALL_TYPE_VT;
795                if (isLocalVideoCapable() != newLocalVideoCapable) {
796                    setLocalVideoCapable(newLocalVideoCapable);
797                    changed = true;
798                }
799            }
800
801            if (remoteCallProfile != null) {
802                    boolean newRemoteVideoCapable = remoteCallProfile.mCallType
803                            == ImsCallProfile.CALL_TYPE_VT;
804
805                    if (isRemoteVideoCapable() != newRemoteVideoCapable) {
806                        setRemoteVideoCapable(newRemoteVideoCapable);
807                        changed = true;
808                    }
809            }
810
811            int newAudioQuality =
812                    getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile);
813            if (getAudioQuality() != newAudioQuality) {
814                setAudioQuality(newAudioQuality);
815                changed = true;
816            }
817        } catch (ImsException e) {
818            // No session in place -- no change
819        }
820
821        return changed;
822    }
823
824    /**
825     * Check for a change in the wifi state of the ImsPhoneCallTracker and update the
826     * {@link ImsPhoneConnection} with this information.
827     *
828     * @return Whether the ImsPhoneCallTracker's usage of wifi has been changed.
829     */
830    public boolean updateWifiState() {
831        // If we've received the wifi state via the ImsCallProfile.EXTRA_CALL_RAT_TYPE extra, we
832        // will no longer use state updates which are based on the onFeatureCapabilityChanged
833        // callback.
834        if (mIsWifiStateFromExtras) {
835            return false;
836        }
837
838        Rlog.d(LOG_TAG, "updateWifiState: " + mOwner.isVowifiEnabled());
839        if (isWifi() != mOwner.isVowifiEnabled()) {
840            setWifi(mOwner.isVowifiEnabled());
841            return true;
842        }
843        return false;
844    }
845
846    /**
847     * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}.
848     * The call is considered to be a WIFI call if the extra value is
849     * {@link ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}.
850     *
851     * @param extras The ImsCallProfile extras.
852     */
853    private void updateWifiStateFromExtras(Bundle extras) {
854        if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)) {
855            // The RIL (sadly) sends us the EXTRA_CALL_RAT_TYPE as a string extra, rather than an
856            // integer extra, so we need to parse it.
857            int radioTechnology;
858            try {
859                radioTechnology = Integer.parseInt(extras.getString(
860                        ImsCallProfile.EXTRA_CALL_RAT_TYPE));
861            } catch (NumberFormatException nfe) {
862                radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
863            }
864
865            // We've received the extra indicating the radio technology, so we will continue to
866            // prefer the radio technology received via this extra going forward.
867            mIsWifiStateFromExtras = true;
868
869            boolean isWifi = radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
870
871            // Report any changes
872            if (isWifi() != isWifi) {
873                setWifi(isWifi);
874            }
875        }
876    }
877
878    /**
879     * Check for a change in call extras of {@link ImsCall}, and
880     * update the {@link ImsPhoneConnection} accordingly.
881     *
882     * @param imsCall The call to check for changes in extras.
883     * @return Whether the extras fields have been changed.
884     */
885     boolean updateExtras(ImsCall imsCall) {
886        if (imsCall == null) {
887            return false;
888        }
889
890        final ImsCallProfile callProfile = imsCall.getCallProfile();
891        final Bundle extras = callProfile != null ? callProfile.mCallExtras : null;
892        if (extras == null && DBG) {
893            Rlog.d(LOG_TAG, "Call profile extras are null.");
894        }
895
896        final boolean changed = !areBundlesEqual(extras, mExtras);
897        if (changed) {
898            updateWifiStateFromExtras(extras);
899
900            mExtras.clear();
901            mExtras.putAll(extras);
902            setConnectionExtras(mExtras);
903        }
904        return changed;
905    }
906
907    private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
908        if (extras == null || newExtras == null) {
909            return extras == newExtras;
910        }
911
912        if (extras.size() != newExtras.size()) {
913            return false;
914        }
915
916        for(String key : extras.keySet()) {
917            if (key != null) {
918                final Object value = extras.get(key);
919                final Object newValue = newExtras.get(key);
920                if (!Objects.equals(value, newValue)) {
921                    return false;
922                }
923            }
924        }
925        return true;
926    }
927
928    /**
929     * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote
930     * {@link ImsCallProfile}. If indicate a HQ audio call if the local stream profile
931     * indicates AMR_WB or EVRC_WB and there is no remote restrict cause.
932     *
933     * @param localCallProfile The local call profile.
934     * @param remoteCallProfile The remote call profile.
935     * @return The audio quality.
936     */
937    private int getAudioQualityFromCallProfile(
938            ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) {
939        if (localCallProfile == null || remoteCallProfile == null
940                || localCallProfile.mMediaProfile == null) {
941            return AUDIO_QUALITY_STANDARD;
942        }
943
944        boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality
945                        == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB
946                || localCallProfile.mMediaProfile.mAudioQuality
947                        == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB)
948                && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE;
949        return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD;
950    }
951
952    /**
953     * Provides a string representation of the {@link ImsPhoneConnection}.  Primarily intended for
954     * use in log statements.
955     *
956     * @return String representation of call.
957     */
958    @Override
959    public String toString() {
960        StringBuilder sb = new StringBuilder();
961        sb.append("[ImsPhoneConnection objId: ");
962        sb.append(System.identityHashCode(this));
963        sb.append(" address:");
964        sb.append(Log.pii(getAddress()));
965        sb.append(" ImsCall:");
966        if (mImsCall == null) {
967            sb.append("null");
968        } else {
969            sb.append(mImsCall);
970        }
971        sb.append("]");
972        return sb.toString();
973    }
974
975    /**
976     * Indicates whether current phone connection is emergency or not
977     * @return boolean: true if emergency, false otherwise
978     */
979    protected boolean isEmergency() {
980        return mIsEmergency;
981    }
982}
983
984