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