• 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.calllog;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.SharedPreferences;
22 import android.content.res.Resources;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.provider.ContactsContract;
26 import android.provider.ContactsContract.CommonDataKinds.Phone;
27 import android.support.v7.widget.RecyclerView;
28 import android.os.Bundle;
29 import android.os.Trace;
30 import android.preference.PreferenceActivity;
31 import android.preference.PreferenceManager;
32 import android.provider.CallLog;
33 import android.support.v7.widget.RecyclerView.ViewHolder;
34 import android.telecom.PhoneAccountHandle;
35 import android.telephony.PhoneNumberUtils;
36 import android.telephony.TelephonyManager;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.view.ContextMenu;
40 import android.view.LayoutInflater;
41 import android.view.MenuItem;
42 import android.view.MenuItem.OnMenuItemClickListener;
43 import android.view.View;
44 import android.view.View.AccessibilityDelegate;
45 import android.view.ViewGroup;
46 import android.view.ViewTreeObserver;
47 import android.view.ContextMenu.ContextMenuInfo;
48 import android.view.accessibility.AccessibilityEvent;
49 
50 import com.android.contacts.common.CallUtil;
51 import com.android.contacts.common.ClipboardUtils;
52 import com.android.contacts.common.util.PermissionsUtil;
53 import com.android.dialer.DialtactsActivity;
54 import com.android.dialer.PhoneCallDetails;
55 import com.android.dialer.R;
56 import com.android.dialer.contactinfo.ContactInfoCache;
57 import com.android.dialer.contactinfo.ContactInfoCache.OnContactInfoChangedListener;
58 import com.android.dialer.util.DialerUtils;
59 import com.android.dialer.util.PhoneNumberUtil;
60 import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
61 
62 import com.google.common.annotations.VisibleForTesting;
63 
64 import java.util.HashMap;
65 
66 /**
67  * Adapter class to fill in data for the Call Log.
68  */
69 public class CallLogAdapter extends GroupingListAdapter
70         implements CallLogGroupBuilder.GroupCreator,
71                 VoicemailPlaybackPresenter.OnVoicemailDeletedListener {
72 
73     /** Interface used to initiate a refresh of the content. */
74     public interface CallFetcher {
fetchCalls()75         public void fetchCalls();
76     }
77 
78     private static final int VIEW_TYPE_SHOW_CALL_HISTORY_LIST_ITEM = 10;
79     private static final int NO_EXPANDED_LIST_ITEM = -1;
80 
81     private static final int VOICEMAIL_PROMO_CARD_POSITION = 0;
82     /**
83      * View type for voicemail promo card.  Note: Numbering starts at 20 to avoid collision
84      * with {@link com.android.common.widget.GroupingListAdapter#ITEM_TYPE_IN_GROUP}, and
85      * {@link CallLogAdapter#VIEW_TYPE_SHOW_CALL_HISTORY_LIST_ITEM}.
86      */
87     private static final int VIEW_TYPE_VOICEMAIL_PROMO_CARD = 20;
88 
89     /**
90      * The key for the show voicemail promo card preference which will determine whether the promo
91      * card was permanently dismissed or not.
92      */
93     private static final String SHOW_VOICEMAIL_PROMO_CARD = "show_voicemail_promo_card";
94     private static final boolean SHOW_VOICEMAIL_PROMO_CARD_DEFAULT = true;
95 
96     protected final Context mContext;
97     private final ContactInfoHelper mContactInfoHelper;
98     private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
99     private final CallFetcher mCallFetcher;
100 
101     protected ContactInfoCache mContactInfoCache;
102 
103     private boolean mIsShowingRecentsTab;
104 
105     private static final String KEY_EXPANDED_POSITION = "expanded_position";
106     private static final String KEY_EXPANDED_ROW_ID = "expanded_row_id";
107 
108     // Tracks the position of the currently expanded list item.
109     private int mCurrentlyExpandedPosition = RecyclerView.NO_POSITION;
110     // Tracks the rowId of the currently expanded list item, so the position can be updated if there
111     // are any changes to the call log entries, such as additions or removals.
112     private long mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM;
113 
114     /**
115      *  Hashmap, keyed by call Id, used to track the day group for a call.  As call log entries are
116      *  put into the primary call groups in {@link com.android.dialer.calllog.CallLogGroupBuilder},
117      *  they are also assigned a secondary "day group".  This hashmap tracks the day group assigned
118      *  to all calls in the call log.  This information is used to trigger the display of a day
119      *  group header above the call log entry at the start of a day group.
120      *  Note: Multiple calls are grouped into a single primary "call group" in the call log, and
121      *  the cursor used to bind rows includes all of these calls.  When determining if a day group
122      *  change has occurred it is necessary to look at the last entry in the call log to determine
123      *  its day group.  This hashmap provides a means of determining the previous day group without
124      *  having to reverse the cursor to the start of the previous day call log entry.
125      */
126     private HashMap<Long,Integer> mDayGroups = new HashMap<Long, Integer>();
127 
128     private boolean mLoading = true;
129 
130     private SharedPreferences mPrefs;
131 
132     private boolean mShowPromoCard = false;
133 
134     /** Instance of helper class for managing views. */
135     private final CallLogListItemHelper mCallLogListItemHelper;
136 
137     /** Cache for repeated requests to TelecomManager. */
138     protected final TelecomCallLogCache mTelecomCallLogCache;
139 
140     /** Helper to group call log entries. */
141     private final CallLogGroupBuilder mCallLogGroupBuilder;
142 
143     /**
144      * The OnClickListener used to expand or collapse the action buttons of a call log entry.
145      */
146     private final View.OnClickListener mExpandCollapseListener = new View.OnClickListener() {
147         @Override
148         public void onClick(View v) {
149             CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder) v.getTag();
150             if (viewHolder == null) {
151                 return;
152             }
153 
154             if (mVoicemailPlaybackPresenter != null) {
155                 // Always reset the voicemail playback state on expand or collapse.
156                 mVoicemailPlaybackPresenter.resetAll();
157             }
158 
159             if (viewHolder.getAdapterPosition() == mCurrentlyExpandedPosition) {
160                 // Hide actions, if the clicked item is the expanded item.
161                 viewHolder.showActions(false);
162 
163                 mCurrentlyExpandedPosition = RecyclerView.NO_POSITION;
164                 mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM;
165             } else {
166                 expandViewHolderActions(viewHolder);
167             }
168 
169         }
170     };
171 
172     /**
173      * Click handler used to dismiss the promo card when the user taps the "ok" button.
174      */
175     private final View.OnClickListener mOkActionListener = new View.OnClickListener() {
176         @Override
177         public void onClick(View view) {
178             dismissVoicemailPromoCard();
179         }
180     };
181 
182     /**
183      * Click handler used to send the user to the voicemail settings screen and then dismiss the
184      * promo card.
185      */
186     private final View.OnClickListener mVoicemailSettingsActionListener =
187             new View.OnClickListener() {
188         @Override
189         public void onClick(View view) {
190             Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
191             mContext.startActivity(intent);
192             dismissVoicemailPromoCard();
193         }
194     };
195 
196     /**
197      * Listener that is triggered to populate the context menu with actions to perform on the call's
198      * number, when the call log entry is long pressed.
199      */
200     private final View.OnCreateContextMenuListener mOnCreateContextMenuListener =
201             new View.OnCreateContextMenuListener() {
202                 @Override
203                 public void onCreateContextMenu(ContextMenu menu, View v,
204                         ContextMenuInfo menuInfo) {
205                     final CallLogListItemViewHolder vh =
206                             (CallLogListItemViewHolder) v.getTag();
207                     if (TextUtils.isEmpty(vh.number)) {
208                         return;
209                     }
210 
211                     menu.setHeaderTitle(vh.number);
212 
213                     final MenuItem copyItem = menu.add(
214                             ContextMenu.NONE,
215                             R.id.context_menu_copy_to_clipboard,
216                             ContextMenu.NONE,
217                             R.string.copy_text);
218 
219                     copyItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
220                         @Override
221                         public boolean onMenuItemClick(MenuItem item) {
222                             ClipboardUtils.copyText(CallLogAdapter.this.mContext, null,
223                                     vh.number, true);
224                             return true;
225                         }
226                     });
227 
228                     // The edit number before call does not show up if any of the conditions apply:
229                     // 1) Number cannot be called
230                     // 2) Number is the voicemail number
231                     // 3) Number is a SIP address
232 
233                     if (!PhoneNumberUtil.canPlaceCallsTo(vh.number, vh.numberPresentation)
234                             || mTelecomCallLogCache.isVoicemailNumber(vh.accountHandle, vh.number)
235                             || PhoneNumberUtil.isSipNumber(vh.number)) {
236                         return;
237                     }
238 
239                     final MenuItem editItem = menu.add(
240                             ContextMenu.NONE,
241                             R.id.context_menu_edit_before_call,
242                             ContextMenu.NONE,
243                             R.string.recentCalls_editNumberBeforeCall);
244 
245                     editItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
246                         @Override
247                         public boolean onMenuItemClick(MenuItem item) {
248                             final Intent intent = new Intent(Intent.ACTION_DIAL,
249                                     CallUtil.getCallUri(vh.number));
250                             intent.setClass(mContext, DialtactsActivity.class);
251                             DialerUtils.startActivityWithErrorToast(mContext, intent);
252                             return true;
253                         }
254                     });
255                 }
256             };
257 
expandViewHolderActions(CallLogListItemViewHolder viewHolder)258     private void expandViewHolderActions(CallLogListItemViewHolder viewHolder) {
259         // If another item is expanded, notify it that it has changed. Its actions will be
260         // hidden when it is re-binded because we change mCurrentlyExpandedPosition below.
261         if (mCurrentlyExpandedPosition != RecyclerView.NO_POSITION) {
262             notifyItemChanged(mCurrentlyExpandedPosition);
263         }
264         // Show the actions for the clicked list item.
265         viewHolder.showActions(true);
266         mCurrentlyExpandedPosition = viewHolder.getAdapterPosition();
267         mCurrentlyExpandedRowId = viewHolder.rowId;
268     }
269 
270     /**
271      * Expand the actions on a list item when focused in Talkback mode, to aid discoverability.
272      */
273     private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
274         @Override
275         public boolean onRequestSendAccessibilityEvent(
276                 ViewGroup host, View child, AccessibilityEvent event) {
277             if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
278                 // Only expand if actions are not already expanded, because triggering the expand
279                 // function on clicks causes the action views to lose the focus indicator.
280                 CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder) host.getTag();
281                 if (mCurrentlyExpandedPosition != viewHolder.getAdapterPosition()) {
282                     expandViewHolderActions((CallLogListItemViewHolder) host.getTag());
283                 }
284             }
285             return super.onRequestSendAccessibilityEvent(host, child, event);
286         }
287     };
288 
289     protected final OnContactInfoChangedListener mOnContactInfoChangedListener =
290             new OnContactInfoChangedListener() {
291                 @Override
292                 public void onContactInfoChanged() {
293                     notifyDataSetChanged();
294                 }
295             };
296 
CallLogAdapter( Context context, CallFetcher callFetcher, ContactInfoHelper contactInfoHelper, VoicemailPlaybackPresenter voicemailPlaybackPresenter, boolean isShowingRecentsTab)297     public CallLogAdapter(
298             Context context,
299             CallFetcher callFetcher,
300             ContactInfoHelper contactInfoHelper,
301             VoicemailPlaybackPresenter voicemailPlaybackPresenter,
302             boolean isShowingRecentsTab) {
303         super(context);
304 
305         mContext = context;
306         mCallFetcher = callFetcher;
307         mContactInfoHelper = contactInfoHelper;
308         mVoicemailPlaybackPresenter = voicemailPlaybackPresenter;
309         if (mVoicemailPlaybackPresenter != null) {
310             mVoicemailPlaybackPresenter.setOnVoicemailDeletedListener(this);
311         }
312         mIsShowingRecentsTab = isShowingRecentsTab;
313 
314         mContactInfoCache = new ContactInfoCache(
315                 mContactInfoHelper, mOnContactInfoChangedListener);
316         if (!PermissionsUtil.hasContactsPermissions(context)) {
317             mContactInfoCache.disableRequestProcessing();
318         }
319 
320         Resources resources = mContext.getResources();
321         CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
322 
323         mTelecomCallLogCache = new TelecomCallLogCache(mContext);
324         PhoneCallDetailsHelper phoneCallDetailsHelper =
325                 new PhoneCallDetailsHelper(mContext, resources, mTelecomCallLogCache);
326         mCallLogListItemHelper =
327                 new CallLogListItemHelper(phoneCallDetailsHelper, resources, mTelecomCallLogCache);
328         mCallLogGroupBuilder = new CallLogGroupBuilder(this);
329         mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
330         maybeShowVoicemailPromoCard();
331     }
332 
onSaveInstanceState(Bundle outState)333     public void onSaveInstanceState(Bundle outState) {
334         outState.putInt(KEY_EXPANDED_POSITION, mCurrentlyExpandedPosition);
335         outState.putLong(KEY_EXPANDED_ROW_ID, mCurrentlyExpandedRowId);
336     }
337 
onRestoreInstanceState(Bundle savedInstanceState)338     public void onRestoreInstanceState(Bundle savedInstanceState) {
339         if (savedInstanceState != null) {
340             mCurrentlyExpandedPosition =
341                     savedInstanceState.getInt(KEY_EXPANDED_POSITION, RecyclerView.NO_POSITION);
342             mCurrentlyExpandedRowId =
343                     savedInstanceState.getLong(KEY_EXPANDED_ROW_ID, NO_EXPANDED_LIST_ITEM);
344         }
345     }
346 
347     /**
348      * Requery on background thread when {@link Cursor} changes.
349      */
350     @Override
onContentChanged()351     protected void onContentChanged() {
352         mCallFetcher.fetchCalls();
353     }
354 
setLoading(boolean loading)355     public void setLoading(boolean loading) {
356         mLoading = loading;
357     }
358 
isEmpty()359     public boolean isEmpty() {
360         if (mLoading) {
361             // We don't want the empty state to show when loading.
362             return false;
363         } else {
364             return getItemCount() == 0;
365         }
366     }
367 
invalidateCache()368     public void invalidateCache() {
369         mContactInfoCache.invalidate();
370     }
371 
startCache()372     public void startCache() {
373         if (PermissionsUtil.hasPermission(mContext, android.Manifest.permission.READ_CONTACTS)) {
374             mContactInfoCache.start();
375         }
376     }
377 
pauseCache()378     public void pauseCache() {
379         mContactInfoCache.stop();
380         mTelecomCallLogCache.reset();
381     }
382 
383     @Override
addGroups(Cursor cursor)384     protected void addGroups(Cursor cursor) {
385         mCallLogGroupBuilder.addGroups(cursor);
386     }
387 
388     @Override
onCreateViewHolder(ViewGroup parent, int viewType)389     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
390         if (viewType == VIEW_TYPE_SHOW_CALL_HISTORY_LIST_ITEM) {
391             return ShowCallHistoryViewHolder.create(mContext, parent);
392         } else if (viewType == VIEW_TYPE_VOICEMAIL_PROMO_CARD) {
393             return createVoicemailPromoCardViewHolder(parent);
394         }
395         return createCallLogEntryViewHolder(parent);
396     }
397 
398     /**
399      * Creates a new call log entry {@link ViewHolder}.
400      *
401      * @param parent the parent view.
402      * @return The {@link ViewHolder}.
403      */
createCallLogEntryViewHolder(ViewGroup parent)404     private ViewHolder createCallLogEntryViewHolder(ViewGroup parent) {
405         LayoutInflater inflater = LayoutInflater.from(mContext);
406         View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
407         CallLogListItemViewHolder viewHolder = CallLogListItemViewHolder.create(
408                 view,
409                 mContext,
410                 mExpandCollapseListener,
411                 mTelecomCallLogCache,
412                 mCallLogListItemHelper,
413                 mVoicemailPlaybackPresenter);
414 
415         viewHolder.callLogEntryView.setTag(viewHolder);
416         viewHolder.callLogEntryView.setAccessibilityDelegate(mAccessibilityDelegate);
417 
418         viewHolder.primaryActionView.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
419         viewHolder.primaryActionView.setTag(viewHolder);
420 
421         return viewHolder;
422     }
423 
424     /**
425      * Binds the views in the entry to the data in the call log.
426      * TODO: This gets called 20-30 times when Dialer starts up for a single call log entry and
427      * should not. It invokes cross-process methods and the repeat execution can get costly.
428      *
429      * @param ViewHolder The view corresponding to this entry.
430      * @param position The position of the entry.
431      */
onBindViewHolder(ViewHolder viewHolder, int position)432     public void onBindViewHolder(ViewHolder viewHolder, int position) {
433         Trace.beginSection("onBindViewHolder: " + position);
434 
435         switch (getItemViewType(position)) {
436             case VIEW_TYPE_SHOW_CALL_HISTORY_LIST_ITEM:
437                 break;
438             case VIEW_TYPE_VOICEMAIL_PROMO_CARD:
439                 bindVoicemailPromoCardViewHolder(viewHolder);
440                 break;
441             default:
442                 bindCallLogListViewHolder(viewHolder, position);
443                 break;
444         }
445 
446         Trace.endSection();
447     }
448 
449     /**
450      * Binds the promo card view holder.
451      *
452      * @param viewHolder The promo card view holder.
453      */
bindVoicemailPromoCardViewHolder(ViewHolder viewHolder)454     protected void bindVoicemailPromoCardViewHolder(ViewHolder viewHolder) {
455         PromoCardViewHolder promoCardViewHolder = (PromoCardViewHolder) viewHolder;
456 
457         promoCardViewHolder.getSettingsTextView().setOnClickListener(
458                 mVoicemailSettingsActionListener);
459         promoCardViewHolder.getOkTextView().setOnClickListener(mOkActionListener);
460     }
461 
462     /**
463      * Binds the view holder for the call log list item view.
464      *
465      * @param viewHolder The call log list item view holder.
466      * @param position The position of the list item.
467      */
468 
bindCallLogListViewHolder(ViewHolder viewHolder, int position)469     private void bindCallLogListViewHolder(ViewHolder viewHolder, int position) {
470         Cursor c = (Cursor) getItem(position);
471         if (c == null) {
472             return;
473         }
474 
475         int count = getGroupSize(position);
476 
477         final String number = c.getString(CallLogQuery.NUMBER);
478         final int numberPresentation = c.getInt(CallLogQuery.NUMBER_PRESENTATION);
479         final PhoneAccountHandle accountHandle = PhoneAccountUtils.getAccount(
480                 c.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME),
481                 c.getString(CallLogQuery.ACCOUNT_ID));
482         final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
483         final ContactInfo cachedContactInfo = mContactInfoHelper.getContactInfo(c);
484         final boolean isVoicemailNumber =
485                 mTelecomCallLogCache.isVoicemailNumber(accountHandle, number);
486 
487         // Note: Binding of the action buttons is done as required in configureActionViews when the
488         // user expands the actions ViewStub.
489 
490         ContactInfo info = ContactInfo.EMPTY;
491         if (PhoneNumberUtil.canPlaceCallsTo(number, numberPresentation) && !isVoicemailNumber) {
492             // Lookup contacts with this number
493             info = mContactInfoCache.getValue(number, countryIso, cachedContactInfo);
494         }
495         CharSequence formattedNumber = info.formattedNumber == null
496                 ? null : PhoneNumberUtils.createTtsSpannable(info.formattedNumber);
497 
498         final PhoneCallDetails details = new PhoneCallDetails(
499                 mContext, number, numberPresentation, formattedNumber, isVoicemailNumber);
500         details.accountHandle = accountHandle;
501         details.callTypes = getCallTypes(c, count);
502         details.countryIso = countryIso;
503         details.date = c.getLong(CallLogQuery.DATE);
504         details.duration = c.getLong(CallLogQuery.DURATION);
505         details.features = getCallFeatures(c, count);
506         details.geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
507         details.transcription = c.getString(CallLogQuery.TRANSCRIPTION);
508         if (details.callTypes[0] == CallLog.Calls.VOICEMAIL_TYPE) {
509             details.isRead = c.getInt(CallLogQuery.IS_READ) == 1;
510         }
511 
512         if (!c.isNull(CallLogQuery.DATA_USAGE)) {
513             details.dataUsage = c.getLong(CallLogQuery.DATA_USAGE);
514         }
515 
516         if (!TextUtils.isEmpty(info.name)) {
517             details.contactUri = info.lookupUri;
518             details.name = info.name;
519             details.numberType = info.type;
520             details.numberLabel = info.label;
521             details.photoUri = info.photoUri;
522             details.sourceType = info.sourceType;
523             details.objectId = info.objectId;
524         }
525 
526         CallLogListItemViewHolder views = (CallLogListItemViewHolder) viewHolder;
527         views.info = info;
528         views.rowId = c.getLong(CallLogQuery.ID);
529         // Store values used when the actions ViewStub is inflated on expansion.
530         views.number = number;
531         views.displayNumber = details.displayNumber;
532         views.numberPresentation = numberPresentation;
533         views.callType = c.getInt(CallLogQuery.CALL_TYPE);
534         views.accountHandle = accountHandle;
535         views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
536         // Stash away the Ids of the calls so that we can support deleting a row in the call log.
537         views.callIds = getCallIds(c, count);
538         views.isBusiness = mContactInfoHelper.isBusiness(info.sourceType);
539         views.numberType = (String) Phone.getTypeLabel(mContext.getResources(), details.numberType,
540                 details.numberLabel);
541         // Default case: an item in the call log.
542         views.primaryActionView.setVisibility(View.VISIBLE);
543 
544         // Check if the day group has changed and display a header if necessary.
545         int currentGroup = getDayGroupForCall(views.rowId);
546         int previousGroup = getPreviousDayGroup(c);
547         if (currentGroup != previousGroup) {
548             views.dayGroupHeader.setVisibility(View.VISIBLE);
549             views.dayGroupHeader.setText(getGroupDescription(currentGroup));
550         } else {
551             views.dayGroupHeader.setVisibility(View.GONE);
552         }
553 
554         mCallLogListItemHelper.setPhoneCallDetails(views, details);
555 
556         if (mCurrentlyExpandedRowId == views.rowId) {
557             // In case ViewHolders were added/removed, update the expanded position if the rowIds
558             // match so that we can restore the correct expanded state on rebind.
559             mCurrentlyExpandedPosition = position;
560         }
561 
562         views.showActions(mCurrentlyExpandedPosition == position);
563 
564         String nameForDefaultImage = null;
565         if (TextUtils.isEmpty(info.name)) {
566             nameForDefaultImage = details.displayNumber;
567         } else {
568             nameForDefaultImage = info.name;
569         }
570         views.setPhoto(info.photoId, info.photoUri, info.lookupUri, nameForDefaultImage,
571                 isVoicemailNumber, views.isBusiness);
572 
573         mCallLogListItemHelper.setPhoneCallDetails(views, details);
574     }
575 
576     @Override
getItemCount()577     public int getItemCount() {
578         return super.getItemCount() + ((isShowingRecentsTab() || mShowPromoCard) ? 1 : 0);
579     }
580 
581     @Override
getItemViewType(int position)582     public int getItemViewType(int position) {
583         if (position == getItemCount() - 1 && isShowingRecentsTab()) {
584             return VIEW_TYPE_SHOW_CALL_HISTORY_LIST_ITEM;
585         } else if (position == VOICEMAIL_PROMO_CARD_POSITION && mShowPromoCard) {
586             return VIEW_TYPE_VOICEMAIL_PROMO_CARD;
587         }
588         return super.getItemViewType(position);
589     }
590 
591     /**
592      * Retrieves an item at the specified position, taking into account the presence of a promo
593      * card.
594      *
595      * @param position The position to retrieve.
596      * @return The item at that position.
597      */
598     @Override
getItem(int position)599     public Object getItem(int position) {
600         return super.getItem(position - (mShowPromoCard ? 1 : 0));
601     }
602 
isShowingRecentsTab()603     protected boolean isShowingRecentsTab() {
604         return mIsShowingRecentsTab;
605     }
606 
607     @Override
onVoicemailDeleted(Uri uri)608     public void onVoicemailDeleted(Uri uri) {
609         mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM;
610         mCurrentlyExpandedPosition = RecyclerView.NO_POSITION;
611     }
612 
613     /**
614      * Retrieves the day group of the previous call in the call log.  Used to determine if the day
615      * group has changed and to trigger display of the day group text.
616      *
617      * @param cursor The call log cursor.
618      * @return The previous day group, or DAY_GROUP_NONE if this is the first call.
619      */
getPreviousDayGroup(Cursor cursor)620     private int getPreviousDayGroup(Cursor cursor) {
621         // We want to restore the position in the cursor at the end.
622         int startingPosition = cursor.getPosition();
623         int dayGroup = CallLogGroupBuilder.DAY_GROUP_NONE;
624         if (cursor.moveToPrevious()) {
625             long previousRowId = cursor.getLong(CallLogQuery.ID);
626             dayGroup = getDayGroupForCall(previousRowId);
627         }
628         cursor.moveToPosition(startingPosition);
629         return dayGroup;
630     }
631 
632     /**
633      * Given a call Id, look up the day group that the call belongs to.  The day group data is
634      * populated in {@link com.android.dialer.calllog.CallLogGroupBuilder}.
635      *
636      * @param callId The call to retrieve the day group for.
637      * @return The day group for the call.
638      */
getDayGroupForCall(long callId)639     private int getDayGroupForCall(long callId) {
640         if (mDayGroups.containsKey(callId)) {
641             return mDayGroups.get(callId);
642         }
643         return CallLogGroupBuilder.DAY_GROUP_NONE;
644     }
645 
646     /**
647      * Returns the call types for the given number of items in the cursor.
648      * <p>
649      * It uses the next {@code count} rows in the cursor to extract the types.
650      * <p>
651      * It position in the cursor is unchanged by this function.
652      */
getCallTypes(Cursor cursor, int count)653     private int[] getCallTypes(Cursor cursor, int count) {
654         int position = cursor.getPosition();
655         int[] callTypes = new int[count];
656         for (int index = 0; index < count; ++index) {
657             callTypes[index] = cursor.getInt(CallLogQuery.CALL_TYPE);
658             cursor.moveToNext();
659         }
660         cursor.moveToPosition(position);
661         return callTypes;
662     }
663 
664     /**
665      * Determine the features which were enabled for any of the calls that make up a call log
666      * entry.
667      *
668      * @param cursor The cursor.
669      * @param count The number of calls for the current call log entry.
670      * @return The features.
671      */
getCallFeatures(Cursor cursor, int count)672     private int getCallFeatures(Cursor cursor, int count) {
673         int features = 0;
674         int position = cursor.getPosition();
675         for (int index = 0; index < count; ++index) {
676             features |= cursor.getInt(CallLogQuery.FEATURES);
677             cursor.moveToNext();
678         }
679         cursor.moveToPosition(position);
680         return features;
681     }
682 
683     /**
684      * Sets whether processing of requests for contact details should be enabled.
685      *
686      * This method should be called in tests to disable such processing of requests when not
687      * needed.
688      */
689     @VisibleForTesting
disableRequestProcessingForTest()690     void disableRequestProcessingForTest() {
691         // TODO: Remove this and test the cache directly.
692         mContactInfoCache.disableRequestProcessing();
693     }
694 
695     @VisibleForTesting
injectContactInfoForTest(String number, String countryIso, ContactInfo contactInfo)696     void injectContactInfoForTest(String number, String countryIso, ContactInfo contactInfo) {
697         // TODO: Remove this and test the cache directly.
698         mContactInfoCache.injectContactInfoForTest(number, countryIso, contactInfo);
699     }
700 
701     @Override
addGroup(int cursorPosition, int size, boolean expanded)702     public void addGroup(int cursorPosition, int size, boolean expanded) {
703         super.addGroup(cursorPosition, size, expanded);
704     }
705 
706     /**
707      * Stores the day group associated with a call in the call log.
708      *
709      * @param rowId The row Id of the current call.
710      * @param dayGroup The day group the call belongs in.
711      */
712     @Override
setDayGroup(long rowId, int dayGroup)713     public void setDayGroup(long rowId, int dayGroup) {
714         if (!mDayGroups.containsKey(rowId)) {
715             mDayGroups.put(rowId, dayGroup);
716         }
717     }
718 
719     /**
720      * Clears the day group associations on re-bind of the call log.
721      */
722     @Override
clearDayGroups()723     public void clearDayGroups() {
724         mDayGroups.clear();
725     }
726 
727     /**
728      * Retrieves the call Ids represented by the current call log row.
729      *
730      * @param cursor Call log cursor to retrieve call Ids from.
731      * @param groupSize Number of calls associated with the current call log row.
732      * @return Array of call Ids.
733      */
getCallIds(final Cursor cursor, final int groupSize)734     private long[] getCallIds(final Cursor cursor, final int groupSize) {
735         // We want to restore the position in the cursor at the end.
736         int startingPosition = cursor.getPosition();
737         long[] ids = new long[groupSize];
738         // Copy the ids of the rows in the group.
739         for (int index = 0; index < groupSize; ++index) {
740             ids[index] = cursor.getLong(CallLogQuery.ID);
741             cursor.moveToNext();
742         }
743         cursor.moveToPosition(startingPosition);
744         return ids;
745     }
746 
747     /**
748      * Determines the description for a day group.
749      *
750      * @param group The day group to retrieve the description for.
751      * @return The day group description.
752      */
getGroupDescription(int group)753     private CharSequence getGroupDescription(int group) {
754        if (group == CallLogGroupBuilder.DAY_GROUP_TODAY) {
755            return mContext.getResources().getString(R.string.call_log_header_today);
756        } else if (group == CallLogGroupBuilder.DAY_GROUP_YESTERDAY) {
757            return mContext.getResources().getString(R.string.call_log_header_yesterday);
758        } else {
759            return mContext.getResources().getString(R.string.call_log_header_other);
760        }
761     }
762 
763     /**
764      * Determines if the voicemail promo card should be shown or not.  The voicemail promo card will
765      * be shown as the first item in the voicemail tab.
766      */
maybeShowVoicemailPromoCard()767     private void maybeShowVoicemailPromoCard() {
768         boolean showPromoCard = mPrefs.getBoolean(SHOW_VOICEMAIL_PROMO_CARD,
769                 SHOW_VOICEMAIL_PROMO_CARD_DEFAULT);
770         mShowPromoCard = (mVoicemailPlaybackPresenter != null) && showPromoCard;
771     }
772 
773     /**
774      * Dismisses the voicemail promo card and refreshes the call log.
775      */
dismissVoicemailPromoCard()776     private void dismissVoicemailPromoCard() {
777         mPrefs.edit().putBoolean(SHOW_VOICEMAIL_PROMO_CARD, false).apply();
778         mShowPromoCard = false;
779         notifyItemRemoved(VOICEMAIL_PROMO_CARD_POSITION);
780     }
781 
782     /**
783      * Creates the view holder for the voicemail promo card.
784      *
785      * @param parent The parent view.
786      * @return The {@link ViewHolder}.
787      */
createVoicemailPromoCardViewHolder(ViewGroup parent)788     protected ViewHolder createVoicemailPromoCardViewHolder(ViewGroup parent) {
789         LayoutInflater inflater = LayoutInflater.from(mContext);
790         View view = inflater.inflate(R.layout.voicemail_promo_card, parent, false);
791 
792         PromoCardViewHolder viewHolder = PromoCardViewHolder.create(view);
793         return viewHolder;
794     }
795 }
796