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