• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.incallui.call;
18 
19 import android.content.Context;
20 import android.hardware.camera2.CameraCharacteristics;
21 import android.net.Uri;
22 import android.os.Build.VERSION;
23 import android.os.Build.VERSION_CODES;
24 import android.os.Bundle;
25 import android.os.Trace;
26 import android.support.annotation.IntDef;
27 import android.support.annotation.NonNull;
28 import android.support.annotation.Nullable;
29 import android.telecom.Call;
30 import android.telecom.Call.Details;
31 import android.telecom.CallAudioState;
32 import android.telecom.Connection;
33 import android.telecom.DisconnectCause;
34 import android.telecom.GatewayInfo;
35 import android.telecom.InCallService.VideoCall;
36 import android.telecom.PhoneAccount;
37 import android.telecom.PhoneAccountHandle;
38 import android.telecom.StatusHints;
39 import android.telecom.TelecomManager;
40 import android.telecom.VideoProfile;
41 import android.telephony.PhoneNumberUtils;
42 import android.text.TextUtils;
43 import com.android.contacts.common.compat.CallCompat;
44 import com.android.contacts.common.compat.TelephonyManagerCompat;
45 import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
46 import com.android.dialer.callintent.CallInitiationType;
47 import com.android.dialer.callintent.CallIntentParser;
48 import com.android.dialer.callintent.CallSpecificAppData;
49 import com.android.dialer.common.Assert;
50 import com.android.dialer.common.LogUtil;
51 import com.android.dialer.configprovider.ConfigProviderBindings;
52 import com.android.dialer.enrichedcall.EnrichedCallCapabilities;
53 import com.android.dialer.enrichedcall.EnrichedCallComponent;
54 import com.android.dialer.enrichedcall.EnrichedCallManager;
55 import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener;
56 import com.android.dialer.enrichedcall.EnrichedCallManager.Filter;
57 import com.android.dialer.enrichedcall.EnrichedCallManager.StateChangedListener;
58 import com.android.dialer.enrichedcall.Session;
59 import com.android.dialer.lightbringer.LightbringerComponent;
60 import com.android.dialer.logging.ContactLookupResult;
61 import com.android.dialer.logging.DialerImpression;
62 import com.android.dialer.logging.Logger;
63 import com.android.dialer.theme.R;
64 import com.android.incallui.audiomode.AudioModeProvider;
65 import com.android.incallui.latencyreport.LatencyReport;
66 import com.android.incallui.util.TelecomCallUtil;
67 import com.android.incallui.videotech.VideoTech;
68 import com.android.incallui.videotech.VideoTech.VideoTechListener;
69 import com.android.incallui.videotech.empty.EmptyVideoTech;
70 import com.android.incallui.videotech.ims.ImsVideoTech;
71 import com.android.incallui.videotech.lightbringer.LightbringerTech;
72 import com.android.incallui.videotech.utils.VideoUtils;
73 import java.lang.annotation.Retention;
74 import java.lang.annotation.RetentionPolicy;
75 import java.util.ArrayList;
76 import java.util.List;
77 import java.util.Locale;
78 import java.util.Objects;
79 import java.util.UUID;
80 import java.util.concurrent.CopyOnWriteArrayList;
81 import java.util.concurrent.TimeUnit;
82 
83 /** Describes a single call and its state. */
84 public class DialerCall implements VideoTechListener, StateChangedListener, CapabilitiesListener {
85 
86   public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
87   public static final int CALL_HISTORY_STATUS_PRESENT = 1;
88   public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
89 
90   // Hard coded property for {@code Call}. Upstreamed change from Motorola.
91   // TODO(b/35359461): Move it to Telecom in framework.
92   public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
93 
94   private static final String ID_PREFIX = "DialerCall_";
95   private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
96       "emergency_callback_window_millis";
97   private static int sIdCounter = 0;
98 
99   /**
100    * A counter used to append to restricted/private/hidden calls so that users can identify them in
101    * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there
102    * are no live calls.
103    */
104   private static int sHiddenCounter;
105 
106   /**
107    * The unique call ID for every call. This will help us to identify each call and allow us the
108    * ability to stitch impressions to calls if needed.
109    */
110   private final String uniqueCallId = UUID.randomUUID().toString();
111 
112   private final Call mTelecomCall;
113   private final LatencyReport mLatencyReport;
114   private final String mId;
115   private final int mHiddenId;
116   private final List<String> mChildCallIds = new ArrayList<>();
117   private final LogState mLogState = new LogState();
118   private final Context mContext;
119   private final DialerCallDelegate mDialerCallDelegate;
120   private final List<DialerCallListener> mListeners = new CopyOnWriteArrayList<>();
121   private final List<CannedTextResponsesLoadedListener> mCannedTextResponsesLoadedListeners =
122       new CopyOnWriteArrayList<>();
123   private final VideoTechManager mVideoTechManager;
124 
125   private boolean mIsEmergencyCall;
126   private Uri mHandle;
127   private int mState = State.INVALID;
128   private DisconnectCause mDisconnectCause;
129 
130   private boolean hasShownWiFiToLteHandoverToast;
131   private boolean doNotShowDialogForHandoffToWifiFailure;
132 
133   private String mChildNumber;
134   private String mLastForwardedNumber;
135   private String mCallSubject;
136   private PhoneAccountHandle mPhoneAccountHandle;
137   @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
138   private boolean mIsSpam;
139   private boolean mIsBlocked;
140   private boolean isInUserSpamList;
141   private boolean isInUserWhiteList;
142   private boolean isInGlobalSpamList;
143   private boolean didShowCameraPermission;
144   private String callProviderLabel;
145   private String callbackNumber;
146   private int mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
147   private EnrichedCallCapabilities mEnrichedCallCapabilities;
148   private Session mEnrichedCallSession;
149 
150   private int answerAndReleaseButtonDisplayedTimes = 0;
151   private boolean releasedByAnsweringSecondCall = false;
152   // Times when a second call is received but AnswerAndRelease button is not shown
153   // since it's not supported.
154   private int secondCallWithoutAnswerAndReleasedButtonTimes = 0;
155 
getNumberFromHandle(Uri handle)156   public static String getNumberFromHandle(Uri handle) {
157     return handle == null ? "" : handle.getSchemeSpecificPart();
158   }
159 
160   /**
161    * Whether the call is put on hold by remote party. This is different than the {@link
162    * State#ONHOLD} state which indicates that the call is being held locally on the device.
163    */
164   private boolean isRemotelyHeld;
165 
166   /** Indicates whether this call is currently in the process of being merged into a conference. */
167   private boolean isMergeInProcess;
168 
169   /**
170    * Indicates whether the phone account associated with this call supports specifying a call
171    * subject.
172    */
173   private boolean mIsCallSubjectSupported;
174 
175   private final Call.Callback mTelecomCallCallback =
176       new Call.Callback() {
177         @Override
178         public void onStateChanged(Call call, int newState) {
179           LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
180           update();
181         }
182 
183         @Override
184         public void onParentChanged(Call call, Call newParent) {
185           LogUtil.v(
186               "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
187           update();
188         }
189 
190         @Override
191         public void onChildrenChanged(Call call, List<Call> children) {
192           update();
193         }
194 
195         @Override
196         public void onDetailsChanged(Call call, Call.Details details) {
197           LogUtil.v("TelecomCallCallback.onStateChanged", " call=" + call + " details=" + details);
198           update();
199         }
200 
201         @Override
202         public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
203           LogUtil.v(
204               "TelecomCallCallback.onStateChanged",
205               "call=" + call + " cannedTextResponses=" + cannedTextResponses);
206           for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) {
207             listener.onCannedTextResponsesLoaded(DialerCall.this);
208           }
209         }
210 
211         @Override
212         public void onPostDialWait(Call call, String remainingPostDialSequence) {
213           LogUtil.v(
214               "TelecomCallCallback.onStateChanged",
215               "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
216           update();
217         }
218 
219         @Override
220         public void onVideoCallChanged(Call call, VideoCall videoCall) {
221           LogUtil.v(
222               "TelecomCallCallback.onStateChanged", "call=" + call + " videoCall=" + videoCall);
223           update();
224         }
225 
226         @Override
227         public void onCallDestroyed(Call call) {
228           LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call);
229           unregisterCallback();
230         }
231 
232         @Override
233         public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
234           LogUtil.v(
235               "DialerCall.onConferenceableCallsChanged",
236               "call %s, conferenceable calls: %d",
237               call,
238               conferenceableCalls.size());
239           update();
240         }
241 
242         @Override
243         public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
244           LogUtil.v(
245               "DialerCall.onConnectionEvent",
246               "Call: " + call + ", Event: " + event + ", Extras: " + extras);
247           switch (event) {
248               // The Previous attempt to Merge two calls together has failed in Telecom. We must
249               // now update the UI to possibly re-enable the Merge button based on the number of
250               // currently conferenceable calls available or Connection Capabilities.
251             case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
252               update();
253               break;
254             case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
255               notifyWiFiToLteHandover();
256               break;
257             case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
258               notifyHandoverToWifiFailed();
259               break;
260             case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
261               isRemotelyHeld = true;
262               update();
263               break;
264             case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
265               isRemotelyHeld = false;
266               update();
267               break;
268             case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC:
269               notifyInternationalCallOnWifi();
270               break;
271             case TelephonyManagerCompat.EVENT_MERGE_START:
272               LogUtil.i("DialerCall.onConnectionEvent", "merge start");
273               isMergeInProcess = true;
274               break;
275             case TelephonyManagerCompat.EVENT_MERGE_COMPLETE:
276               LogUtil.i("DialerCall.onConnectionEvent", "merge complete");
277               isMergeInProcess = false;
278               break;
279             default:
280               break;
281           }
282         }
283       };
284 
285   private long mTimeAddedMs;
286 
DialerCall( Context context, DialerCallDelegate dialerCallDelegate, Call telecomCall, LatencyReport latencyReport, boolean registerCallback)287   public DialerCall(
288       Context context,
289       DialerCallDelegate dialerCallDelegate,
290       Call telecomCall,
291       LatencyReport latencyReport,
292       boolean registerCallback) {
293     Assert.isNotNull(context);
294     mContext = context;
295     mDialerCallDelegate = dialerCallDelegate;
296     mTelecomCall = telecomCall;
297     mLatencyReport = latencyReport;
298     mId = ID_PREFIX + Integer.toString(sIdCounter++);
299 
300     // Must be after assigning mTelecomCall
301     mVideoTechManager = new VideoTechManager(this);
302 
303     updateFromTelecomCall();
304     if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) {
305       mHiddenId = ++sHiddenCounter;
306     } else {
307       mHiddenId = 0;
308     }
309 
310     if (registerCallback) {
311       mTelecomCall.registerCallback(mTelecomCallCallback);
312     }
313 
314     mTimeAddedMs = System.currentTimeMillis();
315     parseCallSpecificAppData();
316 
317     updateEnrichedCallSession();
318   }
319 
translateState(int state)320   private static int translateState(int state) {
321     switch (state) {
322       case Call.STATE_NEW:
323       case Call.STATE_CONNECTING:
324         return DialerCall.State.CONNECTING;
325       case Call.STATE_SELECT_PHONE_ACCOUNT:
326         return DialerCall.State.SELECT_PHONE_ACCOUNT;
327       case Call.STATE_DIALING:
328         return DialerCall.State.DIALING;
329       case Call.STATE_PULLING_CALL:
330         return DialerCall.State.PULLING;
331       case Call.STATE_RINGING:
332         return DialerCall.State.INCOMING;
333       case Call.STATE_ACTIVE:
334         return DialerCall.State.ACTIVE;
335       case Call.STATE_HOLDING:
336         return DialerCall.State.ONHOLD;
337       case Call.STATE_DISCONNECTED:
338         return DialerCall.State.DISCONNECTED;
339       case Call.STATE_DISCONNECTING:
340         return DialerCall.State.DISCONNECTING;
341       default:
342         return DialerCall.State.INVALID;
343     }
344   }
345 
areSame(DialerCall call1, DialerCall call2)346   public static boolean areSame(DialerCall call1, DialerCall call2) {
347     if (call1 == null && call2 == null) {
348       return true;
349     } else if (call1 == null || call2 == null) {
350       return false;
351     }
352 
353     // otherwise compare call Ids
354     return call1.getId().equals(call2.getId());
355   }
356 
areSameNumber(DialerCall call1, DialerCall call2)357   public static boolean areSameNumber(DialerCall call1, DialerCall call2) {
358     if (call1 == null && call2 == null) {
359       return true;
360     } else if (call1 == null || call2 == null) {
361       return false;
362     }
363 
364     // otherwise compare call Numbers
365     return TextUtils.equals(call1.getNumber(), call2.getNumber());
366   }
367 
addListener(DialerCallListener listener)368   public void addListener(DialerCallListener listener) {
369     Assert.isMainThread();
370     mListeners.add(listener);
371   }
372 
removeListener(DialerCallListener listener)373   public void removeListener(DialerCallListener listener) {
374     Assert.isMainThread();
375     mListeners.remove(listener);
376   }
377 
addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener)378   public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
379     Assert.isMainThread();
380     mCannedTextResponsesLoadedListeners.add(listener);
381   }
382 
removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener)383   public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
384     Assert.isMainThread();
385     mCannedTextResponsesLoadedListeners.remove(listener);
386   }
387 
notifyWiFiToLteHandover()388   public void notifyWiFiToLteHandover() {
389     LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
390     for (DialerCallListener listener : mListeners) {
391       listener.onWiFiToLteHandover();
392     }
393   }
394 
notifyHandoverToWifiFailed()395   public void notifyHandoverToWifiFailed() {
396     LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
397     for (DialerCallListener listener : mListeners) {
398       listener.onHandoverToWifiFailure();
399     }
400   }
401 
notifyInternationalCallOnWifi()402   public void notifyInternationalCallOnWifi() {
403     LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
404     for (DialerCallListener dialerCallListener : mListeners) {
405       dialerCallListener.onInternationalCallOnWifi();
406     }
407   }
408 
getTelecomCall()409   /* package-private */ Call getTelecomCall() {
410     return mTelecomCall;
411   }
412 
getStatusHints()413   public StatusHints getStatusHints() {
414     return mTelecomCall.getDetails().getStatusHints();
415   }
416 
getCameraDir()417   public int getCameraDir() {
418     return mCameraDirection;
419   }
420 
setCameraDir(int cameraDir)421   public void setCameraDir(int cameraDir) {
422     if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
423         || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
424       mCameraDirection = cameraDir;
425     } else {
426       mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
427     }
428   }
429 
update()430   private void update() {
431     Trace.beginSection("Update");
432     int oldState = getState();
433     // We want to potentially register a video call callback here.
434     updateFromTelecomCall();
435     if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) {
436       for (DialerCallListener listener : mListeners) {
437         listener.onDialerCallDisconnect();
438       }
439       EnrichedCallComponent.get(mContext)
440           .getEnrichedCallManager()
441           .unregisterCapabilitiesListener(this);
442       EnrichedCallComponent.get(mContext)
443           .getEnrichedCallManager()
444           .unregisterCapabilitiesListener(this);
445     } else {
446       for (DialerCallListener listener : mListeners) {
447         listener.onDialerCallUpdate();
448       }
449     }
450     Trace.endSection();
451   }
452 
updateFromTelecomCall()453   private void updateFromTelecomCall() {
454     LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
455 
456     mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
457 
458     final int translatedState = translateState(mTelecomCall.getState());
459     if (mState != State.BLOCKED) {
460       setState(translatedState);
461       setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
462     }
463 
464     mChildCallIds.clear();
465     final int numChildCalls = mTelecomCall.getChildren().size();
466     for (int i = 0; i < numChildCalls; i++) {
467       mChildCallIds.add(
468           mDialerCallDelegate
469               .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i))
470               .getId());
471     }
472 
473     // The number of conferenced calls can change over the course of the call, so use the
474     // maximum number of conferenced child calls as the metric for conference call usage.
475     mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
476 
477     updateFromCallExtras(mTelecomCall.getDetails().getExtras());
478 
479     // If the handle of the call has changed, update state for the call determining if it is an
480     // emergency call.
481     Uri newHandle = mTelecomCall.getDetails().getHandle();
482     if (!Objects.equals(mHandle, newHandle)) {
483       mHandle = newHandle;
484       updateEmergencyCallState();
485     }
486 
487     // If the phone account handle of the call is set, cache capability bit indicating whether
488     // the phone account supports call subjects.
489     PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
490     if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
491       mPhoneAccountHandle = newPhoneAccountHandle;
492 
493       if (mPhoneAccountHandle != null) {
494         PhoneAccount phoneAccount =
495             mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle);
496         if (phoneAccount != null) {
497           mIsCallSubjectSupported =
498               phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
499         }
500       }
501     }
502   }
503 
504   /**
505    * Tests corruption of the {@code callExtras} bundle by calling {@link
506    * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
507    * be thrown and caught by this function.
508    *
509    * @param callExtras the bundle to verify
510    * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
511    */
areCallExtrasCorrupted(Bundle callExtras)512   protected boolean areCallExtrasCorrupted(Bundle callExtras) {
513     /**
514      * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras
515      * bundle, resulting in a IllegalArgumentException while validating data under {@link
516      * Bundle#containsKey(String)}.
517      */
518     try {
519       callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
520       return false;
521     } catch (IllegalArgumentException e) {
522       LogUtil.e(
523           "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
524       return true;
525     }
526   }
527 
updateFromCallExtras(Bundle callExtras)528   protected void updateFromCallExtras(Bundle callExtras) {
529     if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
530       /**
531        * If the bundle is corrupted, abandon information update as a work around. These are not
532        * critical for the dialer to function.
533        */
534       return;
535     }
536     // Check for a change in the child address and notify any listeners.
537     if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
538       String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
539       if (!Objects.equals(childNumber, mChildNumber)) {
540         mChildNumber = childNumber;
541         for (DialerCallListener listener : mListeners) {
542           listener.onDialerCallChildNumberChange();
543         }
544       }
545     }
546 
547     // Last forwarded number comes in as an array of strings.  We want to choose the
548     // last item in the array.  The forwarding numbers arrive independently of when the
549     // call is originally set up, so we need to notify the the UI of the change.
550     if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
551       ArrayList<String> lastForwardedNumbers =
552           callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
553 
554       if (lastForwardedNumbers != null) {
555         String lastForwardedNumber = null;
556         if (!lastForwardedNumbers.isEmpty()) {
557           lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
558         }
559 
560         if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
561           mLastForwardedNumber = lastForwardedNumber;
562           for (DialerCallListener listener : mListeners) {
563             listener.onDialerCallLastForwardedNumberChange();
564           }
565         }
566       }
567     }
568 
569     // DialerCall subject is present in the extras at the start of call, so we do not need to
570     // notify any other listeners of this.
571     if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
572       String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
573       if (!Objects.equals(mCallSubject, callSubject)) {
574         mCallSubject = callSubject;
575       }
576     }
577   }
578 
getId()579   public String getId() {
580     return mId;
581   }
582 
583   /**
584    * @return name appended with a number if the number is restricted/unknown and the user has
585    *     received more than one restricted/unknown call.
586    */
587   @Nullable
updateNameIfRestricted(@ullable String name)588   public String updateNameIfRestricted(@Nullable String name) {
589     if (name != null && isHiddenNumber() && mHiddenId != 0 && sHiddenCounter > 1) {
590       return mContext.getString(R.string.unknown_counter, name, mHiddenId);
591     }
592     return name;
593   }
594 
clearRestrictedCount()595   public static void clearRestrictedCount() {
596     sHiddenCounter = 0;
597   }
598 
isHiddenNumber()599   private boolean isHiddenNumber() {
600     return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
601         || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
602   }
603 
hasShownWiFiToLteHandoverToast()604   public boolean hasShownWiFiToLteHandoverToast() {
605     return hasShownWiFiToLteHandoverToast;
606   }
607 
setHasShownWiFiToLteHandoverToast()608   public void setHasShownWiFiToLteHandoverToast() {
609     hasShownWiFiToLteHandoverToast = true;
610   }
611 
showWifiHandoverAlertAsToast()612   public boolean showWifiHandoverAlertAsToast() {
613     return doNotShowDialogForHandoffToWifiFailure;
614   }
615 
setDoNotShowDialogForHandoffToWifiFailure(boolean bool)616   public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
617     doNotShowDialogForHandoffToWifiFailure = bool;
618   }
619 
getTimeAddedMs()620   public long getTimeAddedMs() {
621     return mTimeAddedMs;
622   }
623 
624   @Nullable
getNumber()625   public String getNumber() {
626     return TelecomCallUtil.getNumber(mTelecomCall);
627   }
628 
blockCall()629   public void blockCall() {
630     mTelecomCall.reject(false, null);
631     setState(State.BLOCKED);
632   }
633 
634   @Nullable
getHandle()635   public Uri getHandle() {
636     return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
637   }
638 
isEmergencyCall()639   public boolean isEmergencyCall() {
640     return mIsEmergencyCall;
641   }
642 
isPotentialEmergencyCallback()643   public boolean isPotentialEmergencyCallback() {
644     // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
645     // is actually in emergency callback mode (ie data is disabled).
646     if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
647       return true;
648     }
649     // We want to treat any incoming call that arrives a short time after an outgoing emergency call
650     // as a potential emergency callback.
651     if (getExtras() != null
652         && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
653             > 0) {
654       long lastEmergencyCallMillis =
655           getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
656       if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
657         return true;
658       }
659     }
660     return false;
661   }
662 
isInEmergencyCallbackWindow(long timestampMillis)663   boolean isInEmergencyCallbackWindow(long timestampMillis) {
664     long emergencyCallbackWindowMillis =
665         ConfigProviderBindings.get(mContext)
666             .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
667     return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
668   }
669 
getState()670   public int getState() {
671     if (mTelecomCall != null && mTelecomCall.getParent() != null) {
672       return State.CONFERENCED;
673     } else {
674       return mState;
675     }
676   }
677 
setState(int state)678   public void setState(int state) {
679     mState = state;
680     if (mState == State.INCOMING) {
681       mLogState.isIncoming = true;
682     } else if (mState == State.DISCONNECTED) {
683       mLogState.duration =
684           getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
685     }
686   }
687 
getNumberPresentation()688   public int getNumberPresentation() {
689     return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
690   }
691 
getCnapNamePresentation()692   public int getCnapNamePresentation() {
693     return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
694   }
695 
696   @Nullable
getCnapName()697   public String getCnapName() {
698     return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
699   }
700 
getIntentExtras()701   public Bundle getIntentExtras() {
702     return mTelecomCall.getDetails().getIntentExtras();
703   }
704 
705   @Nullable
getExtras()706   public Bundle getExtras() {
707     return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
708   }
709 
710   /** @return The child number for the call, or {@code null} if none specified. */
getChildNumber()711   public String getChildNumber() {
712     return mChildNumber;
713   }
714 
715   /** @return The last forwarded number for the call, or {@code null} if none specified. */
getLastForwardedNumber()716   public String getLastForwardedNumber() {
717     return mLastForwardedNumber;
718   }
719 
720   /** @return The call subject, or {@code null} if none specified. */
getCallSubject()721   public String getCallSubject() {
722     return mCallSubject;
723   }
724 
725   /**
726    * @return {@code true} if the call's phone account supports call subjects, {@code false}
727    *     otherwise.
728    */
isCallSubjectSupported()729   public boolean isCallSubjectSupported() {
730     return mIsCallSubjectSupported;
731   }
732 
733   /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
getDisconnectCause()734   public DisconnectCause getDisconnectCause() {
735     if (mState == State.DISCONNECTED || mState == State.IDLE) {
736       return mDisconnectCause;
737     }
738 
739     return new DisconnectCause(DisconnectCause.UNKNOWN);
740   }
741 
setDisconnectCause(DisconnectCause disconnectCause)742   public void setDisconnectCause(DisconnectCause disconnectCause) {
743     mDisconnectCause = disconnectCause;
744     mLogState.disconnectCause = mDisconnectCause;
745   }
746 
747   /** Returns the possible text message responses. */
getCannedSmsResponses()748   public List<String> getCannedSmsResponses() {
749     return mTelecomCall.getCannedTextResponses();
750   }
751 
752   /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
can(int capabilities)753   public boolean can(int capabilities) {
754     int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
755 
756     if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
757       // We allow you to merge if the capabilities allow it or if it is a call with
758       // conferenceable calls.
759       if (mTelecomCall.getConferenceableCalls().isEmpty()
760           && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
761         // Cannot merge calls if there are no calls to merge with.
762         return false;
763       }
764       capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
765     }
766     return (capabilities == (capabilities & supportedCapabilities));
767   }
768 
hasProperty(int property)769   public boolean hasProperty(int property) {
770     return mTelecomCall.getDetails().hasProperty(property);
771   }
772 
773   @NonNull
getUniqueCallId()774   public String getUniqueCallId() {
775     return uniqueCallId;
776   }
777 
778   /** Gets the time when the call first became active. */
getConnectTimeMillis()779   public long getConnectTimeMillis() {
780     return mTelecomCall.getDetails().getConnectTimeMillis();
781   }
782 
isConferenceCall()783   public boolean isConferenceCall() {
784     return hasProperty(Call.Details.PROPERTY_CONFERENCE);
785   }
786 
787   @Nullable
getGatewayInfo()788   public GatewayInfo getGatewayInfo() {
789     return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
790   }
791 
792   @Nullable
getAccountHandle()793   public PhoneAccountHandle getAccountHandle() {
794     return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
795   }
796 
797   /** @return The {@link VideoCall} instance associated with the {@link Call}. */
getVideoCall()798   public VideoCall getVideoCall() {
799     return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
800   }
801 
getChildCallIds()802   public List<String> getChildCallIds() {
803     return mChildCallIds;
804   }
805 
getParentId()806   public String getParentId() {
807     Call parentCall = mTelecomCall.getParent();
808     if (parentCall != null) {
809       return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
810     }
811     return null;
812   }
813 
getVideoState()814   public int getVideoState() {
815     return mTelecomCall.getDetails().getVideoState();
816   }
817 
isVideoCall()818   public boolean isVideoCall() {
819     return getVideoTech().isTransmittingOrReceiving();
820   }
821 
hasReceivedVideoUpgradeRequest()822   public boolean hasReceivedVideoUpgradeRequest() {
823     return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
824   }
825 
hasSentVideoUpgradeRequest()826   public boolean hasSentVideoUpgradeRequest() {
827     return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
828   }
829 
830   /**
831    * Determines if the call handle is an emergency number or not and caches the result to avoid
832    * repeated calls to isEmergencyNumber.
833    */
updateEmergencyCallState()834   private void updateEmergencyCallState() {
835     mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
836   }
837 
getLogState()838   public LogState getLogState() {
839     return mLogState;
840   }
841 
842   /**
843    * Determines if the call is an external call.
844    *
845    * <p>An external call is one which does not exist locally for the {@link
846    * android.telecom.ConnectionService} it is associated with.
847    *
848    * <p>External calls are only supported in N and higher.
849    *
850    * @return {@code true} if the call is an external call, {@code false} otherwise.
851    */
isExternalCall()852   public boolean isExternalCall() {
853     return VERSION.SDK_INT >= VERSION_CODES.N
854         && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
855   }
856 
857   /**
858    * Determines if answering this call will cause an ongoing video call to be dropped.
859    *
860    * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
861    *     otherwise.
862    */
answeringDisconnectsForegroundVideoCall()863   public boolean answeringDisconnectsForegroundVideoCall() {
864     Bundle extras = getExtras();
865     if (extras == null
866         || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
867       return false;
868     }
869     return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
870   }
871 
parseCallSpecificAppData()872   private void parseCallSpecificAppData() {
873     if (isExternalCall()) {
874       return;
875     }
876 
877     mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
878     if (mLogState.callSpecificAppData == null) {
879 
880       mLogState.callSpecificAppData =
881           CallSpecificAppData.newBuilder()
882               .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION)
883               .build();
884     }
885     if (getState() == State.INCOMING) {
886       mLogState.callSpecificAppData =
887           mLogState
888               .callSpecificAppData
889               .toBuilder()
890               .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION)
891               .build();
892     }
893   }
894 
895   @Override
toString()896   public String toString() {
897     if (mTelecomCall == null) {
898       // This should happen only in testing since otherwise we would never have a null
899       // Telecom call.
900       return String.valueOf(mId);
901     }
902 
903     return String.format(
904         Locale.US,
905         "[%s, %s, %s, %s, children:%s, parent:%s, "
906             + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
907         mId,
908         State.toString(getState()),
909         Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
910         Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
911         mChildCallIds,
912         getParentId(),
913         this.mTelecomCall.getConferenceableCalls(),
914         VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
915         getVideoTech().getSessionModificationState(),
916         getCameraDir());
917   }
918 
toSimpleString()919   public String toSimpleString() {
920     return super.toString();
921   }
922 
923   @CallHistoryStatus
getCallHistoryStatus()924   public int getCallHistoryStatus() {
925     return mCallHistoryStatus;
926   }
927 
setCallHistoryStatus(@allHistoryStatus int callHistoryStatus)928   public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
929     mCallHistoryStatus = callHistoryStatus;
930   }
931 
didShowCameraPermission()932   public boolean didShowCameraPermission() {
933     return didShowCameraPermission;
934   }
935 
setDidShowCameraPermission(boolean didShow)936   public void setDidShowCameraPermission(boolean didShow) {
937     didShowCameraPermission = didShow;
938   }
939 
isInGlobalSpamList()940   public boolean isInGlobalSpamList() {
941     return isInGlobalSpamList;
942   }
943 
setIsInGlobalSpamList(boolean inSpamList)944   public void setIsInGlobalSpamList(boolean inSpamList) {
945     isInGlobalSpamList = inSpamList;
946   }
947 
isInUserSpamList()948   public boolean isInUserSpamList() {
949     return isInUserSpamList;
950   }
951 
setIsInUserSpamList(boolean inSpamList)952   public void setIsInUserSpamList(boolean inSpamList) {
953     isInUserSpamList = inSpamList;
954   }
955 
isInUserWhiteList()956   public boolean isInUserWhiteList() {
957     return isInUserWhiteList;
958   }
959 
setIsInUserWhiteList(boolean inWhiteList)960   public void setIsInUserWhiteList(boolean inWhiteList) {
961     isInUserWhiteList = inWhiteList;
962   }
963 
isSpam()964   public boolean isSpam() {
965     return mIsSpam;
966   }
967 
setSpam(boolean isSpam)968   public void setSpam(boolean isSpam) {
969     mIsSpam = isSpam;
970   }
971 
isBlocked()972   public boolean isBlocked() {
973     return mIsBlocked;
974   }
975 
setBlockedStatus(boolean isBlocked)976   public void setBlockedStatus(boolean isBlocked) {
977     mIsBlocked = isBlocked;
978   }
979 
isRemotelyHeld()980   public boolean isRemotelyHeld() {
981     return isRemotelyHeld;
982   }
983 
isMergeInProcess()984   public boolean isMergeInProcess() {
985     return isMergeInProcess;
986   }
987 
isIncoming()988   public boolean isIncoming() {
989     return mLogState.isIncoming;
990   }
991 
getLatencyReport()992   public LatencyReport getLatencyReport() {
993     return mLatencyReport;
994   }
995 
getAnswerAndReleaseButtonDisplayedTimes()996   public int getAnswerAndReleaseButtonDisplayedTimes() {
997     return answerAndReleaseButtonDisplayedTimes;
998   }
999 
increaseAnswerAndReleaseButtonDisplayedTimes()1000   public void increaseAnswerAndReleaseButtonDisplayedTimes() {
1001     answerAndReleaseButtonDisplayedTimes++;
1002   }
1003 
getReleasedByAnsweringSecondCall()1004   public boolean getReleasedByAnsweringSecondCall() {
1005     return releasedByAnsweringSecondCall;
1006   }
1007 
setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall)1008   public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) {
1009     this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall;
1010   }
1011 
getSecondCallWithoutAnswerAndReleasedButtonTimes()1012   public int getSecondCallWithoutAnswerAndReleasedButtonTimes() {
1013     return secondCallWithoutAnswerAndReleasedButtonTimes;
1014   }
1015 
increaseSecondCallWithoutAnswerAndReleasedButtonTimes()1016   public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() {
1017     secondCallWithoutAnswerAndReleasedButtonTimes++;
1018   }
1019 
1020   @Nullable
getEnrichedCallCapabilities()1021   public EnrichedCallCapabilities getEnrichedCallCapabilities() {
1022     return mEnrichedCallCapabilities;
1023   }
1024 
setEnrichedCallCapabilities( @ullable EnrichedCallCapabilities mEnrichedCallCapabilities)1025   public void setEnrichedCallCapabilities(
1026       @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
1027     this.mEnrichedCallCapabilities = mEnrichedCallCapabilities;
1028   }
1029 
1030   @Nullable
getEnrichedCallSession()1031   public Session getEnrichedCallSession() {
1032     return mEnrichedCallSession;
1033   }
1034 
setEnrichedCallSession(@ullable Session mEnrichedCallSession)1035   public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
1036     this.mEnrichedCallSession = mEnrichedCallSession;
1037   }
1038 
unregisterCallback()1039   public void unregisterCallback() {
1040     mTelecomCall.unregisterCallback(mTelecomCallCallback);
1041   }
1042 
phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault)1043   public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
1044     LogUtil.i(
1045         "DialerCall.phoneAccountSelected",
1046         "accountHandle: %s, setDefault: %b",
1047         accountHandle,
1048         setDefault);
1049     mTelecomCall.phoneAccountSelected(accountHandle, setDefault);
1050   }
1051 
disconnect()1052   public void disconnect() {
1053     LogUtil.i("DialerCall.disconnect", "");
1054     setState(DialerCall.State.DISCONNECTING);
1055     for (DialerCallListener listener : mListeners) {
1056       listener.onDialerCallUpdate();
1057     }
1058     mTelecomCall.disconnect();
1059   }
1060 
hold()1061   public void hold() {
1062     LogUtil.i("DialerCall.hold", "");
1063     mTelecomCall.hold();
1064   }
1065 
unhold()1066   public void unhold() {
1067     LogUtil.i("DialerCall.unhold", "");
1068     mTelecomCall.unhold();
1069   }
1070 
splitFromConference()1071   public void splitFromConference() {
1072     LogUtil.i("DialerCall.splitFromConference", "");
1073     mTelecomCall.splitFromConference();
1074   }
1075 
answer(int videoState)1076   public void answer(int videoState) {
1077     LogUtil.i("DialerCall.answer", "videoState: " + videoState);
1078     mTelecomCall.answer(videoState);
1079   }
1080 
answer()1081   public void answer() {
1082     answer(mTelecomCall.getDetails().getVideoState());
1083   }
1084 
reject(boolean rejectWithMessage, String message)1085   public void reject(boolean rejectWithMessage, String message) {
1086     LogUtil.i("DialerCall.reject", "");
1087     mTelecomCall.reject(rejectWithMessage, message);
1088   }
1089 
1090   /** Return the string label to represent the call provider */
getCallProviderLabel()1091   public String getCallProviderLabel() {
1092     if (callProviderLabel == null) {
1093       PhoneAccount account = getPhoneAccount();
1094       if (account != null && !TextUtils.isEmpty(account.getLabel())) {
1095         List<PhoneAccountHandle> accounts =
1096             mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts();
1097         if (accounts != null && accounts.size() > 1) {
1098           callProviderLabel = account.getLabel().toString();
1099         }
1100       }
1101       if (callProviderLabel == null) {
1102         callProviderLabel = "";
1103       }
1104     }
1105     return callProviderLabel;
1106   }
1107 
getPhoneAccount()1108   private PhoneAccount getPhoneAccount() {
1109     PhoneAccountHandle accountHandle = getAccountHandle();
1110     if (accountHandle == null) {
1111       return null;
1112     }
1113     return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1114   }
1115 
getVideoTech()1116   public VideoTech getVideoTech() {
1117     return mVideoTechManager.getVideoTech();
1118   }
1119 
getCallbackNumber()1120   public String getCallbackNumber() {
1121     if (callbackNumber == null) {
1122       // Show the emergency callback number if either:
1123       // 1. This is an emergency call.
1124       // 2. The phone is in Emergency Callback Mode, which means we should show the callback
1125       //    number.
1126       boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
1127 
1128       if (isEmergencyCall() || showCallbackNumber) {
1129         callbackNumber = getSubscriptionNumber();
1130       } else {
1131         StatusHints statusHints = getTelecomCall().getDetails().getStatusHints();
1132         if (statusHints != null) {
1133           Bundle extras = statusHints.getExtras();
1134           if (extras != null) {
1135             callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER);
1136           }
1137         }
1138       }
1139 
1140       String simNumber =
1141           mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
1142       if (!showCallbackNumber && PhoneNumberUtils.compare(callbackNumber, simNumber)) {
1143         LogUtil.v(
1144             "DialerCall.getCallbackNumber",
1145             "numbers are the same (and callback number is not being forced to show);"
1146                 + " not showing the callback number");
1147         callbackNumber = "";
1148       }
1149       if (callbackNumber == null) {
1150         callbackNumber = "";
1151       }
1152     }
1153     return callbackNumber;
1154   }
1155 
getSubscriptionNumber()1156   private String getSubscriptionNumber() {
1157     // If it's an emergency call, and they're not populating the callback number,
1158     // then try to fall back to the phone sub info (to hopefully get the SIM's
1159     // number directly from the telephony layer).
1160     PhoneAccountHandle accountHandle = getAccountHandle();
1161     if (accountHandle != null) {
1162       PhoneAccount account =
1163           mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1164       if (account != null) {
1165         return getNumberFromHandle(account.getSubscriptionAddress());
1166       }
1167     }
1168     return null;
1169   }
1170 
1171   @Override
onVideoTechStateChanged()1172   public void onVideoTechStateChanged() {
1173     update();
1174   }
1175 
1176   @Override
onSessionModificationStateChanged()1177   public void onSessionModificationStateChanged() {
1178     for (DialerCallListener listener : mListeners) {
1179       listener.onDialerCallSessionModificationStateChange();
1180     }
1181   }
1182 
1183   @Override
onCameraDimensionsChanged(int width, int height)1184   public void onCameraDimensionsChanged(int width, int height) {
1185     InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1186   }
1187 
1188   @Override
onPeerDimensionsChanged(int width, int height)1189   public void onPeerDimensionsChanged(int width, int height) {
1190     InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1191   }
1192 
1193   @Override
onVideoUpgradeRequestReceived()1194   public void onVideoUpgradeRequestReceived() {
1195     LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1196 
1197     for (DialerCallListener listener : mListeners) {
1198       listener.onDialerCallUpgradeToVideo();
1199     }
1200 
1201     update();
1202 
1203     Logger.get(mContext)
1204         .logCallImpression(
1205             DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs());
1206   }
1207 
1208   @Override
onUpgradedToVideo(boolean switchToSpeaker)1209   public void onUpgradedToVideo(boolean switchToSpeaker) {
1210     LogUtil.enterBlock("DialerCall.onUpgradedToVideo");
1211 
1212     if (!switchToSpeaker) {
1213       return;
1214     }
1215 
1216     CallAudioState audioState = AudioModeProvider.getInstance().getAudioState();
1217 
1218     if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
1219       LogUtil.e(
1220           "DialerCall.onUpgradedToVideo",
1221           "toggling speakerphone not allowed when bluetooth supported.");
1222       return;
1223     }
1224 
1225     if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
1226       return;
1227     }
1228 
1229     TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
1230   }
1231 
1232   @Override
onCapabilitiesUpdated()1233   public void onCapabilitiesUpdated() {
1234     if (getNumber() == null) {
1235       return;
1236     }
1237     EnrichedCallCapabilities capabilities =
1238         EnrichedCallComponent.get(mContext).getEnrichedCallManager().getCapabilities(getNumber());
1239     if (capabilities != null) {
1240       setEnrichedCallCapabilities(capabilities);
1241       update();
1242     }
1243   }
1244 
1245   @Override
onEnrichedCallStateChanged()1246   public void onEnrichedCallStateChanged() {
1247     updateEnrichedCallSession();
1248   }
1249 
updateEnrichedCallSession()1250   private void updateEnrichedCallSession() {
1251     if (getNumber() == null) {
1252       return;
1253     }
1254     if (getEnrichedCallSession() != null) {
1255       // State changes to existing sessions are currently handled by the UI components (which have
1256       // their own listeners). Someday instead we could remove those and just call update() here and
1257       // have the usual onDialerCallUpdate update the UI.
1258       dispatchOnEnrichedCallSessionUpdate();
1259       return;
1260     }
1261 
1262     EnrichedCallManager manager = EnrichedCallComponent.get(mContext).getEnrichedCallManager();
1263 
1264     Filter filter =
1265         isIncoming()
1266             ? manager.createIncomingCallComposerFilter()
1267             : manager.createOutgoingCallComposerFilter();
1268 
1269     Session session = manager.getSession(getUniqueCallId(), getNumber(), filter);
1270     if (session == null) {
1271       return;
1272     }
1273 
1274     session.setUniqueDialerCallId(getUniqueCallId());
1275     setEnrichedCallSession(session);
1276 
1277     LogUtil.i(
1278         "DialerCall.updateEnrichedCallSession",
1279         "setting session %d's dialer id to %s",
1280         session.getSessionId(),
1281         getUniqueCallId());
1282 
1283     dispatchOnEnrichedCallSessionUpdate();
1284   }
1285 
dispatchOnEnrichedCallSessionUpdate()1286   private void dispatchOnEnrichedCallSessionUpdate() {
1287     for (DialerCallListener listener : mListeners) {
1288       listener.onEnrichedCallSessionUpdate();
1289     }
1290   }
1291 
onRemovedFromCallList()1292   void onRemovedFromCallList() {
1293     // Ensure we clean up when this call is removed.
1294     mVideoTechManager.dispatchRemovedFromCallList();
1295   }
1296 
1297   /**
1298    * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1299    * means there is no result.
1300    */
1301   @IntDef({
1302     CALL_HISTORY_STATUS_UNKNOWN,
1303     CALL_HISTORY_STATUS_PRESENT,
1304     CALL_HISTORY_STATUS_NOT_PRESENT
1305   })
1306   @Retention(RetentionPolicy.SOURCE)
1307   public @interface CallHistoryStatus {}
1308 
1309   /* Defines different states of this call */
1310   public static class State {
1311 
1312     public static final int INVALID = 0;
1313     public static final int NEW = 1; /* The call is new. */
1314     public static final int IDLE = 2; /* The call is idle.  Nothing active */
1315     public static final int ACTIVE = 3; /* There is an active call */
1316     public static final int INCOMING = 4; /* A normal incoming phone call */
1317     public static final int CALL_WAITING = 5; /* Incoming call while another is active */
1318     public static final int DIALING = 6; /* An outgoing call during dial phase */
1319     public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
1320     public static final int ONHOLD = 8; /* An active phone call placed on hold */
1321     public static final int DISCONNECTING = 9; /* A call is being ended. */
1322     public static final int DISCONNECTED = 10; /* State after a call disconnects */
1323     public static final int CONFERENCED = 11; /* DialerCall part of a conference call */
1324     public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
1325     public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
1326     public static final int BLOCKED = 14; /* The number was found on the block list */
1327     public static final int PULLING = 15; /* An external call being pulled to the device */
1328 
isConnectingOrConnected(int state)1329     public static boolean isConnectingOrConnected(int state) {
1330       switch (state) {
1331         case ACTIVE:
1332         case INCOMING:
1333         case CALL_WAITING:
1334         case CONNECTING:
1335         case DIALING:
1336         case PULLING:
1337         case REDIALING:
1338         case ONHOLD:
1339         case CONFERENCED:
1340           return true;
1341         default:
1342           return false;
1343       }
1344     }
1345 
isDialing(int state)1346     public static boolean isDialing(int state) {
1347       return state == DIALING || state == PULLING || state == REDIALING;
1348     }
1349 
toString(int state)1350     public static String toString(int state) {
1351       switch (state) {
1352         case INVALID:
1353           return "INVALID";
1354         case NEW:
1355           return "NEW";
1356         case IDLE:
1357           return "IDLE";
1358         case ACTIVE:
1359           return "ACTIVE";
1360         case INCOMING:
1361           return "INCOMING";
1362         case CALL_WAITING:
1363           return "CALL_WAITING";
1364         case DIALING:
1365           return "DIALING";
1366         case PULLING:
1367           return "PULLING";
1368         case REDIALING:
1369           return "REDIALING";
1370         case ONHOLD:
1371           return "ONHOLD";
1372         case DISCONNECTING:
1373           return "DISCONNECTING";
1374         case DISCONNECTED:
1375           return "DISCONNECTED";
1376         case CONFERENCED:
1377           return "CONFERENCED";
1378         case SELECT_PHONE_ACCOUNT:
1379           return "SELECT_PHONE_ACCOUNT";
1380         case CONNECTING:
1381           return "CONNECTING";
1382         case BLOCKED:
1383           return "BLOCKED";
1384         default:
1385           return "UNKNOWN";
1386       }
1387     }
1388   }
1389 
1390   /** Camera direction constants */
1391   public static class CameraDirection {
1392     public static final int CAMERA_DIRECTION_UNKNOWN = -1;
1393     public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT;
1394     public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK;
1395   }
1396 
1397   /**
1398    * Tracks any state variables that is useful for logging. There is some amount of overlap with
1399    * existing call member variables, but this duplication helps to ensure that none of these logging
1400    * variables will interface with/and affect call logic.
1401    */
1402   public static class LogState {
1403 
1404     public DisconnectCause disconnectCause;
1405     public boolean isIncoming = false;
1406     public ContactLookupResult.Type contactLookupResult =
1407         ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
1408     public CallSpecificAppData callSpecificAppData;
1409     // If this was a conference call, the total number of calls involved in the conference.
1410     public int conferencedCalls = 0;
1411     public long duration = 0;
1412     public boolean isLogged = false;
1413 
lookupToString(ContactLookupResult.Type lookupType)1414     private static String lookupToString(ContactLookupResult.Type lookupType) {
1415       switch (lookupType) {
1416         case LOCAL_CONTACT:
1417           return "Local";
1418         case LOCAL_CACHE:
1419           return "Cache";
1420         case REMOTE:
1421           return "Remote";
1422         case EMERGENCY:
1423           return "Emergency";
1424         case VOICEMAIL:
1425           return "Voicemail";
1426         default:
1427           return "Not found";
1428       }
1429     }
1430 
initiationToString(CallSpecificAppData callSpecificAppData)1431     private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1432       if (callSpecificAppData == null) {
1433         return "null";
1434       }
1435       switch (callSpecificAppData.getCallInitiationType()) {
1436         case INCOMING_INITIATION:
1437           return "Incoming";
1438         case DIALPAD:
1439           return "Dialpad";
1440         case SPEED_DIAL:
1441           return "Speed Dial";
1442         case REMOTE_DIRECTORY:
1443           return "Remote Directory";
1444         case SMART_DIAL:
1445           return "Smart Dial";
1446         case REGULAR_SEARCH:
1447           return "Regular Search";
1448         case CALL_LOG:
1449           return "DialerCall Log";
1450         case CALL_LOG_FILTER:
1451           return "DialerCall Log Filter";
1452         case VOICEMAIL_LOG:
1453           return "Voicemail Log";
1454         case CALL_DETAILS:
1455           return "DialerCall Details";
1456         case QUICK_CONTACTS:
1457           return "Quick Contacts";
1458         case EXTERNAL_INITIATION:
1459           return "External";
1460         case LAUNCHER_SHORTCUT:
1461           return "Launcher Shortcut";
1462         default:
1463           return "Unknown: " + callSpecificAppData.getCallInitiationType();
1464       }
1465     }
1466 
1467     @Override
toString()1468     public String toString() {
1469       return String.format(
1470           Locale.US,
1471           "["
1472               + "%s, " // DisconnectCause toString already describes the object type
1473               + "isIncoming: %s, "
1474               + "contactLookup: %s, "
1475               + "callInitiation: %s, "
1476               + "duration: %s"
1477               + "]",
1478           disconnectCause,
1479           isIncoming,
1480           lookupToString(contactLookupResult),
1481           initiationToString(callSpecificAppData),
1482           duration);
1483     }
1484   }
1485 
1486   private static class VideoTechManager {
1487     private final Context context;
1488     private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
1489     private final List<VideoTech> videoTechs;
1490     private VideoTech savedTech;
1491 
VideoTechManager(DialerCall call)1492     VideoTechManager(DialerCall call) {
1493       this.context = call.mContext;
1494 
1495       String phoneNumber = call.getNumber();
1496       phoneNumber = phoneNumber != null ? phoneNumber : "";
1497       phoneNumber = phoneNumber.replaceAll("[^+0-9]", "");
1498 
1499       // Insert order here determines the priority of that video tech option
1500       videoTechs = new ArrayList<>();
1501       videoTechs.add(new ImsVideoTech(Logger.get(call.mContext), call, call.mTelecomCall));
1502 
1503       VideoTech rcsVideoTech =
1504           EnrichedCallComponent.get(call.mContext)
1505               .getRcsVideoShareFactory()
1506               .newRcsVideoShare(
1507                   EnrichedCallComponent.get(call.mContext).getEnrichedCallManager(),
1508                   call,
1509                   phoneNumber);
1510       if (rcsVideoTech != null) {
1511         videoTechs.add(rcsVideoTech);
1512       }
1513 
1514       videoTechs.add(
1515           new LightbringerTech(
1516               LightbringerComponent.get(call.mContext).getLightbringer(),
1517               call,
1518               call.mTelecomCall,
1519               phoneNumber));
1520     }
1521 
getVideoTech()1522     VideoTech getVideoTech() {
1523       if (savedTech != null) {
1524         return savedTech;
1525       }
1526 
1527       for (VideoTech tech : videoTechs) {
1528         if (tech.isAvailable(context)) {
1529           // Remember the first VideoTech that becomes available and always use it
1530           savedTech = tech;
1531           return savedTech;
1532         }
1533       }
1534 
1535       return emptyVideoTech;
1536     }
1537 
dispatchCallStateChanged(int newState)1538     void dispatchCallStateChanged(int newState) {
1539       for (VideoTech videoTech : videoTechs) {
1540         videoTech.onCallStateChanged(context, newState);
1541       }
1542     }
1543 
dispatchRemovedFromCallList()1544     void dispatchRemovedFromCallList() {
1545       for (VideoTech videoTech : videoTechs) {
1546         videoTech.onRemovedFromCallList();
1547       }
1548     }
1549   }
1550 
1551   /** Called when canned text responses have been loaded. */
1552   public interface CannedTextResponsesLoadedListener {
onCannedTextResponsesLoaded(DialerCall call)1553     void onCannedTextResponsesLoaded(DialerCall call);
1554   }
1555 }
1556