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