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