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