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.Manifest.permission;
20 import android.annotation.SuppressLint;
21 import android.annotation.TargetApi;
22 import android.content.Context;
23 import android.hardware.camera2.CameraCharacteristics;
24 import android.net.Uri;
25 import android.os.Build;
26 import android.os.Build.VERSION;
27 import android.os.Build.VERSION_CODES;
28 import android.os.Bundle;
29 import android.os.PersistableBundle;
30 import android.os.SystemClock;
31 import android.os.Trace;
32 import android.support.annotation.IntDef;
33 import android.support.annotation.NonNull;
34 import android.support.annotation.Nullable;
35 import android.support.annotation.VisibleForTesting;
36 import android.support.v4.os.BuildCompat;
37 import android.telecom.Call;
38 import android.telecom.Call.Details;
39 import android.telecom.Call.RttCall;
40 import android.telecom.CallAudioState;
41 import android.telecom.Connection;
42 import android.telecom.DisconnectCause;
43 import android.telecom.GatewayInfo;
44 import android.telecom.InCallService.VideoCall;
45 import android.telecom.PhoneAccount;
46 import android.telecom.PhoneAccountHandle;
47 import android.telecom.StatusHints;
48 import android.telecom.TelecomManager;
49 import android.telecom.VideoProfile;
50 import android.text.TextUtils;
51 import android.widget.Toast;
52 import com.android.contacts.common.compat.CallCompat;
53 import com.android.dialer.assisteddialing.ConcreteCreator;
54 import com.android.dialer.assisteddialing.TransformationInfo;
55 import com.android.dialer.blocking.FilteredNumbersUtil;
56 import com.android.dialer.callintent.CallInitiationType;
57 import com.android.dialer.callintent.CallIntentParser;
58 import com.android.dialer.callintent.CallSpecificAppData;
59 import com.android.dialer.common.Assert;
60 import com.android.dialer.common.LogUtil;
61 import com.android.dialer.common.concurrent.DefaultFutureCallback;
62 import com.android.dialer.compat.telephony.TelephonyManagerCompat;
63 import com.android.dialer.configprovider.ConfigProviderComponent;
64 import com.android.dialer.duo.DuoComponent;
65 import com.android.dialer.enrichedcall.EnrichedCallCapabilities;
66 import com.android.dialer.enrichedcall.EnrichedCallComponent;
67 import com.android.dialer.enrichedcall.EnrichedCallManager;
68 import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener;
69 import com.android.dialer.enrichedcall.EnrichedCallManager.Filter;
70 import com.android.dialer.enrichedcall.EnrichedCallManager.StateChangedListener;
71 import com.android.dialer.enrichedcall.Session;
72 import com.android.dialer.location.GeoUtil;
73 import com.android.dialer.logging.ContactLookupResult;
74 import com.android.dialer.logging.ContactLookupResult.Type;
75 import com.android.dialer.logging.DialerImpression;
76 import com.android.dialer.logging.Logger;
77 import com.android.dialer.preferredsim.PreferredAccountRecorder;
78 import com.android.dialer.rtt.RttTranscript;
79 import com.android.dialer.rtt.RttTranscriptUtil;
80 import com.android.dialer.spam.status.SpamStatus;
81 import com.android.dialer.telecom.TelecomCallUtil;
82 import com.android.dialer.telecom.TelecomUtil;
83 import com.android.dialer.theme.common.R;
84 import com.android.dialer.time.Clock;
85 import com.android.dialer.util.PermissionsUtil;
86 import com.android.incallui.audiomode.AudioModeProvider;
87 import com.android.incallui.call.state.DialerCallState;
88 import com.android.incallui.latencyreport.LatencyReport;
89 import com.android.incallui.rtt.protocol.RttChatMessage;
90 import com.android.incallui.videotech.VideoTech;
91 import com.android.incallui.videotech.VideoTech.VideoTechListener;
92 import com.android.incallui.videotech.duo.DuoVideoTech;
93 import com.android.incallui.videotech.empty.EmptyVideoTech;
94 import com.android.incallui.videotech.ims.ImsVideoTech;
95 import com.android.incallui.videotech.utils.VideoUtils;
96 import com.google.common.base.Optional;
97 import com.google.common.util.concurrent.Futures;
98 import com.google.common.util.concurrent.MoreExecutors;
99 import java.io.IOException;
100 import java.lang.annotation.Retention;
101 import java.lang.annotation.RetentionPolicy;
102 import java.util.ArrayList;
103 import java.util.List;
104 import java.util.Locale;
105 import java.util.Objects;
106 import java.util.UUID;
107 import java.util.concurrent.CopyOnWriteArrayList;
108 import java.util.concurrent.TimeUnit;
109 
110 /** Describes a single call and its state. */
111 public class DialerCall implements VideoTechListener, StateChangedListener, CapabilitiesListener {
112 
113   public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
114   public static final int CALL_HISTORY_STATUS_PRESENT = 1;
115   public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
116 
117   // Hard coded property for {@code Call}. Upstreamed change from Motorola.
118   // TODO(a bug): Move it to Telecom in framework.
119   public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
120 
121   private static final String ID_PREFIX = "DialerCall_";
122 
123   @VisibleForTesting
124   public static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
125       "emergency_callback_window_millis";
126 
127   private static int idCounter = 0;
128 
129   public static final int UNKNOWN_PEER_DIMENSIONS = -1;
130 
131   /**
132    * A counter used to append to restricted/private/hidden calls so that users can identify them in
133    * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there
134    * are no live calls.
135    */
136   private static int hiddenCounter;
137 
138   /**
139    * The unique call ID for every call. This will help us to identify each call and allow us the
140    * ability to stitch impressions to calls if needed.
141    */
142   private final String uniqueCallId = UUID.randomUUID().toString();
143 
144   private final Call telecomCall;
145   private final LatencyReport latencyReport;
146   private final String id;
147   private final int hiddenId;
148   private final List<String> childCallIds = new ArrayList<>();
149   private final LogState logState = new LogState();
150   private final Context context;
151   private final DialerCallDelegate dialerCallDelegate;
152   private final List<DialerCallListener> listeners = new CopyOnWriteArrayList<>();
153   private final List<CannedTextResponsesLoadedListener> cannedTextResponsesLoadedListeners =
154       new CopyOnWriteArrayList<>();
155   private final VideoTechManager videoTechManager;
156 
157   private boolean isSpeakEasyCall;
158   private boolean isEmergencyCall;
159   private Uri handle;
160   private int state = DialerCallState.INVALID;
161   private DisconnectCause disconnectCause;
162 
163   private boolean hasShownLteToWiFiHandoverToast;
164   private boolean hasShownWiFiToLteHandoverToast;
165   private boolean doNotShowDialogForHandoffToWifiFailure;
166 
167   private String childNumber;
168   private String lastForwardedNumber;
169   private boolean isCallForwarded;
170   private String callSubject;
171   @Nullable private PhoneAccountHandle phoneAccountHandle;
172   @CallHistoryStatus private int callHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
173 
174   @Nullable private SpamStatus spamStatus;
175   private boolean isBlocked;
176 
177   private boolean didShowCameraPermission;
178   private boolean didDismissVideoChargesAlertDialog;
179   private PersistableBundle carrierConfig;
180   private String callProviderLabel;
181   private String callbackNumber;
182   private int cameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
183   private EnrichedCallCapabilities enrichedCallCapabilities;
184   private Session enrichedCallSession;
185 
186   private int answerAndReleaseButtonDisplayedTimes = 0;
187   private boolean releasedByAnsweringSecondCall = false;
188   // Times when a second call is received but AnswerAndRelease button is not shown
189   // since it's not supported.
190   private int secondCallWithoutAnswerAndReleasedButtonTimes = 0;
191   private VideoTech videoTech;
192 
193   private com.android.dialer.logging.VideoTech.Type selectedAvailableVideoTechType =
194       com.android.dialer.logging.VideoTech.Type.NONE;
195   private boolean isVoicemailNumber;
196   private List<PhoneAccountHandle> callCapableAccounts;
197   private String countryIso;
198 
199   private volatile boolean feedbackRequested = false;
200 
201   private Clock clock = System::currentTimeMillis;
202 
203   @Nullable private PreferredAccountRecorder preferredAccountRecorder;
204   private boolean isCallRemoved;
205 
getNumberFromHandle(Uri handle)206   public static String getNumberFromHandle(Uri handle) {
207     return handle == null ? "" : handle.getSchemeSpecificPart();
208   }
209 
210   /**
211    * Whether the call is put on hold by remote party. This is different than the {@link
212    * DialerCallState#ONHOLD} state which indicates that the call is being held locally on the
213    * device.
214    */
215   private boolean isRemotelyHeld;
216 
217   /** Indicates whether this call is currently in the process of being merged into a conference. */
218   private boolean isMergeInProcess;
219 
220   /**
221    * Indicates whether the phone account associated with this call supports specifying a call
222    * subject.
223    */
224   private boolean isCallSubjectSupported;
225 
getRttTranscript()226   public RttTranscript getRttTranscript() {
227     return rttTranscript;
228   }
229 
setRttTranscript(RttTranscript rttTranscript)230   public void setRttTranscript(RttTranscript rttTranscript) {
231     this.rttTranscript = rttTranscript;
232   }
233 
234   private RttTranscript rttTranscript;
235 
236   private final Call.Callback telecomCallCallback =
237       new Call.Callback() {
238         @Override
239         public void onStateChanged(Call call, int newState) {
240           LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
241           update();
242         }
243 
244         @Override
245         public void onParentChanged(Call call, Call newParent) {
246           LogUtil.v(
247               "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
248           update();
249         }
250 
251         @Override
252         public void onChildrenChanged(Call call, List<Call> children) {
253           update();
254         }
255 
256         @Override
257         public void onDetailsChanged(Call call, Call.Details details) {
258           LogUtil.v(
259               "TelecomCallCallback.onDetailsChanged", " call=" + call + " details=" + details);
260           update();
261         }
262 
263         @Override
264         public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
265           LogUtil.v(
266               "TelecomCallCallback.onCannedTextResponsesLoaded",
267               "call=" + call + " cannedTextResponses=" + cannedTextResponses);
268           for (CannedTextResponsesLoadedListener listener : cannedTextResponsesLoadedListeners) {
269             listener.onCannedTextResponsesLoaded(DialerCall.this);
270           }
271         }
272 
273         @Override
274         public void onPostDialWait(Call call, String remainingPostDialSequence) {
275           LogUtil.v(
276               "TelecomCallCallback.onPostDialWait",
277               "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
278           update();
279         }
280 
281         @Override
282         public void onVideoCallChanged(Call call, VideoCall videoCall) {
283           LogUtil.v(
284               "TelecomCallCallback.onVideoCallChanged", "call=" + call + " videoCall=" + videoCall);
285           update();
286         }
287 
288         @Override
289         public void onCallDestroyed(Call call) {
290           LogUtil.v("TelecomCallCallback.onCallDestroyed", "call=" + call);
291           unregisterCallback();
292         }
293 
294         @Override
295         public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
296           LogUtil.v(
297               "TelecomCallCallback.onConferenceableCallsChanged",
298               "call %s, conferenceable calls: %d",
299               call,
300               conferenceableCalls.size());
301           update();
302         }
303 
304         @Override
305         public void onRttModeChanged(Call call, int mode) {
306           LogUtil.v("TelecomCallCallback.onRttModeChanged", "mode=%d", mode);
307         }
308 
309         @Override
310         public void onRttRequest(Call call, int id) {
311           LogUtil.v("TelecomCallCallback.onRttRequest", "id=%d", id);
312           for (DialerCallListener listener : listeners) {
313             listener.onDialerCallUpgradeToRtt(id);
314           }
315         }
316 
317         @Override
318         public void onRttInitiationFailure(Call call, int reason) {
319           LogUtil.v("TelecomCallCallback.onRttInitiationFailure", "reason=%d", reason);
320           Toast.makeText(context, R.string.rtt_call_not_available_toast, Toast.LENGTH_LONG).show();
321           update();
322         }
323 
324         @Override
325         public void onRttStatusChanged(Call call, boolean enabled, RttCall rttCall) {
326           LogUtil.v("TelecomCallCallback.onRttStatusChanged", "enabled=%b", enabled);
327           if (enabled) {
328             Logger.get(context)
329                 .logCallImpression(
330                     DialerImpression.Type.RTT_MID_CALL_ENABLED,
331                     getUniqueCallId(),
332                     getTimeAddedMs());
333           }
334           update();
335         }
336 
337         @Override
338         public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
339           LogUtil.v(
340               "TelecomCallCallback.onConnectionEvent",
341               "Call: " + call + ", Event: " + event + ", Extras: " + extras);
342           switch (event) {
343               // The Previous attempt to Merge two calls together has failed in Telecom. We must
344               // now update the UI to possibly re-enable the Merge button based on the number of
345               // currently conferenceable calls available or Connection Capabilities.
346             case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
347               update();
348               break;
349             case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
350               notifyWiFiToLteHandover();
351               break;
352             case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI:
353               onLteToWifiHandover();
354               break;
355             case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
356               notifyHandoverToWifiFailed();
357               break;
358             case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
359               isRemotelyHeld = true;
360               update();
361               break;
362             case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
363               isRemotelyHeld = false;
364               update();
365               break;
366             case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC:
367               notifyInternationalCallOnWifi();
368               break;
369             case TelephonyManagerCompat.EVENT_MERGE_START:
370               LogUtil.i("DialerCall.onConnectionEvent", "merge start");
371               isMergeInProcess = true;
372               break;
373             case TelephonyManagerCompat.EVENT_MERGE_COMPLETE:
374               LogUtil.i("DialerCall.onConnectionEvent", "merge complete");
375               isMergeInProcess = false;
376               break;
377             case TelephonyManagerCompat.EVENT_CALL_FORWARDED:
378               // Only handle this event for P+ since it's unreliable pre-P.
379               if (BuildCompat.isAtLeastP()) {
380                 isCallForwarded = true;
381                 update();
382               }
383               break;
384             default:
385               break;
386           }
387         }
388       };
389 
390   private long timeAddedMs;
391   private int peerDimensionWidth = UNKNOWN_PEER_DIMENSIONS;
392   private int peerDimensionHeight = UNKNOWN_PEER_DIMENSIONS;
393 
DialerCall( Context context, DialerCallDelegate dialerCallDelegate, Call telecomCall, LatencyReport latencyReport, boolean registerCallback)394   public DialerCall(
395       Context context,
396       DialerCallDelegate dialerCallDelegate,
397       Call telecomCall,
398       LatencyReport latencyReport,
399       boolean registerCallback) {
400     Assert.isNotNull(context);
401     this.context = context;
402     this.dialerCallDelegate = dialerCallDelegate;
403     this.telecomCall = telecomCall;
404     this.latencyReport = latencyReport;
405     id = ID_PREFIX + Integer.toString(idCounter++);
406 
407     // Must be after assigning mTelecomCall
408     videoTechManager = new VideoTechManager(this);
409 
410     updateFromTelecomCall();
411     if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) {
412       hiddenId = ++hiddenCounter;
413     } else {
414       hiddenId = 0;
415     }
416 
417     if (registerCallback) {
418       this.telecomCall.registerCallback(telecomCallCallback);
419     }
420 
421     timeAddedMs = System.currentTimeMillis();
422     parseCallSpecificAppData();
423 
424     updateEnrichedCallSession();
425   }
426 
translateState(int state)427   private static int translateState(int state) {
428     switch (state) {
429       case Call.STATE_NEW:
430       case Call.STATE_CONNECTING:
431         return DialerCallState.CONNECTING;
432       case Call.STATE_SELECT_PHONE_ACCOUNT:
433         return DialerCallState.SELECT_PHONE_ACCOUNT;
434       case Call.STATE_DIALING:
435         return DialerCallState.DIALING;
436       case Call.STATE_PULLING_CALL:
437         return DialerCallState.PULLING;
438       case Call.STATE_RINGING:
439         return DialerCallState.INCOMING;
440       case Call.STATE_ACTIVE:
441         return DialerCallState.ACTIVE;
442       case Call.STATE_HOLDING:
443         return DialerCallState.ONHOLD;
444       case Call.STATE_DISCONNECTED:
445         return DialerCallState.DISCONNECTED;
446       case Call.STATE_DISCONNECTING:
447         return DialerCallState.DISCONNECTING;
448       default:
449         return DialerCallState.INVALID;
450     }
451   }
452 
areSame(DialerCall call1, DialerCall call2)453   public static boolean areSame(DialerCall call1, DialerCall call2) {
454     if (call1 == null && call2 == null) {
455       return true;
456     } else if (call1 == null || call2 == null) {
457       return false;
458     }
459 
460     // otherwise compare call Ids
461     return call1.getId().equals(call2.getId());
462   }
463 
addListener(DialerCallListener listener)464   public void addListener(DialerCallListener listener) {
465     Assert.isMainThread();
466     listeners.add(listener);
467   }
468 
removeListener(DialerCallListener listener)469   public void removeListener(DialerCallListener listener) {
470     Assert.isMainThread();
471     listeners.remove(listener);
472   }
473 
addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener)474   public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
475     Assert.isMainThread();
476     cannedTextResponsesLoadedListeners.add(listener);
477   }
478 
removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener)479   public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
480     Assert.isMainThread();
481     cannedTextResponsesLoadedListeners.remove(listener);
482   }
483 
onLteToWifiHandover()484   private void onLteToWifiHandover() {
485     LogUtil.enterBlock("DialerCall.onLteToWifiHandover");
486     if (hasShownLteToWiFiHandoverToast) {
487       return;
488     }
489 
490     Toast.makeText(context, R.string.video_call_lte_to_wifi_handover_toast, Toast.LENGTH_LONG)
491         .show();
492     hasShownLteToWiFiHandoverToast = true;
493   }
494 
notifyWiFiToLteHandover()495   public void notifyWiFiToLteHandover() {
496     LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
497     for (DialerCallListener listener : listeners) {
498       listener.onWiFiToLteHandover();
499     }
500   }
501 
notifyHandoverToWifiFailed()502   public void notifyHandoverToWifiFailed() {
503     LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
504     for (DialerCallListener listener : listeners) {
505       listener.onHandoverToWifiFailure();
506     }
507   }
508 
notifyInternationalCallOnWifi()509   public void notifyInternationalCallOnWifi() {
510     LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
511     for (DialerCallListener dialerCallListener : listeners) {
512       dialerCallListener.onInternationalCallOnWifi();
513     }
514   }
515 
getTelecomCall()516   /* package-private */ Call getTelecomCall() {
517     return telecomCall;
518   }
519 
getStatusHints()520   public StatusHints getStatusHints() {
521     return telecomCall.getDetails().getStatusHints();
522   }
523 
getCameraDir()524   public int getCameraDir() {
525     return cameraDirection;
526   }
527 
setCameraDir(int cameraDir)528   public void setCameraDir(int cameraDir) {
529     if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
530         || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
531       cameraDirection = cameraDir;
532     } else {
533       cameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
534     }
535   }
536 
wasParentCall()537   public boolean wasParentCall() {
538     return logState.conferencedCalls != 0;
539   }
540 
isVoiceMailNumber()541   public boolean isVoiceMailNumber() {
542     return isVoicemailNumber;
543   }
544 
getCallCapableAccounts()545   public List<PhoneAccountHandle> getCallCapableAccounts() {
546     return callCapableAccounts;
547   }
548 
getCountryIso()549   public String getCountryIso() {
550     return countryIso;
551   }
552 
updateIsVoiceMailNumber()553   private void updateIsVoiceMailNumber() {
554     if (getHandle() != null && PhoneAccount.SCHEME_VOICEMAIL.equals(getHandle().getScheme())) {
555       isVoicemailNumber = true;
556       return;
557     }
558 
559     if (!PermissionsUtil.hasPermission(context, permission.READ_PHONE_STATE)) {
560       isVoicemailNumber = false;
561       return;
562     }
563 
564     isVoicemailNumber = TelecomUtil.isVoicemailNumber(context, getAccountHandle(), getNumber());
565   }
566 
update()567   private void update() {
568     Trace.beginSection("DialerCall.update");
569     int oldState = getState();
570     // Clear any cache here that could potentially change on update.
571     videoTech = null;
572     // We want to potentially register a video call callback here.
573     updateFromTelecomCall();
574     if (oldState != getState() && getState() == DialerCallState.DISCONNECTED) {
575       for (DialerCallListener listener : listeners) {
576         listener.onDialerCallDisconnect();
577       }
578       EnrichedCallComponent.get(context)
579           .getEnrichedCallManager()
580           .unregisterCapabilitiesListener(this);
581       EnrichedCallComponent.get(context)
582           .getEnrichedCallManager()
583           .unregisterStateChangedListener(this);
584     } else {
585       for (DialerCallListener listener : listeners) {
586         listener.onDialerCallUpdate();
587       }
588     }
589     Trace.endSection();
590   }
591 
592   @SuppressWarnings("MissingPermission")
updateFromTelecomCall()593   private void updateFromTelecomCall() {
594     Trace.beginSection("DialerCall.updateFromTelecomCall");
595     LogUtil.v("DialerCall.updateFromTelecomCall", telecomCall.toString());
596 
597     videoTechManager.dispatchCallStateChanged(telecomCall.getState(), getAccountHandle());
598 
599     final int translatedState = translateState(telecomCall.getState());
600     if (state != DialerCallState.BLOCKED) {
601       setState(translatedState);
602       setDisconnectCause(telecomCall.getDetails().getDisconnectCause());
603     }
604 
605     childCallIds.clear();
606     final int numChildCalls = telecomCall.getChildren().size();
607     for (int i = 0; i < numChildCalls; i++) {
608       childCallIds.add(
609           dialerCallDelegate
610               .getDialerCallFromTelecomCall(telecomCall.getChildren().get(i))
611               .getId());
612     }
613 
614     // The number of conferenced calls can change over the course of the call, so use the
615     // maximum number of conferenced child calls as the metric for conference call usage.
616     logState.conferencedCalls = Math.max(numChildCalls, logState.conferencedCalls);
617 
618     updateFromCallExtras(telecomCall.getDetails().getExtras());
619 
620     // If the handle of the call has changed, update state for the call determining if it is an
621     // emergency call.
622     Uri newHandle = telecomCall.getDetails().getHandle();
623     if (!Objects.equals(handle, newHandle)) {
624       handle = newHandle;
625       updateEmergencyCallState();
626     }
627 
628     TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
629     // If the phone account handle of the call is set, cache capability bit indicating whether
630     // the phone account supports call subjects.
631     PhoneAccountHandle newPhoneAccountHandle = telecomCall.getDetails().getAccountHandle();
632     if (!Objects.equals(phoneAccountHandle, newPhoneAccountHandle)) {
633       phoneAccountHandle = newPhoneAccountHandle;
634 
635       if (phoneAccountHandle != null) {
636         PhoneAccount phoneAccount = telecomManager.getPhoneAccount(phoneAccountHandle);
637         if (phoneAccount != null) {
638           isCallSubjectSupported =
639               phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
640           if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
641             cacheCarrierConfiguration(phoneAccountHandle);
642           }
643         }
644       }
645     }
646     if (PermissionsUtil.hasPermission(context, permission.READ_PHONE_STATE)) {
647       updateIsVoiceMailNumber();
648       callCapableAccounts = telecomManager.getCallCapablePhoneAccounts();
649       countryIso = GeoUtil.getCurrentCountryIso(context);
650     }
651     Trace.endSection();
652   }
653 
654   /**
655    * Caches frequently used carrier configuration locally.
656    *
657    * @param accountHandle The PhoneAccount handle.
658    */
659   @SuppressLint("MissingPermission")
cacheCarrierConfiguration(PhoneAccountHandle accountHandle)660   private void cacheCarrierConfiguration(PhoneAccountHandle accountHandle) {
661     if (!PermissionsUtil.hasPermission(context, permission.READ_PHONE_STATE)) {
662       return;
663     }
664     if (VERSION.SDK_INT < VERSION_CODES.O) {
665       return;
666     }
667     // TODO(a bug): This may take several seconds to complete, revisit it to move it to worker
668     // thread.
669     carrierConfig =
670         TelephonyManagerCompat.getTelephonyManagerForPhoneAccountHandle(context, accountHandle)
671             .getCarrierConfig();
672   }
673 
674   /**
675    * Tests corruption of the {@code callExtras} bundle by calling {@link
676    * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
677    * be thrown and caught by this function.
678    *
679    * @param callExtras the bundle to verify
680    * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
681    */
areCallExtrasCorrupted(Bundle callExtras)682   protected boolean areCallExtrasCorrupted(Bundle callExtras) {
683     /**
684      * There's currently a bug in Telephony service (a bug) that could corrupt the extras
685      * bundle, resulting in a IllegalArgumentException while validating data under {@link
686      * Bundle#containsKey(String)}.
687      */
688     try {
689       callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
690       return false;
691     } catch (IllegalArgumentException e) {
692       LogUtil.e(
693           "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
694       return true;
695     }
696   }
697 
updateFromCallExtras(Bundle callExtras)698   protected void updateFromCallExtras(Bundle callExtras) {
699     if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
700       /**
701        * If the bundle is corrupted, abandon information update as a work around. These are not
702        * critical for the dialer to function.
703        */
704       return;
705     }
706     // Check for a change in the child address and notify any listeners.
707     if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
708       String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
709       if (!Objects.equals(childNumber, this.childNumber)) {
710         this.childNumber = childNumber;
711         for (DialerCallListener listener : listeners) {
712           listener.onDialerCallChildNumberChange();
713         }
714       }
715     }
716 
717     // Last forwarded number comes in as an array of strings.  We want to choose the
718     // last item in the array.  The forwarding numbers arrive independently of when the
719     // call is originally set up, so we need to notify the the UI of the change.
720     if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
721       ArrayList<String> lastForwardedNumbers =
722           callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
723 
724       if (lastForwardedNumbers != null) {
725         String lastForwardedNumber = null;
726         if (!lastForwardedNumbers.isEmpty()) {
727           lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
728         }
729 
730         if (!Objects.equals(lastForwardedNumber, this.lastForwardedNumber)) {
731           this.lastForwardedNumber = lastForwardedNumber;
732           for (DialerCallListener listener : listeners) {
733             listener.onDialerCallLastForwardedNumberChange();
734           }
735         }
736       }
737     }
738 
739     // DialerCall subject is present in the extras at the start of call, so we do not need to
740     // notify any other listeners of this.
741     if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
742       String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
743       if (!Objects.equals(this.callSubject, callSubject)) {
744         this.callSubject = callSubject;
745       }
746     }
747   }
748 
getId()749   public String getId() {
750     return id;
751   }
752 
753   /**
754    * @return name appended with a number if the number is restricted/unknown and the user has
755    *     received more than one restricted/unknown call.
756    */
757   @Nullable
updateNameIfRestricted(@ullable String name)758   public String updateNameIfRestricted(@Nullable String name) {
759     if (name != null && isHiddenNumber() && hiddenId != 0 && hiddenCounter > 1) {
760       return context.getString(R.string.unknown_counter, name, hiddenId);
761     }
762     return name;
763   }
764 
clearRestrictedCount()765   public static void clearRestrictedCount() {
766     hiddenCounter = 0;
767   }
768 
isHiddenNumber()769   private boolean isHiddenNumber() {
770     return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
771         || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
772   }
773 
hasShownWiFiToLteHandoverToast()774   public boolean hasShownWiFiToLteHandoverToast() {
775     return hasShownWiFiToLteHandoverToast;
776   }
777 
setHasShownWiFiToLteHandoverToast()778   public void setHasShownWiFiToLteHandoverToast() {
779     hasShownWiFiToLteHandoverToast = true;
780   }
781 
showWifiHandoverAlertAsToast()782   public boolean showWifiHandoverAlertAsToast() {
783     return doNotShowDialogForHandoffToWifiFailure;
784   }
785 
setDoNotShowDialogForHandoffToWifiFailure(boolean bool)786   public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
787     doNotShowDialogForHandoffToWifiFailure = bool;
788   }
789 
showVideoChargesAlertDialog()790   public boolean showVideoChargesAlertDialog() {
791     if (carrierConfig == null) {
792       return false;
793     }
794     return carrierConfig.getBoolean(
795         TelephonyManagerCompat.CARRIER_CONFIG_KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL);
796   }
797 
getTimeAddedMs()798   public long getTimeAddedMs() {
799     return timeAddedMs;
800   }
801 
802   @Nullable
getNumber()803   public String getNumber() {
804     return TelecomCallUtil.getNumber(telecomCall);
805   }
806 
blockCall()807   public void blockCall() {
808     telecomCall.reject(false, null);
809     setState(DialerCallState.BLOCKED);
810   }
811 
812   @Nullable
getHandle()813   public Uri getHandle() {
814     return telecomCall == null ? null : telecomCall.getDetails().getHandle();
815   }
816 
isEmergencyCall()817   public boolean isEmergencyCall() {
818     return isEmergencyCall;
819   }
820 
isPotentialEmergencyCallback()821   public boolean isPotentialEmergencyCallback() {
822     // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
823     // is actually in emergency callback mode (ie data is disabled).
824     if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
825       return true;
826     }
827 
828     // Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS is available starting in O
829     if (VERSION.SDK_INT < VERSION_CODES.O) {
830       long timestampMillis = FilteredNumbersUtil.getLastEmergencyCallTimeMillis(context);
831       return isInEmergencyCallbackWindow(timestampMillis);
832     }
833 
834     // We want to treat any incoming call that arrives a short time after an outgoing emergency call
835     // as a potential emergency callback.
836     if (getExtras() != null
837         && getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0) > 0) {
838       long lastEmergencyCallMillis =
839           getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
840       if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
841         return true;
842       }
843     }
844     return false;
845   }
846 
isInEmergencyCallbackWindow(long timestampMillis)847   boolean isInEmergencyCallbackWindow(long timestampMillis) {
848     long emergencyCallbackWindowMillis =
849         ConfigProviderComponent.get(context)
850             .getConfigProvider()
851             .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
852     return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
853   }
854 
getState()855   public int getState() {
856     if (telecomCall != null && telecomCall.getParent() != null) {
857       return DialerCallState.CONFERENCED;
858     } else {
859       return state;
860     }
861   }
862 
getNonConferenceState()863   public int getNonConferenceState() {
864     return state;
865   }
866 
setState(int state)867   public void setState(int state) {
868     if (state == DialerCallState.INCOMING) {
869       logState.isIncoming = true;
870     }
871     updateCallTiming(state);
872 
873     this.state = state;
874   }
875 
updateCallTiming(int newState)876   private void updateCallTiming(int newState) {
877     if (newState == DialerCallState.ACTIVE) {
878       if (this.state == DialerCallState.ACTIVE) {
879         LogUtil.i("DialerCall.updateCallTiming", "state is already active");
880         return;
881       }
882       logState.dialerConnectTimeMillis = clock.currentTimeMillis();
883       logState.dialerConnectTimeMillisElapsedRealtime = SystemClock.elapsedRealtime();
884     }
885 
886     if (newState == DialerCallState.DISCONNECTED) {
887       long newDuration =
888           getConnectTimeMillis() == 0 ? 0 : clock.currentTimeMillis() - getConnectTimeMillis();
889       if (this.state == DialerCallState.DISCONNECTED) {
890         LogUtil.i(
891             "DialerCall.setState",
892             "ignoring state transition from DISCONNECTED to DISCONNECTED."
893                 + " Duration would have changed from %s to %s",
894             logState.telecomDurationMillis,
895             newDuration);
896         return;
897       }
898       logState.telecomDurationMillis = newDuration;
899       logState.dialerDurationMillis =
900           logState.dialerConnectTimeMillis == 0
901               ? 0
902               : clock.currentTimeMillis() - logState.dialerConnectTimeMillis;
903       logState.dialerDurationMillisElapsedRealtime =
904           logState.dialerConnectTimeMillisElapsedRealtime == 0
905               ? 0
906               : SystemClock.elapsedRealtime() - logState.dialerConnectTimeMillisElapsedRealtime;
907     }
908   }
909 
910   @VisibleForTesting
setClock(Clock clock)911   void setClock(Clock clock) {
912     this.clock = clock;
913   }
914 
getNumberPresentation()915   public int getNumberPresentation() {
916     return telecomCall == null ? -1 : telecomCall.getDetails().getHandlePresentation();
917   }
918 
getCnapNamePresentation()919   public int getCnapNamePresentation() {
920     return telecomCall == null ? -1 : telecomCall.getDetails().getCallerDisplayNamePresentation();
921   }
922 
923   @Nullable
getCnapName()924   public String getCnapName() {
925     return telecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
926   }
927 
getIntentExtras()928   public Bundle getIntentExtras() {
929     return telecomCall.getDetails().getIntentExtras();
930   }
931 
932   @Nullable
getExtras()933   public Bundle getExtras() {
934     return telecomCall == null ? null : telecomCall.getDetails().getExtras();
935   }
936 
937   /** @return The child number for the call, or {@code null} if none specified. */
getChildNumber()938   public String getChildNumber() {
939     return childNumber;
940   }
941 
942   /** @return The last forwarded number for the call, or {@code null} if none specified. */
getLastForwardedNumber()943   public String getLastForwardedNumber() {
944     return lastForwardedNumber;
945   }
946 
isCallForwarded()947   public boolean isCallForwarded() {
948     return isCallForwarded;
949   }
950 
951   /** @return The call subject, or {@code null} if none specified. */
getCallSubject()952   public String getCallSubject() {
953     return callSubject;
954   }
955 
956   /**
957    * @return {@code true} if the call's phone account supports call subjects, {@code false}
958    *     otherwise.
959    */
isCallSubjectSupported()960   public boolean isCallSubjectSupported() {
961     return isCallSubjectSupported;
962   }
963 
964   /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
getDisconnectCause()965   public DisconnectCause getDisconnectCause() {
966     if (state == DialerCallState.DISCONNECTED || state == DialerCallState.IDLE) {
967       return disconnectCause;
968     }
969 
970     return new DisconnectCause(DisconnectCause.UNKNOWN);
971   }
972 
setDisconnectCause(DisconnectCause disconnectCause)973   public void setDisconnectCause(DisconnectCause disconnectCause) {
974     this.disconnectCause = disconnectCause;
975     logState.disconnectCause = this.disconnectCause;
976   }
977 
978   /** Returns the possible text message responses. */
getCannedSmsResponses()979   public List<String> getCannedSmsResponses() {
980     return telecomCall.getCannedTextResponses();
981   }
982 
983   /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
984   @TargetApi(28)
can(int capabilities)985   public boolean can(int capabilities) {
986     int supportedCapabilities = telecomCall.getDetails().getCallCapabilities();
987 
988     if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
989       boolean hasConferenceableCall = false;
990       // RTT call is not conferenceable, it's a bug (a bug) in Telecom and we work around it
991       // here before it's fixed in Telecom.
992       for (Call call : telecomCall.getConferenceableCalls()) {
993         if (!(BuildCompat.isAtLeastP() && call.isRttActive())) {
994           hasConferenceableCall = true;
995           break;
996         }
997       }
998       // We allow you to merge if the capabilities allow it or if it is a call with
999       // conferenceable calls.
1000       if (!hasConferenceableCall
1001           && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
1002         // Cannot merge calls if there are no calls to merge with.
1003         return false;
1004       }
1005       capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
1006     }
1007     return (capabilities == (capabilities & supportedCapabilities));
1008   }
1009 
hasProperty(int property)1010   public boolean hasProperty(int property) {
1011     return telecomCall.getDetails().hasProperty(property);
1012   }
1013 
1014   @NonNull
getUniqueCallId()1015   public String getUniqueCallId() {
1016     return uniqueCallId;
1017   }
1018 
1019   /** Gets the time when the call first became active. */
getConnectTimeMillis()1020   public long getConnectTimeMillis() {
1021     return telecomCall.getDetails().getConnectTimeMillis();
1022   }
1023 
1024   /**
1025    * Gets the time when the call is created (see {@link Details#getCreationTimeMillis()}). This is
1026    * the same time that is logged as the start time in the Call Log (see {@link
1027    * android.provider.CallLog.Calls#DATE}).
1028    */
1029   @TargetApi(VERSION_CODES.O)
getCreationTimeMillis()1030   public long getCreationTimeMillis() {
1031     return telecomCall.getDetails().getCreationTimeMillis();
1032   }
1033 
isConferenceCall()1034   public boolean isConferenceCall() {
1035     return hasProperty(Call.Details.PROPERTY_CONFERENCE);
1036   }
1037 
1038   @Nullable
getGatewayInfo()1039   public GatewayInfo getGatewayInfo() {
1040     return telecomCall == null ? null : telecomCall.getDetails().getGatewayInfo();
1041   }
1042 
1043   @Nullable
getAccountHandle()1044   public PhoneAccountHandle getAccountHandle() {
1045     return telecomCall == null ? null : telecomCall.getDetails().getAccountHandle();
1046   }
1047 
1048   /** @return The {@link VideoCall} instance associated with the {@link Call}. */
getVideoCall()1049   public VideoCall getVideoCall() {
1050     return telecomCall == null ? null : telecomCall.getVideoCall();
1051   }
1052 
getChildCallIds()1053   public List<String> getChildCallIds() {
1054     return childCallIds;
1055   }
1056 
getParentId()1057   public String getParentId() {
1058     Call parentCall = telecomCall.getParent();
1059     if (parentCall != null) {
1060       return dialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
1061     }
1062     return null;
1063   }
1064 
getVideoState()1065   public int getVideoState() {
1066     return telecomCall.getDetails().getVideoState();
1067   }
1068 
isVideoCall()1069   public boolean isVideoCall() {
1070     return getVideoTech().isTransmittingOrReceiving() || VideoProfile.isVideo(getVideoState());
1071   }
1072 
1073   @TargetApi(28)
isActiveRttCall()1074   public boolean isActiveRttCall() {
1075     if (BuildCompat.isAtLeastP()) {
1076       return getTelecomCall().isRttActive();
1077     } else {
1078       return false;
1079     }
1080   }
1081 
1082   @TargetApi(28)
1083   @Nullable
getRttCall()1084   public RttCall getRttCall() {
1085     if (!isActiveRttCall()) {
1086       return null;
1087     }
1088     return getTelecomCall().getRttCall();
1089   }
1090 
1091   @TargetApi(28)
isPhoneAccountRttCapable()1092   public boolean isPhoneAccountRttCapable() {
1093     PhoneAccount phoneAccount = getPhoneAccount();
1094     if (phoneAccount == null) {
1095       return false;
1096     }
1097     if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
1098       return false;
1099     }
1100     return true;
1101   }
1102 
1103   @TargetApi(28)
canUpgradeToRttCall()1104   public boolean canUpgradeToRttCall() {
1105     if (!isPhoneAccountRttCapable()) {
1106       return false;
1107     }
1108     if (isActiveRttCall()) {
1109       return false;
1110     }
1111     if (isVideoCall()) {
1112       return false;
1113     }
1114     if (isConferenceCall()) {
1115       return false;
1116     }
1117     if (CallList.getInstance().hasActiveRttCall()) {
1118       return false;
1119     }
1120     return true;
1121   }
1122 
1123   @TargetApi(28)
sendRttUpgradeRequest()1124   public void sendRttUpgradeRequest() {
1125     getTelecomCall().sendRttRequest();
1126   }
1127 
1128   @TargetApi(28)
respondToRttRequest(boolean accept, int rttRequestId)1129   public void respondToRttRequest(boolean accept, int rttRequestId) {
1130     Logger.get(context)
1131         .logCallImpression(
1132             accept
1133                 ? DialerImpression.Type.RTT_MID_CALL_ACCEPTED
1134                 : DialerImpression.Type.RTT_MID_CALL_REJECTED,
1135             getUniqueCallId(),
1136             getTimeAddedMs());
1137     getTelecomCall().respondToRttRequest(rttRequestId, accept);
1138   }
1139 
1140   @TargetApi(28)
saveRttTranscript()1141   private void saveRttTranscript() {
1142     if (!BuildCompat.isAtLeastP()) {
1143       return;
1144     }
1145     if (getRttCall() != null) {
1146       // Save any remaining text in the buffer that's not shown by UI yet.
1147       // This may happen when the call is switched to background before disconnect.
1148       try {
1149         String messageLeft = getRttCall().readImmediately();
1150         if (!TextUtils.isEmpty(messageLeft)) {
1151           rttTranscript =
1152               RttChatMessage.getRttTranscriptWithNewRemoteMessage(rttTranscript, messageLeft);
1153         }
1154       } catch (IOException e) {
1155         LogUtil.e("DialerCall.saveRttTranscript", "error when reading remaining message", e);
1156       }
1157     }
1158     // Don't save transcript if it's empty.
1159     if (rttTranscript.getMessagesCount() == 0) {
1160       return;
1161     }
1162     Futures.addCallback(
1163         RttTranscriptUtil.saveRttTranscript(context, rttTranscript),
1164         new DefaultFutureCallback<>(),
1165         MoreExecutors.directExecutor());
1166   }
1167 
hasReceivedVideoUpgradeRequest()1168   public boolean hasReceivedVideoUpgradeRequest() {
1169     return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
1170   }
1171 
hasSentVideoUpgradeRequest()1172   public boolean hasSentVideoUpgradeRequest() {
1173     return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
1174   }
1175 
hasSentRttUpgradeRequest()1176   public boolean hasSentRttUpgradeRequest() {
1177     return false;
1178   }
1179 
1180   /**
1181    * Determines if the call handle is an emergency number or not and caches the result to avoid
1182    * repeated calls to isEmergencyNumber.
1183    */
updateEmergencyCallState()1184   private void updateEmergencyCallState() {
1185     isEmergencyCall = TelecomCallUtil.isEmergencyCall(telecomCall);
1186   }
1187 
getLogState()1188   public LogState getLogState() {
1189     return logState;
1190   }
1191 
1192   /**
1193    * Determines if the call is an external call.
1194    *
1195    * <p>An external call is one which does not exist locally for the {@link
1196    * android.telecom.ConnectionService} it is associated with.
1197    *
1198    * @return {@code true} if the call is an external call, {@code false} otherwise.
1199    */
isExternalCall()1200   boolean isExternalCall() {
1201     return hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
1202   }
1203 
1204   /**
1205    * Determines if answering this call will cause an ongoing video call to be dropped.
1206    *
1207    * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
1208    *     otherwise.
1209    */
answeringDisconnectsForegroundVideoCall()1210   public boolean answeringDisconnectsForegroundVideoCall() {
1211     Bundle extras = getExtras();
1212     if (extras == null
1213         || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
1214       return false;
1215     }
1216     return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
1217   }
1218 
parseCallSpecificAppData()1219   private void parseCallSpecificAppData() {
1220     if (isExternalCall()) {
1221       return;
1222     }
1223 
1224     logState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
1225     if (logState.callSpecificAppData == null) {
1226 
1227       logState.callSpecificAppData =
1228           CallSpecificAppData.newBuilder()
1229               .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION)
1230               .build();
1231     }
1232     if (getState() == DialerCallState.INCOMING) {
1233       logState.callSpecificAppData =
1234           logState
1235               .callSpecificAppData
1236               .toBuilder()
1237               .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION)
1238               .build();
1239     }
1240   }
1241 
1242   @Override
toString()1243   public String toString() {
1244     if (telecomCall == null) {
1245       // This should happen only in testing since otherwise we would never have a null
1246       // Telecom call.
1247       return String.valueOf(id);
1248     }
1249 
1250     return String.format(
1251         Locale.US,
1252         "[%s, %s, %s, %s, children:%s, parent:%s, "
1253             + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
1254         id,
1255         DialerCallState.toString(getState()),
1256         Details.capabilitiesToString(telecomCall.getDetails().getCallCapabilities()),
1257         Details.propertiesToString(telecomCall.getDetails().getCallProperties()),
1258         childCallIds,
1259         getParentId(),
1260         this.telecomCall.getConferenceableCalls(),
1261         VideoProfile.videoStateToString(telecomCall.getDetails().getVideoState()),
1262         getVideoTech().getSessionModificationState(),
1263         getCameraDir());
1264   }
1265 
toSimpleString()1266   public String toSimpleString() {
1267     return super.toString();
1268   }
1269 
1270   @CallHistoryStatus
getCallHistoryStatus()1271   public int getCallHistoryStatus() {
1272     return callHistoryStatus;
1273   }
1274 
setCallHistoryStatus(@allHistoryStatus int callHistoryStatus)1275   public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
1276     this.callHistoryStatus = callHistoryStatus;
1277   }
1278 
didShowCameraPermission()1279   public boolean didShowCameraPermission() {
1280     return didShowCameraPermission;
1281   }
1282 
setDidShowCameraPermission(boolean didShow)1283   public void setDidShowCameraPermission(boolean didShow) {
1284     didShowCameraPermission = didShow;
1285   }
1286 
didDismissVideoChargesAlertDialog()1287   public boolean didDismissVideoChargesAlertDialog() {
1288     return didDismissVideoChargesAlertDialog;
1289   }
1290 
setDidDismissVideoChargesAlertDialog(boolean didDismiss)1291   public void setDidDismissVideoChargesAlertDialog(boolean didDismiss) {
1292     didDismissVideoChargesAlertDialog = didDismiss;
1293   }
1294 
setSpamStatus(@ullable SpamStatus spamStatus)1295   public void setSpamStatus(@Nullable SpamStatus spamStatus) {
1296     this.spamStatus = spamStatus;
1297   }
1298 
getSpamStatus()1299   public Optional<SpamStatus> getSpamStatus() {
1300     return Optional.fromNullable(spamStatus);
1301   }
1302 
isSpam()1303   public boolean isSpam() {
1304     if (spamStatus == null || !spamStatus.isSpam()) {
1305       return false;
1306     }
1307 
1308     if (!isIncoming()) {
1309       return false;
1310     }
1311 
1312     if (isPotentialEmergencyCallback()) {
1313       return false;
1314     }
1315 
1316     return true;
1317   }
1318 
isBlocked()1319   public boolean isBlocked() {
1320     return isBlocked;
1321   }
1322 
setBlockedStatus(boolean isBlocked)1323   public void setBlockedStatus(boolean isBlocked) {
1324     this.isBlocked = isBlocked;
1325   }
1326 
isRemotelyHeld()1327   public boolean isRemotelyHeld() {
1328     return isRemotelyHeld;
1329   }
1330 
isMergeInProcess()1331   public boolean isMergeInProcess() {
1332     return isMergeInProcess;
1333   }
1334 
isIncoming()1335   public boolean isIncoming() {
1336     return logState.isIncoming;
1337   }
1338 
1339   /**
1340    * Try and determine if the call used assisted dialing.
1341    *
1342    * <p>We will not be able to verify a call underwent assisted dialing until the Platform
1343    * implmentation is complete in P+.
1344    *
1345    * @return a boolean indicating assisted dialing may have been performed
1346    */
isAssistedDialed()1347   public boolean isAssistedDialed() {
1348     if (getIntentExtras() != null) {
1349       // P and below uses the existence of USE_ASSISTED_DIALING to indicate assisted dialing
1350       // was used. The Dialer client is responsible for performing assisted dialing before
1351       // placing the outgoing call.
1352       //
1353       // The existence of the assisted dialing extras indicates that assisted dialing took place.
1354       if (getIntentExtras().getBoolean(TelephonyManagerCompat.USE_ASSISTED_DIALING, false)
1355           && getAssistedDialingExtras() != null
1356           && Build.VERSION.SDK_INT <= ConcreteCreator.BUILD_CODE_CEILING) {
1357         return true;
1358       }
1359     }
1360 
1361     return false;
1362   }
1363 
1364   @Nullable
getAssistedDialingExtras()1365   public TransformationInfo getAssistedDialingExtras() {
1366     if (getIntentExtras() == null) {
1367       return null;
1368     }
1369 
1370     if (getIntentExtras().getBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS) == null) {
1371       return null;
1372     }
1373 
1374     // Used in N-OMR1
1375     return TransformationInfo.newInstanceFromBundle(
1376         getIntentExtras().getBundle(TelephonyManagerCompat.ASSISTED_DIALING_EXTRAS));
1377   }
1378 
getLatencyReport()1379   public LatencyReport getLatencyReport() {
1380     return latencyReport;
1381   }
1382 
getAnswerAndReleaseButtonDisplayedTimes()1383   public int getAnswerAndReleaseButtonDisplayedTimes() {
1384     return answerAndReleaseButtonDisplayedTimes;
1385   }
1386 
increaseAnswerAndReleaseButtonDisplayedTimes()1387   public void increaseAnswerAndReleaseButtonDisplayedTimes() {
1388     answerAndReleaseButtonDisplayedTimes++;
1389   }
1390 
getReleasedByAnsweringSecondCall()1391   public boolean getReleasedByAnsweringSecondCall() {
1392     return releasedByAnsweringSecondCall;
1393   }
1394 
setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall)1395   public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) {
1396     this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall;
1397   }
1398 
getSecondCallWithoutAnswerAndReleasedButtonTimes()1399   public int getSecondCallWithoutAnswerAndReleasedButtonTimes() {
1400     return secondCallWithoutAnswerAndReleasedButtonTimes;
1401   }
1402 
increaseSecondCallWithoutAnswerAndReleasedButtonTimes()1403   public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() {
1404     secondCallWithoutAnswerAndReleasedButtonTimes++;
1405   }
1406 
1407   @Nullable
getEnrichedCallCapabilities()1408   public EnrichedCallCapabilities getEnrichedCallCapabilities() {
1409     return enrichedCallCapabilities;
1410   }
1411 
setEnrichedCallCapabilities( @ullable EnrichedCallCapabilities mEnrichedCallCapabilities)1412   public void setEnrichedCallCapabilities(
1413       @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
1414     this.enrichedCallCapabilities = mEnrichedCallCapabilities;
1415   }
1416 
1417   @Nullable
getEnrichedCallSession()1418   public Session getEnrichedCallSession() {
1419     return enrichedCallSession;
1420   }
1421 
setEnrichedCallSession(@ullable Session mEnrichedCallSession)1422   public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
1423     this.enrichedCallSession = mEnrichedCallSession;
1424   }
1425 
unregisterCallback()1426   public void unregisterCallback() {
1427     telecomCall.unregisterCallback(telecomCallCallback);
1428   }
1429 
phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault)1430   public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
1431     LogUtil.i(
1432         "DialerCall.phoneAccountSelected",
1433         "accountHandle: %s, setDefault: %b",
1434         accountHandle,
1435         setDefault);
1436     telecomCall.phoneAccountSelected(accountHandle, setDefault);
1437   }
1438 
disconnect()1439   public void disconnect() {
1440     LogUtil.i("DialerCall.disconnect", "");
1441     setState(DialerCallState.DISCONNECTING);
1442     for (DialerCallListener listener : listeners) {
1443       listener.onDialerCallUpdate();
1444     }
1445     telecomCall.disconnect();
1446   }
1447 
hold()1448   public void hold() {
1449     LogUtil.i("DialerCall.hold", "");
1450     telecomCall.hold();
1451   }
1452 
unhold()1453   public void unhold() {
1454     LogUtil.i("DialerCall.unhold", "");
1455     telecomCall.unhold();
1456   }
1457 
splitFromConference()1458   public void splitFromConference() {
1459     LogUtil.i("DialerCall.splitFromConference", "");
1460     telecomCall.splitFromConference();
1461   }
1462 
answer(int videoState)1463   public void answer(int videoState) {
1464     LogUtil.i("DialerCall.answer", "videoState: " + videoState);
1465     telecomCall.answer(videoState);
1466   }
1467 
answer()1468   public void answer() {
1469     answer(telecomCall.getDetails().getVideoState());
1470   }
1471 
reject(boolean rejectWithMessage, String message)1472   public void reject(boolean rejectWithMessage, String message) {
1473     LogUtil.i("DialerCall.reject", "");
1474     telecomCall.reject(rejectWithMessage, message);
1475   }
1476 
1477   /** Return the string label to represent the call provider */
getCallProviderLabel()1478   public String getCallProviderLabel() {
1479     if (callProviderLabel == null) {
1480       PhoneAccount account = getPhoneAccount();
1481       if (account != null && !TextUtils.isEmpty(account.getLabel())) {
1482         if (callCapableAccounts != null && callCapableAccounts.size() > 1) {
1483           callProviderLabel = account.getLabel().toString();
1484         }
1485       }
1486       if (callProviderLabel == null) {
1487         callProviderLabel = "";
1488       }
1489     }
1490     return callProviderLabel;
1491   }
1492 
getPhoneAccount()1493   private PhoneAccount getPhoneAccount() {
1494     PhoneAccountHandle accountHandle = getAccountHandle();
1495     if (accountHandle == null) {
1496       return null;
1497     }
1498     return context.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1499   }
1500 
getVideoTech()1501   public VideoTech getVideoTech() {
1502     if (videoTech == null) {
1503       videoTech = videoTechManager.getVideoTech(getAccountHandle());
1504 
1505       // Only store the first video tech type found to be available during the life of the call.
1506       if (selectedAvailableVideoTechType == com.android.dialer.logging.VideoTech.Type.NONE) {
1507         // Update the video tech.
1508         selectedAvailableVideoTechType = videoTech.getVideoTechType();
1509       }
1510     }
1511     return videoTech;
1512   }
1513 
getCallbackNumber()1514   public String getCallbackNumber() {
1515     if (callbackNumber == null) {
1516       // Show the emergency callback number if either:
1517       // 1. This is an emergency call.
1518       // 2. The phone is in Emergency Callback Mode, which means we should show the callback
1519       //    number.
1520       boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
1521 
1522       if (isEmergencyCall() || showCallbackNumber) {
1523         callbackNumber =
1524             context.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
1525       }
1526 
1527       if (callbackNumber == null) {
1528         callbackNumber = "";
1529       }
1530     }
1531     return callbackNumber;
1532   }
1533 
getSimCountryIso()1534   public String getSimCountryIso() {
1535     String simCountryIso =
1536         TelephonyManagerCompat.getTelephonyManagerForPhoneAccountHandle(context, getAccountHandle())
1537             .getSimCountryIso();
1538     if (!TextUtils.isEmpty(simCountryIso)) {
1539       simCountryIso = simCountryIso.toUpperCase(Locale.US);
1540     }
1541     return simCountryIso;
1542   }
1543 
1544   @Override
onVideoTechStateChanged()1545   public void onVideoTechStateChanged() {
1546     update();
1547   }
1548 
1549   @Override
onSessionModificationStateChanged()1550   public void onSessionModificationStateChanged() {
1551     Trace.beginSection("DialerCall.onSessionModificationStateChanged");
1552     for (DialerCallListener listener : listeners) {
1553       listener.onDialerCallSessionModificationStateChange();
1554     }
1555     Trace.endSection();
1556   }
1557 
1558   @Override
onCameraDimensionsChanged(int width, int height)1559   public void onCameraDimensionsChanged(int width, int height) {
1560     InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1561   }
1562 
1563   @Override
onPeerDimensionsChanged(int width, int height)1564   public void onPeerDimensionsChanged(int width, int height) {
1565     peerDimensionWidth = width;
1566     peerDimensionHeight = height;
1567     InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1568   }
1569 
1570   @Override
onVideoUpgradeRequestReceived()1571   public void onVideoUpgradeRequestReceived() {
1572     LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1573 
1574     for (DialerCallListener listener : listeners) {
1575       listener.onDialerCallUpgradeToVideo();
1576     }
1577 
1578     update();
1579 
1580     Logger.get(context)
1581         .logCallImpression(
1582             DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs());
1583   }
1584 
1585   @Override
onUpgradedToVideo(boolean switchToSpeaker)1586   public void onUpgradedToVideo(boolean switchToSpeaker) {
1587     LogUtil.enterBlock("DialerCall.onUpgradedToVideo");
1588 
1589     if (!switchToSpeaker) {
1590       return;
1591     }
1592 
1593     CallAudioState audioState = AudioModeProvider.getInstance().getAudioState();
1594 
1595     if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
1596       LogUtil.e(
1597           "DialerCall.onUpgradedToVideo",
1598           "toggling speakerphone not allowed when bluetooth supported.");
1599       return;
1600     }
1601 
1602     if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
1603       return;
1604     }
1605 
1606     TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
1607   }
1608 
1609   @Override
onCapabilitiesUpdated()1610   public void onCapabilitiesUpdated() {
1611     if (getNumber() == null) {
1612       return;
1613     }
1614     EnrichedCallCapabilities capabilities =
1615         EnrichedCallComponent.get(context).getEnrichedCallManager().getCapabilities(getNumber());
1616     if (capabilities != null) {
1617       setEnrichedCallCapabilities(capabilities);
1618       update();
1619     }
1620   }
1621 
1622   @Override
onEnrichedCallStateChanged()1623   public void onEnrichedCallStateChanged() {
1624     updateEnrichedCallSession();
1625   }
1626 
1627   @Override
onImpressionLoggingNeeded(DialerImpression.Type impressionType)1628   public void onImpressionLoggingNeeded(DialerImpression.Type impressionType) {
1629     Logger.get(context).logCallImpression(impressionType, getUniqueCallId(), getTimeAddedMs());
1630     if (impressionType == DialerImpression.Type.LIGHTBRINGER_UPGRADE_REQUESTED) {
1631       if (getLogState().contactLookupResult == Type.NOT_FOUND) {
1632         Logger.get(context)
1633             .logCallImpression(
1634                 DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_UPGRADE_REQUESTED,
1635                 getUniqueCallId(),
1636                 getTimeAddedMs());
1637       }
1638     }
1639   }
1640 
updateEnrichedCallSession()1641   private void updateEnrichedCallSession() {
1642     if (getNumber() == null) {
1643       return;
1644     }
1645     if (getEnrichedCallSession() != null) {
1646       // State changes to existing sessions are currently handled by the UI components (which have
1647       // their own listeners). Someday instead we could remove those and just call update() here and
1648       // have the usual onDialerCallUpdate update the UI.
1649       dispatchOnEnrichedCallSessionUpdate();
1650       return;
1651     }
1652 
1653     EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager();
1654 
1655     Filter filter =
1656         isIncoming()
1657             ? manager.createIncomingCallComposerFilter()
1658             : manager.createOutgoingCallComposerFilter();
1659 
1660     Session session = manager.getSession(getUniqueCallId(), getNumber(), filter);
1661     if (session == null) {
1662       return;
1663     }
1664 
1665     session.setUniqueDialerCallId(getUniqueCallId());
1666     setEnrichedCallSession(session);
1667 
1668     LogUtil.i(
1669         "DialerCall.updateEnrichedCallSession",
1670         "setting session %d's dialer id to %s",
1671         session.getSessionId(),
1672         getUniqueCallId());
1673 
1674     dispatchOnEnrichedCallSessionUpdate();
1675   }
1676 
dispatchOnEnrichedCallSessionUpdate()1677   private void dispatchOnEnrichedCallSessionUpdate() {
1678     for (DialerCallListener listener : listeners) {
1679       listener.onEnrichedCallSessionUpdate();
1680     }
1681   }
1682 
onRemovedFromCallList()1683   void onRemovedFromCallList() {
1684     LogUtil.enterBlock("DialerCall.onRemovedFromCallList");
1685     // Ensure we clean up when this call is removed.
1686     if (videoTechManager != null) {
1687       videoTechManager.dispatchRemovedFromCallList();
1688     }
1689     // TODO(wangqi): Consider moving this to a DialerCallListener.
1690     if (rttTranscript != null && !isCallRemoved) {
1691       saveRttTranscript();
1692     }
1693     isCallRemoved = true;
1694   }
1695 
getSelectedAvailableVideoTechType()1696   public com.android.dialer.logging.VideoTech.Type getSelectedAvailableVideoTechType() {
1697     return selectedAvailableVideoTechType;
1698   }
1699 
markFeedbackRequested()1700   public void markFeedbackRequested() {
1701     feedbackRequested = true;
1702   }
1703 
isFeedbackRequested()1704   public boolean isFeedbackRequested() {
1705     return feedbackRequested;
1706   }
1707 
1708   /**
1709    * If the in call UI has shown the phone account selection dialog for the call, the {@link
1710    * PreferredAccountRecorder} to record the result from the dialog.
1711    */
1712   @Nullable
getPreferredAccountRecorder()1713   public PreferredAccountRecorder getPreferredAccountRecorder() {
1714     return preferredAccountRecorder;
1715   }
1716 
setPreferredAccountRecorder(PreferredAccountRecorder preferredAccountRecorder)1717   public void setPreferredAccountRecorder(PreferredAccountRecorder preferredAccountRecorder) {
1718     this.preferredAccountRecorder = preferredAccountRecorder;
1719   }
1720 
1721   /** Indicates the call is eligible for SpeakEasy */
isSpeakEasyEligible()1722   public boolean isSpeakEasyEligible() {
1723 
1724     PhoneAccount phoneAccount = getPhoneAccount();
1725 
1726     if (phoneAccount == null) {
1727       return false;
1728     }
1729 
1730     if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
1731       return false;
1732     }
1733 
1734     return !isPotentialEmergencyCallback()
1735         && !isEmergencyCall()
1736         && !isActiveRttCall()
1737         && !isConferenceCall()
1738         && !isVideoCall()
1739         && !isVoiceMailNumber()
1740         && !hasReceivedVideoUpgradeRequest()
1741         && !isVoipCallNotSupportedBySpeakeasy();
1742   }
1743 
isVoipCallNotSupportedBySpeakeasy()1744   private boolean isVoipCallNotSupportedBySpeakeasy() {
1745     Bundle extras = getIntentExtras();
1746 
1747     if (extras == null) {
1748       return false;
1749     }
1750 
1751     // Indicates an VOIP call.
1752     String callid = extras.getString("callid");
1753 
1754     if (TextUtils.isEmpty(callid)) {
1755       LogUtil.i("DialerCall.isVoipCallNotSupportedBySpeakeasy", "callid was empty");
1756       return false;
1757     }
1758 
1759     LogUtil.i("DialerCall.isVoipCallNotSupportedBySpeakeasy", "call is not eligible");
1760     return true;
1761   }
1762 
1763   /** Indicates the user has selected SpeakEasy */
isSpeakEasyCall()1764   public boolean isSpeakEasyCall() {
1765     if (!isSpeakEasyEligible()) {
1766       return false;
1767     }
1768     return isSpeakEasyCall;
1769   }
1770 
1771   /** Sets the user preference for SpeakEasy */
setIsSpeakEasyCall(boolean isSpeakEasyCall)1772   public void setIsSpeakEasyCall(boolean isSpeakEasyCall) {
1773     this.isSpeakEasyCall = isSpeakEasyCall;
1774     if (listeners != null) {
1775       for (DialerCallListener listener : listeners) {
1776         listener.onDialerCallSpeakEasyStateChange();
1777       }
1778     }
1779   }
1780 
1781   /**
1782    * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1783    * means there is no result.
1784    */
1785   @IntDef({
1786     CALL_HISTORY_STATUS_UNKNOWN,
1787     CALL_HISTORY_STATUS_PRESENT,
1788     CALL_HISTORY_STATUS_NOT_PRESENT
1789   })
1790   @Retention(RetentionPolicy.SOURCE)
1791   public @interface CallHistoryStatus {}
1792 
1793   /** Camera direction constants */
1794   public static class CameraDirection {
1795     public static final int CAMERA_DIRECTION_UNKNOWN = -1;
1796     public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT;
1797     public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK;
1798   }
1799 
1800   /**
1801    * Tracks any state variables that is useful for logging. There is some amount of overlap with
1802    * existing call member variables, but this duplication helps to ensure that none of these logging
1803    * variables will interface with/and affect call logic.
1804    */
1805   public static class LogState {
1806 
1807     public DisconnectCause disconnectCause;
1808     public boolean isIncoming = false;
1809     public ContactLookupResult.Type contactLookupResult =
1810         ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
1811     public CallSpecificAppData callSpecificAppData;
1812     // If this was a conference call, the total number of calls involved in the conference.
1813     public int conferencedCalls = 0;
1814     public boolean isLogged = false;
1815 
1816     // Result of subtracting android.telecom.Call.Details#getConnectTimeMillis from the current time
1817     public long telecomDurationMillis = 0;
1818 
1819     // Result of a call to System.currentTimeMillis when Dialer sees that a call
1820     // moves to the ACTIVE state
1821     long dialerConnectTimeMillis = 0;
1822 
1823     // Same as dialer_connect_time_millis, using SystemClock.elapsedRealtime
1824     // instead
1825     long dialerConnectTimeMillisElapsedRealtime = 0;
1826 
1827     // Result of subtracting dialer_connect_time_millis from System.currentTimeMillis
1828     public long dialerDurationMillis = 0;
1829 
1830     // Same as dialerDurationMillis, using SystemClock.elapsedRealtime instead
1831     public long dialerDurationMillisElapsedRealtime = 0;
1832 
lookupToString(ContactLookupResult.Type lookupType)1833     private static String lookupToString(ContactLookupResult.Type lookupType) {
1834       switch (lookupType) {
1835         case LOCAL_CONTACT:
1836           return "Local";
1837         case LOCAL_CACHE:
1838           return "Cache";
1839         case REMOTE:
1840           return "Remote";
1841         case EMERGENCY:
1842           return "Emergency";
1843         case VOICEMAIL:
1844           return "Voicemail";
1845         default:
1846           return "Not found";
1847       }
1848     }
1849 
initiationToString(CallSpecificAppData callSpecificAppData)1850     private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1851       if (callSpecificAppData == null) {
1852         return "null";
1853       }
1854       switch (callSpecificAppData.getCallInitiationType()) {
1855         case INCOMING_INITIATION:
1856           return "Incoming";
1857         case DIALPAD:
1858           return "Dialpad";
1859         case SPEED_DIAL:
1860           return "Speed Dial";
1861         case REMOTE_DIRECTORY:
1862           return "Remote Directory";
1863         case SMART_DIAL:
1864           return "Smart Dial";
1865         case REGULAR_SEARCH:
1866           return "Regular Search";
1867         case CALL_LOG:
1868           return "DialerCall Log";
1869         case CALL_LOG_FILTER:
1870           return "DialerCall Log Filter";
1871         case VOICEMAIL_LOG:
1872           return "Voicemail Log";
1873         case CALL_DETAILS:
1874           return "DialerCall Details";
1875         case QUICK_CONTACTS:
1876           return "Quick Contacts";
1877         case EXTERNAL_INITIATION:
1878           return "External";
1879         case LAUNCHER_SHORTCUT:
1880           return "Launcher Shortcut";
1881         default:
1882           return "Unknown: " + callSpecificAppData.getCallInitiationType();
1883       }
1884     }
1885 
1886     @Override
toString()1887     public String toString() {
1888       return String.format(
1889           Locale.US,
1890           "["
1891               + "%s, " // DisconnectCause toString already describes the object type
1892               + "isIncoming: %s, "
1893               + "contactLookup: %s, "
1894               + "callInitiation: %s, "
1895               + "duration: %s"
1896               + "]",
1897           disconnectCause,
1898           isIncoming,
1899           lookupToString(contactLookupResult),
1900           initiationToString(callSpecificAppData),
1901           telecomDurationMillis);
1902     }
1903   }
1904 
1905   /** Coordinates the available VideoTech implementations for a call. */
1906   @VisibleForTesting
1907   public static class VideoTechManager {
1908     private final Context context;
1909     private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
1910     private final VideoTech rcsVideoShare;
1911     private final List<VideoTech> videoTechs;
1912     private VideoTech savedTech;
1913 
1914     @VisibleForTesting
VideoTechManager(DialerCall call)1915     public VideoTechManager(DialerCall call) {
1916       this.context = call.context;
1917 
1918       String phoneNumber = call.getNumber();
1919       phoneNumber = phoneNumber != null ? phoneNumber : "";
1920       phoneNumber = phoneNumber.replaceAll("[^+0-9]", "");
1921 
1922       // Insert order here determines the priority of that video tech option
1923       videoTechs = new ArrayList<>();
1924 
1925       videoTechs.add(new ImsVideoTech(Logger.get(call.context), call, call.telecomCall));
1926 
1927       rcsVideoShare =
1928           EnrichedCallComponent.get(call.context)
1929               .getRcsVideoShareFactory()
1930               .newRcsVideoShare(
1931                   EnrichedCallComponent.get(call.context).getEnrichedCallManager(),
1932                   call,
1933                   phoneNumber);
1934       videoTechs.add(rcsVideoShare);
1935 
1936       videoTechs.add(
1937           new DuoVideoTech(
1938               DuoComponent.get(call.context).getDuo(), call, call.telecomCall, phoneNumber));
1939 
1940       savedTech = emptyVideoTech;
1941     }
1942 
1943     @VisibleForTesting
getVideoTech(PhoneAccountHandle phoneAccountHandle)1944     public VideoTech getVideoTech(PhoneAccountHandle phoneAccountHandle) {
1945       if (savedTech == emptyVideoTech) {
1946         for (VideoTech tech : videoTechs) {
1947           if (tech.isAvailable(context, phoneAccountHandle)) {
1948             savedTech = tech;
1949             savedTech.becomePrimary();
1950             break;
1951           }
1952         }
1953       } else if (savedTech instanceof DuoVideoTech
1954           && rcsVideoShare.isAvailable(context, phoneAccountHandle)) {
1955         // RCS Video Share will become available after the capability exchange which is slower than
1956         // Duo reading local contacts for reachability. If Video Share becomes available and we are
1957         // not in the middle of any session changes, let it take over.
1958         savedTech = rcsVideoShare;
1959         rcsVideoShare.becomePrimary();
1960       }
1961 
1962       return savedTech;
1963     }
1964 
1965     @VisibleForTesting
dispatchCallStateChanged(int newState, PhoneAccountHandle phoneAccountHandle)1966     public void dispatchCallStateChanged(int newState, PhoneAccountHandle phoneAccountHandle) {
1967       for (VideoTech videoTech : videoTechs) {
1968         videoTech.onCallStateChanged(context, newState, phoneAccountHandle);
1969       }
1970     }
1971 
dispatchRemovedFromCallList()1972     void dispatchRemovedFromCallList() {
1973       for (VideoTech videoTech : videoTechs) {
1974         videoTech.onRemovedFromCallList();
1975       }
1976     }
1977   }
1978 
1979   /** Called when canned text responses have been loaded. */
1980   public interface CannedTextResponsesLoadedListener {
onCannedTextResponsesLoaded(DialerCall call)1981     void onCannedTextResponsesLoaded(DialerCall call);
1982   }
1983 
1984   /** Gets peer dimension width. */
getPeerDimensionWidth()1985   public int getPeerDimensionWidth() {
1986     return peerDimensionWidth;
1987   }
1988 
1989   /** Gets peer dimension height. */
getPeerDimensionHeight()1990   public int getPeerDimensionHeight() {
1991     return peerDimensionHeight;
1992   }
1993 }
1994