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