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