/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.dialer.app.calllog; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.os.AsyncTask; import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.view.ContextMenu; import android.view.MenuItem; import android.view.View; import android.view.ViewStub; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.QuickContactBadge; import android.widget.TextView; import android.widget.Toast; import com.android.contacts.common.ClipboardUtils; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.contacts.common.dialog.CallSubjectDialog; import com.android.contacts.common.util.UriUtils; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; import com.android.dialer.app.calllog.calllogcache.CallLogCache; import com.android.dialer.app.voicemail.VoicemailPlaybackLayout; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.blocking.BlockedNumbersMigrator; import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.blocking.FilteredNumbersUtil; import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.callcomposer.CallComposerContact; import com.android.dialer.calldetails.CallDetailsEntries; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; import com.android.dialer.lightbringer.Lightbringer; import com.android.dialer.lightbringer.LightbringerComponent; import com.android.dialer.logging.ContactSource; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.logging.ScreenEvent; import com.android.dialer.phonenumbercache.CachedNumberLookupService; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumbercache.PhoneNumberCache; import com.android.dialer.phonenumberutil.PhoneNumberHelper; import com.android.dialer.util.CallUtil; import com.android.dialer.util.DialerUtils; /** * This is an object containing references to views contained by the call log list item. This * improves performance by reducing the frequency with which we need to find views by IDs. * *

This object also contains UI logic pertaining to the view, to isolate it from the * CallLogAdapter. */ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, MenuItem.OnMenuItemClickListener, View.OnCreateContextMenuListener { /** The root view of the call log list item */ public final View rootView; /** The quick contact badge for the contact. */ public final QuickContactBadge quickContactView; /** The primary action view of the entry. */ public final View primaryActionView; /** The details of the phone call. */ public final PhoneCallDetailsViews phoneCallDetailsViews; /** The text of the header for a day grouping. */ public final TextView dayGroupHeader; /** The view containing the details for the call log row, including the action buttons. */ public final CardView callLogEntryView; /** The actionable view which places a call to the number corresponding to the call log row. */ public final ImageView primaryActionButtonView; private final Context mContext; private final CallLogCache mCallLogCache; private final CallLogListItemHelper mCallLogListItemHelper; private final CachedNumberLookupService mCachedNumberLookupService; private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter; private final OnClickListener mBlockReportListener; /** Whether the data fields are populated by the worker thread, ready to be shown. */ public boolean isLoaded; /** The view containing call log item actions. Null until the ViewStub is inflated. */ public View actionsView; /** The button views below are assigned only when the action section is expanded. */ public VoicemailPlaybackLayout voicemailPlaybackView; public View callButtonView; public View videoCallButtonView; public View createNewContactButtonView; public View addToExistingContactButtonView; public View sendMessageView; public View blockReportView; public View blockView; public View unblockView; public View reportNotSpamView; public View detailsButtonView; public View callWithNoteButtonView; public View callComposeButtonView; public View sendVoicemailButtonView; public ImageView workIconView; public ImageView checkBoxView; /** * The row Id for the first call associated with the call log entry. Used as a key for the map * used to track which call log entries have the action button section expanded. */ public long rowId; /** * The call Ids for the calls represented by the current call log entry. Used when the user * deletes a call log entry. */ public long[] callIds; /** * The callable phone number for the current call log entry. Cached here as the call back intent * is set only when the actions ViewStub is inflated. */ public String number; /** The post-dial numbers that are dialed following the phone number. */ public String postDialDigits; /** The formatted phone number to display. */ public String displayNumber; /** * The phone number presentation for the current call log entry. Cached here as the call back * intent is set only when the actions ViewStub is inflated. */ public int numberPresentation; /** The type of the phone number (e.g. main, work, etc). */ public String numberType; /** * The country iso for the call. Cached here as the call back intent is set only when the actions * ViewStub is inflated. */ public String countryIso; /** * The type of call for the current call log entry. Cached here as the call back intent is set * only when the actions ViewStub is inflated. */ public int callType; /** * ID for blocked numbers database. Set when context menu is created, if the number is blocked. */ public Integer blockId; /** * The account for the current call log entry. Cached here as the call back intent is set only * when the actions ViewStub is inflated. */ public PhoneAccountHandle accountHandle; /** * If the call has an associated voicemail message, the URI of the voicemail message for playback. * Cached here as the voicemail intent is only set when the actions ViewStub is inflated. */ public String voicemailUri; /** * The name or number associated with the call. Cached here for use when setting content * descriptions on buttons in the actions ViewStub when it is inflated. */ @Nullable public CharSequence nameOrNumber; /** * The call type or Location associated with the call. Cached here for use when setting text for a * voicemail log's call button */ public CharSequence callTypeOrLocation; /** The contact info for the contact displayed in this list item. */ public volatile ContactInfo info; /** Whether spam feature is enabled, which affects UI. */ public boolean isSpamFeatureEnabled; /** Whether the current log entry is a spam number or not. */ public boolean isSpam; public boolean isCallComposerCapable; public boolean lightbringerReady; private View.OnClickListener mExpandCollapseListener; private final View.OnLongClickListener longPressListener; private boolean mVoicemailPrimaryActionButtonClicked; public int dayGroupHeaderVisibility; public CharSequence dayGroupHeaderText; public boolean isAttachedToWindow; public AsyncTask asyncTask; private CallDetailsEntries callDetailsEntries; private 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) { super(rootView); mContext = context; mExpandCollapseListener = expandCollapseListener; longPressListener = longClickListener; mCallLogCache = callLogCache; mCallLogListItemHelper = callLogListItemHelper; mVoicemailPlaybackPresenter = voicemailPlaybackPresenter; mBlockReportListener = blockReportListener; mCachedNumberLookupService = PhoneNumberCache.get(mContext).getCachedNumberLookupService(); this.rootView = rootView; this.quickContactView = quickContactView; this.primaryActionView = primaryActionView; this.phoneCallDetailsViews = phoneCallDetailsViews; this.callLogEntryView = callLogEntryView; this.dayGroupHeader = dayGroupHeader; this.primaryActionButtonView = primaryActionButtonView; this.workIconView = (ImageView) rootView.findViewById(R.id.work_profile_icon); this.checkBoxView = (ImageView) rootView.findViewById(R.id.quick_contact_checkbox); // Set text height to false on the TextViews so they don't have extra padding. phoneCallDetailsViews.nameView.setElegantTextHeight(false); phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false); quickContactView.setOverlay(null); if (CompatUtils.hasPrioritizedMimeType()) { quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); } primaryActionButtonView.setOnClickListener(this); primaryActionView.setOnClickListener(mExpandCollapseListener); if (mVoicemailPlaybackPresenter != null) { primaryActionView.setOnLongClickListener(longPressListener); quickContactView.setOnLongClickListener(longPressListener); } else { primaryActionView.setOnCreateContextMenuListener(this); } } public static CallLogListItemViewHolder create( View view, Context context, OnClickListener blockReportListener, View.OnClickListener expandCollapseListener, View.OnLongClickListener longClickListener, CallLogCache callLogCache, CallLogListItemHelper callLogListItemHelper, VoicemailPlaybackPresenter voicemailPlaybackPresenter) { return new CallLogListItemViewHolder( context, blockReportListener, expandCollapseListener, longClickListener, callLogCache, callLogListItemHelper, voicemailPlaybackPresenter, view, (QuickContactBadge) view.findViewById(R.id.quick_contact_photo), view.findViewById(R.id.primary_action_view), PhoneCallDetailsViews.fromView(view), (CardView) view.findViewById(R.id.call_log_row), (TextView) view.findViewById(R.id.call_log_day_group_label), (ImageView) view.findViewById(R.id.primary_action_button)); } public static CallLogListItemViewHolder createForTest(Context context) { Resources resources = context.getResources(); CallLogCache callLogCache = CallLogCache.getCallLogCache(context); PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(context, resources, callLogCache); CallLogListItemViewHolder viewHolder = new CallLogListItemViewHolder( context, null, null /* expandCollapseListener */, null, callLogCache, new CallLogListItemHelper(phoneCallDetailsHelper, resources, callLogCache), null /* voicemailPlaybackPresenter */, new View(context), new QuickContactBadge(context), new View(context), PhoneCallDetailsViews.createForTest(context), new CardView(context), new TextView(context), new ImageView(context)); viewHolder.detailsButtonView = new TextView(context); viewHolder.actionsView = new View(context); viewHolder.voicemailPlaybackView = new VoicemailPlaybackLayout(context); viewHolder.workIconView = new ImageButton(context); viewHolder.checkBoxView = new ImageButton(context); return viewHolder; } @Override public boolean onMenuItemClick(MenuItem item) { int resId = item.getItemId(); if (resId == R.id.context_menu_copy_to_clipboard) { ClipboardUtils.copyText(mContext, null, number, true); return true; } else if (resId == R.id.context_menu_copy_transcript_to_clipboard) { ClipboardUtils.copyText( mContext, null, phoneCallDetailsViews.voicemailTranscriptionView.getText(), true); return true; } else if (resId == R.id.context_menu_edit_before_call) { final Intent intent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(number)); intent.setClass(mContext, DialtactsActivity.class); DialerUtils.startActivityWithErrorToast(mContext, intent); return true; } else if (resId == R.id.context_menu_block_report_spam) { Logger.get(mContext) .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_BLOCK_REPORT_SPAM); maybeShowBlockNumberMigrationDialog( new BlockedNumbersMigrator.Listener() { @Override public void onComplete() { mBlockReportListener.onBlockReportSpam( displayNumber, number, countryIso, callType, info.sourceType); } }); } else if (resId == R.id.context_menu_block) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_BLOCK_NUMBER); maybeShowBlockNumberMigrationDialog( new BlockedNumbersMigrator.Listener() { @Override public void onComplete() { mBlockReportListener.onBlock( displayNumber, number, countryIso, callType, info.sourceType); } }); } else if (resId == R.id.context_menu_unblock) { Logger.get(mContext) .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_UNBLOCK_NUMBER); mBlockReportListener.onUnblock( displayNumber, number, countryIso, callType, info.sourceType, isSpam, blockId); } else if (resId == R.id.context_menu_report_not_spam) { Logger.get(mContext) .logImpression(DialerImpression.Type.CALL_LOG_CONTEXT_MENU_REPORT_AS_NOT_SPAM); mBlockReportListener.onReportNotSpam( displayNumber, number, countryIso, callType, info.sourceType); } return false; } /** * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not inflated * during initial binding, so click handlers, tags and accessibility text must be set here, if * necessary. */ public void inflateActionViewStub() { ViewStub stub = (ViewStub) rootView.findViewById(R.id.call_log_entry_actions_stub); if (stub != null) { actionsView = stub.inflate(); voicemailPlaybackView = (VoicemailPlaybackLayout) actionsView.findViewById(R.id.voicemail_playback_layout); voicemailPlaybackView.setViewHolder(this); callButtonView = actionsView.findViewById(R.id.call_action); callButtonView.setOnClickListener(this); videoCallButtonView = actionsView.findViewById(R.id.video_call_action); videoCallButtonView.setOnClickListener(this); createNewContactButtonView = actionsView.findViewById(R.id.create_new_contact_action); createNewContactButtonView.setOnClickListener(this); addToExistingContactButtonView = actionsView.findViewById(R.id.add_to_existing_contact_action); addToExistingContactButtonView.setOnClickListener(this); sendMessageView = actionsView.findViewById(R.id.send_message_action); sendMessageView.setOnClickListener(this); blockReportView = actionsView.findViewById(R.id.block_report_action); blockReportView.setOnClickListener(this); blockView = actionsView.findViewById(R.id.block_action); blockView.setOnClickListener(this); unblockView = actionsView.findViewById(R.id.unblock_action); unblockView.setOnClickListener(this); reportNotSpamView = actionsView.findViewById(R.id.report_not_spam_action); reportNotSpamView.setOnClickListener(this); detailsButtonView = actionsView.findViewById(R.id.details_action); detailsButtonView.setOnClickListener(this); callWithNoteButtonView = actionsView.findViewById(R.id.call_with_note_action); callWithNoteButtonView.setOnClickListener(this); callComposeButtonView = actionsView.findViewById(R.id.call_compose_action); callComposeButtonView.setOnClickListener(this); sendVoicemailButtonView = actionsView.findViewById(R.id.share_voicemail); sendVoicemailButtonView.setOnClickListener(this); } } private void updatePrimaryActionButton(boolean isExpanded) { if (nameOrNumber == null) { LogUtil.e("CallLogListItemViewHolder.updatePrimaryActionButton", "name or number is null"); } // Calling expandTemplate with a null parameter will cause a NullPointerException. CharSequence validNameOrNumber = nameOrNumber == null ? "" : nameOrNumber; if (!TextUtils.isEmpty(voicemailUri)) { // Treat as voicemail list item; show play button if not expanded. if (!isExpanded) { primaryActionButtonView.setImageResource(R.drawable.quantum_ic_play_arrow_white_24); primaryActionButtonView.setContentDescription( TextUtils.expandTemplate( mContext.getString(R.string.description_voicemail_action), validNameOrNumber)); primaryActionButtonView.setVisibility(View.VISIBLE); } else { primaryActionButtonView.setVisibility(View.GONE); } } else { // Treat as normal list item; show call button, if possible. if (PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation)) { boolean isVoicemailNumber = mCallLogCache.isVoicemailNumber(accountHandle, number); if (isVoicemailNumber) { // Call to generic voicemail number, in case there are multiple accounts. primaryActionButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider()); } else { primaryActionButtonView.setTag( IntentProvider.getReturnCallIntentProvider(number + postDialDigits)); } primaryActionButtonView.setContentDescription( TextUtils.expandTemplate( mContext.getString(R.string.description_call_action), validNameOrNumber)); primaryActionButtonView.setImageResource(R.drawable.quantum_ic_call_white_24); primaryActionButtonView.setVisibility(View.VISIBLE); } else { primaryActionButtonView.setTag(null); primaryActionButtonView.setVisibility(View.GONE); } } } /** * Binds text titles, click handlers and intents to the voicemail, details and callback action * buttons. */ private void bindActionButtons() { boolean canPlaceCallToNumber = PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation); if (isFullyUndialableVoicemail()) { // Sometimes the voicemail server will report the message is from some non phone number // source. If the number does not contains any dialable digit treat it as it is from a unknown // number, remove all action buttons but still show the voicemail playback layout. callButtonView.setVisibility(View.GONE); videoCallButtonView.setVisibility(View.GONE); detailsButtonView.setVisibility(View.GONE); createNewContactButtonView.setVisibility(View.GONE); addToExistingContactButtonView.setVisibility(View.GONE); sendMessageView.setVisibility(View.GONE); callWithNoteButtonView.setVisibility(View.GONE); callComposeButtonView.setVisibility(View.GONE); blockReportView.setVisibility(View.GONE); blockView.setVisibility(View.GONE); unblockView.setVisibility(View.GONE); reportNotSpamView.setVisibility(View.GONE); voicemailPlaybackView.setVisibility(View.VISIBLE); Uri uri = Uri.parse(voicemailUri); mVoicemailPlaybackPresenter.setPlaybackView( voicemailPlaybackView, rowId, uri, mVoicemailPrimaryActionButtonClicked, sendVoicemailButtonView); mVoicemailPrimaryActionButtonClicked = false; CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); return; } if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) { callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number)); ((TextView) callButtonView.findViewById(R.id.call_action_text)) .setText( TextUtils.expandTemplate( mContext.getString(R.string.call_log_action_call), nameOrNumber == null ? "" : nameOrNumber)); TextView callTypeOrLocationView = ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text)); if (callType == Calls.VOICEMAIL_TYPE && !TextUtils.isEmpty(callTypeOrLocation)) { callTypeOrLocationView.setText(callTypeOrLocation); callTypeOrLocationView.setVisibility(View.VISIBLE); } else { callTypeOrLocationView.setVisibility(View.GONE); } callButtonView.setVisibility(View.VISIBLE); } else { callButtonView.setVisibility(View.GONE); } if (hasPlacedCarrierVideoCall() || canSupportCarrierVideoCall()) { videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number)); videoCallButtonView.setVisibility(View.VISIBLE); } else if (lightbringerReady) { videoCallButtonView.setTag(IntentProvider.getLightbringerIntentProvider(number)); videoCallButtonView.setVisibility(View.VISIBLE); } else { videoCallButtonView.setVisibility(View.GONE); } // For voicemail calls, show the voicemail playback layout; hide otherwise. if (callType == Calls.VOICEMAIL_TYPE && mVoicemailPlaybackPresenter != null && !TextUtils.isEmpty(voicemailUri)) { voicemailPlaybackView.setVisibility(View.VISIBLE); Uri uri = Uri.parse(voicemailUri); mVoicemailPlaybackPresenter.setPlaybackView( voicemailPlaybackView, rowId, uri, mVoicemailPrimaryActionButtonClicked, sendVoicemailButtonView); mVoicemailPrimaryActionButtonClicked = false; CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); } else { voicemailPlaybackView.setVisibility(View.GONE); sendVoicemailButtonView.setVisibility(View.GONE); } if (callType == Calls.VOICEMAIL_TYPE) { detailsButtonView.setVisibility(View.GONE); } else { detailsButtonView.setVisibility(View.VISIBLE); detailsButtonView.setTag( IntentProvider.getCallDetailIntentProvider(callDetailsEntries, buildContact())); } boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam); if (!isBlockedOrSpam && info != null && UriUtils.isEncodedContactUri(info.lookupUri)) { createNewContactButtonView.setTag( IntentProvider.getAddContactIntentProvider( info.lookupUri, info.name, info.number, info.type, true /* isNewContact */)); createNewContactButtonView.setVisibility(View.VISIBLE); addToExistingContactButtonView.setTag( IntentProvider.getAddContactIntentProvider( info.lookupUri, info.name, info.number, info.type, false /* isNewContact */)); addToExistingContactButtonView.setVisibility(View.VISIBLE); } else { createNewContactButtonView.setVisibility(View.GONE); addToExistingContactButtonView.setVisibility(View.GONE); } boolean isVoicemailNumber = mCallLogCache.isVoicemailNumber(accountHandle, number); if (canPlaceCallToNumber && !isBlockedOrSpam && !isVoicemailNumber) { sendMessageView.setTag(IntentProvider.getSendSmsIntentProvider(number)); sendMessageView.setVisibility(View.VISIBLE); } else { sendMessageView.setVisibility(View.GONE); } mCallLogListItemHelper.setActionContentDescriptions(this); boolean supportsCallSubject = mCallLogCache.doesAccountSupportCallSubject(accountHandle); callWithNoteButtonView.setVisibility( supportsCallSubject && !isVoicemailNumber && info != null ? View.VISIBLE : View.GONE); callComposeButtonView.setVisibility(isCallComposerCapable ? View.VISIBLE : View.GONE); updateBlockReportActions(isVoicemailNumber); } private boolean isFullyUndialableVoicemail() { if (callType == Calls.VOICEMAIL_TYPE) { if (!hasDialableChar(number)) { return true; } } return false; } private static boolean hasDialableChar(CharSequence number) { if (TextUtils.isEmpty(number)) { return false; } for (char c : number.toString().toCharArray()) { if (PhoneNumberUtils.isDialable(c)) { return true; } } return false; } private boolean hasPlacedCarrierVideoCall() { if (!phoneCallDetailsViews.callTypeIcons.isVideoShown()) { return false; } if (accountHandle == null) { return false; } if (accountHandle .getComponentName() .equals(getLightbringer().getPhoneAccountComponentName(mContext))) { return false; } return true; } private boolean canSupportCarrierVideoCall() { return mCallLogCache.canRelyOnVideoPresence() && info != null && (info.carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0; } /** * Show or hide the action views, such as voicemail, details, and add contact. * *

If the action views have never been shown yet for this view, inflate the view stub. */ public void showActions(boolean show) { showOrHideVoicemailTranscriptionView(show); if (show) { if (!isLoaded) { // b/31268128 for some unidentified reason showActions() can be called before the item is // loaded, causing NPE on uninitialized fields. Just log and return here, showActions() will // be called again once the item is loaded. LogUtil.e( "CallLogListItemViewHolder.showActions", "called before item is loaded", new Exception()); return; } // Inflate the view stub if necessary, and wire up the event handlers. inflateActionViewStub(); bindActionButtons(); actionsView.setVisibility(View.VISIBLE); actionsView.setAlpha(1.0f); } else { // When recycling a view, it is possible the actionsView ViewStub was previously // inflated so we should hide it in this case. if (actionsView != null) { actionsView.setVisibility(View.GONE); } } updatePrimaryActionButton(show); } private void showOrHideVoicemailTranscriptionView(boolean isExpanded) { if (callType != Calls.VOICEMAIL_TYPE) { return; } final TextView view = phoneCallDetailsViews.voicemailTranscriptionView; if (!isExpanded || TextUtils.isEmpty(view.getText())) { view.setVisibility(View.GONE); return; } view.setVisibility(View.VISIBLE); } public void updatePhoto() { quickContactView.assignContactUri(info.lookupUri); if (isSpamFeatureEnabled && isSpam) { quickContactView.setImageDrawable(mContext.getDrawable(R.drawable.blocked_contact)); return; } final String displayName = TextUtils.isEmpty(info.name) ? displayNumber : info.name; ContactPhotoManager.getInstance(mContext) .loadDialerThumbnailOrPhoto( quickContactView, info.lookupUri, info.photoId, info.photoUri, displayName, getContactType()); } private int getContactType() { int contactType = ContactPhotoManager.TYPE_DEFAULT; if (mCallLogCache.isVoicemailNumber(accountHandle, number)) { contactType = ContactPhotoManager.TYPE_VOICEMAIL; } else if (isSpam) { contactType = ContactPhotoManager.TYPE_SPAM; } else if (mCachedNumberLookupService != null && mCachedNumberLookupService.isBusiness(info.sourceType)) { contactType = ContactPhotoManager.TYPE_BUSINESS; } else if (numberPresentation == TelecomManager.PRESENTATION_RESTRICTED) { contactType = ContactPhotoManager.TYPE_GENERIC_AVATAR; } return contactType; } @Override public void onClick(View view) { if (view.getId() == R.id.primary_action_button) { CallLogAsyncTaskUtil.markCallAsRead(mContext, callIds); } if (view.getId() == R.id.primary_action_button && !TextUtils.isEmpty(voicemailUri)) { Logger.get(mContext).logImpression(DialerImpression.Type.VOICEMAIL_PLAY_AUDIO_DIRECTLY); mVoicemailPrimaryActionButtonClicked = true; mExpandCollapseListener.onClick(primaryActionView); } else if (view.getId() == R.id.call_with_note_action) { CallSubjectDialog.start( (Activity) mContext, info.photoId, info.photoUri, info.lookupUri, (String) nameOrNumber /* top line of contact view in call subject dialog */, number, TextUtils.isEmpty(info.name) ? null : displayNumber, /* second line of contact view in dialog. */ numberType, /* phone number type (e.g. mobile) in second line of contact view */ getContactType(), accountHandle); } else if (view.getId() == R.id.block_report_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_BLOCK_REPORT_SPAM); maybeShowBlockNumberMigrationDialog( new BlockedNumbersMigrator.Listener() { @Override public void onComplete() { mBlockReportListener.onBlockReportSpam( displayNumber, number, countryIso, callType, info.sourceType); } }); } else if (view.getId() == R.id.block_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_BLOCK_NUMBER); maybeShowBlockNumberMigrationDialog( new BlockedNumbersMigrator.Listener() { @Override public void onComplete() { mBlockReportListener.onBlock( displayNumber, number, countryIso, callType, info.sourceType); } }); } else if (view.getId() == R.id.unblock_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_UNBLOCK_NUMBER); mBlockReportListener.onUnblock( displayNumber, number, countryIso, callType, info.sourceType, isSpam, blockId); } else if (view.getId() == R.id.report_not_spam_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_REPORT_AS_NOT_SPAM); mBlockReportListener.onReportNotSpam( displayNumber, number, countryIso, callType, info.sourceType); } else if (view.getId() == R.id.call_compose_action) { LogUtil.i("CallLogListItemViewHolder.onClick", "share and call pressed"); Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SHARE_AND_CALL); Activity activity = (Activity) mContext; activity.startActivityForResult( CallComposerActivity.newIntent(activity, buildContact()), DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_COMPOSE); } else if (view.getId() == R.id.share_voicemail) { Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_PRESSED); mVoicemailPlaybackPresenter.shareVoicemail(); } else { logCallLogAction(view.getId()); final IntentProvider intentProvider = (IntentProvider) view.getTag(); if (intentProvider == null) { return; } final Intent intent = intentProvider.getIntent(mContext); // See IntentProvider.getCallDetailIntentProvider() for why this may be null. if (intent == null) { return; } // We check to see if we are starting a Lightbringer intent. The reason is Lightbringer // intents need to be started using startActivityForResult instead of the usual startActivity String packageName = intent.getPackage(); if (packageName != null && packageName.equals(getLightbringer().getPackageName(mContext))) { startLightbringerActivity(intent); } else { DialerUtils.startActivityWithErrorToast(mContext, intent); } } } private void startLightbringerActivity(Intent intent) { try { Activity activity = (Activity) mContext; activity.startActivityForResult(intent, DialtactsActivity.ACTIVITY_REQUEST_CODE_LIGHTBRINGER); } catch (ActivityNotFoundException e) { Toast.makeText(mContext, R.string.activity_not_available, Toast.LENGTH_SHORT).show(); } } private CallComposerContact buildContact() { CallComposerContact.Builder contact = CallComposerContact.newBuilder(); contact.setPhotoId(info.photoId); if (info.photoUri != null) { contact.setPhotoUri(info.photoUri.toString()); } if (info.lookupUri != null) { contact.setContactUri(info.lookupUri.toString()); } if (nameOrNumber != null) { contact.setNameOrNumber((String) nameOrNumber); } contact.setContactType(getContactType()); contact.setNumber(number); /* second line of contact view. */ if (!TextUtils.isEmpty(info.name)) { contact.setDisplayNumber(displayNumber); } /* phone number type (e.g. mobile) in second line of contact view */ contact.setNumberLabel(numberType); return contact.build(); } private void logCallLogAction(int id) { if (id == R.id.send_message_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SEND_MESSAGE); } else if (id == R.id.add_to_existing_contact_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_ADD_TO_CONTACT); } else if (id == R.id.create_new_contact_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_CREATE_NEW_CONTACT); } } private void maybeShowBlockNumberMigrationDialog(BlockedNumbersMigrator.Listener listener) { if (!FilteredNumberCompat.maybeShowBlockNumberMigrationDialog( mContext, ((Activity) mContext).getFragmentManager(), listener)) { listener.onComplete(); } } private void updateBlockReportActions(boolean isVoicemailNumber) { // Set block/spam actions. blockReportView.setVisibility(View.GONE); blockView.setVisibility(View.GONE); unblockView.setVisibility(View.GONE); reportNotSpamView.setVisibility(View.GONE); String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); if (isVoicemailNumber || !FilteredNumbersUtil.canBlockNumber(mContext, e164Number, number) || !FilteredNumberCompat.canAttemptBlockOperations(mContext)) { return; } boolean isBlocked = blockId != null; if (isBlocked) { unblockView.setVisibility(View.VISIBLE); } else { if (isSpamFeatureEnabled) { if (isSpam) { blockView.setVisibility(View.VISIBLE); reportNotSpamView.setVisibility(View.VISIBLE); } else { blockReportView.setVisibility(View.VISIBLE); } } else { blockView.setVisibility(View.VISIBLE); } } } public void setDetailedPhoneDetails(CallDetailsEntries callDetailsEntries) { this.callDetailsEntries = callDetailsEntries; } @VisibleForTesting public CallDetailsEntries getDetailedPhoneDetails() { return callDetailsEntries; } @NonNull private Lightbringer getLightbringer() { return LightbringerComponent.get(mContext).getLightbringer(); } @Override public void onCreateContextMenu( final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { if (TextUtils.isEmpty(number)) { return; } if (callType == CallLog.Calls.VOICEMAIL_TYPE) { menu.setHeaderTitle(mContext.getResources().getText(R.string.voicemail)); } else { menu.setHeaderTitle( PhoneNumberUtilsCompat.createTtsSpannable( BidiFormatter.getInstance().unicodeWrap(number, TextDirectionHeuristics.LTR))); } menu.add( ContextMenu.NONE, R.id.context_menu_copy_to_clipboard, ContextMenu.NONE, R.string.action_copy_number_text) .setOnMenuItemClickListener(this); // The edit number before call does not show up if any of the conditions apply: // 1) Number cannot be called // 2) Number is the voicemail number // 3) Number is a SIP address if (PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation) && !mCallLogCache.isVoicemailNumber(accountHandle, number) && !PhoneNumberHelper.isSipNumber(number)) { menu.add( ContextMenu.NONE, R.id.context_menu_edit_before_call, ContextMenu.NONE, R.string.action_edit_number_before_call) .setOnMenuItemClickListener(this); } if (callType == CallLog.Calls.VOICEMAIL_TYPE && phoneCallDetailsViews.voicemailTranscriptionView.length() > 0) { menu.add( ContextMenu.NONE, R.id.context_menu_copy_transcript_to_clipboard, ContextMenu.NONE, R.string.copy_transcript_text) .setOnMenuItemClickListener(this); } String e164Number = PhoneNumberUtils.formatNumberToE164(number, countryIso); boolean isVoicemailNumber = mCallLogCache.isVoicemailNumber(accountHandle, number); if (!isVoicemailNumber && FilteredNumbersUtil.canBlockNumber(mContext, e164Number, number) && FilteredNumberCompat.canAttemptBlockOperations(mContext)) { boolean isBlocked = blockId != null; if (isBlocked) { menu.add( ContextMenu.NONE, R.id.context_menu_unblock, ContextMenu.NONE, R.string.call_log_action_unblock_number) .setOnMenuItemClickListener(this); } else { if (isSpamFeatureEnabled) { if (isSpam) { menu.add( ContextMenu.NONE, R.id.context_menu_report_not_spam, ContextMenu.NONE, R.string.call_log_action_remove_spam) .setOnMenuItemClickListener(this); menu.add( ContextMenu.NONE, R.id.context_menu_block, ContextMenu.NONE, R.string.call_log_action_block_number) .setOnMenuItemClickListener(this); } else { menu.add( ContextMenu.NONE, R.id.context_menu_block_report_spam, ContextMenu.NONE, R.string.call_log_action_block_report_number) .setOnMenuItemClickListener(this); } } else { menu.add( ContextMenu.NONE, R.id.context_menu_block, ContextMenu.NONE, R.string.call_log_action_block_number) .setOnMenuItemClickListener(this); } } } Logger.get(mContext).logScreenView(ScreenEvent.Type.CALL_LOG_CONTEXT_MENU, (Activity) mContext); } public interface OnClickListener { void onBlockReportSpam( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType); void onBlock( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType); void onUnblock( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType, boolean isSpam, Integer blockId); void onReportNotSpam( String displayNumber, String number, String countryIso, int callType, ContactSource.Type contactSourceType); } }