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