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