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