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