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