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