Home | History | Annotate | Download | only in call
      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 
    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 
    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
    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 
    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 
    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 
    423   public void addListener(DialerCallListener listener) {
    424     Assert.isMainThread();
    425     listeners.add(listener);
    426   }
    427 
    428   public void removeListener(DialerCallListener listener) {
    429     Assert.isMainThread();
    430     listeners.remove(listener);
    431   }
    432 
    433   public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
    434     Assert.isMainThread();
    435     cannedTextResponsesLoadedListeners.add(listener);
    436   }
    437 
    438   public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
    439     Assert.isMainThread();
    440     cannedTextResponsesLoadedListeners.remove(listener);
    441   }
    442 
    443   public void notifyWiFiToLteHandover() {
    444     LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
    445     for (DialerCallListener listener : listeners) {
    446       listener.onWiFiToLteHandover();
    447     }
    448   }
    449 
    450   public void notifyHandoverToWifiFailed() {
    451     LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
    452     for (DialerCallListener listener : listeners) {
    453       listener.onHandoverToWifiFailure();
    454     }
    455   }
    456 
    457   public void notifyInternationalCallOnWifi() {
    458     LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
    459     for (DialerCallListener dialerCallListener : listeners) {
    460       dialerCallListener.onInternationalCallOnWifi();
    461     }
    462   }
    463 
    464   /* package-private */ Call getTelecomCall() {
    465     return telecomCall;
    466   }
    467 
    468   public StatusHints getStatusHints() {
    469     return telecomCall.getDetails().getStatusHints();
    470   }
    471 
    472   public int getCameraDir() {
    473     return cameraDirection;
    474   }
    475 
    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 
    485   public boolean wasParentCall() {
    486     return logState.conferencedCalls != 0;
    487   }
    488 
    489   public boolean isVoiceMailNumber() {
    490     return isVoicemailNumber;
    491   }
    492 
    493   public List<PhoneAccountHandle> getCallCapableAccounts() {
    494     return callCapableAccounts;
    495   }
    496 
    497   public String getCountryIso() {
    498     return countryIso;
    499   }
    500 
    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 
    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")
    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    */
    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 
    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 
    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
    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 
    690   public static void clearRestrictedCount() {
    691     hiddenCounter = 0;
    692   }
    693 
    694   private boolean isHiddenNumber() {
    695     return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
    696         || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
    697   }
    698 
    699   public boolean hasShownWiFiToLteHandoverToast() {
    700     return hasShownWiFiToLteHandoverToast;
    701   }
    702 
    703   public void setHasShownWiFiToLteHandoverToast() {
    704     hasShownWiFiToLteHandoverToast = true;
    705   }
    706 
    707   public boolean showWifiHandoverAlertAsToast() {
    708     return doNotShowDialogForHandoffToWifiFailure;
    709   }
    710 
    711   public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
    712     doNotShowDialogForHandoffToWifiFailure = bool;
    713   }
    714 
    715   public long getTimeAddedMs() {
    716     return timeAddedMs;
    717   }
    718 
    719   @Nullable
    720   public String getNumber() {
    721     return TelecomCallUtil.getNumber(telecomCall);
    722   }
    723 
    724   public void blockCall() {
    725     telecomCall.reject(false, null);
    726     setState(State.BLOCKED);
    727   }
    728 
    729   @Nullable
    730   public Uri getHandle() {
    731     return telecomCall == null ? null : telecomCall.getDetails().getHandle();
    732   }
    733 
    734   public boolean isEmergencyCall() {
    735     return isEmergencyCall;
    736   }
    737 
    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 
    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 
    765   public int getState() {
    766     if (telecomCall != null && telecomCall.getParent() != null) {
    767       return State.CONFERENCED;
    768     } else {
    769       return state;
    770     }
    771   }
    772 
    773   public int getNonConferenceState() {
    774     return state;
    775   }
    776 
    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 
    797   public int getNumberPresentation() {
    798     return telecomCall == null ? -1 : telecomCall.getDetails().getHandlePresentation();
    799   }
    800 
    801   public int getCnapNamePresentation() {
    802     return telecomCall == null ? -1 : telecomCall.getDetails().getCallerDisplayNamePresentation();
    803   }
    804 
    805   @Nullable
    806   public String getCnapName() {
    807     return telecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
    808   }
    809 
    810   public Bundle getIntentExtras() {
    811     return telecomCall.getDetails().getIntentExtras();
    812   }
    813 
    814   @Nullable
    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. */
    820   public String getChildNumber() {
    821     return childNumber;
    822   }
    823 
    824   /** @return The last forwarded number for the call, or {@code null} if none specified. */
    825   public String getLastForwardedNumber() {
    826     return lastForwardedNumber;
    827   }
    828 
    829   public boolean isCallForwarded() {
    830     return isCallForwarded;
    831   }
    832 
    833   /** @return The call subject, or {@code null} if none specified. */
    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    */
    842   public boolean isCallSubjectSupported() {
    843     return isCallSubjectSupported;
    844   }
    845 
    846   /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
    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 
    855   public void setDisconnectCause(DisconnectCause disconnectCause) {
    856     this.disconnectCause = disconnectCause;
    857     logState.disconnectCause = this.disconnectCause;
    858   }
    859 
    860   /** Returns the possible text message responses. */
    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. */
    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 
    882   public boolean hasProperty(int property) {
    883     return telecomCall.getDetails().hasProperty(property);
    884   }
    885 
    886   @NonNull
    887   public String getUniqueCallId() {
    888     return uniqueCallId;
    889   }
    890 
    891   /** Gets the time when the call first became active. */
    892   public long getConnectTimeMillis() {
    893     return telecomCall.getDetails().getConnectTimeMillis();
    894   }
    895 
    896   public boolean isConferenceCall() {
    897     return hasProperty(Call.Details.PROPERTY_CONFERENCE);
    898   }
    899 
    900   @Nullable
    901   public GatewayInfo getGatewayInfo() {
    902     return telecomCall == null ? null : telecomCall.getDetails().getGatewayInfo();
    903   }
    904 
    905   @Nullable
    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}. */
    911   public VideoCall getVideoCall() {
    912     return telecomCall == null ? null : telecomCall.getVideoCall();
    913   }
    914 
    915   public List<String> getChildCallIds() {
    916     return childCallIds;
    917   }
    918 
    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 
    927   public int getVideoState() {
    928     return telecomCall.getDetails().getVideoState();
    929   }
    930 
    931   public boolean isVideoCall() {
    932     return getVideoTech().isTransmittingOrReceiving() || VideoProfile.isVideo(getVideoState());
    933   }
    934 
    935   @TargetApi(28)
    936   public boolean isRttCall() {
    937     if (BuildCompat.isAtLeastP()) {
    938       return getTelecomCall().isRttActive();
    939     } else {
    940       return false;
    941     }
    942   }
    943 
    944   @TargetApi(28)
    945   public RttCall getRttCall() {
    946     if (!isRttCall()) {
    947       return null;
    948     }
    949     return getTelecomCall().getRttCall();
    950   }
    951 
    952   public boolean hasReceivedVideoUpgradeRequest() {
    953     return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
    954   }
    955 
    956   public boolean hasSentVideoUpgradeRequest() {
    957     return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
    958   }
    959 
    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    */
    968   private void updateEmergencyCallState() {
    969     isEmergencyCall = TelecomCallUtil.isEmergencyCall(telecomCall);
    970   }
    971 
    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    */
    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    */
    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 
   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
   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 
   1053   public String toSimpleString() {
   1054     return super.toString();
   1055   }
   1056 
   1057   @CallHistoryStatus
   1058   public int getCallHistoryStatus() {
   1059     return callHistoryStatus;
   1060   }
   1061 
   1062   public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
   1063     this.callHistoryStatus = callHistoryStatus;
   1064   }
   1065 
   1066   public boolean didShowCameraPermission() {
   1067     return didShowCameraPermission;
   1068   }
   1069 
   1070   public void setDidShowCameraPermission(boolean didShow) {
   1071     didShowCameraPermission = didShow;
   1072   }
   1073 
   1074   @Nullable
   1075   public Boolean isInGlobalSpamList() {
   1076     return isInGlobalSpamList;
   1077   }
   1078 
   1079   public void setIsInGlobalSpamList(boolean inSpamList) {
   1080     isInGlobalSpamList = inSpamList;
   1081   }
   1082 
   1083   @Nullable
   1084   public Boolean isInUserSpamList() {
   1085     return isInUserSpamList;
   1086   }
   1087 
   1088   public void setIsInUserSpamList(boolean inSpamList) {
   1089     isInUserSpamList = inSpamList;
   1090   }
   1091 
   1092   @Nullable
   1093   public Boolean isInUserWhiteList() {
   1094     return isInUserWhiteList;
   1095   }
   1096 
   1097   public void setIsInUserWhiteList(boolean inWhiteList) {
   1098     isInUserWhiteList = inWhiteList;
   1099   }
   1100 
   1101   public boolean isSpam() {
   1102     return isSpam;
   1103   }
   1104 
   1105   public void setSpam(boolean isSpam) {
   1106     this.isSpam = isSpam;
   1107   }
   1108 
   1109   public boolean isBlocked() {
   1110     return isBlocked;
   1111   }
   1112 
   1113   public void setBlockedStatus(boolean isBlocked) {
   1114     this.isBlocked = isBlocked;
   1115   }
   1116 
   1117   public boolean isRemotelyHeld() {
   1118     return isRemotelyHeld;
   1119   }
   1120 
   1121   public boolean isMergeInProcess() {
   1122     return isMergeInProcess;
   1123   }
   1124 
   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    */
   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
   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 
   1169   public LatencyReport getLatencyReport() {
   1170     return latencyReport;
   1171   }
   1172 
   1173   public int getAnswerAndReleaseButtonDisplayedTimes() {
   1174     return answerAndReleaseButtonDisplayedTimes;
   1175   }
   1176 
   1177   public void increaseAnswerAndReleaseButtonDisplayedTimes() {
   1178     answerAndReleaseButtonDisplayedTimes++;
   1179   }
   1180 
   1181   public boolean getReleasedByAnsweringSecondCall() {
   1182     return releasedByAnsweringSecondCall;
   1183   }
   1184 
   1185   public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) {
   1186     this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall;
   1187   }
   1188 
   1189   public int getSecondCallWithoutAnswerAndReleasedButtonTimes() {
   1190     return secondCallWithoutAnswerAndReleasedButtonTimes;
   1191   }
   1192 
   1193   public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() {
   1194     secondCallWithoutAnswerAndReleasedButtonTimes++;
   1195   }
   1196 
   1197   @Nullable
   1198   public EnrichedCallCapabilities getEnrichedCallCapabilities() {
   1199     return enrichedCallCapabilities;
   1200   }
   1201 
   1202   public void setEnrichedCallCapabilities(
   1203       @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
   1204     this.enrichedCallCapabilities = mEnrichedCallCapabilities;
   1205   }
   1206 
   1207   @Nullable
   1208   public Session getEnrichedCallSession() {
   1209     return enrichedCallSession;
   1210   }
   1211 
   1212   public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
   1213     this.enrichedCallSession = mEnrichedCallSession;
   1214   }
   1215 
   1216   public void unregisterCallback() {
   1217     telecomCall.unregisterCallback(telecomCallCallback);
   1218   }
   1219 
   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 
   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 
   1238   public void hold() {
   1239     LogUtil.i("DialerCall.hold", "");
   1240     telecomCall.hold();
   1241   }
   1242 
   1243   public void unhold() {
   1244     LogUtil.i("DialerCall.unhold", "");
   1245     telecomCall.unhold();
   1246   }
   1247 
   1248   public void splitFromConference() {
   1249     LogUtil.i("DialerCall.splitFromConference", "");
   1250     telecomCall.splitFromConference();
   1251   }
   1252 
   1253   public void answer(int videoState) {
   1254     LogUtil.i("DialerCall.answer", "videoState: " + videoState);
   1255     telecomCall.answer(videoState);
   1256   }
   1257 
   1258   public void answer() {
   1259     answer(telecomCall.getDetails().getVideoState());
   1260   }
   1261 
   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 */
   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 
   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 
   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 
   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 
   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
   1335   public void onVideoTechStateChanged() {
   1336     update();
   1337   }
   1338 
   1339   @Override
   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
   1349   public void onCameraDimensionsChanged(int width, int height) {
   1350     InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
   1351   }
   1352 
   1353   @Override
   1354   public void onPeerDimensionsChanged(int width, int height) {
   1355     InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
   1356   }
   1357 
   1358   @Override
   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
   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
   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
   1411   public void onEnrichedCallStateChanged() {
   1412     updateEnrichedCallSession();
   1413   }
   1414 
   1415   @Override
   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 
   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 
   1465   private void dispatchOnEnrichedCallSessionUpdate() {
   1466     for (DialerCallListener listener : listeners) {
   1467       listener.onEnrichedCallSessionUpdate();
   1468     }
   1469   }
   1470 
   1471   void onRemovedFromCallList() {
   1472     // Ensure we clean up when this call is removed.
   1473     videoTechManager.dispatchRemovedFromCallList();
   1474   }
   1475 
   1476   public com.android.dialer.logging.VideoTech.Type getSelectedAvailableVideoTechType() {
   1477     return selectedAvailableVideoTechType;
   1478   }
   1479 
   1480   public void markFeedbackRequested() {
   1481     feedbackRequested = true;
   1482   }
   1483 
   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 
   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 
   1538     public static boolean isDialing(int state) {
   1539       return state == DIALING || state == PULLING || state == REDIALING;
   1540     }
   1541 
   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 
   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 
   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
   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
   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
   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
   1739     public void dispatchCallStateChanged(int newState, PhoneAccountHandle phoneAccountHandle) {
   1740       for (VideoTech videoTech : videoTechs) {
   1741         videoTech.onCallStateChanged(context, newState, phoneAccountHandle);
   1742       }
   1743     }
   1744 
   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 {
   1754     void onCannedTextResponsesLoaded(DialerCall call);
   1755   }
   1756 }
   1757