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