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