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