Home | History | Annotate | Download | only in calllog
      1 /*
      2  * Copyright (C) 2011 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.dialer.app.calllog;
     18 
     19 import android.Manifest.permission;
     20 import android.annotation.SuppressLint;
     21 import android.app.Activity;
     22 import android.content.ActivityNotFoundException;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.res.Resources;
     26 import android.net.Uri;
     27 import android.os.AsyncTask;
     28 import android.provider.CallLog;
     29 import android.provider.CallLog.Calls;
     30 import android.provider.ContactsContract.CommonDataKinds.Phone;
     31 import android.support.annotation.IntDef;
     32 import android.support.annotation.Nullable;
     33 import android.support.annotation.RequiresPermission;
     34 import android.support.annotation.VisibleForTesting;
     35 import android.support.v7.widget.CardView;
     36 import android.support.v7.widget.RecyclerView;
     37 import android.telecom.PhoneAccount;
     38 import android.telecom.PhoneAccountHandle;
     39 import android.telecom.TelecomManager;
     40 import android.telecom.VideoProfile;
     41 import android.telephony.PhoneNumberUtils;
     42 import android.telephony.TelephonyManager;
     43 import android.text.BidiFormatter;
     44 import android.text.TextDirectionHeuristics;
     45 import android.text.TextUtils;
     46 import android.view.ContextMenu;
     47 import android.view.LayoutInflater;
     48 import android.view.MenuItem;
     49 import android.view.View;
     50 import android.view.ViewStub;
     51 import android.widget.ImageButton;
     52 import android.widget.ImageView;
     53 import android.widget.TextView;
     54 import android.widget.Toast;
     55 import com.android.contacts.common.dialog.CallSubjectDialog;
     56 import com.android.dialer.app.DialtactsActivity;
     57 import com.android.dialer.app.R;
     58 import com.android.dialer.app.calllog.CallLogAdapter.OnActionModeStateChangedListener;
     59 import com.android.dialer.app.calllog.calllogcache.CallLogCache;
     60 import com.android.dialer.app.voicemail.VoicemailPlaybackLayout;
     61 import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter;
     62 import com.android.dialer.blocking.BlockedNumbersMigrator;
     63 import com.android.dialer.blocking.FilteredNumberCompat;
     64 import com.android.dialer.blocking.FilteredNumbersUtil;
     65 import com.android.dialer.callcomposer.CallComposerActivity;
     66 import com.android.dialer.calldetails.CallDetailsActivity;
     67 import com.android.dialer.calldetails.CallDetailsEntries;
     68 import com.android.dialer.callintent.CallIntentBuilder;
     69 import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction;
     70 import com.android.dialer.clipboard.ClipboardUtils;
     71 import com.android.dialer.common.Assert;
     72 import com.android.dialer.common.LogUtil;
     73 import com.android.dialer.common.concurrent.AsyncTaskExecutors;
     74 import com.android.dialer.compat.CompatUtils;
     75 import com.android.dialer.configprovider.ConfigProviderBindings;
     76 import com.android.dialer.constants.ActivityRequestCodes;
     77 import com.android.dialer.contactphoto.ContactPhotoManager;
     78 import com.android.dialer.dialercontact.DialerContact;
     79 import com.android.dialer.dialercontact.SimDetails;
     80 import com.android.dialer.duo.Duo;
     81 import com.android.dialer.duo.DuoComponent;
     82 import com.android.dialer.duo.DuoConstants;
     83 import com.android.dialer.lettertile.LetterTileDrawable;
     84 import com.android.dialer.lettertile.LetterTileDrawable.ContactType;
     85 import com.android.dialer.logging.ContactSource;
     86 import com.android.dialer.logging.ContactSource.Type;
     87 import com.android.dialer.logging.DialerImpression;
     88 import com.android.dialer.logging.InteractionEvent;
     89 import com.android.dialer.logging.Logger;
     90 import com.android.dialer.logging.ScreenEvent;
     91 import com.android.dialer.logging.UiAction;
     92 import com.android.dialer.performancereport.PerformanceReport;
     93 import com.android.dialer.phonenumbercache.CachedNumberLookupService;
     94 import com.android.dialer.phonenumbercache.ContactInfo;
     95 import com.android.dialer.phonenumbercache.PhoneNumberCache;
     96 import com.android.dialer.phonenumberutil.PhoneNumberHelper;
     97 import com.android.dialer.telecom.TelecomUtil;
     98 import com.android.dialer.util.CallUtil;
     99 import com.android.dialer.util.DialerUtils;
    100 import com.android.dialer.util.UriUtils;
    101 import java.lang.annotation.Retention;
    102 import java.lang.annotation.RetentionPolicy;
    103 import java.lang.ref.WeakReference;
    104 
    105 /**
    106  * This is an object containing references to views contained by the call log list item. This
    107  * improves performance by reducing the frequency with which we need to find views by IDs.
    108  *
    109  * <p>This object also contains UI logic pertaining to the view, to isolate it from the
    110  * CallLogAdapter.
    111  */
    112 public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
    113     implements View.OnClickListener,
    114         MenuItem.OnMenuItemClickListener,
    115         View.OnCreateContextMenuListener {
    116 
    117   private static final String TASK_DELETE = "task_delete";
    118 
    119   /** The root view of the call log list item */
    120   public final View rootView;
    121   /** The quick contact badge for the contact. */
    122   public final DialerQuickContactBadge quickContactView;
    123   /** The primary action view of the entry. */
    124   public final View primaryActionView;
    125   /** The details of the phone call. */
    126   public final PhoneCallDetailsViews phoneCallDetailsViews;
    127   /** The text of the header for a day grouping. */
    128   public final TextView dayGroupHeader;
    129   /** The view containing the details for the call log row, including the action buttons. */
    130   public final CardView callLogEntryView;
    131   /** The actionable view which places a call to the number corresponding to the call log row. */
    132   public final ImageView primaryActionButtonView;
    133 
    134   private final Context context;
    135   @Nullable private final PhoneAccountHandle defaultPhoneAccountHandle;
    136   private final CallLogCache callLogCache;
    137   private final CallLogListItemHelper callLogListItemHelper;
    138   private final CachedNumberLookupService cachedNumberLookupService;
    139   private final VoicemailPlaybackPresenter voicemailPlaybackPresenter;
    140   private final OnClickListener blockReportListener;
    141   @HostUi private final int hostUi;
    142   /** Whether the data fields are populated by the worker thread, ready to be shown. */
    143   public boolean isLoaded;
    144   /** The view containing call log item actions. Null until the ViewStub is inflated. */
    145   public View actionsView;
    146   /** The button views below are assigned only when the action section is expanded. */
    147   public VoicemailPlaybackLayout voicemailPlaybackView;
    148 
    149   public View callButtonView;
    150   public View videoCallButtonView;
    151   public View setUpVideoButtonView;
    152   public View inviteVideoButtonView;
    153   public View createNewContactButtonView;
    154   public View addToExistingContactButtonView;
    155   public View sendMessageView;
    156   public View blockReportView;
    157   public View blockView;
    158   public View unblockView;
    159   public View reportNotSpamView;
    160   public View detailsButtonView;
    161   public View callWithNoteButtonView;
    162   public View callComposeButtonView;
    163   public View sendVoicemailButtonView;
    164   public ImageView workIconView;
    165   public ImageView checkBoxView;
    166   /**
    167    * The row Id for the first call associated with the call log entry. Used as a key for the map
    168    * used to track which call log entries have the action button section expanded.
    169    */
    170   public long rowId;
    171   /**
    172    * The call Ids for the calls represented by the current call log entry. Used when the user
    173    * deletes a call log entry.
    174    */
    175   public long[] callIds;
    176   /**
    177    * The callable phone number for the current call log entry. Cached here as the call back intent
    178    * is set only when the actions ViewStub is inflated.
    179    */
    180   @Nullable public String number;
    181   /** The post-dial numbers that are dialed following the phone number. */
    182   public String postDialDigits;
    183   /** The formatted phone number to display. */
    184   public String displayNumber;
    185   /**
    186    * The phone number presentation for the current call log entry. Cached here as the call back
    187    * intent is set only when the actions ViewStub is inflated.
    188    */
    189   public int numberPresentation;
    190   /** The type of the phone number (e.g. main, work, etc). */
    191   public String numberType;
    192   /**
    193    * The country iso for the call. Cached here as the call back intent is set only when the actions
    194    * ViewStub is inflated.
    195    */
    196   public String countryIso;
    197   /**
    198    * The type of call for the current call log entry. Cached here as the call back intent is set
    199    * only when the actions ViewStub is inflated.
    200    */
    201   public int callType;
    202   /**
    203    * ID for blocked numbers database. Set when context menu is created, if the number is blocked.
    204    */
    205   public Integer blockId;
    206   /**
    207    * The account for the current call log entry. Cached here as the call back intent is set only
    208    * when the actions ViewStub is inflated.
    209    */
    210   public PhoneAccountHandle accountHandle;
    211   /**
    212    * If the call has an associated voicemail message, the URI of the voicemail message for playback.
    213    * Cached here as the voicemail intent is only set when the actions ViewStub is inflated.
    214    */
    215   public String voicemailUri;
    216   /**
    217    * The name or number associated with the call. Cached here for use when setting content
    218    * descriptions on buttons in the actions ViewStub when it is inflated.
    219    */
    220   @Nullable public CharSequence nameOrNumber;
    221   /**
    222    * The call type or Location associated with the call. Cached here for use when setting text for a
    223    * voicemail log's call button
    224    */
    225   public CharSequence callTypeOrLocation;
    226   /** The contact info for the contact displayed in this list item. */
    227   public volatile ContactInfo info;
    228   /** Whether spam feature is enabled, which affects UI. */
    229   public boolean isSpamFeatureEnabled;
    230   /** Whether the current log entry is a spam number or not. */
    231   public boolean isSpam;
    232 
    233   public boolean isCallComposerCapable;
    234 
    235   private View.OnClickListener expandCollapseListener;
    236   private final OnActionModeStateChangedListener onActionModeStateChangedListener;
    237   private final View.OnLongClickListener longPressListener;
    238   private boolean voicemailPrimaryActionButtonClicked;
    239 
    240   public int callbackAction;
    241   public int dayGroupHeaderVisibility;
    242   public CharSequence dayGroupHeaderText;
    243   public boolean isAttachedToWindow;
    244 
    245   public AsyncTask<Void, Void, ?> asyncTask;
    246   private CallDetailsEntries callDetailsEntries;
    247 
    248   private CallLogListItemViewHolder(
    249       Context context,
    250       OnClickListener blockReportListener,
    251       View.OnClickListener expandCollapseListener,
    252       View.OnLongClickListener longClickListener,
    253       CallLogAdapter.OnActionModeStateChangedListener actionModeStateChangedListener,
    254       CallLogCache callLogCache,
    255       CallLogListItemHelper callLogListItemHelper,
    256       VoicemailPlaybackPresenter voicemailPlaybackPresenter,
    257       View rootView,
    258       DialerQuickContactBadge dialerQuickContactView,
    259       View primaryActionView,
    260       PhoneCallDetailsViews phoneCallDetailsViews,
    261       CardView callLogEntryView,
    262       TextView dayGroupHeader,
    263       ImageView primaryActionButtonView) {
    264     super(rootView);
    265 
    266     this.context = context;
    267     this.expandCollapseListener = expandCollapseListener;
    268     onActionModeStateChangedListener = actionModeStateChangedListener;
    269     longPressListener = longClickListener;
    270     this.callLogCache = callLogCache;
    271     this.callLogListItemHelper = callLogListItemHelper;
    272     this.voicemailPlaybackPresenter = voicemailPlaybackPresenter;
    273     this.blockReportListener = blockReportListener;
    274     cachedNumberLookupService = PhoneNumberCache.get(this.context).getCachedNumberLookupService();
    275 
    276     // Cache this to avoid having to look it up each time we bind to a call log entry
    277     defaultPhoneAccountHandle =
    278         TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL);
    279 
    280     this.rootView = rootView;
    281     this.quickContactView = dialerQuickContactView;
    282     this.primaryActionView = primaryActionView;
    283     this.phoneCallDetailsViews = phoneCallDetailsViews;
    284     this.callLogEntryView = callLogEntryView;
    285     this.dayGroupHeader = dayGroupHeader;
    286     this.primaryActionButtonView = primaryActionButtonView;
    287     this.workIconView = (ImageView) rootView.findViewById(R.id.work_profile_icon);
    288     this.checkBoxView = (ImageView) rootView.findViewById(R.id.quick_contact_checkbox);
    289 
    290     // Set text height to false on the TextViews so they don't have extra padding.
    291     phoneCallDetailsViews.nameView.setElegantTextHeight(false);
    292     phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false);
    293 
    294     if (this.context instanceof CallLogActivity) {
    295       hostUi = HostUi.CALL_HISTORY;
    296       Logger.get(this.context)
    297           .logQuickContactOnTouch(
    298               quickContactView, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CALL_HISTORY, true);
    299     } else if (this.voicemailPlaybackPresenter == null) {
    300       hostUi = HostUi.CALL_LOG;
    301       Logger.get(this.context)
    302           .logQuickContactOnTouch(
    303               quickContactView, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CALL_LOG, true);
    304     } else {
    305       hostUi = HostUi.VOICEMAIL;
    306       Logger.get(this.context)
    307           .logQuickContactOnTouch(
    308               quickContactView, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_VOICEMAIL, false);
    309     }
    310 
    311     quickContactView.setOverlay(null);
    312     if (CompatUtils.hasPrioritizedMimeType()) {
    313       quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
    314     }
    315     primaryActionButtonView.setOnClickListener(this);
    316     primaryActionView.setOnClickListener(this.expandCollapseListener);
    317     if (this.voicemailPlaybackPresenter != null
    318         && ConfigProviderBindings.get(this.context)
    319             .getBoolean(
    320                 CallLogAdapter.ENABLE_CALL_LOG_MULTI_SELECT,
    321                 CallLogAdapter.ENABLE_CALL_LOG_MULTI_SELECT_FLAG)) {
    322       primaryActionView.setOnLongClickListener(longPressListener);
    323       quickContactView.setOnLongClickListener(longPressListener);
    324       quickContactView.setMulitSelectListeners(
    325           this.expandCollapseListener, onActionModeStateChangedListener);
    326     } else {
    327       primaryActionView.setOnCreateContextMenuListener(this);
    328     }
    329   }
    330 
    331   public static CallLogListItemViewHolder create(
    332       View view,
    333       Context context,
    334       OnClickListener blockReportListener,
    335       View.OnClickListener expandCollapseListener,
    336       View.OnLongClickListener longClickListener,
    337       CallLogAdapter.OnActionModeStateChangedListener actionModeStateChangeListener,
    338       CallLogCache callLogCache,
    339       CallLogListItemHelper callLogListItemHelper,
    340       VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
    341 
    342     return new CallLogListItemViewHolder(
    343         context,
    344         blockReportListener,
    345         expandCollapseListener,
    346         longClickListener,
    347         actionModeStateChangeListener,
    348         callLogCache,
    349         callLogListItemHelper,
    350         voicemailPlaybackPresenter,
    351         view,
    352         (DialerQuickContactBadge) view.findViewById(R.id.quick_contact_photo),
    353         view.findViewById(R.id.primary_action_view),
    354         PhoneCallDetailsViews.fromView(view),
    355         (CardView) view.findViewById(R.id.call_log_row),
    356         (TextView) view.findViewById(R.id.call_log_day_group_label),
    357         (ImageView) view.findViewById(R.id.primary_action_button));
    358   }
    359 
    360   public static CallLogListItemViewHolder createForTest(Context context) {
    361     return createForTest(context, null, null, new CallLogCache(context));
    362   }
    363 
    364   public static CallLogListItemViewHolder createForTest(
    365       Context context,
    366       View.OnClickListener expandCollapseListener,
    367       VoicemailPlaybackPresenter voicemailPlaybackPresenter,
    368       CallLogCache callLogCache) {
    369     Resources resources = context.getResources();
    370     PhoneCallDetailsHelper phoneCallDetailsHelper =
    371         new PhoneCallDetailsHelper(context, resources, callLogCache);
    372 
    373     CallLogListItemViewHolder viewHolder =
    374         new CallLogListItemViewHolder(
    375             context,
    376             null,
    377             expandCollapseListener /* expandCollapseListener */,
    378             null,
    379             null,
    380             callLogCache,
    381             new CallLogListItemHelper(phoneCallDetailsHelper, resources, callLogCache),
    382             voicemailPlaybackPresenter,
    383             LayoutInflater.from(context).inflate(R.layout.call_log_list_item, null),
    384             new DialerQuickContactBadge(context),
    385             new View(context),
    386             PhoneCallDetailsViews.createForTest(context),
    387             new CardView(context),
    388             new TextView(context),
    389             new ImageView(context));
    390     viewHolder.detailsButtonView = new TextView(context);
    391     viewHolder.actionsView = new View(context);
    392     viewHolder.voicemailPlaybackView = new VoicemailPlaybackLayout(context);
    393     viewHolder.workIconView = new ImageButton(context);
    394     viewHolder.checkBoxView = new ImageButton(context);
    395     return viewHolder;
    396   }
    397 
    398   @Override
    399   public boolean onMenuItemClick(MenuItem item) {
    400     int resId = item.getItemId();
    401     if (resId == R.id.context_menu_copy_to_clipboard) {
    402       ClipboardUtils.copyText(context, null, number, true);
    403       return true;
    404     } else if (resId == R.id.context_menu_copy_transcript_to_clipboard) {
    405       ClipboardUtils.copyText(
    406           context, null, phoneCallDetailsViews.voicemailTranscriptionView.getText(), true);
    407       return true;
    408     } else if (resId == R.id.context_menu_edit_before_call) {
    409       final Intent intent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(number));
    410       intent.setClass(context, DialtactsActivity.class);
    411       DialerUtils.startActivityWithErrorToast(context, intent);
    412       return true;
    413     } else if (resId == R.id.context_menu_block_report_spam) {
    414       Logger.get(context)
    415           .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_BLOCK_REPORT_SPAM);
    416       maybeShowBlockNumberMigrationDialog(
    417           new BlockedNumbersMigrator.Listener() {
    418             @Override
    419             public void onComplete() {
    420               blockReportListener.onBlockReportSpam(
    421                   displayNumber, number, countryIso, callType, info.sourceType);
    422             }
    423           });
    424     } else if (resId == R.id.context_menu_block) {
    425       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_BLOCK_NUMBER);
    426       maybeShowBlockNumberMigrationDialog(
    427           new BlockedNumbersMigrator.Listener() {
    428             @Override
    429             public void onComplete() {
    430               blockReportListener.onBlock(
    431                   displayNumber, number, countryIso, callType, info.sourceType);
    432             }
    433           });
    434     } else if (resId == R.id.context_menu_unblock) {
    435       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_UNBLOCK_NUMBER);
    436       blockReportListener.onUnblock(
    437           displayNumber, number, countryIso, callType, info.sourceType, isSpam, blockId);
    438     } else if (resId == R.id.context_menu_report_not_spam) {
    439       Logger.get(context)
    440           .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_REPORT_AS_NOT_SPAM);
    441       blockReportListener.onReportNotSpam(
    442           displayNumber, number, countryIso, callType, info.sourceType);
    443     } else if (resId == R.id.context_menu_delete) {
    444       AsyncTaskExecutors.createAsyncTaskExecutor()
    445           .submit(TASK_DELETE, new DeleteCallTask(context, callIds));
    446     }
    447     return false;
    448   }
    449 
    450   /**
    451    * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not inflated
    452    * during initial binding, so click handlers, tags and accessibility text must be set here, if
    453    * necessary.
    454    */
    455   public void inflateActionViewStub() {
    456     ViewStub stub = (ViewStub) rootView.findViewById(R.id.call_log_entry_actions_stub);
    457     if (stub != null) {
    458       actionsView = stub.inflate();
    459 
    460       voicemailPlaybackView =
    461           (VoicemailPlaybackLayout) actionsView.findViewById(R.id.voicemail_playback_layout);
    462       voicemailPlaybackView.setViewHolder(this);
    463 
    464       callButtonView = actionsView.findViewById(R.id.call_action);
    465       callButtonView.setOnClickListener(this);
    466 
    467       videoCallButtonView = actionsView.findViewById(R.id.video_call_action);
    468       videoCallButtonView.setOnClickListener(this);
    469 
    470       setUpVideoButtonView = actionsView.findViewById(R.id.set_up_video_action);
    471       setUpVideoButtonView.setOnClickListener(this);
    472 
    473       inviteVideoButtonView = actionsView.findViewById(R.id.invite_video_action);
    474       inviteVideoButtonView.setOnClickListener(this);
    475 
    476       createNewContactButtonView = actionsView.findViewById(R.id.create_new_contact_action);
    477       createNewContactButtonView.setOnClickListener(this);
    478 
    479       addToExistingContactButtonView =
    480           actionsView.findViewById(R.id.add_to_existing_contact_action);
    481       addToExistingContactButtonView.setOnClickListener(this);
    482 
    483       sendMessageView = actionsView.findViewById(R.id.send_message_action);
    484       sendMessageView.setOnClickListener(this);
    485 
    486       blockReportView = actionsView.findViewById(R.id.block_report_action);
    487       blockReportView.setOnClickListener(this);
    488 
    489       blockView = actionsView.findViewById(R.id.block_action);
    490       blockView.setOnClickListener(this);
    491 
    492       unblockView = actionsView.findViewById(R.id.unblock_action);
    493       unblockView.setOnClickListener(this);
    494 
    495       reportNotSpamView = actionsView.findViewById(R.id.report_not_spam_action);
    496       reportNotSpamView.setOnClickListener(this);
    497 
    498       detailsButtonView = actionsView.findViewById(R.id.details_action);
    499       detailsButtonView.setOnClickListener(this);
    500 
    501       callWithNoteButtonView = actionsView.findViewById(R.id.call_with_note_action);
    502       callWithNoteButtonView.setOnClickListener(this);
    503 
    504       callComposeButtonView = actionsView.findViewById(R.id.call_compose_action);
    505       callComposeButtonView.setOnClickListener(this);
    506 
    507       sendVoicemailButtonView = actionsView.findViewById(R.id.share_voicemail);
    508       sendVoicemailButtonView.setOnClickListener(this);
    509     }
    510   }
    511 
    512   private void updatePrimaryActionButton(boolean isExpanded) {
    513 
    514     if (nameOrNumber == null) {
    515       LogUtil.e("CallLogListItemViewHolder.updatePrimaryActionButton", "name or number is null");
    516     }
    517 
    518     // Calling expandTemplate with a null parameter will cause a NullPointerException.
    519     CharSequence validNameOrNumber = nameOrNumber == null ? "" : nameOrNumber;
    520 
    521     if (!TextUtils.isEmpty(voicemailUri)) {
    522       // Treat as voicemail list item; show play button if not expanded.
    523       if (!isExpanded) {
    524         primaryActionButtonView.setImageResource(R.drawable.quantum_ic_play_arrow_white_24);
    525         primaryActionButtonView.setContentDescription(
    526             TextUtils.expandTemplate(
    527                 context.getString(R.string.description_voicemail_action), validNameOrNumber));
    528         primaryActionButtonView.setVisibility(View.VISIBLE);
    529       } else {
    530         primaryActionButtonView.setVisibility(View.GONE);
    531       }
    532       return;
    533     }
    534 
    535     // Treat as normal list item; show call button, if possible.
    536     if (!PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation)) {
    537       primaryActionButtonView.setTag(null);
    538       primaryActionButtonView.setVisibility(View.GONE);
    539       return;
    540     }
    541 
    542     switch (callbackAction) {
    543       case CallbackAction.IMS_VIDEO:
    544         primaryActionButtonView.setTag(
    545             IntentProvider.getReturnVideoCallIntentProvider(number, accountHandle));
    546         primaryActionButtonView.setContentDescription(
    547             TextUtils.expandTemplate(
    548                 context.getString(R.string.description_video_call_action), validNameOrNumber));
    549         primaryActionButtonView.setImageResource(R.drawable.quantum_ic_videocam_vd_theme_24);
    550         primaryActionButtonView.setVisibility(View.VISIBLE);
    551         break;
    552       case CallbackAction.DUO:
    553         if (showDuoPrimaryButton()) {
    554           CallIntentBuilder.increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount();
    555           primaryActionButtonView.setTag(IntentProvider.getDuoVideoIntentProvider(number));
    556         } else {
    557           primaryActionButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
    558         }
    559         primaryActionButtonView.setContentDescription(
    560             TextUtils.expandTemplate(
    561                 context.getString(R.string.description_video_call_action), validNameOrNumber));
    562         primaryActionButtonView.setImageResource(R.drawable.quantum_ic_videocam_vd_theme_24);
    563         primaryActionButtonView.setVisibility(View.VISIBLE);
    564         break;
    565       case CallbackAction.VOICE:
    566         if (callLogCache.isVoicemailNumber(accountHandle, number)) {
    567           // Call to generic voicemail number, in case there are multiple accounts
    568           primaryActionButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider(null));
    569         } else if (canSupportAssistedDialing()) {
    570           primaryActionButtonView.setTag(
    571               IntentProvider.getAssistedDialIntentProvider(
    572                   number + postDialDigits,
    573                   context,
    574                   context.getSystemService(TelephonyManager.class)));
    575         } else {
    576           primaryActionButtonView.setTag(
    577               IntentProvider.getReturnCallIntentProvider(number + postDialDigits));
    578         }
    579 
    580         primaryActionButtonView.setContentDescription(
    581             TextUtils.expandTemplate(
    582                 context.getString(R.string.description_call_action), validNameOrNumber));
    583         primaryActionButtonView.setImageResource(R.drawable.quantum_ic_call_vd_theme_24);
    584         primaryActionButtonView.setVisibility(View.VISIBLE);
    585         break;
    586       default:
    587         primaryActionButtonView.setTag(null);
    588         primaryActionButtonView.setVisibility(View.GONE);
    589     }
    590   }
    591 
    592   /**
    593    * Binds text titles, click handlers and intents to the voicemail, details and callback action
    594    * buttons.
    595    */
    596   private void bindActionButtons() {
    597     boolean canPlaceCallToNumber = PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation);
    598 
    599     // Hide the call buttons by default. We then set it to be visible when appropriate below.
    600     // This saves us having to remember to set it to GONE in multiple places.
    601     callButtonView.setVisibility(View.GONE);
    602     videoCallButtonView.setVisibility(View.GONE);
    603     setUpVideoButtonView.setVisibility(View.GONE);
    604     inviteVideoButtonView.setVisibility(View.GONE);
    605 
    606     if (isFullyUndialableVoicemail()) {
    607       // Sometimes the voicemail server will report the message is from some non phone number
    608       // source. If the number does not contains any dialable digit treat it as it is from a unknown
    609       // number, remove all action buttons but still show the voicemail playback layout.
    610       detailsButtonView.setVisibility(View.GONE);
    611       createNewContactButtonView.setVisibility(View.GONE);
    612       addToExistingContactButtonView.setVisibility(View.GONE);
    613       sendMessageView.setVisibility(View.GONE);
    614       callWithNoteButtonView.setVisibility(View.GONE);
    615       callComposeButtonView.setVisibility(View.GONE);
    616       blockReportView.setVisibility(View.GONE);
    617       blockView.setVisibility(View.GONE);
    618       unblockView.setVisibility(View.GONE);
    619       reportNotSpamView.setVisibility(View.GONE);
    620 
    621       voicemailPlaybackView.setVisibility(View.VISIBLE);
    622       Uri uri = Uri.parse(voicemailUri);
    623       voicemailPlaybackPresenter.setPlaybackView(
    624           voicemailPlaybackView,
    625           rowId,
    626           uri,
    627           voicemailPrimaryActionButtonClicked,
    628           sendVoicemailButtonView);
    629       voicemailPrimaryActionButtonClicked = false;
    630       CallLogAsyncTaskUtil.markVoicemailAsRead(context, uri);
    631       return;
    632     }
    633 
    634     TextView callTypeOrLocationView =
    635         ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text));
    636 
    637     if (canPlaceCallToNumber) {
    638       if (canSupportAssistedDialing()) {
    639         callButtonView.setTag(
    640             IntentProvider.getAssistedDialIntentProvider(
    641                 number, context, context.getSystemService(TelephonyManager.class)));
    642       } else {
    643         callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number));
    644       }
    645       callTypeOrLocationView.setVisibility(View.GONE);
    646     }
    647 
    648     if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) {
    649       ((TextView) callButtonView.findViewById(R.id.call_action_text))
    650           .setText(
    651               TextUtils.expandTemplate(
    652                   context.getString(R.string.call_log_action_call),
    653                   nameOrNumber == null ? "" : nameOrNumber));
    654 
    655       if (callType == Calls.VOICEMAIL_TYPE && !TextUtils.isEmpty(callTypeOrLocation)) {
    656         callTypeOrLocationView.setText(callTypeOrLocation);
    657         callTypeOrLocationView.setVisibility(View.VISIBLE);
    658       }
    659       callButtonView.setVisibility(View.VISIBLE);
    660     }
    661 
    662     boolean isVoicemailNumber = callLogCache.isVoicemailNumber(accountHandle, number);
    663 
    664     switch (callbackAction) {
    665       case CallbackAction.IMS_VIDEO:
    666       case CallbackAction.DUO:
    667         // For an IMS video call or a Duo call, the secondary action should always be a
    668         // voice callback.
    669         callButtonView.setVisibility(View.VISIBLE);
    670         videoCallButtonView.setVisibility(View.GONE);
    671         break;
    672       case CallbackAction.VOICE:
    673         Duo duo = DuoComponent.get(context).getDuo();
    674         // For a voice call, set the secondary callback action to be an IMS video call if it is
    675         // available. Otherwise try to set it as a Duo call.
    676         if (CallUtil.isVideoEnabled(context)
    677             && (hasPlacedCarrierVideoCall() || canSupportCarrierVideoCall())) {
    678           videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
    679           videoCallButtonView.setVisibility(View.VISIBLE);
    680           break;
    681         }
    682 
    683         if (isVoicemailNumber) {
    684           break;
    685         }
    686 
    687         boolean identifiedSpamCall = isSpamFeatureEnabled && isSpam;
    688         if (duo.isReachable(context, number)) {
    689           videoCallButtonView.setTag(IntentProvider.getDuoVideoIntentProvider(number));
    690           videoCallButtonView.setVisibility(View.VISIBLE);
    691         } else if (duo.isActivated(context) && !identifiedSpamCall) {
    692           if (ConfigProviderBindings.get(context)
    693               .getBoolean("enable_call_log_duo_invite_button", false)) {
    694             inviteVideoButtonView.setTag(IntentProvider.getDuoInviteIntentProvider(number));
    695             inviteVideoButtonView.setVisibility(View.VISIBLE);
    696             Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_INVITE_SHOWN);
    697           }
    698         } else if (duo.isEnabled(context) && !identifiedSpamCall) {
    699           if (!duo.isInstalled(context)) {
    700             if (ConfigProviderBindings.get(context)
    701                 .getBoolean("enable_call_log_install_duo_button", false)) {
    702               setUpVideoButtonView.setTag(IntentProvider.getInstallDuoIntentProvider());
    703               setUpVideoButtonView.setVisibility(View.VISIBLE);
    704               Logger.get(context)
    705                   .logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_INSTALL_SHOWN);
    706             }
    707           } else {
    708             if (ConfigProviderBindings.get(context)
    709                 .getBoolean("enable_call_log_activate_duo_button", false)) {
    710               setUpVideoButtonView.setTag(IntentProvider.getSetUpDuoIntentProvider());
    711               setUpVideoButtonView.setVisibility(View.VISIBLE);
    712               Logger.get(context)
    713                   .logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_ACTIVATE_SHOWN);
    714             }
    715           }
    716         }
    717         break;
    718       default:
    719         callButtonView.setVisibility(View.GONE);
    720         videoCallButtonView.setVisibility(View.GONE);
    721     }
    722 
    723     // For voicemail calls, show the voicemail playback layout; hide otherwise.
    724     if (callType == Calls.VOICEMAIL_TYPE
    725         && voicemailPlaybackPresenter != null
    726         && !TextUtils.isEmpty(voicemailUri)) {
    727       voicemailPlaybackView.setVisibility(View.VISIBLE);
    728 
    729       Uri uri = Uri.parse(voicemailUri);
    730       voicemailPlaybackPresenter.setPlaybackView(
    731           voicemailPlaybackView,
    732           rowId,
    733           uri,
    734           voicemailPrimaryActionButtonClicked,
    735           sendVoicemailButtonView);
    736       voicemailPrimaryActionButtonClicked = false;
    737       CallLogAsyncTaskUtil.markVoicemailAsRead(context, uri);
    738     } else {
    739       voicemailPlaybackView.setVisibility(View.GONE);
    740       sendVoicemailButtonView.setVisibility(View.GONE);
    741     }
    742 
    743     if (callType == Calls.VOICEMAIL_TYPE) {
    744       detailsButtonView.setVisibility(View.GONE);
    745     } else {
    746       detailsButtonView.setVisibility(View.VISIBLE);
    747       boolean canReportCallerId =
    748           cachedNumberLookupService != null
    749               && cachedNumberLookupService.canReportAsInvalid(info.sourceType, info.objectId);
    750       detailsButtonView.setTag(
    751           IntentProvider.getCallDetailIntentProvider(
    752               callDetailsEntries, buildContact(), canReportCallerId, canSupportAssistedDialing()));
    753     }
    754 
    755     boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam);
    756 
    757     if (!isBlockedOrSpam && info != null && UriUtils.isEncodedContactUri(info.lookupUri)) {
    758       createNewContactButtonView.setTag(
    759           IntentProvider.getAddContactIntentProvider(
    760               info.lookupUri, info.name, info.number, info.type, true /* isNewContact */));
    761       createNewContactButtonView.setVisibility(View.VISIBLE);
    762 
    763       addToExistingContactButtonView.setTag(
    764           IntentProvider.getAddContactIntentProvider(
    765               info.lookupUri, info.name, info.number, info.type, false /* isNewContact */));
    766       addToExistingContactButtonView.setVisibility(View.VISIBLE);
    767     } else {
    768       createNewContactButtonView.setVisibility(View.GONE);
    769       addToExistingContactButtonView.setVisibility(View.GONE);
    770     }
    771 
    772     if (canPlaceCallToNumber && !isBlockedOrSpam && !isVoicemailNumber) {
    773       sendMessageView.setTag(IntentProvider.getSendSmsIntentProvider(number));
    774       sendMessageView.setVisibility(View.VISIBLE);
    775     } else {
    776       sendMessageView.setVisibility(View.GONE);
    777     }
    778 
    779     callLogListItemHelper.setActionContentDescriptions(this);
    780 
    781     boolean supportsCallSubject = callLogCache.doesAccountSupportCallSubject(accountHandle);
    782     callWithNoteButtonView.setVisibility(
    783         supportsCallSubject && !isVoicemailNumber && info != null ? View.VISIBLE : View.GONE);
    784 
    785     callComposeButtonView.setVisibility(isCallComposerCapable ? View.VISIBLE : View.GONE);
    786 
    787     updateBlockReportActions(isVoicemailNumber);
    788   }
    789 
    790   private boolean isFullyUndialableVoicemail() {
    791     if (callType == Calls.VOICEMAIL_TYPE) {
    792       if (!hasDialableChar(number)) {
    793         return true;
    794       }
    795     }
    796     return false;
    797   }
    798 
    799   private boolean showDuoPrimaryButton() {
    800     return accountHandle != null
    801         && accountHandle.getComponentName().equals(DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME)
    802         && DuoComponent.get(context).getDuo().isReachable(context, number);
    803   }
    804 
    805   private static boolean hasDialableChar(CharSequence number) {
    806     if (TextUtils.isEmpty(number)) {
    807       return false;
    808     }
    809     for (char c : number.toString().toCharArray()) {
    810       if (PhoneNumberUtils.isDialable(c)) {
    811         return true;
    812       }
    813     }
    814     return false;
    815   }
    816 
    817   private boolean hasPlacedCarrierVideoCall() {
    818     if (!phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
    819       return false;
    820     }
    821     if (accountHandle == null) {
    822       return false;
    823     }
    824     if (defaultPhoneAccountHandle == null) {
    825       return false;
    826     }
    827     return accountHandle.getComponentName().equals(defaultPhoneAccountHandle.getComponentName());
    828   }
    829 
    830   private boolean canSupportAssistedDialing() {
    831     return info != null && info.lookupKey != null;
    832   }
    833 
    834   private boolean canSupportCarrierVideoCall() {
    835     return callLogCache.canRelyOnVideoPresence()
    836         && info != null
    837         && (info.carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
    838   }
    839 
    840   /**
    841    * Show or hide the action views, such as voicemail, details, and add contact.
    842    *
    843    * <p>If the action views have never been shown yet for this view, inflate the view stub.
    844    */
    845   public void showActions(boolean show) {
    846     showOrHideVoicemailTranscriptionView(show);
    847 
    848     if (show) {
    849       if (!isLoaded) {
    850         // a bug for some unidentified reason showActions() can be called before the item is
    851         // loaded, causing NPE on uninitialized fields. Just log and return here, showActions() will
    852         // be called again once the item is loaded.
    853         LogUtil.e(
    854             "CallLogListItemViewHolder.showActions",
    855             "called before item is loaded",
    856             new Exception());
    857         return;
    858       }
    859 
    860       // Inflate the view stub if necessary, and wire up the event handlers.
    861       inflateActionViewStub();
    862       bindActionButtons();
    863       actionsView.setVisibility(View.VISIBLE);
    864       actionsView.setAlpha(1.0f);
    865     } else {
    866       // When recycling a view, it is possible the actionsView ViewStub was previously
    867       // inflated so we should hide it in this case.
    868       if (actionsView != null) {
    869         actionsView.setVisibility(View.GONE);
    870       }
    871     }
    872 
    873     updatePrimaryActionButton(show);
    874   }
    875 
    876   private void showOrHideVoicemailTranscriptionView(boolean isExpanded) {
    877     if (callType != Calls.VOICEMAIL_TYPE) {
    878       return;
    879     }
    880 
    881     View transcriptContainerView = phoneCallDetailsViews.transcriptionView;
    882     TextView transcriptView = phoneCallDetailsViews.voicemailTranscriptionView;
    883     TextView transcriptBrandingView = phoneCallDetailsViews.voicemailTranscriptionBrandingView;
    884     if (!isExpanded) {
    885       transcriptContainerView.setVisibility(View.GONE);
    886       return;
    887     }
    888 
    889     boolean show = false;
    890     if (TextUtils.isEmpty(transcriptView.getText())) {
    891       transcriptView.setVisibility(View.GONE);
    892     } else {
    893       transcriptView.setVisibility(View.VISIBLE);
    894       show = true;
    895     }
    896     if (TextUtils.isEmpty(transcriptBrandingView.getText())) {
    897       transcriptBrandingView.setVisibility(View.GONE);
    898     } else {
    899       transcriptBrandingView.setVisibility(View.VISIBLE);
    900       show = true;
    901     }
    902     if (show) {
    903       transcriptContainerView.setVisibility(View.VISIBLE);
    904     } else {
    905       transcriptContainerView.setVisibility(View.GONE);
    906     }
    907   }
    908 
    909   public void updatePhoto() {
    910     quickContactView.assignContactUri(info.lookupUri);
    911 
    912     if (isSpamFeatureEnabled && isSpam) {
    913       quickContactView.setImageDrawable(context.getDrawable(R.drawable.blocked_contact));
    914       return;
    915     }
    916 
    917     final String displayName = TextUtils.isEmpty(info.name) ? displayNumber : info.name;
    918     ContactPhotoManager.getInstance(context)
    919         .loadDialerThumbnailOrPhoto(
    920             quickContactView,
    921             info.lookupUri,
    922             info.photoId,
    923             info.photoUri,
    924             displayName,
    925             getContactType());
    926   }
    927 
    928   private @ContactType int getContactType() {
    929     return LetterTileDrawable.getContactTypeFromPrimitives(
    930         callLogCache.isVoicemailNumber(accountHandle, number),
    931         isSpam,
    932         cachedNumberLookupService != null && cachedNumberLookupService.isBusiness(info.sourceType),
    933         numberPresentation,
    934         false);
    935   }
    936 
    937   @Override
    938   public void onClick(View view) {
    939     if (view.getId() == R.id.primary_action_button) {
    940       CallLogAsyncTaskUtil.markCallAsRead(context, callIds);
    941     }
    942 
    943     if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) {
    944       Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_PLAY_AUDIO_DIRECTLY);
    945       voicemailPrimaryActionButtonClicked = true;
    946       expandCollapseListener.onClick(primaryActionView);
    947       return;
    948     }
    949 
    950     if (view.getId() == R.id.call_with_note_action) {
    951       CallSubjectDialog.start(
    952           (Activity) context,
    953           info.photoId,
    954           info.photoUri,
    955           info.lookupUri,
    956           (String) nameOrNumber /* top line of contact view in call subject dialog */,
    957           number,
    958           TextUtils.isEmpty(info.name) ? null : displayNumber, /* second line of contact
    959                                                                            view in dialog. */
    960           numberType, /* phone number type (e.g. mobile) in second line of contact view */
    961           getContactType(),
    962           accountHandle);
    963       return;
    964     }
    965 
    966     if (view.getId() == R.id.block_report_action) {
    967       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_BLOCK_REPORT_SPAM);
    968       maybeShowBlockNumberMigrationDialog(
    969           new BlockedNumbersMigrator.Listener() {
    970             @Override
    971             public void onComplete() {
    972               blockReportListener.onBlockReportSpam(
    973                   displayNumber, number, countryIso, callType, info.sourceType);
    974             }
    975           });
    976       return;
    977     }
    978 
    979     if (view.getId() == R.id.block_action) {
    980       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_BLOCK_NUMBER);
    981       maybeShowBlockNumberMigrationDialog(
    982           new BlockedNumbersMigrator.Listener() {
    983             @Override
    984             public void onComplete() {
    985               blockReportListener.onBlock(
    986                   displayNumber, number, countryIso, callType, info.sourceType);
    987             }
    988           });
    989       return;
    990     }
    991 
    992     if (view.getId() == R.id.unblock_action) {
    993       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_UNBLOCK_NUMBER);
    994       blockReportListener.onUnblock(
    995           displayNumber, number, countryIso, callType, info.sourceType, isSpam, blockId);
    996       return;
    997     }
    998 
    999     if (view.getId() == R.id.report_not_spam_action) {
   1000       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_REPORT_AS_NOT_SPAM);
   1001       blockReportListener.onReportNotSpam(
   1002           displayNumber, number, countryIso, callType, info.sourceType);
   1003       return;
   1004     }
   1005 
   1006     if (view.getId() == R.id.call_compose_action) {
   1007       LogUtil.i("CallLogListItemViewHolder.onClick", "share and call pressed");
   1008       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_SHARE_AND_CALL);
   1009       Activity activity = (Activity) context;
   1010       activity.startActivityForResult(
   1011           CallComposerActivity.newIntent(activity, buildContact()),
   1012           ActivityRequestCodes.DIALTACTS_CALL_COMPOSER);
   1013       return;
   1014     }
   1015 
   1016     if (view.getId() == R.id.share_voicemail) {
   1017       Logger.get(context).logImpression(DialerImpression.Type.VVM_SHARE_PRESSED);
   1018       voicemailPlaybackPresenter.shareVoicemail();
   1019       return;
   1020     }
   1021 
   1022     logCallLogAction(view.getId());
   1023 
   1024     final IntentProvider intentProvider = (IntentProvider) view.getTag();
   1025     if (intentProvider == null) {
   1026       return;
   1027     }
   1028 
   1029     final Intent intent = intentProvider.getIntent(context);
   1030     // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
   1031     if (intent == null) {
   1032       return;
   1033     }
   1034 
   1035     // We check to see if we are starting a Duo intent. The reason is Duo
   1036     // intents need to be started using startActivityForResult instead of the usual startActivity
   1037     String packageName = intent.getPackage();
   1038     if (DuoConstants.PACKAGE_NAME.equals(packageName)) {
   1039       startDuoActivity(intent);
   1040     } else if (CallDetailsActivity.isLaunchIntent(intent)) {
   1041       PerformanceReport.recordClick(UiAction.Type.OPEN_CALL_DETAIL);
   1042       ((Activity) context)
   1043           .startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_CALL_DETAILS);
   1044     } else {
   1045       if (Intent.ACTION_CALL.equals(intent.getAction())
   1046           && intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, -1)
   1047               == VideoProfile.STATE_BIDIRECTIONAL) {
   1048         Logger.get(context).logImpression(DialerImpression.Type.IMS_VIDEO_REQUESTED_FROM_CALL_LOG);
   1049       } else if (intent.getDataString() != null
   1050           && intent.getDataString().contains(DuoConstants.PACKAGE_NAME)) {
   1051         Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_INSTALL);
   1052       }
   1053 
   1054       DialerUtils.startActivityWithErrorToast(context, intent);
   1055     }
   1056   }
   1057 
   1058   private static boolean isNonContactEntry(ContactInfo info) {
   1059     if (info == null || info.sourceType != Type.SOURCE_TYPE_DIRECTORY) {
   1060       return true;
   1061     }
   1062     return false;
   1063   }
   1064 
   1065   private void startDuoActivity(Intent intent) {
   1066     if (DuoConstants.DUO_ACTIVATE_ACTION.equals(intent.getAction())) {
   1067       Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_ACTIVATE);
   1068     } else if (DuoConstants.DUO_INVITE_ACTION.equals(intent.getAction())) {
   1069       Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_INVITE);
   1070     } else if (DuoConstants.DUO_CALL_ACTION.equals(intent.getAction())) {
   1071       Logger.get(context)
   1072           .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_CALL_LOG);
   1073       if (isNonContactEntry(info)) {
   1074         Logger.get(context)
   1075             .logImpression(
   1076                 DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_VIDEO_REQUESTED_FROM_CALL_LOG);
   1077       }
   1078     } else {
   1079       throw Assert.createIllegalStateFailException(
   1080           "Duo intent with invalid action" + intent.getAction());
   1081     }
   1082 
   1083     try {
   1084       Activity activity = (Activity) context;
   1085       activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO);
   1086     } catch (ActivityNotFoundException e) {
   1087       Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show();
   1088     }
   1089   }
   1090 
   1091   private DialerContact buildContact() {
   1092     DialerContact.Builder contact = DialerContact.newBuilder();
   1093     contact.setPhotoId(info.photoId);
   1094     if (info.photoUri != null) {
   1095       contact.setPhotoUri(info.photoUri.toString());
   1096     }
   1097     if (info.lookupUri != null) {
   1098       contact.setContactUri(info.lookupUri.toString());
   1099     }
   1100     if (nameOrNumber != null) {
   1101       contact.setNameOrNumber((String) nameOrNumber);
   1102     }
   1103     contact.setContactType(getContactType());
   1104     if (number != null) {
   1105       contact.setNumber(number);
   1106     }
   1107 
   1108     if (!TextUtils.isEmpty(postDialDigits)) {
   1109       contact.setPostDialDigits(postDialDigits);
   1110     }
   1111 
   1112     /* second line of contact view. */
   1113     if (!TextUtils.isEmpty(info.name)) {
   1114       contact.setDisplayNumber(displayNumber);
   1115     }
   1116     /* phone number type (e.g. mobile) in second line of contact view */
   1117     contact.setNumberLabel(numberType);
   1118 
   1119     /* third line of contact view. */
   1120     String accountLabel = callLogCache.getAccountLabel(accountHandle);
   1121     if (!TextUtils.isEmpty(accountLabel)) {
   1122       SimDetails.Builder simDetails = SimDetails.newBuilder().setNetwork(accountLabel);
   1123       simDetails.setColor(callLogCache.getAccountColor(accountHandle));
   1124       contact.setSimDetails(simDetails.build());
   1125     }
   1126     return contact.build();
   1127   }
   1128 
   1129   private void logCallLogAction(int id) {
   1130     if (id == R.id.send_message_action) {
   1131       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_SEND_MESSAGE);
   1132     } else if (id == R.id.add_to_existing_contact_action) {
   1133       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_ADD_TO_CONTACT);
   1134       switch (hostUi) {
   1135         case HostUi.CALL_HISTORY:
   1136           Logger.get(context)
   1137               .logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_CALL_HISTORY);
   1138           break;
   1139         case HostUi.CALL_LOG:
   1140           Logger.get(context).logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_CALL_LOG);
   1141           break;
   1142         case HostUi.VOICEMAIL:
   1143           Logger.get(context).logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_VOICEMAIL);
   1144           break;
   1145         default:
   1146           throw Assert.createIllegalStateFailException();
   1147       }
   1148     } else if (id == R.id.create_new_contact_action) {
   1149       Logger.get(context).logImpression(DialerImpression.Type.CALL_LOG_CREATE_NEW_CONTACT);
   1150       switch (hostUi) {
   1151         case HostUi.CALL_HISTORY:
   1152           Logger.get(context)
   1153               .logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_CALL_HISTORY);
   1154           break;
   1155         case HostUi.CALL_LOG:
   1156           Logger.get(context).logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_CALL_LOG);
   1157           break;
   1158         case HostUi.VOICEMAIL:
   1159           Logger.get(context)
   1160               .logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_VOICEMAIL);
   1161           break;
   1162         default:
   1163           throw Assert.createIllegalStateFailException();
   1164       }
   1165     }
   1166   }
   1167 
   1168   private void maybeShowBlockNumberMigrationDialog(BlockedNumbersMigrator.Listener listener) {
   1169     if (!FilteredNumberCompat.maybeShowBlockNumberMigrationDialog(
   1170         context, ((Activity) context).getFragmentManager(), listener)) {
   1171       listener.onComplete();
   1172     }
   1173   }
   1174 
   1175   private void updateBlockReportActions(boolean isVoicemailNumber) {
   1176     // Set block/spam actions.
   1177     blockReportView.setVisibility(View.GONE);
   1178     blockView.setVisibility(View.GONE);
   1179     unblockView.setVisibility(View.GONE);
   1180     reportNotSpamView.setVisibility(View.GONE);
   1181     String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
   1182     if (isVoicemailNumber
   1183         || !FilteredNumbersUtil.canBlockNumber(context, e164Number, number)
   1184         || !FilteredNumberCompat.canAttemptBlockOperations(context)) {
   1185       return;
   1186     }
   1187     boolean isBlocked = blockId != null;
   1188     if (isBlocked) {
   1189       unblockView.setVisibility(View.VISIBLE);
   1190     } else {
   1191       if (isSpamFeatureEnabled) {
   1192         if (isSpam) {
   1193           blockView.setVisibility(View.VISIBLE);
   1194           reportNotSpamView.setVisibility(View.VISIBLE);
   1195         } else {
   1196           blockReportView.setVisibility(View.VISIBLE);
   1197         }
   1198       } else {
   1199         blockView.setVisibility(View.VISIBLE);
   1200       }
   1201     }
   1202   }
   1203 
   1204   public void setDetailedPhoneDetails(CallDetailsEntries callDetailsEntries) {
   1205     this.callDetailsEntries = callDetailsEntries;
   1206   }
   1207 
   1208   @VisibleForTesting
   1209   public CallDetailsEntries getDetailedPhoneDetails() {
   1210     return callDetailsEntries;
   1211   }
   1212 
   1213   @Override
   1214   public void onCreateContextMenu(
   1215       final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
   1216     if (TextUtils.isEmpty(number)) {
   1217       return;
   1218     }
   1219 
   1220     if (callType == CallLog.Calls.VOICEMAIL_TYPE) {
   1221       menu.setHeaderTitle(context.getResources().getText(R.string.voicemail));
   1222     } else {
   1223       menu.setHeaderTitle(
   1224           PhoneNumberUtils.createTtsSpannable(
   1225               BidiFormatter.getInstance().unicodeWrap(number, TextDirectionHeuristics.LTR)));
   1226     }
   1227 
   1228     menu.add(
   1229             ContextMenu.NONE,
   1230             R.id.context_menu_copy_to_clipboard,
   1231             ContextMenu.NONE,
   1232             R.string.action_copy_number_text)
   1233         .setOnMenuItemClickListener(this);
   1234 
   1235     // The edit number before call does not show up if any of the conditions apply:
   1236     // 1) Number cannot be called
   1237     // 2) Number is the voicemail number
   1238     // 3) Number is a SIP address
   1239 
   1240     if (PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation)
   1241         && !callLogCache.isVoicemailNumber(accountHandle, number)
   1242         && !PhoneNumberHelper.isSipNumber(number)) {
   1243       menu.add(
   1244               ContextMenu.NONE,
   1245               R.id.context_menu_edit_before_call,
   1246               ContextMenu.NONE,
   1247               R.string.action_edit_number_before_call)
   1248           .setOnMenuItemClickListener(this);
   1249     }
   1250 
   1251     if (callType == CallLog.Calls.VOICEMAIL_TYPE
   1252         && phoneCallDetailsViews.voicemailTranscriptionView.length() > 0) {
   1253       menu.add(
   1254               ContextMenu.NONE,
   1255               R.id.context_menu_copy_transcript_to_clipboard,
   1256               ContextMenu.NONE,
   1257               R.string.copy_transcript_text)
   1258           .setOnMenuItemClickListener(this);
   1259     }
   1260 
   1261     String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
   1262     boolean isVoicemailNumber = callLogCache.isVoicemailNumber(accountHandle, number);
   1263     if (!isVoicemailNumber
   1264         && FilteredNumbersUtil.canBlockNumber(context, e164Number, number)
   1265         && FilteredNumberCompat.canAttemptBlockOperations(context)) {
   1266       boolean isBlocked = blockId != null;
   1267       if (isBlocked) {
   1268         menu.add(
   1269                 ContextMenu.NONE,
   1270                 R.id.context_menu_unblock,
   1271                 ContextMenu.NONE,
   1272                 R.string.call_log_action_unblock_number)
   1273             .setOnMenuItemClickListener(this);
   1274       } else {
   1275         if (isSpamFeatureEnabled) {
   1276           if (isSpam) {
   1277             menu.add(
   1278                     ContextMenu.NONE,
   1279                     R.id.context_menu_report_not_spam,
   1280                     ContextMenu.NONE,
   1281                     R.string.call_log_action_remove_spam)
   1282                 .setOnMenuItemClickListener(this);
   1283             menu.add(
   1284                     ContextMenu.NONE,
   1285                     R.id.context_menu_block,
   1286                     ContextMenu.NONE,
   1287                     R.string.call_log_action_block_number)
   1288                 .setOnMenuItemClickListener(this);
   1289           } else {
   1290             menu.add(
   1291                     ContextMenu.NONE,
   1292                     R.id.context_menu_block_report_spam,
   1293                     ContextMenu.NONE,
   1294                     R.string.call_log_action_block_report_number)
   1295                 .setOnMenuItemClickListener(this);
   1296           }
   1297         } else {
   1298           menu.add(
   1299                   ContextMenu.NONE,
   1300                   R.id.context_menu_block,
   1301                   ContextMenu.NONE,
   1302                   R.string.call_log_action_block_number)
   1303               .setOnMenuItemClickListener(this);
   1304         }
   1305       }
   1306     }
   1307 
   1308     if (callType != CallLog.Calls.VOICEMAIL_TYPE) {
   1309       menu.add(ContextMenu.NONE, R.id.context_menu_delete, ContextMenu.NONE, R.string.delete)
   1310           .setOnMenuItemClickListener(this);
   1311     }
   1312 
   1313     Logger.get(context).logScreenView(ScreenEvent.Type.CALL_LOG_CONTEXT_MENU, (Activity) context);
   1314   }
   1315 
   1316   /** Specifies where the view holder belongs. */
   1317   @IntDef({HostUi.CALL_LOG, HostUi.CALL_HISTORY, HostUi.VOICEMAIL})
   1318   @Retention(RetentionPolicy.SOURCE)
   1319   private @interface HostUi {
   1320     int CALL_LOG = 0;
   1321     int CALL_HISTORY = 1;
   1322     int VOICEMAIL = 2;
   1323   }
   1324 
   1325   public interface OnClickListener {
   1326 
   1327     void onBlockReportSpam(
   1328         String displayNumber,
   1329         String number,
   1330         String countryIso,
   1331         int callType,
   1332         ContactSource.Type contactSourceType);
   1333 
   1334     void onBlock(
   1335         String displayNumber,
   1336         String number,
   1337         String countryIso,
   1338         int callType,
   1339         ContactSource.Type contactSourceType);
   1340 
   1341     void onUnblock(
   1342         String displayNumber,
   1343         String number,
   1344         String countryIso,
   1345         int callType,
   1346         ContactSource.Type contactSourceType,
   1347         boolean isSpam,
   1348         Integer blockId);
   1349 
   1350     void onReportNotSpam(
   1351         String displayNumber,
   1352         String number,
   1353         String countryIso,
   1354         int callType,
   1355         ContactSource.Type contactSourceType);
   1356   }
   1357 
   1358   private static class DeleteCallTask extends AsyncTask<Void, Void, Void> {
   1359     // Using a weak reference to hold the Context so that there is no memory leak.
   1360     private final WeakReference<Context> contextWeakReference;
   1361 
   1362     private final String callIdsStr;
   1363 
   1364     DeleteCallTask(Context context, long[] callIdsArray) {
   1365       this.contextWeakReference = new WeakReference<>(context);
   1366       this.callIdsStr = concatCallIds(callIdsArray);
   1367     }
   1368 
   1369     @Override
   1370     // Suppress the lint check here as the user will not be able to see call log entries if
   1371     // permission.WRITE_CALL_LOG is not granted.
   1372     @SuppressLint("MissingPermission")
   1373     @RequiresPermission(value = permission.WRITE_CALL_LOG)
   1374     protected Void doInBackground(Void... params) {
   1375       Context context = contextWeakReference.get();
   1376       if (context == null) {
   1377         return null;
   1378       }
   1379 
   1380       if (callIdsStr != null) {
   1381         context
   1382             .getContentResolver()
   1383             .delete(
   1384                 Calls.CONTENT_URI,
   1385                 CallLog.Calls._ID + " IN (" + callIdsStr + ")" /* where */,
   1386                 null /* selectionArgs */);
   1387       }
   1388 
   1389       return null;
   1390     }
   1391 
   1392     @Override
   1393     public void onPostExecute(Void result) {}
   1394 
   1395     private String concatCallIds(long[] callIds) {
   1396       if (callIds == null || callIds.length == 0) {
   1397         return null;
   1398       }
   1399 
   1400       StringBuilder str = new StringBuilder();
   1401       for (long callId : callIds) {
   1402         if (str.length() != 0) {
   1403           str.append(",");
   1404         }
   1405         str.append(callId);
   1406       }
   1407 
   1408       return str.toString();
   1409     }
   1410   }
   1411 }
   1412