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 
17 package com.android.internal.telephony.imsphone;
18 
19 import android.annotation.UnsupportedAppUsage;
20 import android.content.Context;
21 import android.net.Uri;
22 import android.os.AsyncResult;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.os.Messenger;
28 import android.os.PersistableBundle;
29 import android.os.PowerManager;
30 import android.os.Registrant;
31 import android.os.SystemClock;
32 import android.telecom.VideoProfile;
33 import android.telephony.CarrierConfigManager;
34 import android.telephony.DisconnectCause;
35 import android.telephony.PhoneNumberUtils;
36 import android.telephony.Rlog;
37 import android.telephony.ServiceState;
38 import android.telephony.ims.ImsCallProfile;
39 import android.telephony.ims.ImsStreamMediaProfile;
40 import android.text.TextUtils;
41 
42 import com.android.ims.ImsCall;
43 import com.android.ims.ImsException;
44 import com.android.ims.internal.ImsVideoCallProviderWrapper;
45 import com.android.internal.telephony.CallStateException;
46 import com.android.internal.telephony.Connection;
47 import com.android.internal.telephony.Phone;
48 import com.android.internal.telephony.PhoneConstants;
49 import com.android.internal.telephony.UUSInfo;
50 import com.android.internal.telephony.metrics.TelephonyMetrics;
51 
52 import java.util.Objects;
53 
54 /**
55  * {@hide}
56  */
57 public class ImsPhoneConnection extends Connection implements
58         ImsVideoCallProviderWrapper.ImsVideoProviderWrapperCallback {
59 
60     private static final String LOG_TAG = "ImsPhoneConnection";
61     private static final boolean DBG = true;
62 
63     //***** Instance Variables
64 
65     @UnsupportedAppUsage
66     private ImsPhoneCallTracker mOwner;
67     @UnsupportedAppUsage
68     private ImsPhoneCall mParent;
69     private ImsCall mImsCall;
70     private Bundle mExtras = new Bundle();
71     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
72 
73     @UnsupportedAppUsage
74     private boolean mDisconnected;
75 
76     /*
77     int mIndex;          // index in ImsPhoneCallTracker.connections[], -1 if unassigned
78                         // The GSM index is 1 + this
79     */
80 
81     /*
82      * These time/timespan values are based on System.currentTimeMillis(),
83      * i.e., "wall clock" time.
84      */
85     private long mDisconnectTime;
86 
87     private UUSInfo mUusInfo;
88     private Handler mHandler;
89     private final Messenger mHandlerMessenger;
90 
91     private PowerManager.WakeLock mPartialWakeLock;
92 
93     // The cached connect time of the connection when it turns into a conference.
94     private long mConferenceConnectTime = 0;
95 
96     // The cached delay to be used between DTMF tones fetched from carrier config.
97     private int mDtmfToneDelay = 0;
98 
99     private boolean mIsEmergency = false;
100 
101     /**
102      * Used to indicate that video state changes detected by
103      * {@link #updateMediaCapabilities(ImsCall)} should be ignored.  When a video state change from
104      * unpaused to paused occurs, we set this flag and then update the existing video state when
105      * new {@link #onReceiveSessionModifyResponse(int, VideoProfile, VideoProfile)} callbacks come
106      * in.  When the video un-pauses we continue receiving the video state updates.
107      */
108     private boolean mShouldIgnoreVideoStateChanges = false;
109 
110     private ImsVideoCallProviderWrapper mImsVideoCallProviderWrapper;
111 
112     private int mPreciseDisconnectCause = 0;
113 
114     private ImsRttTextHandler mRttTextHandler;
115     private android.telecom.Connection.RttTextStream mRttTextStream;
116     // This reflects the RTT status as reported to us by the IMS stack via the media profile.
117     private boolean mIsRttEnabledForCall = false;
118 
119     /**
120      * Used to indicate that this call is in the midst of being merged into a conference.
121      */
122     private boolean mIsMergeInProcess = false;
123 
124     /**
125      * Used as an override to determine whether video is locally available for this call.
126      * This allows video availability to be overridden in the case that the modem says video is
127      * currently available, but mobile data is off and the carrier is metering data for video
128      * calls.
129      */
130     private boolean mIsLocalVideoCapable = true;
131 
132     // Store the current audio codec
133     private int mAudioCodec = ImsStreamMediaProfile.AUDIO_QUALITY_NONE;
134 
135     //***** Event Constants
136     private static final int EVENT_DTMF_DONE = 1;
137     private static final int EVENT_PAUSE_DONE = 2;
138     private static final int EVENT_NEXT_POST_DIAL = 3;
139     private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
140     private static final int EVENT_DTMF_DELAY_DONE = 5;
141 
142     //***** Constants
143     private static final int PAUSE_DELAY_MILLIS = 3 * 1000;
144     private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
145 
146     //***** Inner Classes
147 
148     class MyHandler extends Handler {
MyHandler(Looper l)149         MyHandler(Looper l) {super(l);}
150 
151         @Override
152         public void
handleMessage(Message msg)153         handleMessage(Message msg) {
154 
155             switch (msg.what) {
156                 case EVENT_NEXT_POST_DIAL:
157                 case EVENT_DTMF_DELAY_DONE:
158                 case EVENT_PAUSE_DONE:
159                     processNextPostDialChar();
160                     break;
161                 case EVENT_WAKE_LOCK_TIMEOUT:
162                     releaseWakeLock();
163                     break;
164                 case EVENT_DTMF_DONE:
165                     // We may need to add a delay specified by carrier between DTMF tones that are
166                     // sent out.
167                     mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE),
168                             mDtmfToneDelay);
169                     break;
170             }
171         }
172     }
173 
174     //***** Constructors
175 
176     /** This is probably an MT call */
ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct, ImsPhoneCall parent, boolean isUnknown)177     public ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct,
178            ImsPhoneCall parent, boolean isUnknown) {
179         super(PhoneConstants.PHONE_TYPE_IMS);
180         createWakeLock(phone.getContext());
181         acquireWakeLock();
182 
183         mOwner = ct;
184         mHandler = new MyHandler(mOwner.getLooper());
185         mHandlerMessenger = new Messenger(mHandler);
186         mImsCall = imsCall;
187 
188         if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
189             mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
190             mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA);
191             mNumberPresentation = ImsCallProfile.OIRToPresentation(
192                     imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR));
193             mCnapNamePresentation = ImsCallProfile.OIRToPresentation(
194                     imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
195             updateMediaCapabilities(imsCall);
196         } else {
197             mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
198             mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
199         }
200 
201         mIsIncoming = !isUnknown;
202         mCreateTime = System.currentTimeMillis();
203         mUusInfo = null;
204 
205         // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally
206         // in the ImsPhoneConnection.  This isn't going to inform any listeners (since the original
207         // connection is not likely to be associated with a TelephonyConnection yet).
208         updateExtras(imsCall);
209 
210         mParent = parent;
211         mParent.attach(this,
212                 (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING));
213 
214         fetchDtmfToneDelay(phone);
215 
216         if (phone.getContext().getResources().getBoolean(
217                 com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
218             setAudioModeIsVoip(true);
219         }
220     }
221 
222     /** This is an MO call, created when dialing */
ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct, ImsPhoneCall parent, boolean isEmergency)223     public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct,
224             ImsPhoneCall parent, boolean isEmergency) {
225         super(PhoneConstants.PHONE_TYPE_IMS);
226         createWakeLock(phone.getContext());
227         acquireWakeLock();
228 
229         mOwner = ct;
230         mHandler = new MyHandler(mOwner.getLooper());
231         mHandlerMessenger = new Messenger(mHandler);
232 
233         mDialString = dialString;
234 
235         mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
236         mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
237 
238         //mIndex = -1;
239 
240         mIsIncoming = false;
241         mCnapName = null;
242         mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
243         mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
244         mCreateTime = System.currentTimeMillis();
245 
246         mParent = parent;
247         parent.attachFake(this, ImsPhoneCall.State.DIALING);
248 
249         mIsEmergency = isEmergency;
250         if (isEmergency) {
251             setEmergencyCallInfo(mOwner);
252         }
253 
254         fetchDtmfToneDelay(phone);
255 
256         if (phone.getContext().getResources().getBoolean(
257                 com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
258             setAudioModeIsVoip(true);
259         }
260     }
261 
dispose()262     public void dispose() {
263     }
264 
265     static boolean
equalsHandlesNulls(Object a, Object b)266     equalsHandlesNulls (Object a, Object b) {
267         return (a == null) ? (b == null) : a.equals (b);
268     }
269 
270     static boolean
equalsBaseDialString(String a, String b)271     equalsBaseDialString (String a, String b) {
272         return (a == null) ? (b == null) : (b != null && a.startsWith (b));
273     }
274 
applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities)275     private int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) {
276         Rlog.i(LOG_TAG, "applyLocalCallCapabilities - localProfile = " + localProfile);
277         capabilities = removeCapability(capabilities,
278                 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
279 
280         if (!mIsLocalVideoCapable) {
281             Rlog.i(LOG_TAG, "applyLocalCallCapabilities - disabling video (overidden)");
282             return capabilities;
283         }
284         switch (localProfile.mCallType) {
285             case ImsCallProfile.CALL_TYPE_VT:
286                 // Fall-through
287             case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE:
288                 capabilities = addCapability(capabilities,
289                         Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
290                 break;
291         }
292         return capabilities;
293     }
294 
applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities)295     private static int applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities) {
296         Rlog.w(LOG_TAG, "applyRemoteCallCapabilities - remoteProfile = "+remoteProfile);
297         capabilities = removeCapability(capabilities,
298                 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
299 
300         switch (remoteProfile.mCallType) {
301             case ImsCallProfile.CALL_TYPE_VT:
302                 // fall-through
303             case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE:
304                 capabilities = addCapability(capabilities,
305                         Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
306                 break;
307         }
308         return capabilities;
309     }
310 
311     @Override
getOrigDialString()312     public String getOrigDialString(){
313         return mDialString;
314     }
315 
316     @UnsupportedAppUsage
317     @Override
getCall()318     public ImsPhoneCall getCall() {
319         return mParent;
320     }
321 
322     @Override
getDisconnectTime()323     public long getDisconnectTime() {
324         return mDisconnectTime;
325     }
326 
327     @Override
getHoldingStartTime()328     public long getHoldingStartTime() {
329         return mHoldingStartTime;
330     }
331 
332     @Override
getHoldDurationMillis()333     public long getHoldDurationMillis() {
334         if (getState() != ImsPhoneCall.State.HOLDING) {
335             // If not holding, return 0
336             return 0;
337         } else {
338             return SystemClock.elapsedRealtime() - mHoldingStartTime;
339         }
340     }
341 
setDisconnectCause(int cause)342     public void setDisconnectCause(int cause) {
343         mCause = cause;
344     }
345 
346     @Override
getVendorDisconnectCause()347     public String getVendorDisconnectCause() {
348       return null;
349     }
350 
351     @UnsupportedAppUsage
getOwner()352     public ImsPhoneCallTracker getOwner () {
353         return mOwner;
354     }
355 
356     @Override
getState()357     public ImsPhoneCall.State getState() {
358         if (mDisconnected) {
359             return ImsPhoneCall.State.DISCONNECTED;
360         } else {
361             return super.getState();
362         }
363     }
364 
365     @Override
deflect(String number)366     public void deflect(String number) throws CallStateException {
367         if (mParent.getState().isRinging()) {
368             try {
369                 if (mImsCall != null) {
370                     mImsCall.deflect(number);
371                 } else {
372                     throw new CallStateException("no valid ims call to deflect");
373                 }
374             } catch (ImsException e) {
375                 throw new CallStateException("cannot deflect call");
376             }
377         } else {
378             throw new CallStateException("phone not ringing");
379         }
380     }
381 
382     @Override
hangup()383     public void hangup() throws CallStateException {
384         if (!mDisconnected) {
385             mOwner.hangup(this);
386         } else {
387             throw new CallStateException ("disconnected");
388         }
389     }
390 
391     @Override
separate()392     public void separate() throws CallStateException {
393         throw new CallStateException ("not supported");
394     }
395 
396     @Override
proceedAfterWaitChar()397     public void proceedAfterWaitChar() {
398         if (mPostDialState != PostDialState.WAIT) {
399             Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
400                     + "getPostDialState() to be WAIT but was " + mPostDialState);
401             return;
402         }
403 
404         setPostDialState(PostDialState.STARTED);
405 
406         processNextPostDialChar();
407     }
408 
409     @Override
proceedAfterWildChar(String str)410     public void proceedAfterWildChar(String str) {
411         if (mPostDialState != PostDialState.WILD) {
412             Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
413                     + "getPostDialState() to be WILD but was " + mPostDialState);
414             return;
415         }
416 
417         setPostDialState(PostDialState.STARTED);
418 
419         // make a new postDialString, with the wild char replacement string
420         // at the beginning, followed by the remaining postDialString.
421 
422         StringBuilder buf = new StringBuilder(str);
423         buf.append(mPostDialString.substring(mNextPostDialChar));
424         mPostDialString = buf.toString();
425         mNextPostDialChar = 0;
426         if (Phone.DEBUG_PHONE) {
427             Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " +
428                     mPostDialString);
429         }
430 
431         processNextPostDialChar();
432     }
433 
434     @Override
cancelPostDial()435     public void cancelPostDial() {
436         setPostDialState(PostDialState.CANCELLED);
437     }
438 
439     /**
440      * Called when this Connection is being hung up locally (eg, user pressed "end")
441      */
442     void
onHangupLocal()443     onHangupLocal() {
444         mCause = DisconnectCause.LOCAL;
445     }
446 
447     /** Called when the connection has been disconnected */
448     @Override
onDisconnect(int cause)449     public boolean onDisconnect(int cause) {
450         Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
451         if (mCause != DisconnectCause.LOCAL || cause == DisconnectCause.INCOMING_REJECTED) {
452             mCause = cause;
453         }
454         return onDisconnect();
455     }
456 
457     @UnsupportedAppUsage
onDisconnect()458     public boolean onDisconnect() {
459         boolean changed = false;
460 
461         if (!mDisconnected) {
462             //mIndex = -1;
463 
464             mDisconnectTime = System.currentTimeMillis();
465             mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
466             mDisconnected = true;
467 
468             mOwner.mPhone.notifyDisconnect(this);
469             notifyDisconnect(mCause);
470 
471             if (mParent != null) {
472                 changed = mParent.connectionDisconnected(this);
473             } else {
474                 Rlog.d(LOG_TAG, "onDisconnect: no parent");
475             }
476             synchronized (this) {
477                 if (mRttTextHandler != null) {
478                     mRttTextHandler.tearDown();
479                 }
480                 if (mImsCall != null) mImsCall.close();
481                 mImsCall = null;
482             }
483         }
484         releaseWakeLock();
485         return changed;
486     }
487 
488     /**
489      * An incoming or outgoing call has connected
490      */
491     void
onConnectedInOrOut()492     onConnectedInOrOut() {
493         mConnectTime = System.currentTimeMillis();
494         mConnectTimeReal = SystemClock.elapsedRealtime();
495         mDuration = 0;
496 
497         if (Phone.DEBUG_PHONE) {
498             Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime);
499         }
500 
501         if (!mIsIncoming) {
502             // outgoing calls only
503             processNextPostDialChar();
504         }
505         releaseWakeLock();
506     }
507 
508     /*package*/ void
onStartedHolding()509     onStartedHolding() {
510         mHoldingStartTime = SystemClock.elapsedRealtime();
511     }
512     /**
513      * Performs the appropriate action for a post-dial char, but does not
514      * notify application. returns false if the character is invalid and
515      * should be ignored
516      */
517     private boolean
processPostDialChar(char c)518     processPostDialChar(char c) {
519         if (PhoneNumberUtils.is12Key(c)) {
520             Message dtmfComplete = mHandler.obtainMessage(EVENT_DTMF_DONE);
521             dtmfComplete.replyTo = mHandlerMessenger;
522             mOwner.sendDtmf(c, dtmfComplete);
523         } else if (c == PhoneNumberUtils.PAUSE) {
524             // From TS 22.101:
525             // It continues...
526             // Upon the called party answering the UE shall send the DTMF digits
527             // automatically to the network after a delay of 3 seconds( 20 ).
528             // The digits shall be sent according to the procedures and timing
529             // specified in 3GPP TS 24.008 [13]. The first occurrence of the
530             // "DTMF Control Digits Separator" shall be used by the ME to
531             // distinguish between the addressing digits (i.e. the phone number)
532             // and the DTMF digits. Upon subsequent occurrences of the
533             // separator,
534             // the UE shall pause again for 3 seconds ( 20 ) before sending
535             // any further DTMF digits.
536             mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
537                     PAUSE_DELAY_MILLIS);
538         } else if (c == PhoneNumberUtils.WAIT) {
539             setPostDialState(PostDialState.WAIT);
540         } else if (c == PhoneNumberUtils.WILD) {
541             setPostDialState(PostDialState.WILD);
542         } else {
543             return false;
544         }
545 
546         return true;
547     }
548 
549     @Override
finalize()550     protected void finalize() {
551         releaseWakeLock();
552     }
553 
554     private void
processNextPostDialChar()555     processNextPostDialChar() {
556         char c = 0;
557         Registrant postDialHandler;
558 
559         if (mPostDialState == PostDialState.CANCELLED) {
560             //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail");
561             return;
562         }
563 
564         if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) {
565             setPostDialState(PostDialState.COMPLETE);
566 
567             // notifyMessage.arg1 is 0 on complete
568             c = 0;
569         } else {
570             boolean isValid;
571 
572             setPostDialState(PostDialState.STARTED);
573 
574             c = mPostDialString.charAt(mNextPostDialChar++);
575 
576             isValid = processPostDialChar(c);
577 
578             if (!isValid) {
579                 // Will call processNextPostDialChar
580                 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
581                 // Don't notify application
582                 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
583                 return;
584             }
585         }
586 
587         notifyPostDialListenersNextChar(c);
588 
589         // TODO: remove the following code since the handler no longer executes anything.
590         postDialHandler = mOwner.mPhone.getPostDialHandler();
591 
592         Message notifyMessage;
593 
594         if (postDialHandler != null
595                 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
596             // The AsyncResult.result is the Connection object
597             PostDialState state = mPostDialState;
598             AsyncResult ar = AsyncResult.forMessage(notifyMessage);
599             ar.result = this;
600             ar.userObj = state;
601 
602             // arg1 is the character that was/is being processed
603             notifyMessage.arg1 = c;
604 
605             //Rlog.v(LOG_TAG,
606             //      "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
607             notifyMessage.sendToTarget();
608         }
609     }
610 
611     /**
612      * Set post dial state and acquire wake lock while switching to "started"
613      * state, the wake lock will be released if state switches out of "started"
614      * state or after WAKE_LOCK_TIMEOUT_MILLIS.
615      * @param s new PostDialState
616      */
setPostDialState(PostDialState s)617     private void setPostDialState(PostDialState s) {
618         if (mPostDialState != PostDialState.STARTED
619                 && s == PostDialState.STARTED) {
620             acquireWakeLock();
621             Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
622             mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
623         } else if (mPostDialState == PostDialState.STARTED
624                 && s != PostDialState.STARTED) {
625             mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
626             releaseWakeLock();
627         }
628         mPostDialState = s;
629         notifyPostDialListeners();
630     }
631 
632     @UnsupportedAppUsage
633     private void
createWakeLock(Context context)634     createWakeLock(Context context) {
635         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
636         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
637     }
638 
639     @UnsupportedAppUsage
640     private void
acquireWakeLock()641     acquireWakeLock() {
642         Rlog.d(LOG_TAG, "acquireWakeLock");
643         mPartialWakeLock.acquire();
644     }
645 
646     void
releaseWakeLock()647     releaseWakeLock() {
648         if (mPartialWakeLock != null) {
649             synchronized (mPartialWakeLock) {
650                 if (mPartialWakeLock.isHeld()) {
651                     Rlog.d(LOG_TAG, "releaseWakeLock");
652                     mPartialWakeLock.release();
653                 }
654             }
655         }
656     }
657 
fetchDtmfToneDelay(Phone phone)658     private void fetchDtmfToneDelay(Phone phone) {
659         CarrierConfigManager configMgr = (CarrierConfigManager)
660                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
661         PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId());
662         if (b != null) {
663             mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT);
664         }
665     }
666 
667     @Override
getNumberPresentation()668     public int getNumberPresentation() {
669         return mNumberPresentation;
670     }
671 
672     @Override
getUUSInfo()673     public UUSInfo getUUSInfo() {
674         return mUusInfo;
675     }
676 
677     @Override
getOrigConnection()678     public Connection getOrigConnection() {
679         return null;
680     }
681 
682     @UnsupportedAppUsage
683     @Override
isMultiparty()684     public synchronized boolean isMultiparty() {
685         return mImsCall != null && mImsCall.isMultiparty();
686     }
687 
688     /**
689      * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
690      * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
691      * {@link ImsCall} is a member of a conference hosted on another device.
692      *
693      * @return {@code true} if this call is the origin of the conference call it is a member of,
694      *      {@code false} otherwise.
695      */
696     @Override
isConferenceHost()697     public synchronized boolean isConferenceHost() {
698         return mImsCall != null && mImsCall.isConferenceHost();
699     }
700 
701     @Override
isMemberOfPeerConference()702     public boolean isMemberOfPeerConference() {
703         return !isConferenceHost();
704     }
705 
getImsCall()706     public synchronized ImsCall getImsCall() {
707         return mImsCall;
708     }
709 
setImsCall(ImsCall imsCall)710     public synchronized void setImsCall(ImsCall imsCall) {
711         mImsCall = imsCall;
712     }
713 
changeParent(ImsPhoneCall parent)714     public void changeParent(ImsPhoneCall parent) {
715         mParent = parent;
716     }
717 
718     /**
719      * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been
720      *     changed, and {@code false} otherwise.
721      */
update(ImsCall imsCall, ImsPhoneCall.State state)722     public boolean update(ImsCall imsCall, ImsPhoneCall.State state) {
723         if (state == ImsPhoneCall.State.ACTIVE) {
724             // If the state of the call is active, but there is a pending request to the RIL to hold
725             // the call, we will skip this update.  This is really a signalling delay or failure
726             // from the RIL, but we will prevent it from going through as we will end up erroneously
727             // making this call active when really it should be on hold.
728             if (imsCall.isPendingHold()) {
729                 Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping");
730                 return false;
731             }
732 
733             if (mParent.getState().isRinging() || mParent.getState().isDialing()) {
734                 onConnectedInOrOut();
735             }
736 
737             if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) {
738                 //mForegroundCall should be IDLE
739                 //when accepting WAITING call
740                 //before accept WAITING call,
741                 //the ACTIVE call should be held ahead
742                 mParent.detach(this);
743                 mParent = mOwner.mForegroundCall;
744                 mParent.attach(this);
745             }
746         } else if (state == ImsPhoneCall.State.HOLDING) {
747             onStartedHolding();
748         }
749 
750         boolean updateParent = mParent.update(this, imsCall, state);
751         boolean updateAddressDisplay = updateAddressDisplay(imsCall);
752         boolean updateMediaCapabilities = updateMediaCapabilities(imsCall);
753         boolean updateExtras = updateExtras(imsCall);
754 
755         return updateParent || updateAddressDisplay || updateMediaCapabilities || updateExtras;
756     }
757 
758     @Override
getPreciseDisconnectCause()759     public int getPreciseDisconnectCause() {
760         return mPreciseDisconnectCause;
761     }
762 
setPreciseDisconnectCause(int cause)763     public void setPreciseDisconnectCause(int cause) {
764         mPreciseDisconnectCause = cause;
765     }
766 
767     /**
768      * Notifies this Connection of a request to disconnect a participant of the conference managed
769      * by the connection.
770      *
771      * @param endpoint the {@link android.net.Uri} of the participant to disconnect.
772      */
773     @Override
onDisconnectConferenceParticipant(Uri endpoint)774     public void onDisconnectConferenceParticipant(Uri endpoint) {
775         ImsCall imsCall = getImsCall();
776         if (imsCall == null) {
777             return;
778         }
779         try {
780             imsCall.removeParticipants(new String[]{endpoint.toString()});
781         } catch (ImsException e) {
782             // No session in place -- no change
783             Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+
784                     "Failed to disconnect endpoint = " + endpoint);
785         }
786     }
787 
788     /**
789      * Sets the conference connect time.  Used when an {@code ImsConference} is created to out of
790      * this phone connection.
791      *
792      * @param conferenceConnectTime The conference connect time.
793      */
setConferenceConnectTime(long conferenceConnectTime)794     public void setConferenceConnectTime(long conferenceConnectTime) {
795         mConferenceConnectTime = conferenceConnectTime;
796     }
797 
798     /**
799      * @return The conference connect time.
800      */
getConferenceConnectTime()801     public long getConferenceConnectTime() {
802         return mConferenceConnectTime;
803     }
804 
805     /**
806      * Check for a change in the address display related fields for the {@link ImsCall}, and
807      * update the {@link ImsPhoneConnection} with this information.
808      *
809      * @param imsCall The call to check for changes in address display fields.
810      * @return Whether the address display fields have been changed.
811      */
updateAddressDisplay(ImsCall imsCall)812     public boolean updateAddressDisplay(ImsCall imsCall) {
813         if (imsCall == null) {
814             return false;
815         }
816 
817         boolean changed = false;
818         ImsCallProfile callProfile = imsCall.getCallProfile();
819         if (callProfile != null && isIncoming()) {
820             // Only look for changes to the address for incoming calls.  The originating identity
821             // can change for outgoing calls due to, for example, a call being forwarded to
822             // voicemail.  This address change does not need to be presented to the user.
823             String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI);
824             String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA);
825             int nump = ImsCallProfile.OIRToPresentation(
826                     callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR));
827             int namep = ImsCallProfile.OIRToPresentation(
828                     callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
829             if (Phone.DEBUG_PHONE) {
830                 Rlog.d(LOG_TAG, "updateAddressDisplay: callId = " + getTelecomCallId()
831                         + " address = " + Rlog.pii(LOG_TAG, address) + " name = "
832                         + Rlog.pii(LOG_TAG, name) + " nump = " + nump + " namep = " + namep);
833             }
834             if (!mIsMergeInProcess) {
835                 // Only process changes to the name and address when a merge is not in process.
836                 // When call A initiated a merge with call B to form a conference C, there is a
837                 // point in time when the ImsCall transfers the conference call session into A,
838                 // at which point the ImsConferenceController creates the conference in Telecom.
839                 // For some carriers C will have a unique conference URI address.  Swapping the
840                 // conference session into A, which is about to be disconnected, to be logged to
841                 // the call log using the conference address.  To prevent this we suppress updates
842                 // to the call address while a merge is in process.
843                 if (!equalsBaseDialString(mAddress, address)) {
844                     mAddress = address;
845                     changed = true;
846                 }
847                 if (TextUtils.isEmpty(name)) {
848                     if (!TextUtils.isEmpty(mCnapName)) {
849                         mCnapName = "";
850                         changed = true;
851                     }
852                 } else if (!name.equals(mCnapName)) {
853                     mCnapName = name;
854                     changed = true;
855                 }
856                 if (mNumberPresentation != nump) {
857                     mNumberPresentation = nump;
858                     changed = true;
859                 }
860                 if (mCnapNamePresentation != namep) {
861                     mCnapNamePresentation = namep;
862                     changed = true;
863                 }
864             }
865         }
866         return changed;
867     }
868 
869     /**
870      * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and
871      * update the {@link ImsPhoneConnection} with this information.
872      *
873      * @param imsCall The call to check for changes in media capabilities.
874      * @return Whether the media capabilities have been changed.
875      */
updateMediaCapabilities(ImsCall imsCall)876     public boolean updateMediaCapabilities(ImsCall imsCall) {
877         if (imsCall == null) {
878             return false;
879         }
880 
881         boolean changed = false;
882 
883         try {
884             // The actual call profile (negotiated between local and peer).
885             ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile();
886 
887             if (negotiatedCallProfile != null) {
888                 int oldVideoState = getVideoState();
889                 int newVideoState = ImsCallProfile
890                         .getVideoStateFromImsCallProfile(negotiatedCallProfile);
891 
892                 if (oldVideoState != newVideoState) {
893                     // The video state has changed.  See also code in onReceiveSessionModifyResponse
894                     // below.  When the video enters a paused state, subsequent changes to the video
895                     // state will not be reported by the modem.  In onReceiveSessionModifyResponse
896                     // we will be updating the current video state while paused to include any
897                     // changes the modem reports via the video provider.  When the video enters an
898                     // unpaused state, we will resume passing the video states from the modem as is.
899                     if (VideoProfile.isPaused(oldVideoState) &&
900                             !VideoProfile.isPaused(newVideoState)) {
901                         // Video entered un-paused state; recognize updates from now on; we want to
902                         // ensure that the new un-paused state is propagated to Telecom, so change
903                         // this now.
904                         mShouldIgnoreVideoStateChanges = false;
905                     }
906 
907                     if (!mShouldIgnoreVideoStateChanges) {
908                         updateVideoState(newVideoState);
909                         changed = true;
910                     } else {
911                         Rlog.d(LOG_TAG, "updateMediaCapabilities - ignoring video state change " +
912                                 "due to paused state.");
913                     }
914 
915                     if (!VideoProfile.isPaused(oldVideoState) &&
916                             VideoProfile.isPaused(newVideoState)) {
917                         // Video entered pause state; ignore updates until un-paused.  We do this
918                         // after setVideoState is called above to ensure Telecom is notified that
919                         // the device has entered paused state.
920                         mShouldIgnoreVideoStateChanges = true;
921                     }
922                 }
923 
924                 if (negotiatedCallProfile.mMediaProfile != null) {
925                     mIsRttEnabledForCall = negotiatedCallProfile.mMediaProfile.isRttCall();
926 
927                     if (mIsRttEnabledForCall && mRttTextHandler == null) {
928                         Rlog.d(LOG_TAG, "updateMediaCapabilities -- turning RTT on, profile="
929                                 + negotiatedCallProfile);
930                         startRttTextProcessing();
931                         onRttInitiated();
932                         changed = true;
933                     } else if (!mIsRttEnabledForCall && mRttTextHandler != null) {
934                         Rlog.d(LOG_TAG, "updateMediaCapabilities -- turning RTT off, profile="
935                                 + negotiatedCallProfile);
936                         mRttTextHandler.tearDown();
937                         mRttTextHandler = null;
938                         onRttTerminated();
939                         changed = true;
940                     }
941                 }
942             }
943 
944             // Check for a change in the capabilities for the call and update
945             // {@link ImsPhoneConnection} with this information.
946             int capabilities = getConnectionCapabilities();
947 
948             // Use carrier config to determine if downgrading directly to audio-only is supported.
949             if (mOwner.isCarrierDowngradeOfVtCallSupported()) {
950                 capabilities = addCapability(capabilities,
951                         Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE |
952                                 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL);
953             } else {
954                 capabilities = removeCapability(capabilities,
955                         Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE |
956                                 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL);
957             }
958 
959             // Get the current local call capabilities which might be voice or video or both.
960             ImsCallProfile localCallProfile = imsCall.getLocalCallProfile();
961             Rlog.v(LOG_TAG, "update localCallProfile=" + localCallProfile);
962             if (localCallProfile != null) {
963                 capabilities = applyLocalCallCapabilities(localCallProfile, capabilities);
964             }
965 
966             // Get the current remote call capabilities which might be voice or video or both.
967             ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile();
968             Rlog.v(LOG_TAG, "update remoteCallProfile=" + remoteCallProfile);
969             if (remoteCallProfile != null) {
970                 capabilities = applyRemoteCallCapabilities(remoteCallProfile, capabilities);
971             }
972             if (getConnectionCapabilities() != capabilities) {
973                 setConnectionCapabilities(capabilities);
974                 changed = true;
975             }
976 
977             if (!mOwner.isViLteDataMetered()) {
978                 Rlog.v(LOG_TAG, "data is not metered");
979             } else {
980                 if (mImsVideoCallProviderWrapper != null) {
981                     mImsVideoCallProviderWrapper.setIsVideoEnabled(
982                             hasCapabilities(Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
983                 }
984             }
985 
986             // Metrics for audio codec
987             if (localCallProfile != null
988                     && localCallProfile.mMediaProfile.mAudioQuality != mAudioCodec) {
989                 mAudioCodec = localCallProfile.mMediaProfile.mAudioQuality;
990                 mMetrics.writeAudioCodecIms(mOwner.mPhone.getPhoneId(), imsCall.getCallSession());
991             }
992 
993             int newAudioQuality =
994                     getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile);
995             if (getAudioQuality() != newAudioQuality) {
996                 setAudioQuality(newAudioQuality);
997                 changed = true;
998             }
999         } catch (ImsException e) {
1000             // No session in place -- no change
1001         }
1002 
1003         return changed;
1004     }
1005 
updateVideoState(int newVideoState)1006     private void updateVideoState(int newVideoState) {
1007         if (mImsVideoCallProviderWrapper != null) {
1008             mImsVideoCallProviderWrapper.onVideoStateChanged(newVideoState);
1009         }
1010         setVideoState(newVideoState);
1011     }
1012 
1013 
1014     /**
1015      * Send a RTT upgrade request to the remote party.
1016      * @param textStream RTT text stream to use
1017      */
startRtt(android.telecom.Connection.RttTextStream textStream)1018     public void startRtt(android.telecom.Connection.RttTextStream textStream) {
1019         ImsCall imsCall = getImsCall();
1020         if (imsCall != null) {
1021             getImsCall().sendRttModifyRequest(true);
1022             setCurrentRttTextStream(textStream);
1023         }
1024     }
1025 
1026     /**
1027      * Terminate the current RTT session.
1028      */
stopRtt()1029     public void stopRtt() {
1030         getImsCall().sendRttModifyRequest(false);
1031     }
1032 
1033     /**
1034      * Sends the user's response to a remotely-issued RTT upgrade request
1035      *
1036      * @param textStream A valid {@link android.telecom.Connection.RttTextStream} if the user
1037      *                   accepts, {@code null} if not.
1038      */
sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream)1039     public void sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream) {
1040         boolean accept = textStream != null;
1041         ImsCall imsCall = getImsCall();
1042 
1043         if (imsCall != null) {
1044             imsCall.sendRttModifyResponse(accept);
1045             if (accept) {
1046                 setCurrentRttTextStream(textStream);
1047             } else {
1048                 Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections");
1049             }
1050         }
1051     }
1052 
onRttMessageReceived(String message)1053     public void onRttMessageReceived(String message) {
1054         synchronized (this) {
1055             if (mRttTextHandler == null) {
1056                 Rlog.w(LOG_TAG, "onRttMessageReceived: RTT text handler not available."
1057                         + " Attempting to create one.");
1058                 if (mRttTextStream == null) {
1059                     Rlog.e(LOG_TAG, "onRttMessageReceived:"
1060                             + " Unable to process incoming message. No textstream available");
1061                     return;
1062                 }
1063                 createRttTextHandler();
1064             }
1065         }
1066         mRttTextHandler.sendToInCall(message);
1067     }
1068 
onRttAudioIndicatorChanged(ImsStreamMediaProfile profile)1069     public void onRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
1070         Bundle extras = new Bundle();
1071         extras.putBoolean(android.telecom.Connection.EXTRA_IS_RTT_AUDIO_PRESENT,
1072                 profile.isReceivingRttAudio());
1073         onConnectionEvent(android.telecom.Connection.EVENT_RTT_AUDIO_INDICATION_CHANGED,
1074                 extras);
1075     }
1076 
setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream)1077     public void setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream) {
1078         synchronized (this) {
1079             mRttTextStream = rttTextStream;
1080             if (mRttTextHandler == null && mIsRttEnabledForCall) {
1081                 Rlog.i(LOG_TAG, "setCurrentRttTextStream: Creating a text handler");
1082                 createRttTextHandler();
1083             }
1084         }
1085     }
1086 
hasRttTextStream()1087     public boolean hasRttTextStream() {
1088         return mRttTextStream != null;
1089     }
1090 
isRttEnabledForCall()1091     public boolean isRttEnabledForCall() {
1092         return mIsRttEnabledForCall;
1093     }
1094 
startRttTextProcessing()1095     public void startRttTextProcessing() {
1096         synchronized (this) {
1097             if (mRttTextStream == null) {
1098                 Rlog.w(LOG_TAG, "startRttTextProcessing: no RTT text stream. Ignoring.");
1099                 return;
1100             }
1101             if (mRttTextHandler != null) {
1102                 Rlog.w(LOG_TAG, "startRttTextProcessing: RTT text handler already exists");
1103                 return;
1104             }
1105             createRttTextHandler();
1106         }
1107     }
1108 
1109     // Make sure to synchronize on ImsPhoneConnection.this before calling.
createRttTextHandler()1110     private void createRttTextHandler() {
1111         mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(),
1112                 (message) -> {
1113                     ImsCall imsCall = getImsCall();
1114                     if (imsCall != null) {
1115                         imsCall.sendRttMessage(message);
1116                     }
1117                 });
1118         mRttTextHandler.initialize(mRttTextStream);
1119     }
1120 
1121     /**
1122      * Updates the IMS call rat based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}.
1123      *
1124      * @param extras The ImsCallProfile extras.
1125      */
updateImsCallRatFromExtras(Bundle extras)1126     private void updateImsCallRatFromExtras(Bundle extras) {
1127         if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE) ||
1128                 extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) {
1129 
1130             ImsCall call = getImsCall();
1131             int callTech = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
1132             if (call != null) {
1133                 callTech = call.getRadioTechnology();
1134             }
1135 
1136             // Report any changes for call tech change
1137             setCallRadioTech(callTech);
1138         }
1139     }
1140 
updateEmergencyCallFromExtras(Bundle extras)1141     private void updateEmergencyCallFromExtras(Bundle extras) {
1142         if (extras.getBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL)) {
1143             setIsNetworkIdentifiedEmergencyCall(true);
1144         }
1145     }
1146 
1147     /**
1148      * Check for a change in call extras of {@link ImsCall}, and
1149      * update the {@link ImsPhoneConnection} accordingly.
1150      *
1151      * @param imsCall The call to check for changes in extras.
1152      * @return Whether the extras fields have been changed.
1153      */
updateExtras(ImsCall imsCall)1154      boolean updateExtras(ImsCall imsCall) {
1155         if (imsCall == null) {
1156             return false;
1157         }
1158 
1159         final ImsCallProfile callProfile = imsCall.getCallProfile();
1160         final Bundle extras = callProfile != null ? callProfile.mCallExtras : null;
1161         if (extras == null && DBG) {
1162             Rlog.d(LOG_TAG, "Call profile extras are null.");
1163         }
1164 
1165         final boolean changed = !areBundlesEqual(extras, mExtras);
1166         if (changed) {
1167             updateImsCallRatFromExtras(extras);
1168             updateEmergencyCallFromExtras(extras);
1169             mExtras.clear();
1170             mExtras.putAll(extras);
1171             setConnectionExtras(mExtras);
1172         }
1173         return changed;
1174     }
1175 
areBundlesEqual(Bundle extras, Bundle newExtras)1176     private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
1177         if (extras == null || newExtras == null) {
1178             return extras == newExtras;
1179         }
1180 
1181         if (extras.size() != newExtras.size()) {
1182             return false;
1183         }
1184 
1185         for(String key : extras.keySet()) {
1186             if (key != null) {
1187                 final Object value = extras.get(key);
1188                 final Object newValue = newExtras.get(key);
1189                 if (!Objects.equals(value, newValue)) {
1190                     return false;
1191                 }
1192             }
1193         }
1194         return true;
1195     }
1196 
1197     /**
1198      * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote
1199      * {@link ImsCallProfile}. Indicate a HD audio call if the local stream profile
1200      * is AMR_WB, EVRC_WB, EVS_WB, EVS_SWB, EVS_FB and
1201      * there is no remote restrict cause.
1202      *
1203      * @param localCallProfile The local call profile.
1204      * @param remoteCallProfile The remote call profile.
1205      * @return The audio quality.
1206      */
getAudioQualityFromCallProfile( ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile)1207     private int getAudioQualityFromCallProfile(
1208             ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) {
1209         if (localCallProfile == null || remoteCallProfile == null
1210                 || localCallProfile.mMediaProfile == null) {
1211             return AUDIO_QUALITY_STANDARD;
1212         }
1213 
1214         final boolean isEvsCodecHighDef = (localCallProfile.mMediaProfile.mAudioQuality
1215                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB
1216                 || localCallProfile.mMediaProfile.mAudioQuality
1217                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB
1218                 || localCallProfile.mMediaProfile.mAudioQuality
1219                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB);
1220 
1221         final boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality
1222                         == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB
1223                 || localCallProfile.mMediaProfile.mAudioQuality
1224                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB
1225                 || isEvsCodecHighDef)
1226                 && remoteCallProfile.getRestrictCause() == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE;
1227         return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD;
1228     }
1229 
1230     /**
1231      * Provides a string representation of the {@link ImsPhoneConnection}.  Primarily intended for
1232      * use in log statements.
1233      *
1234      * @return String representation of call.
1235      */
1236     @Override
toString()1237     public String toString() {
1238         StringBuilder sb = new StringBuilder();
1239         sb.append("[ImsPhoneConnection objId: ");
1240         sb.append(System.identityHashCode(this));
1241         sb.append(" telecomCallID: ");
1242         sb.append(getTelecomCallId());
1243         sb.append(" address: ");
1244         sb.append(Rlog.pii(LOG_TAG, getAddress()));
1245         sb.append(" ImsCall: ");
1246         synchronized (this) {
1247             if (mImsCall == null) {
1248                 sb.append("null");
1249             } else {
1250                 sb.append(mImsCall);
1251             }
1252         }
1253         sb.append("]");
1254         return sb.toString();
1255     }
1256 
1257     @Override
setVideoProvider(android.telecom.Connection.VideoProvider videoProvider)1258     public void setVideoProvider(android.telecom.Connection.VideoProvider videoProvider) {
1259         super.setVideoProvider(videoProvider);
1260 
1261         if (videoProvider instanceof ImsVideoCallProviderWrapper) {
1262             mImsVideoCallProviderWrapper = (ImsVideoCallProviderWrapper) videoProvider;
1263         }
1264     }
1265 
1266     /**
1267      * Indicates whether current phone connection is emergency or not
1268      * @return boolean: true if emergency, false otherwise
1269      */
isEmergency()1270     protected boolean isEmergency() {
1271         return mIsEmergency;
1272     }
1273 
1274     /**
1275      * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification
1276      * responses received.
1277      *
1278      * @param status The status of the original request.
1279      * @param requestProfile The requested video profile.
1280      * @param responseProfile The response upon video profile.
1281      */
1282     @Override
onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)1283     public void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile,
1284             VideoProfile responseProfile) {
1285         if (status == android.telecom.Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS &&
1286                 mShouldIgnoreVideoStateChanges) {
1287             int currentVideoState = getVideoState();
1288             int newVideoState = responseProfile.getVideoState();
1289 
1290             // If the current video state is paused, the modem will not send us any changes to
1291             // the TX and RX bits of the video state.  Until the video is un-paused we will
1292             // "fake out" the video state by applying the changes that the modem reports via a
1293             // response.
1294 
1295             // First, find out whether there was a change to the TX or RX bits:
1296             int changedBits = currentVideoState ^ newVideoState;
1297             changedBits &= VideoProfile.STATE_BIDIRECTIONAL;
1298             if (changedBits == 0) {
1299                 // No applicable change, bail out.
1300                 return;
1301             }
1302 
1303             // Turn off any existing bits that changed.
1304             currentVideoState &= ~(changedBits & currentVideoState);
1305             // Turn on any new bits that turned on.
1306             currentVideoState |= changedBits & newVideoState;
1307 
1308             Rlog.d(LOG_TAG, "onReceiveSessionModifyResponse : received " +
1309                     VideoProfile.videoStateToString(requestProfile.getVideoState()) +
1310                     " / " +
1311                     VideoProfile.videoStateToString(responseProfile.getVideoState()) +
1312                     " while paused ; sending new videoState = " +
1313                     VideoProfile.videoStateToString(currentVideoState));
1314             setVideoState(currentVideoState);
1315         }
1316     }
1317 
1318     /**
1319      * Issues a request to pause the video using {@link VideoProfile#STATE_PAUSED} from a source
1320      * other than the InCall UI.
1321      *
1322      * @param source The source of the pause request.
1323      */
pauseVideo(int source)1324     public void pauseVideo(int source) {
1325         if (mImsVideoCallProviderWrapper == null) {
1326             return;
1327         }
1328 
1329         mImsVideoCallProviderWrapper.pauseVideo(getVideoState(), source);
1330     }
1331 
1332     /**
1333      * Issues a request to resume the video using {@link VideoProfile#STATE_PAUSED} from a source
1334      * other than the InCall UI.
1335      *
1336      * @param source The source of the resume request.
1337      */
resumeVideo(int source)1338     public void resumeVideo(int source) {
1339         if (mImsVideoCallProviderWrapper == null) {
1340             return;
1341         }
1342 
1343         mImsVideoCallProviderWrapper.resumeVideo(getVideoState(), source);
1344     }
1345 
1346     /**
1347      * Determines if a specified source has issued a pause request.
1348      *
1349      * @param source The source.
1350      * @return {@code true} if the source issued a pause request, {@code false} otherwise.
1351      */
wasVideoPausedFromSource(int source)1352     public boolean wasVideoPausedFromSource(int source) {
1353         if (mImsVideoCallProviderWrapper == null) {
1354             return false;
1355         }
1356 
1357         return mImsVideoCallProviderWrapper.wasVideoPausedFromSource(source);
1358     }
1359 
1360     /**
1361      * Mark the call as in the process of being merged and inform the UI of the merge start.
1362      */
handleMergeStart()1363     public void handleMergeStart() {
1364         mIsMergeInProcess = true;
1365         onConnectionEvent(android.telecom.Connection.EVENT_MERGE_START, null);
1366     }
1367 
1368     /**
1369      * Mark the call as done merging and inform the UI of the merge start.
1370      */
handleMergeComplete()1371     public void handleMergeComplete() {
1372         mIsMergeInProcess = false;
1373         onConnectionEvent(android.telecom.Connection.EVENT_MERGE_COMPLETE, null);
1374     }
1375 
changeToPausedState()1376     public void changeToPausedState() {
1377         int newVideoState = getVideoState() | VideoProfile.STATE_PAUSED;
1378         Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToPausedState - setting paused bit; "
1379                 + "newVideoState=" + VideoProfile.videoStateToString(newVideoState));
1380         updateVideoState(newVideoState);
1381         mShouldIgnoreVideoStateChanges = true;
1382     }
1383 
changeToUnPausedState()1384     public void changeToUnPausedState() {
1385         int newVideoState = getVideoState() & ~VideoProfile.STATE_PAUSED;
1386         Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToUnPausedState - unsetting paused bit; "
1387                 + "newVideoState=" + VideoProfile.videoStateToString(newVideoState));
1388         updateVideoState(newVideoState);
1389         mShouldIgnoreVideoStateChanges = false;
1390     }
1391 
setLocalVideoCapable(boolean isVideoEnabled)1392     public void setLocalVideoCapable(boolean isVideoEnabled) {
1393         mIsLocalVideoCapable = isVideoEnabled;
1394         Rlog.i(LOG_TAG, "setLocalVideoCapable: mIsLocalVideoCapable = " + mIsLocalVideoCapable
1395                 + "; updating local video availability.");
1396         updateMediaCapabilities(getImsCall());
1397     }
1398 }
1399