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