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