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