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