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