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