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