• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.contactsfragment;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.provider.ContactsContract.Contacts;
23 import android.support.annotation.IntDef;
24 import android.support.v4.util.ArrayMap;
25 import android.support.v7.widget.RecyclerView;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import com.android.dialer.common.Assert;
30 import com.android.dialer.common.LogUtil;
31 import com.android.dialer.contactphoto.ContactPhotoManager;
32 import com.android.dialer.contactsfragment.ContactsFragment.Header;
33 import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener;
34 import com.android.dialer.lettertile.LetterTileDrawable;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 
38 /** List adapter for the union of all contacts associated with every account on the device. */
39 final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
40 
41   private static final int UNKNOWN_VIEW_TYPE = 0;
42   private static final int ADD_CONTACT_VIEW_TYPE = 1;
43   private static final int CONTACT_VIEW_TYPE = 2;
44 
45   /** An Enum for the different row view types shown by this adapter. */
46   @Retention(RetentionPolicy.SOURCE)
47   @IntDef({UNKNOWN_VIEW_TYPE, ADD_CONTACT_VIEW_TYPE, CONTACT_VIEW_TYPE})
48   @interface ContactsViewType {}
49 
50   private final ArrayMap<ContactViewHolder, Integer> holderMap = new ArrayMap<>();
51   private final Context context;
52   private final @Header int header;
53   private final OnContactSelectedListener onContactSelectedListener;
54 
55   // List of contact sublist headers
56   private String[] headers = new String[0];
57   // Number of contacts that correspond to each header in {@code headers}.
58   private int[] counts = new int[0];
59   // Cursor with list of contacts
60   private Cursor cursor;
61 
ContactsAdapter( Context context, @Header int header, OnContactSelectedListener onContactSelectedListener)62   ContactsAdapter(
63       Context context, @Header int header, OnContactSelectedListener onContactSelectedListener) {
64     this.context = context;
65     this.header = header;
66     this.onContactSelectedListener = Assert.isNotNull(onContactSelectedListener);
67   }
68 
updateCursor(Cursor cursor)69   void updateCursor(Cursor cursor) {
70     this.cursor = cursor;
71     headers = cursor.getExtras().getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
72     counts = cursor.getExtras().getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
73     if (counts != null) {
74       int sum = 0;
75       for (int count : counts) {
76         sum += count;
77       }
78 
79       if (sum != cursor.getCount()) {
80         LogUtil.e(
81             "ContactsAdapter", "Count sum (%d) != cursor count (%d).", sum, cursor.getCount());
82       }
83     }
84     notifyDataSetChanged();
85   }
86 
87   @Override
onCreateViewHolder( ViewGroup parent, @ContactsViewType int viewType)88   public RecyclerView.ViewHolder onCreateViewHolder(
89       ViewGroup parent, @ContactsViewType int viewType) {
90     switch (viewType) {
91       case ADD_CONTACT_VIEW_TYPE:
92         return new AddContactViewHolder(
93             LayoutInflater.from(context).inflate(R.layout.add_contact_row, parent, false));
94       case CONTACT_VIEW_TYPE:
95         return new ContactViewHolder(
96             LayoutInflater.from(context).inflate(R.layout.contact_row, parent, false),
97             onContactSelectedListener);
98       case UNKNOWN_VIEW_TYPE:
99       default:
100         throw Assert.createIllegalStateFailException("Invalid view type: " + viewType);
101     }
102   }
103 
104   @Override
onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)105   public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
106     if (viewHolder instanceof AddContactViewHolder) {
107       return;
108     }
109 
110     ContactViewHolder contactViewHolder = (ContactViewHolder) viewHolder;
111     holderMap.put(contactViewHolder, position);
112     cursor.moveToPosition(position);
113     if (header != Header.NONE) {
114       cursor.moveToPrevious();
115     }
116 
117     String name = getDisplayName(cursor);
118     String header = getHeaderString(position);
119     Uri contactUri = getContactUri(cursor);
120 
121     ContactPhotoManager.getInstance(context)
122         .loadDialerThumbnailOrPhoto(
123             contactViewHolder.getPhoto(),
124             contactUri,
125             getPhotoId(cursor),
126             getPhotoUri(cursor),
127             name,
128             LetterTileDrawable.TYPE_DEFAULT);
129 
130     String photoDescription =
131         context.getString(com.android.contacts.common.R.string.description_quick_contact_for, name);
132     contactViewHolder.getPhoto().setContentDescription(photoDescription);
133 
134     // Always show the view holder's header if it's the first item in the list. Otherwise, compare
135     // it to the previous element and only show the anchored header if the row elements fall into
136     // the same sublists.
137     boolean showHeader = position == 0 || !header.equals(getHeaderString(position - 1));
138     contactViewHolder.bind(header, name, contactUri, getContactId(cursor), showHeader);
139   }
140 
141   /**
142    * Returns {@link #ADD_CONTACT_VIEW_TYPE} if the adapter was initialized with {@link
143    * Header#ADD_CONTACT} and the position is 0. Otherwise, {@link #CONTACT_VIEW_TYPE}.
144    */
145   @Override
getItemViewType(int position)146   public @ContactsViewType int getItemViewType(int position) {
147     if (header != Header.NONE && position == 0) {
148       return ADD_CONTACT_VIEW_TYPE;
149     }
150     return CONTACT_VIEW_TYPE;
151   }
152 
153   @Override
onViewRecycled(RecyclerView.ViewHolder contactViewHolder)154   public void onViewRecycled(RecyclerView.ViewHolder contactViewHolder) {
155     super.onViewRecycled(contactViewHolder);
156     if (contactViewHolder instanceof ContactViewHolder) {
157       holderMap.remove(contactViewHolder);
158     }
159   }
160 
refreshHeaders()161   void refreshHeaders() {
162     for (ContactViewHolder holder : holderMap.keySet()) {
163       int position = holderMap.get(holder);
164       boolean showHeader =
165           position == 0 || !getHeaderString(position).equals(getHeaderString(position - 1));
166       int visibility = showHeader ? View.VISIBLE : View.INVISIBLE;
167       holder.getHeaderView().setVisibility(visibility);
168     }
169   }
170 
171   @Override
getItemCount()172   public int getItemCount() {
173     int count = cursor == null || cursor.isClosed() ? 0 : cursor.getCount();
174     // Manually insert the header if one exists.
175     if (header != Header.NONE) {
176       count++;
177     }
178     return count;
179   }
180 
getDisplayName(Cursor cursor)181   private static String getDisplayName(Cursor cursor) {
182     return cursor.getString(ContactsCursorLoader.CONTACT_DISPLAY_NAME);
183   }
184 
getPhotoId(Cursor cursor)185   private static long getPhotoId(Cursor cursor) {
186     return cursor.getLong(ContactsCursorLoader.CONTACT_PHOTO_ID);
187   }
188 
getPhotoUri(Cursor cursor)189   private static Uri getPhotoUri(Cursor cursor) {
190     String photoUri = cursor.getString(ContactsCursorLoader.CONTACT_PHOTO_URI);
191     return photoUri == null ? null : Uri.parse(photoUri);
192   }
193 
getContactUri(Cursor cursor)194   private static Uri getContactUri(Cursor cursor) {
195     long contactId = getContactId(cursor);
196     String lookupKey = cursor.getString(ContactsCursorLoader.CONTACT_LOOKUP_KEY);
197     return Contacts.getLookupUri(contactId, lookupKey);
198   }
199 
getContactId(Cursor cursor)200   private static long getContactId(Cursor cursor) {
201     return cursor.getLong(ContactsCursorLoader.CONTACT_ID);
202   }
203 
getHeaderString(int position)204   String getHeaderString(int position) {
205     if (header != Header.NONE) {
206       if (position == 0) {
207         return "+";
208       }
209       position--;
210     }
211 
212     int index = -1;
213     int sum = 0;
214     while (sum <= position) {
215       sum += counts[++index];
216     }
217     return headers[index];
218   }
219 }
220