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( 132 com.android.dialer.contactphoto.R.string.description_quick_contact_for, name); 133 contactViewHolder.getPhoto().setContentDescription(photoDescription); 134 135 // Always show the view holder's header if it's the first item in the list. Otherwise, compare 136 // it to the previous element and only show the anchored header if the row elements fall into 137 // the same sublists. 138 boolean showHeader = position == 0 || !header.equals(getHeaderString(position - 1)); 139 contactViewHolder.bind(header, name, contactUri, getContactId(cursor), showHeader); 140 } 141 142 /** 143 * Returns {@link #ADD_CONTACT_VIEW_TYPE} if the adapter was initialized with {@link 144 * Header#ADD_CONTACT} and the position is 0. Otherwise, {@link #CONTACT_VIEW_TYPE}. 145 */ 146 @Override getItemViewType(int position)147 public @ContactsViewType int getItemViewType(int position) { 148 if (header != Header.NONE && position == 0) { 149 return ADD_CONTACT_VIEW_TYPE; 150 } 151 return CONTACT_VIEW_TYPE; 152 } 153 154 @Override onViewRecycled(RecyclerView.ViewHolder contactViewHolder)155 public void onViewRecycled(RecyclerView.ViewHolder contactViewHolder) { 156 super.onViewRecycled(contactViewHolder); 157 if (contactViewHolder instanceof ContactViewHolder) { 158 holderMap.remove(contactViewHolder); 159 } 160 } 161 refreshHeaders()162 void refreshHeaders() { 163 for (ContactViewHolder holder : holderMap.keySet()) { 164 int position = holderMap.get(holder); 165 boolean showHeader = 166 position == 0 || !getHeaderString(position).equals(getHeaderString(position - 1)); 167 int visibility = showHeader ? View.VISIBLE : View.INVISIBLE; 168 holder.getHeaderView().setVisibility(visibility); 169 } 170 } 171 172 @Override getItemCount()173 public int getItemCount() { 174 int count = cursor == null || cursor.isClosed() ? 0 : cursor.getCount(); 175 // Manually insert the header if one exists. 176 if (header != Header.NONE) { 177 count++; 178 } 179 return count; 180 } 181 getDisplayName(Cursor cursor)182 private static String getDisplayName(Cursor cursor) { 183 return cursor.getString(ContactsCursorLoader.CONTACT_DISPLAY_NAME); 184 } 185 getPhotoId(Cursor cursor)186 private static long getPhotoId(Cursor cursor) { 187 return cursor.getLong(ContactsCursorLoader.CONTACT_PHOTO_ID); 188 } 189 getPhotoUri(Cursor cursor)190 private static Uri getPhotoUri(Cursor cursor) { 191 String photoUri = cursor.getString(ContactsCursorLoader.CONTACT_PHOTO_URI); 192 return photoUri == null ? null : Uri.parse(photoUri); 193 } 194 getContactUri(Cursor cursor)195 private static Uri getContactUri(Cursor cursor) { 196 long contactId = getContactId(cursor); 197 String lookupKey = cursor.getString(ContactsCursorLoader.CONTACT_LOOKUP_KEY); 198 return Contacts.getLookupUri(contactId, lookupKey); 199 } 200 getContactId(Cursor cursor)201 private static long getContactId(Cursor cursor) { 202 return cursor.getLong(ContactsCursorLoader.CONTACT_ID); 203 } 204 getHeaderString(int position)205 String getHeaderString(int position) { 206 if (header != Header.NONE) { 207 if (position == 0) { 208 return "+"; 209 } 210 position--; 211 } 212 213 int index = -1; 214 int sum = 0; 215 while (sum <= position) { 216 sum += counts[++index]; 217 } 218 return headers[index]; 219 } 220 } 221