• 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.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.Resources;
23 import android.net.Uri;
24 import android.os.AsyncTask;
25 import android.provider.CallLog;
26 import android.provider.CallLog.Calls;
27 import android.provider.ContactsContract.CommonDataKinds.Phone;
28 import android.support.annotation.NonNull;
29 import android.support.annotation.Nullable;
30 import android.support.annotation.VisibleForTesting;
31 import android.support.v7.widget.CardView;
32 import android.support.v7.widget.RecyclerView;
33 import android.telecom.PhoneAccountHandle;
34 import android.telecom.TelecomManager;
35 import android.telephony.PhoneNumberUtils;
36 import android.text.BidiFormatter;
37 import android.text.TextDirectionHeuristics;
38 import android.text.TextUtils;
39 import android.view.ContextMenu;
40 import android.view.MenuItem;
41 import android.view.View;
42 import android.view.ViewStub;
43 import android.widget.ImageButton;
44 import android.widget.ImageView;
45 import android.widget.QuickContactBadge;
46 import android.widget.TextView;
47 import com.android.contacts.common.ClipboardUtils;
48 import com.android.contacts.common.ContactPhotoManager;
49 import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
50 import com.android.contacts.common.dialog.CallSubjectDialog;
51 import com.android.contacts.common.util.UriUtils;
52 import com.android.dialer.app.DialtactsActivity;
53 import com.android.dialer.app.R;
54 import com.android.dialer.app.calllog.calllogcache.CallLogCache;
55 import com.android.dialer.app.voicemail.VoicemailPlaybackLayout;
56 import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter;
57 import com.android.dialer.blocking.BlockedNumbersMigrator;
58 import com.android.dialer.blocking.FilteredNumberCompat;
59 import com.android.dialer.blocking.FilteredNumbersUtil;
60 import com.android.dialer.callcomposer.CallComposerActivity;
61 import com.android.dialer.callcomposer.CallComposerContact;
62 import com.android.dialer.calldetails.CallDetailsEntries;
63 import com.android.dialer.common.ConfigProviderBindings;
64 import com.android.dialer.common.LogUtil;
65 import com.android.dialer.compat.CompatUtils;
66 import com.android.dialer.lightbringer.Lightbringer;
67 import com.android.dialer.lightbringer.LightbringerComponent;
68 import com.android.dialer.logging.ContactSource;
69 import com.android.dialer.logging.DialerImpression;
70 import com.android.dialer.logging.Logger;
71 import com.android.dialer.logging.ScreenEvent;
72 import com.android.dialer.phonenumbercache.CachedNumberLookupService;
73 import com.android.dialer.phonenumbercache.ContactInfo;
74 import com.android.dialer.phonenumbercache.PhoneNumberCache;
75 import com.android.dialer.phonenumberutil.PhoneNumberHelper;
76 import com.android.dialer.util.CallUtil;
77 import com.android.dialer.util.DialerUtils;
78 
79 /**
80  * This is an object containing references to views contained by the call log list item. This
81  * improves performance by reducing the frequency with which we need to find views by IDs.
82  *
83  * <p>This object also contains UI logic pertaining to the view, to isolate it from the
84  * CallLogAdapter.
85  */
86 public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
87     implements View.OnClickListener,
88         MenuItem.OnMenuItemClickListener,
89         View.OnCreateContextMenuListener {
90   /** The root view of the call log list item */
91   public final View rootView;
92   /** The quick contact badge for the contact. */
93   public final QuickContactBadge quickContactView;
94   /** The primary action view of the entry. */
95   public final View primaryActionView;
96   /** The details of the phone call. */
97   public final PhoneCallDetailsViews phoneCallDetailsViews;
98   /** The text of the header for a day grouping. */
99   public final TextView dayGroupHeader;
100   /** The view containing the details for the call log row, including the action buttons. */
101   public final CardView callLogEntryView;
102   /** The actionable view which places a call to the number corresponding to the call log row. */
103   public final ImageView primaryActionButtonView;
104 
105   private final Context mContext;
106   private final CallLogCache mCallLogCache;
107   private final CallLogListItemHelper mCallLogListItemHelper;
108   private final CachedNumberLookupService mCachedNumberLookupService;
109   private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
110   private final OnClickListener mBlockReportListener;
111   /** Whether the data fields are populated by the worker thread, ready to be shown. */
112   public boolean isLoaded;
113   /** The view containing call log item actions. Null until the ViewStub is inflated. */
114   public View actionsView;
115   /** The button views below are assigned only when the action section is expanded. */
116   public VoicemailPlaybackLayout voicemailPlaybackView;
117 
118   public View callButtonView;
119   public View videoCallButtonView;
120   public View createNewContactButtonView;
121   public View addToExistingContactButtonView;
122   public View sendMessageView;
123   public View blockReportView;
124   public View blockView;
125   public View unblockView;
126   public View reportNotSpamView;
127   public View detailsButtonView;
128   public View callWithNoteButtonView;
129   public View callComposeButtonView;
130   public View sendVoicemailButtonView;
131   public ImageView workIconView;
132   public ImageView checkBoxView;
133   /**
134    * The row Id for the first call associated with the call log entry. Used as a key for the map
135    * used to track which call log entries have the action button section expanded.
136    */
137   public long rowId;
138   /**
139    * The call Ids for the calls represented by the current call log entry. Used when the user
140    * deletes a call log entry.
141    */
142   public long[] callIds;
143   /**
144    * The callable phone number for the current call log entry. Cached here as the call back intent
145    * is set only when the actions ViewStub is inflated.
146    */
147   public String number;
148   /** The post-dial numbers that are dialed following the phone number. */
149   public String postDialDigits;
150   /** The formatted phone number to display. */
151   public String displayNumber;
152   /**
153    * The phone number presentation for the current call log entry. Cached here as the call back
154    * intent is set only when the actions ViewStub is inflated.
155    */
156   public int numberPresentation;
157   /** The type of the phone number (e.g. main, work, etc). */
158   public String numberType;
159   /**
160    * The country iso for the call. Cached here as the call back intent is set only when the actions
161    * ViewStub is inflated.
162    */
163   public String countryIso;
164   /**
165    * The type of call for the current call log entry. Cached here as the call back intent is set
166    * only when the actions ViewStub is inflated.
167    */
168   public int callType;
169   /**
170    * ID for blocked numbers database. Set when context menu is created, if the number is blocked.
171    */
172   public Integer blockId;
173   /**
174    * The account for the current call log entry. Cached here as the call back intent is set only
175    * when the actions ViewStub is inflated.
176    */
177   public PhoneAccountHandle accountHandle;
178   /**
179    * If the call has an associated voicemail message, the URI of the voicemail message for playback.
180    * Cached here as the voicemail intent is only set when the actions ViewStub is inflated.
181    */
182   public String voicemailUri;
183   /**
184    * The name or number associated with the call. Cached here for use when setting content
185    * descriptions on buttons in the actions ViewStub when it is inflated.
186    */
187   @Nullable public CharSequence nameOrNumber;
188   /**
189    * The call type or Location associated with the call. Cached here for use when setting text for a
190    * voicemail log's call button
191    */
192   public CharSequence callTypeOrLocation;
193   /** The contact info for the contact displayed in this list item. */
194   public volatile ContactInfo info;
195   /** Whether spam feature is enabled, which affects UI. */
196   public boolean isSpamFeatureEnabled;
197   /** Whether the current log entry is a spam number or not. */
198   public boolean isSpam;
199 
200   public boolean isCallComposerCapable;
201   public boolean lightbringerReady;
202 
203   private View.OnClickListener mExpandCollapseListener;
204   private final View.OnLongClickListener longPressListener;
205   private boolean mVoicemailPrimaryActionButtonClicked;
206 
207   public int dayGroupHeaderVisibility;
208   public CharSequence dayGroupHeaderText;
209   public boolean isAttachedToWindow;
210 
211   public AsyncTask<Void, Void, ?> asyncTask;
212   private CallDetailsEntries callDetailsEntries;
213 
CallLogListItemViewHolder( Context context, OnClickListener blockReportListener, View.OnClickListener expandCollapseListener, View.OnLongClickListener longClickListener, CallLogCache callLogCache, CallLogListItemHelper callLogListItemHelper, VoicemailPlaybackPresenter voicemailPlaybackPresenter, View rootView, QuickContactBadge quickContactView, View primaryActionView, PhoneCallDetailsViews phoneCallDetailsViews, CardView callLogEntryView, TextView dayGroupHeader, ImageView primaryActionButtonView)214   private CallLogListItemViewHolder(
215       Context context,
216       OnClickListener blockReportListener,
217       View.OnClickListener expandCollapseListener,
218       View.OnLongClickListener longClickListener,
219       CallLogCache callLogCache,
220       CallLogListItemHelper callLogListItemHelper,
221       VoicemailPlaybackPresenter voicemailPlaybackPresenter,
222       View rootView,
223       QuickContactBadge quickContactView,
224       View primaryActionView,
225       PhoneCallDetailsViews phoneCallDetailsViews,
226       CardView callLogEntryView,
227       TextView dayGroupHeader,
228       ImageView primaryActionButtonView) {
229     super(rootView);
230 
231     mContext = context;
232     mExpandCollapseListener = expandCollapseListener;
233     longPressListener = longClickListener;
234     mCallLogCache = callLogCache;
235     mCallLogListItemHelper = callLogListItemHelper;
236     mVoicemailPlaybackPresenter = voicemailPlaybackPresenter;
237     mBlockReportListener = blockReportListener;
238     mCachedNumberLookupService = PhoneNumberCache.get(mContext).getCachedNumberLookupService();
239 
240     this.rootView = rootView;
241     this.quickContactView = quickContactView;
242     this.primaryActionView = primaryActionView;
243     this.phoneCallDetailsViews = phoneCallDetailsViews;
244     this.callLogEntryView = callLogEntryView;
245     this.dayGroupHeader = dayGroupHeader;
246     this.primaryActionButtonView = primaryActionButtonView;
247     this.workIconView = (ImageView) rootView.findViewById(R.id.work_profile_icon);
248     this.checkBoxView = (ImageView) rootView.findViewById(R.id.quick_contact_checkbox);
249 
250     // Set text height to false on the TextViews so they don't have extra padding.
251     phoneCallDetailsViews.nameView.setElegantTextHeight(false);
252     phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false);
253 
254     quickContactView.setOverlay(null);
255     if (CompatUtils.hasPrioritizedMimeType()) {
256       quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
257     }
258     primaryActionButtonView.setOnClickListener(this);
259     primaryActionView.setOnClickListener(mExpandCollapseListener);
260     if (mVoicemailPlaybackPresenter != null
261         && ConfigProviderBindings.get(mContext)
262             .getBoolean(
263                 CallLogAdapter.ENABLE_CALL_LOG_MULTI_SELECT,
264                 CallLogAdapter.ENABLE_CALL_LOG_MULTI_SELECT_FLAG)) {
265       primaryActionView.setOnLongClickListener(longPressListener);
266       quickContactView.setOnLongClickListener(longPressListener);
267     } else {
268       primaryActionView.setOnCreateContextMenuListener(this);
269     }
270   }
271 
create( View view, Context context, OnClickListener blockReportListener, View.OnClickListener expandCollapseListener, View.OnLongClickListener longClickListener, CallLogCache callLogCache, CallLogListItemHelper callLogListItemHelper, VoicemailPlaybackPresenter voicemailPlaybackPresenter)272   public static CallLogListItemViewHolder create(
273       View view,
274       Context context,
275       OnClickListener blockReportListener,
276       View.OnClickListener expandCollapseListener,
277       View.OnLongClickListener longClickListener,
278       CallLogCache callLogCache,
279       CallLogListItemHelper callLogListItemHelper,
280       VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
281 
282     return new CallLogListItemViewHolder(
283         context,
284         blockReportListener,
285         expandCollapseListener,
286         longClickListener,
287         callLogCache,
288         callLogListItemHelper,
289         voicemailPlaybackPresenter,
290         view,
291         (QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
292         view.findViewById(R.id.primary_action_view),
293         PhoneCallDetailsViews.fromView(view),
294         (CardView) view.findViewById(R.id.call_log_row),
295         (TextView) view.findViewById(R.id.call_log_day_group_label),
296         (ImageView) view.findViewById(R.id.primary_action_button));
297   }
298 
createForTest(Context context)299   public static CallLogListItemViewHolder createForTest(Context context) {
300     Resources resources = context.getResources();
301     CallLogCache callLogCache = CallLogCache.getCallLogCache(context);
302     PhoneCallDetailsHelper phoneCallDetailsHelper =
303         new PhoneCallDetailsHelper(context, resources, callLogCache);
304 
305     CallLogListItemViewHolder viewHolder =
306         new CallLogListItemViewHolder(
307             context,
308             null,
309             null /* expandCollapseListener */,
310             null,
311             callLogCache,
312             new CallLogListItemHelper(phoneCallDetailsHelper, resources, callLogCache),
313             null /* voicemailPlaybackPresenter */,
314             new View(context),
315             new QuickContactBadge(context),
316             new View(context),
317             PhoneCallDetailsViews.createForTest(context),
318             new CardView(context),
319             new TextView(context),
320             new ImageView(context));
321     viewHolder.detailsButtonView = new TextView(context);
322     viewHolder.actionsView = new View(context);
323     viewHolder.voicemailPlaybackView = new VoicemailPlaybackLayout(context);
324     viewHolder.workIconView = new ImageButton(context);
325     viewHolder.checkBoxView = new ImageButton(context);
326     return viewHolder;
327   }
328 
329   @Override
onMenuItemClick(MenuItem item)330   public boolean onMenuItemClick(MenuItem item) {
331     int resId = item.getItemId();
332     if (resId == R.id.context_menu_copy_to_clipboard) {
333       ClipboardUtils.copyText(mContext, null, number, true);
334       return true;
335     } else if (resId == R.id.context_menu_copy_transcript_to_clipboard) {
336       ClipboardUtils.copyText(
337           mContext, null, phoneCallDetailsViews.voicemailTranscriptionView.getText(), true);
338       return true;
339     } else if (resId == R.id.context_menu_edit_before_call) {
340       final Intent intent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(number));
341       intent.setClass(mContext, DialtactsActivity.class);
342       DialerUtils.startActivityWithErrorToast(mContext, intent);
343       return true;
344     } else if (resId == R.id.context_menu_block_report_spam) {
345       Logger.get(mContext)
346           .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_BLOCK_REPORT_SPAM);
347       maybeShowBlockNumberMigrationDialog(
348           new BlockedNumbersMigrator.Listener() {
349             @Override
350             public void onComplete() {
351               mBlockReportListener.onBlockReportSpam(
352                   displayNumber, number, countryIso, callType, info.sourceType);
353             }
354           });
355     } else if (resId == R.id.context_menu_block) {
356       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_BLOCK_NUMBER);
357       maybeShowBlockNumberMigrationDialog(
358           new BlockedNumbersMigrator.Listener() {
359             @Override
360             public void onComplete() {
361               mBlockReportListener.onBlock(
362                   displayNumber, number, countryIso, callType, info.sourceType);
363             }
364           });
365     } else if (resId == R.id.context_menu_unblock) {
366       Logger.get(mContext)
367           .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_UNBLOCK_NUMBER);
368       mBlockReportListener.onUnblock(
369           displayNumber, number, countryIso, callType, info.sourceType, isSpam, blockId);
370     } else if (resId == R.id.context_menu_report_not_spam) {
371       Logger.get(mContext)
372           .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_REPORT_AS_NOT_SPAM);
373       mBlockReportListener.onReportNotSpam(
374           displayNumber, number, countryIso, callType, info.sourceType);
375     }
376     return false;
377   }
378 
379   /**
380    * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not inflated
381    * during initial binding, so click handlers, tags and accessibility text must be set here, if
382    * necessary.
383    */
inflateActionViewStub()384   public void inflateActionViewStub() {
385     ViewStub stub = (ViewStub) rootView.findViewById(R.id.call_log_entry_actions_stub);
386     if (stub != null) {
387       actionsView = stub.inflate();
388 
389       voicemailPlaybackView =
390           (VoicemailPlaybackLayout) actionsView.findViewById(R.id.voicemail_playback_layout);
391       voicemailPlaybackView.setViewHolder(this);
392 
393       callButtonView = actionsView.findViewById(R.id.call_action);
394       callButtonView.setOnClickListener(this);
395 
396       videoCallButtonView = actionsView.findViewById(R.id.video_call_action);
397       videoCallButtonView.setOnClickListener(this);
398 
399       createNewContactButtonView = actionsView.findViewById(R.id.create_new_contact_action);
400       createNewContactButtonView.setOnClickListener(this);
401 
402       addToExistingContactButtonView =
403           actionsView.findViewById(R.id.add_to_existing_contact_action);
404       addToExistingContactButtonView.setOnClickListener(this);
405 
406       sendMessageView = actionsView.findViewById(R.id.send_message_action);
407       sendMessageView.setOnClickListener(this);
408 
409       blockReportView = actionsView.findViewById(R.id.block_report_action);
410       blockReportView.setOnClickListener(this);
411 
412       blockView = actionsView.findViewById(R.id.block_action);
413       blockView.setOnClickListener(this);
414 
415       unblockView = actionsView.findViewById(R.id.unblock_action);
416       unblockView.setOnClickListener(this);
417 
418       reportNotSpamView = actionsView.findViewById(R.id.report_not_spam_action);
419       reportNotSpamView.setOnClickListener(this);
420 
421       detailsButtonView = actionsView.findViewById(R.id.details_action);
422       detailsButtonView.setOnClickListener(this);
423 
424       callWithNoteButtonView = actionsView.findViewById(R.id.call_with_note_action);
425       callWithNoteButtonView.setOnClickListener(this);
426 
427       callComposeButtonView = actionsView.findViewById(R.id.call_compose_action);
428       callComposeButtonView.setOnClickListener(this);
429 
430       sendVoicemailButtonView = actionsView.findViewById(R.id.share_voicemail);
431       sendVoicemailButtonView.setOnClickListener(this);
432     }
433   }
434 
updatePrimaryActionButton(boolean isExpanded)435   private void updatePrimaryActionButton(boolean isExpanded) {
436 
437     if (nameOrNumber == null) {
438       LogUtil.e("CallLogListItemViewHolder.updatePrimaryActionButton", "name or number is null");
439     }
440 
441     // Calling expandTemplate with a null parameter will cause a NullPointerException.
442     CharSequence validNameOrNumber = nameOrNumber == null ? "" : nameOrNumber;
443 
444     if (!TextUtils.isEmpty(voicemailUri)) {
445       // Treat as voicemail list item; show play button if not expanded.
446       if (!isExpanded) {
447         primaryActionButtonView.setImageResource(R.drawable.quantum_ic_play_arrow_white_24);
448         primaryActionButtonView.setContentDescription(
449             TextUtils.expandTemplate(
450                 mContext.getString(R.string.description_voicemail_action), validNameOrNumber));
451         primaryActionButtonView.setVisibility(View.VISIBLE);
452       } else {
453         primaryActionButtonView.setVisibility(View.GONE);
454       }
455     } else {
456       // Treat as normal list item; show call button, if possible.
457       if (PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation)) {
458         boolean isVoicemailNumber = mCallLogCache.isVoicemailNumber(accountHandle, number);
459         if (isVoicemailNumber) {
460           // Call to generic voicemail number, in case there are multiple accounts.
461           primaryActionButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider());
462         } else {
463           primaryActionButtonView.setTag(
464               IntentProvider.getReturnCallIntentProvider(number + postDialDigits));
465         }
466 
467         primaryActionButtonView.setContentDescription(
468             TextUtils.expandTemplate(
469                 mContext.getString(R.string.description_call_action), validNameOrNumber));
470         primaryActionButtonView.setImageResource(R.drawable.quantum_ic_call_white_24);
471         primaryActionButtonView.setVisibility(View.VISIBLE);
472       } else {
473         primaryActionButtonView.setTag(null);
474         primaryActionButtonView.setVisibility(View.GONE);
475       }
476     }
477   }
478 
479   /**
480    * Binds text titles, click handlers and intents to the voicemail, details and callback action
481    * buttons.
482    */
bindActionButtons()483   private void bindActionButtons() {
484     boolean canPlaceCallToNumber = PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation);
485 
486     if (isFullyUndialableVoicemail()) {
487       // Sometimes the voicemail server will report the message is from some non phone number
488       // source. If the number does not contains any dialable digit treat it as it is from a unknown
489       // number, remove all action buttons but still show the voicemail playback layout.
490       callButtonView.setVisibility(View.GONE);
491       videoCallButtonView.setVisibility(View.GONE);
492       detailsButtonView.setVisibility(View.GONE);
493       createNewContactButtonView.setVisibility(View.GONE);
494       addToExistingContactButtonView.setVisibility(View.GONE);
495       sendMessageView.setVisibility(View.GONE);
496       callWithNoteButtonView.setVisibility(View.GONE);
497       callComposeButtonView.setVisibility(View.GONE);
498       blockReportView.setVisibility(View.GONE);
499       blockView.setVisibility(View.GONE);
500       unblockView.setVisibility(View.GONE);
501       reportNotSpamView.setVisibility(View.GONE);
502 
503       voicemailPlaybackView.setVisibility(View.VISIBLE);
504       Uri uri = Uri.parse(voicemailUri);
505       mVoicemailPlaybackPresenter.setPlaybackView(
506           voicemailPlaybackView,
507           rowId,
508           uri,
509           mVoicemailPrimaryActionButtonClicked,
510           sendVoicemailButtonView);
511       mVoicemailPrimaryActionButtonClicked = false;
512       CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri);
513       return;
514     }
515 
516     if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) {
517       callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number));
518       ((TextView) callButtonView.findViewById(R.id.call_action_text))
519           .setText(
520               TextUtils.expandTemplate(
521                   mContext.getString(R.string.call_log_action_call),
522                   nameOrNumber == null ? "" : nameOrNumber));
523       TextView callTypeOrLocationView =
524           ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text));
525       if (callType == Calls.VOICEMAIL_TYPE && !TextUtils.isEmpty(callTypeOrLocation)) {
526         callTypeOrLocationView.setText(callTypeOrLocation);
527         callTypeOrLocationView.setVisibility(View.VISIBLE);
528       } else {
529         callTypeOrLocationView.setVisibility(View.GONE);
530       }
531       callButtonView.setVisibility(View.VISIBLE);
532     } else {
533       callButtonView.setVisibility(View.GONE);
534     }
535 
536     if (hasPlacedCarrierVideoCall() || canSupportCarrierVideoCall()) {
537       videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
538       videoCallButtonView.setVisibility(View.VISIBLE);
539     } else if (lightbringerReady) {
540       videoCallButtonView.setTag(IntentProvider.getLightbringerIntentProvider(number));
541       videoCallButtonView.setVisibility(View.VISIBLE);
542     } else {
543       videoCallButtonView.setVisibility(View.GONE);
544     }
545 
546     // For voicemail calls, show the voicemail playback layout; hide otherwise.
547     if (callType == Calls.VOICEMAIL_TYPE
548         && mVoicemailPlaybackPresenter != null
549         && !TextUtils.isEmpty(voicemailUri)) {
550       voicemailPlaybackView.setVisibility(View.VISIBLE);
551 
552       Uri uri = Uri.parse(voicemailUri);
553       mVoicemailPlaybackPresenter.setPlaybackView(
554           voicemailPlaybackView,
555           rowId,
556           uri,
557           mVoicemailPrimaryActionButtonClicked,
558           sendVoicemailButtonView);
559       mVoicemailPrimaryActionButtonClicked = false;
560       CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri);
561     } else {
562       voicemailPlaybackView.setVisibility(View.GONE);
563       sendVoicemailButtonView.setVisibility(View.GONE);
564     }
565 
566     if (callType == Calls.VOICEMAIL_TYPE) {
567       detailsButtonView.setVisibility(View.GONE);
568     } else {
569       detailsButtonView.setVisibility(View.VISIBLE);
570       detailsButtonView.setTag(
571           IntentProvider.getCallDetailIntentProvider(callDetailsEntries, buildContact()));
572     }
573 
574     boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam);
575 
576     if (!isBlockedOrSpam && info != null && UriUtils.isEncodedContactUri(info.lookupUri)) {
577       createNewContactButtonView.setTag(
578           IntentProvider.getAddContactIntentProvider(
579               info.lookupUri, info.name, info.number, info.type, true /* isNewContact */));
580       createNewContactButtonView.setVisibility(View.VISIBLE);
581 
582       addToExistingContactButtonView.setTag(
583           IntentProvider.getAddContactIntentProvider(
584               info.lookupUri, info.name, info.number, info.type, false /* isNewContact */));
585       addToExistingContactButtonView.setVisibility(View.VISIBLE);
586     } else {
587       createNewContactButtonView.setVisibility(View.GONE);
588       addToExistingContactButtonView.setVisibility(View.GONE);
589     }
590 
591     boolean isVoicemailNumber = mCallLogCache.isVoicemailNumber(accountHandle, number);
592     if (canPlaceCallToNumber && !isBlockedOrSpam && !isVoicemailNumber) {
593       sendMessageView.setTag(IntentProvider.getSendSmsIntentProvider(number));
594       sendMessageView.setVisibility(View.VISIBLE);
595     } else {
596       sendMessageView.setVisibility(View.GONE);
597     }
598 
599     mCallLogListItemHelper.setActionContentDescriptions(this);
600 
601     boolean supportsCallSubject = mCallLogCache.doesAccountSupportCallSubject(accountHandle);
602     callWithNoteButtonView.setVisibility(
603         supportsCallSubject && !isVoicemailNumber && info != null ? View.VISIBLE : View.GONE);
604 
605     callComposeButtonView.setVisibility(isCallComposerCapable ? View.VISIBLE : View.GONE);
606 
607     updateBlockReportActions(isVoicemailNumber);
608   }
609 
isFullyUndialableVoicemail()610   private boolean isFullyUndialableVoicemail() {
611     if (callType == Calls.VOICEMAIL_TYPE) {
612       if (!hasDialableChar(number)) {
613         return true;
614       }
615     }
616     return false;
617   }
618 
hasDialableChar(CharSequence number)619   private static boolean hasDialableChar(CharSequence number) {
620     if (TextUtils.isEmpty(number)) {
621       return false;
622     }
623     for (char c : number.toString().toCharArray()) {
624       if (PhoneNumberUtils.isDialable(c)) {
625         return true;
626       }
627     }
628     return false;
629   }
630 
hasPlacedCarrierVideoCall()631   private boolean hasPlacedCarrierVideoCall() {
632     if (!phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
633       return false;
634     }
635     if (accountHandle == null) {
636       return false;
637     }
638     if (accountHandle
639         .getComponentName()
640         .equals(getLightbringer().getPhoneAccountComponentName(mContext))) {
641       return false;
642     }
643     return true;
644   }
645 
canSupportCarrierVideoCall()646   private boolean canSupportCarrierVideoCall() {
647     return mCallLogCache.canRelyOnVideoPresence()
648         && info != null
649         && (info.carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
650   }
651 
652   /**
653    * Show or hide the action views, such as voicemail, details, and add contact.
654    *
655    * <p>If the action views have never been shown yet for this view, inflate the view stub.
656    */
showActions(boolean show)657   public void showActions(boolean show) {
658     showOrHideVoicemailTranscriptionView(show);
659 
660     if (show) {
661       if (!isLoaded) {
662         // b/31268128 for some unidentified reason showActions() can be called before the item is
663         // loaded, causing NPE on uninitialized fields. Just log and return here, showActions() will
664         // be called again once the item is loaded.
665         LogUtil.e(
666             "CallLogListItemViewHolder.showActions",
667             "called before item is loaded",
668             new Exception());
669         return;
670       }
671 
672       // Inflate the view stub if necessary, and wire up the event handlers.
673       inflateActionViewStub();
674       bindActionButtons();
675       actionsView.setVisibility(View.VISIBLE);
676       actionsView.setAlpha(1.0f);
677     } else {
678       // When recycling a view, it is possible the actionsView ViewStub was previously
679       // inflated so we should hide it in this case.
680       if (actionsView != null) {
681         actionsView.setVisibility(View.GONE);
682       }
683     }
684 
685     updatePrimaryActionButton(show);
686   }
687 
showOrHideVoicemailTranscriptionView(boolean isExpanded)688   private void showOrHideVoicemailTranscriptionView(boolean isExpanded) {
689     if (callType != Calls.VOICEMAIL_TYPE) {
690       return;
691     }
692 
693     final TextView view = phoneCallDetailsViews.voicemailTranscriptionView;
694     if (!isExpanded || TextUtils.isEmpty(view.getText())) {
695       view.setVisibility(View.GONE);
696       return;
697     }
698     view.setVisibility(View.VISIBLE);
699   }
700 
updatePhoto()701   public void updatePhoto() {
702     quickContactView.assignContactUri(info.lookupUri);
703 
704     if (isSpamFeatureEnabled && isSpam) {
705       quickContactView.setImageDrawable(mContext.getDrawable(R.drawable.blocked_contact));
706       return;
707     }
708 
709     final String displayName = TextUtils.isEmpty(info.name) ? displayNumber : info.name;
710     ContactPhotoManager.getInstance(mContext)
711         .loadDialerThumbnailOrPhoto(
712             quickContactView,
713             info.lookupUri,
714             info.photoId,
715             info.photoUri,
716             displayName,
717             getContactType());
718   }
719 
getContactType()720   private int getContactType() {
721     int contactType = ContactPhotoManager.TYPE_DEFAULT;
722     if (mCallLogCache.isVoicemailNumber(accountHandle, number)) {
723       contactType = ContactPhotoManager.TYPE_VOICEMAIL;
724     } else if (isSpam) {
725       contactType = ContactPhotoManager.TYPE_SPAM;
726     } else if (mCachedNumberLookupService != null
727         && mCachedNumberLookupService.isBusiness(info.sourceType)) {
728       contactType = ContactPhotoManager.TYPE_BUSINESS;
729     } else if (numberPresentation == TelecomManager.PRESENTATION_RESTRICTED) {
730       contactType = ContactPhotoManager.TYPE_GENERIC_AVATAR;
731     }
732     return contactType;
733   }
734 
735   @Override
onClick(View view)736   public void onClick(View view) {
737     if (view.getId() == R.id.primary_action_button) {
738       CallLogAsyncTaskUtil.markCallAsRead(mContext, callIds);
739     }
740 
741     if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) {
742       Logger.get(mContext).logImpression(DialerImpression.Type.VOICEMAIL_PLAY_AUDIO_DIRECTLY);
743       mVoicemailPrimaryActionButtonClicked = true;
744       mExpandCollapseListener.onClick(primaryActionView);
745     } else if (view.getId() == R.id.call_with_note_action) {
746       CallSubjectDialog.start(
747           (Activity) mContext,
748           info.photoId,
749           info.photoUri,
750           info.lookupUri,
751           (String) nameOrNumber /* top line of contact view in call subject dialog */,
752           number,
753           TextUtils.isEmpty(info.name) ? null : displayNumber, /* second line of contact
754                                                                            view in dialog. */
755           numberType, /* phone number type (e.g. mobile) in second line of contact view */
756           getContactType(),
757           accountHandle);
758     } else if (view.getId() == R.id.block_report_action) {
759       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_BLOCK_REPORT_SPAM);
760       maybeShowBlockNumberMigrationDialog(
761           new BlockedNumbersMigrator.Listener() {
762             @Override
763             public void onComplete() {
764               mBlockReportListener.onBlockReportSpam(
765                   displayNumber, number, countryIso, callType, info.sourceType);
766             }
767           });
768     } else if (view.getId() == R.id.block_action) {
769       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_BLOCK_NUMBER);
770       maybeShowBlockNumberMigrationDialog(
771           new BlockedNumbersMigrator.Listener() {
772             @Override
773             public void onComplete() {
774               mBlockReportListener.onBlock(
775                   displayNumber, number, countryIso, callType, info.sourceType);
776             }
777           });
778     } else if (view.getId() == R.id.unblock_action) {
779       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_UNBLOCK_NUMBER);
780       mBlockReportListener.onUnblock(
781           displayNumber, number, countryIso, callType, info.sourceType, isSpam, blockId);
782     } else if (view.getId() == R.id.report_not_spam_action) {
783       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_REPORT_AS_NOT_SPAM);
784       mBlockReportListener.onReportNotSpam(
785           displayNumber, number, countryIso, callType, info.sourceType);
786     } else if (view.getId() == R.id.call_compose_action) {
787       LogUtil.i("CallLogListItemViewHolder.onClick", "share and call pressed");
788       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SHARE_AND_CALL);
789       Activity activity = (Activity) mContext;
790       activity.startActivityForResult(
791           CallComposerActivity.newIntent(activity, buildContact()),
792           DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_COMPOSE);
793     } else if (view.getId() == R.id.share_voicemail) {
794       Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_PRESSED);
795       mVoicemailPlaybackPresenter.shareVoicemail();
796     } else {
797       logCallLogAction(view.getId());
798       final IntentProvider intentProvider = (IntentProvider) view.getTag();
799       if (intentProvider != null) {
800         final Intent intent = intentProvider.getIntent(mContext);
801         // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
802         if (intent != null) {
803           DialerUtils.startActivityWithErrorToast(mContext, intent);
804         }
805       }
806     }
807   }
808 
buildContact()809   private CallComposerContact buildContact() {
810     CallComposerContact.Builder contact = CallComposerContact.newBuilder();
811     contact.setPhotoId(info.photoId);
812     if (info.photoUri != null) {
813       contact.setPhotoUri(info.photoUri.toString());
814     }
815     if (info.lookupUri != null) {
816       contact.setContactUri(info.lookupUri.toString());
817     }
818     if (nameOrNumber != null) {
819       contact.setNameOrNumber((String) nameOrNumber);
820     }
821     contact.setContactType(getContactType());
822     contact.setNumber(number);
823     /* second line of contact view. */
824     if (!TextUtils.isEmpty(info.name)) {
825       contact.setDisplayNumber(displayNumber);
826     }
827     /* phone number type (e.g. mobile) in second line of contact view */
828     contact.setNumberLabel(numberType);
829     return contact.build();
830   }
831 
logCallLogAction(int id)832   private void logCallLogAction(int id) {
833     if (id == R.id.send_message_action) {
834       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SEND_MESSAGE);
835     } else if (id == R.id.add_to_existing_contact_action) {
836       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_ADD_TO_CONTACT);
837     } else if (id == R.id.create_new_contact_action) {
838       Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_CREATE_NEW_CONTACT);
839     }
840   }
841 
maybeShowBlockNumberMigrationDialog(BlockedNumbersMigrator.Listener listener)842   private void maybeShowBlockNumberMigrationDialog(BlockedNumbersMigrator.Listener listener) {
843     if (!FilteredNumberCompat.maybeShowBlockNumberMigrationDialog(
844         mContext, ((Activity) mContext).getFragmentManager(), listener)) {
845       listener.onComplete();
846     }
847   }
848 
updateBlockReportActions(boolean isVoicemailNumber)849   private void updateBlockReportActions(boolean isVoicemailNumber) {
850     // Set block/spam actions.
851     blockReportView.setVisibility(View.GONE);
852     blockView.setVisibility(View.GONE);
853     unblockView.setVisibility(View.GONE);
854     reportNotSpamView.setVisibility(View.GONE);
855     String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
856     if (isVoicemailNumber
857         || !FilteredNumbersUtil.canBlockNumber(mContext, e164Number, number)
858         || !FilteredNumberCompat.canAttemptBlockOperations(mContext)) {
859       return;
860     }
861     boolean isBlocked = blockId != null;
862     if (isBlocked) {
863       unblockView.setVisibility(View.VISIBLE);
864     } else {
865       if (isSpamFeatureEnabled) {
866         if (isSpam) {
867           blockView.setVisibility(View.VISIBLE);
868           reportNotSpamView.setVisibility(View.VISIBLE);
869         } else {
870           blockReportView.setVisibility(View.VISIBLE);
871         }
872       } else {
873         blockView.setVisibility(View.VISIBLE);
874       }
875     }
876   }
877 
setDetailedPhoneDetails(CallDetailsEntries callDetailsEntries)878   public void setDetailedPhoneDetails(CallDetailsEntries callDetailsEntries) {
879     this.callDetailsEntries = callDetailsEntries;
880   }
881 
882   @VisibleForTesting
getDetailedPhoneDetails()883   public CallDetailsEntries getDetailedPhoneDetails() {
884     return callDetailsEntries;
885   }
886 
887   @NonNull
getLightbringer()888   private Lightbringer getLightbringer() {
889     return LightbringerComponent.get(mContext).getLightbringer();
890   }
891 
892   @Override
onCreateContextMenu( final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)893   public void onCreateContextMenu(
894       final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
895     if (TextUtils.isEmpty(number)) {
896       return;
897     }
898 
899     if (callType == CallLog.Calls.VOICEMAIL_TYPE) {
900       menu.setHeaderTitle(mContext.getResources().getText(R.string.voicemail));
901     } else {
902       menu.setHeaderTitle(
903           PhoneNumberUtilsCompat.createTtsSpannable(
904               BidiFormatter.getInstance().unicodeWrap(number, TextDirectionHeuristics.LTR)));
905     }
906 
907     menu.add(
908             ContextMenu.NONE,
909             R.id.context_menu_copy_to_clipboard,
910             ContextMenu.NONE,
911             R.string.action_copy_number_text)
912         .setOnMenuItemClickListener(this);
913 
914     // The edit number before call does not show up if any of the conditions apply:
915     // 1) Number cannot be called
916     // 2) Number is the voicemail number
917     // 3) Number is a SIP address
918 
919     if (PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation)
920         && !mCallLogCache.isVoicemailNumber(accountHandle, number)
921         && !PhoneNumberHelper.isSipNumber(number)) {
922       menu.add(
923               ContextMenu.NONE,
924               R.id.context_menu_edit_before_call,
925               ContextMenu.NONE,
926               R.string.action_edit_number_before_call)
927           .setOnMenuItemClickListener(this);
928     }
929 
930     if (callType == CallLog.Calls.VOICEMAIL_TYPE
931         && phoneCallDetailsViews.voicemailTranscriptionView.length() > 0) {
932       menu.add(
933               ContextMenu.NONE,
934               R.id.context_menu_copy_transcript_to_clipboard,
935               ContextMenu.NONE,
936               R.string.copy_transcript_text)
937           .setOnMenuItemClickListener(this);
938     }
939 
940     String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso);
941     boolean isVoicemailNumber = mCallLogCache.isVoicemailNumber(accountHandle, number);
942     if (!isVoicemailNumber
943         && FilteredNumbersUtil.canBlockNumber(mContext, e164Number, number)
944         && FilteredNumberCompat.canAttemptBlockOperations(mContext)) {
945       boolean isBlocked = blockId != null;
946       if (isBlocked) {
947         menu.add(
948                 ContextMenu.NONE,
949                 R.id.context_menu_unblock,
950                 ContextMenu.NONE,
951                 R.string.call_log_action_unblock_number)
952             .setOnMenuItemClickListener(this);
953       } else {
954         if (isSpamFeatureEnabled) {
955           if (isSpam) {
956             menu.add(
957                     ContextMenu.NONE,
958                     R.id.context_menu_report_not_spam,
959                     ContextMenu.NONE,
960                     R.string.call_log_action_remove_spam)
961                 .setOnMenuItemClickListener(this);
962             menu.add(
963                     ContextMenu.NONE,
964                     R.id.context_menu_block,
965                     ContextMenu.NONE,
966                     R.string.call_log_action_block_number)
967                 .setOnMenuItemClickListener(this);
968           } else {
969             menu.add(
970                     ContextMenu.NONE,
971                     R.id.context_menu_block_report_spam,
972                     ContextMenu.NONE,
973                     R.string.call_log_action_block_report_number)
974                 .setOnMenuItemClickListener(this);
975           }
976         } else {
977           menu.add(
978                   ContextMenu.NONE,
979                   R.id.context_menu_block,
980                   ContextMenu.NONE,
981                   R.string.call_log_action_block_number)
982               .setOnMenuItemClickListener(this);
983         }
984       }
985     }
986 
987     Logger.get(mContext).logScreenView(ScreenEvent.Type.CALL_LOG_CONTEXT_MENU, (Activity) mContext);
988   }
989 
990   public interface OnClickListener {
991 
onBlockReportSpam( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType)992     void onBlockReportSpam(
993         String displayNumber,
994         String number,
995         String countryIso,
996         int callType,
997         ContactSource.Type contactSourceType);
998 
onBlock( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType)999     void onBlock(
1000         String displayNumber,
1001         String number,
1002         String countryIso,
1003         int callType,
1004         ContactSource.Type contactSourceType);
1005 
onUnblock( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType, boolean isSpam, Integer blockId)1006     void onUnblock(
1007         String displayNumber,
1008         String number,
1009         String countryIso,
1010         int callType,
1011         ContactSource.Type contactSourceType,
1012         boolean isSpam,
1013         Integer blockId);
1014 
onReportNotSpam( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType)1015     void onReportNotSpam(
1016         String displayNumber,
1017         String number,
1018         String countryIso,
1019         int callType,
1020         ContactSource.Type contactSourceType);
1021   }
1022 }
1023