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.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 
    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 
    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 
    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 
    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 
    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 
    345   public void addListener(DialerCallListener listener) {
    346     Assert.isMainThread();
    347     mListeners.add(listener);
    348   }
    349 
    350   public void removeListener(DialerCallListener listener) {
    351     Assert.isMainThread();
    352     mListeners.remove(listener);
    353   }
    354 
    355   public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
    356     Assert.isMainThread();
    357     mCannedTextResponsesLoadedListeners.add(listener);
    358   }
    359 
    360   public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
    361     Assert.isMainThread();
    362     mCannedTextResponsesLoadedListeners.remove(listener);
    363   }
    364 
    365   public void notifyWiFiToLteHandover() {
    366     LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
    367     for (DialerCallListener listener : mListeners) {
    368       listener.onWiFiToLteHandover();
    369     }
    370   }
    371 
    372   public void notifyHandoverToWifiFailed() {
    373     LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
    374     for (DialerCallListener listener : mListeners) {
    375       listener.onHandoverToWifiFailure();
    376     }
    377   }
    378 
    379   public void notifyInternationalCallOnWifi() {
    380     LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
    381     for (DialerCallListener dialerCallListener : mListeners) {
    382       dialerCallListener.onInternationalCallOnWifi();
    383     }
    384   }
    385 
    386   /* package-private */ Call getTelecomCall() {
    387     return mTelecomCall;
    388   }
    389 
    390   public StatusHints getStatusHints() {
    391     return mTelecomCall.getDetails().getStatusHints();
    392   }
    393 
    394   public int getCameraDir() {
    395     return mCameraDirection;
    396   }
    397 
    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 
    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 
    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    */
    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 
    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 
    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
    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 
    566   public static void clearRestrictedCount() {
    567     sHiddenCounter = 0;
    568   }
    569 
    570   private boolean isHiddenNumber() {
    571     return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
    572         || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
    573   }
    574 
    575   public boolean hasShownWiFiToLteHandoverToast() {
    576     return hasShownWiFiToLteHandoverToast;
    577   }
    578 
    579   public void setHasShownWiFiToLteHandoverToast() {
    580     hasShownWiFiToLteHandoverToast = true;
    581   }
    582 
    583   public boolean showWifiHandoverAlertAsToast() {
    584     return doNotShowDialogForHandoffToWifiFailure;
    585   }
    586 
    587   public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
    588     doNotShowDialogForHandoffToWifiFailure = bool;
    589   }
    590 
    591   public long getTimeAddedMs() {
    592     return mTimeAddedMs;
    593   }
    594 
    595   @Nullable
    596   public String getNumber() {
    597     return TelecomCallUtil.getNumber(mTelecomCall);
    598   }
    599 
    600   public void blockCall() {
    601     mTelecomCall.reject(false, null);
    602     setState(State.BLOCKED);
    603   }
    604 
    605   @Nullable
    606   public Uri getHandle() {
    607     return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
    608   }
    609 
    610   public boolean isEmergencyCall() {
    611     return mIsEmergencyCall;
    612   }
    613 
    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 
    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 
    641   public int getState() {
    642     if (mTelecomCall != null && mTelecomCall.getParent() != null) {
    643       return State.CONFERENCED;
    644     } else {
    645       return mState;
    646     }
    647   }
    648 
    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 
    659   public int getNumberPresentation() {
    660     return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
    661   }
    662 
    663   public int getCnapNamePresentation() {
    664     return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
    665   }
    666 
    667   @Nullable
    668   public String getCnapName() {
    669     return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
    670   }
    671 
    672   public Bundle getIntentExtras() {
    673     return mTelecomCall.getDetails().getIntentExtras();
    674   }
    675 
    676   @Nullable
    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. */
    682   public String getChildNumber() {
    683     return mChildNumber;
    684   }
    685 
    686   /** @return The last forwarded number for the call, or {@code null} if none specified. */
    687   public String getLastForwardedNumber() {
    688     return mLastForwardedNumber;
    689   }
    690 
    691   /** @return The call subject, or {@code null} if none specified. */
    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    */
    700   public boolean isCallSubjectSupported() {
    701     return mIsCallSubjectSupported;
    702   }
    703 
    704   /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
    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 
    713   public void setDisconnectCause(DisconnectCause disconnectCause) {
    714     mDisconnectCause = disconnectCause;
    715     mLogState.disconnectCause = mDisconnectCause;
    716   }
    717 
    718   /** Returns the possible text message responses. */
    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. */
    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 
    740   public boolean hasProperty(int property) {
    741     return mTelecomCall.getDetails().hasProperty(property);
    742   }
    743 
    744   @NonNull
    745   public String getUniqueCallId() {
    746     return uniqueCallId;
    747   }
    748 
    749   /** Gets the time when the call first became active. */
    750   public long getConnectTimeMillis() {
    751     return mTelecomCall.getDetails().getConnectTimeMillis();
    752   }
    753 
    754   public boolean isConferenceCall() {
    755     return hasProperty(Call.Details.PROPERTY_CONFERENCE);
    756   }
    757 
    758   @Nullable
    759   public GatewayInfo getGatewayInfo() {
    760     return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
    761   }
    762 
    763   @Nullable
    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}. */
    769   public VideoCall getVideoCall() {
    770     return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
    771   }
    772 
    773   public List<String> getChildCallIds() {
    774     return mChildCallIds;
    775   }
    776 
    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 
    785   public int getVideoState() {
    786     return mTelecomCall.getDetails().getVideoState();
    787   }
    788 
    789   public boolean isVideoCall() {
    790     return getVideoTech().isTransmittingOrReceiving();
    791   }
    792 
    793   public boolean hasReceivedVideoUpgradeRequest() {
    794     return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
    795   }
    796 
    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    */
    805   private void updateEmergencyCallState() {
    806     mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
    807   }
    808 
    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    */
    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    */
    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 
    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
    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 
    890   public String toSimpleString() {
    891     return super.toString();
    892   }
    893 
    894   @CallHistoryStatus
    895   public int getCallHistoryStatus() {
    896     return mCallHistoryStatus;
    897   }
    898 
    899   public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
    900     mCallHistoryStatus = callHistoryStatus;
    901   }
    902 
    903   public boolean didShowCameraPermission() {
    904     return didShowCameraPermission;
    905   }
    906 
    907   public void setDidShowCameraPermission(boolean didShow) {
    908     didShowCameraPermission = didShow;
    909   }
    910 
    911   public boolean isInGlobalSpamList() {
    912     return isInGlobalSpamList;
    913   }
    914 
    915   public void setIsInGlobalSpamList(boolean inSpamList) {
    916     isInGlobalSpamList = inSpamList;
    917   }
    918 
    919   public boolean isInUserSpamList() {
    920     return isInUserSpamList;
    921   }
    922 
    923   public void setIsInUserSpamList(boolean inSpamList) {
    924     isInUserSpamList = inSpamList;
    925   }
    926 
    927   public boolean isInUserWhiteList() {
    928     return isInUserWhiteList;
    929   }
    930 
    931   public void setIsInUserWhiteList(boolean inWhiteList) {
    932     isInUserWhiteList = inWhiteList;
    933   }
    934 
    935   public boolean isSpam() {
    936     return mIsSpam;
    937   }
    938 
    939   public void setSpam(boolean isSpam) {
    940     mIsSpam = isSpam;
    941   }
    942 
    943   public boolean isBlocked() {
    944     return mIsBlocked;
    945   }
    946 
    947   public void setBlockedStatus(boolean isBlocked) {
    948     mIsBlocked = isBlocked;
    949   }
    950 
    951   public boolean isRemotelyHeld() {
    952     return isRemotelyHeld;
    953   }
    954 
    955   public boolean isIncoming() {
    956     return mLogState.isIncoming;
    957   }
    958 
    959   public LatencyReport getLatencyReport() {
    960     return mLatencyReport;
    961   }
    962 
    963   @Nullable
    964   public EnrichedCallCapabilities getEnrichedCallCapabilities() {
    965     return mEnrichedCallCapabilities;
    966   }
    967 
    968   public void setEnrichedCallCapabilities(
    969       @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
    970     this.mEnrichedCallCapabilities = mEnrichedCallCapabilities;
    971   }
    972 
    973   @Nullable
    974   public Session getEnrichedCallSession() {
    975     return mEnrichedCallSession;
    976   }
    977 
    978   public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
    979     this.mEnrichedCallSession = mEnrichedCallSession;
    980   }
    981 
    982   public void unregisterCallback() {
    983     mTelecomCall.unregisterCallback(mTelecomCallCallback);
    984   }
    985 
    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 
    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 
   1004   public void hold() {
   1005     LogUtil.i("DialerCall.hold", "");
   1006     mTelecomCall.hold();
   1007   }
   1008 
   1009   public void unhold() {
   1010     LogUtil.i("DialerCall.unhold", "");
   1011     mTelecomCall.unhold();
   1012   }
   1013 
   1014   public void splitFromConference() {
   1015     LogUtil.i("DialerCall.splitFromConference", "");
   1016     mTelecomCall.splitFromConference();
   1017   }
   1018 
   1019   public void answer(int videoState) {
   1020     LogUtil.i("DialerCall.answer", "videoState: " + videoState);
   1021     mTelecomCall.answer(videoState);
   1022   }
   1023 
   1024   public void answer() {
   1025     answer(mTelecomCall.getDetails().getVideoState());
   1026   }
   1027 
   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 */
   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 
   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 
   1059   public VideoTech getVideoTech() {
   1060     return mVideoTechManager.getVideoTech();
   1061   }
   1062 
   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 
   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
   1115   public void onVideoTechStateChanged() {
   1116     update();
   1117   }
   1118 
   1119   @Override
   1120   public void onSessionModificationStateChanged() {
   1121     for (DialerCallListener listener : mListeners) {
   1122       listener.onDialerCallSessionModificationStateChange();
   1123     }
   1124   }
   1125 
   1126   @Override
   1127   public void onCameraDimensionsChanged(int width, int height) {
   1128     InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
   1129   }
   1130 
   1131   @Override
   1132   public void onPeerDimensionsChanged(int width, int height) {
   1133     InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
   1134   }
   1135 
   1136   @Override
   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
   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 
   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 
   1224     public static boolean isDialing(int state) {
   1225       return state == DIALING || state == PULLING || state == REDIALING;
   1226     }
   1227 
   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 
   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 
   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
   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 
   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 
   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 
   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 {
   1421     void onCannedTextResponsesLoaded(DialerCall call);
   1422   }
   1423 }
   1424