RecipientAlternatesAdapter.java revision f30a42800318f6790d55421f8f6980eb38db4d3c
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 17package com.android.ex.chips; 18 19import android.content.Context; 20import android.database.Cursor; 21import android.database.MatrixCursor; 22import android.text.util.Rfc822Token; 23import android.text.util.Rfc822Tokenizer; 24import android.util.Log; 25import android.view.LayoutInflater; 26import android.view.View; 27import android.view.ViewGroup; 28import android.widget.CursorAdapter; 29import android.widget.ImageView; 30import android.widget.TextView; 31 32import com.android.ex.chips.Queries.Query; 33 34import java.util.ArrayList; 35import java.util.HashMap; 36import java.util.HashSet; 37 38/** 39 * RecipientAlternatesAdapter backs the RecipientEditTextView for managing contacts 40 * queried by email or by phone number. 41 */ 42public class RecipientAlternatesAdapter extends CursorAdapter { 43 static final int MAX_LOOKUPS = 50; 44 private final LayoutInflater mLayoutInflater; 45 46 private final long mCurrentId; 47 48 private int mCheckedItemPosition = -1; 49 50 private OnCheckedItemChangedListener mCheckedItemChangedListener; 51 52 private static final String TAG = "RecipAlternates"; 53 54 public static final int QUERY_TYPE_EMAIL = 0; 55 public static final int QUERY_TYPE_PHONE = 1; 56 private Query mQuery; 57 58 public static HashMap<String, RecipientEntry> getMatchingRecipients(Context context, 59 ArrayList<String> inAddresses) { 60 return getMatchingRecipients(context, inAddresses, QUERY_TYPE_EMAIL); 61 } 62 63 /** 64 * Get a HashMap of address to RecipientEntry that contains all contact 65 * information for a contact with the provided address, if one exists. This 66 * may block the UI, so run it in an async task. 67 * 68 * @param context Context. 69 * @param inAddresses Array of addresses on which to perform the lookup. 70 * @return HashMap<String,RecipientEntry> 71 */ 72 public static HashMap<String, RecipientEntry> getMatchingRecipients(Context context, 73 ArrayList<String> inAddresses, int addressType) { 74 Queries.Query query; 75 if (addressType == QUERY_TYPE_EMAIL) { 76 query = Queries.EMAIL; 77 } else { 78 query = Queries.PHONE; 79 } 80 int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size()); 81 String[] addresses = new String[addressesSize]; 82 StringBuilder bindString = new StringBuilder(); 83 // Create the "?" string and set up arguments. 84 for (int i = 0; i < addressesSize; i++) { 85 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase()); 86 addresses[i] = (tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i)); 87 bindString.append("?"); 88 if (i < addressesSize - 1) { 89 bindString.append(","); 90 } 91 } 92 93 if (Log.isLoggable(TAG, Log.DEBUG)) { 94 Log.d(TAG, "Doing reverse lookup for " + addresses.toString()); 95 } 96 97 HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>(); 98 Cursor c = context.getContentResolver().query( 99 query.getContentUri(), 100 query.getProjection(), 101 query.getProjection()[Queries.Query.DESTINATION] + " IN (" + bindString.toString() 102 + ")", addresses, null); 103 104 if (c != null) { 105 try { 106 if (c.moveToFirst()) { 107 do { 108 String address = c.getString(Queries.Query.DESTINATION); 109 recipientEntries.put(address, RecipientEntry.constructTopLevelEntry( 110 c.getString(Queries.Query.NAME), 111 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE), 112 c.getString(Queries.Query.DESTINATION), 113 c.getInt(Queries.Query.DESTINATION_TYPE), 114 c.getString(Queries.Query.DESTINATION_LABEL), 115 c.getLong(Queries.Query.CONTACT_ID), 116 c.getLong(Queries.Query.DATA_ID), 117 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI), 118 true)); 119 if (Log.isLoggable(TAG, Log.DEBUG)) { 120 Log.d(TAG, "Received reverse look up information for " + address 121 + " RESULTS: " 122 + " NAME : " + c.getString(Queries.Query.NAME) 123 + " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID) 124 + " ADDRESS :" + c.getString(Queries.Query.DESTINATION)); 125 } 126 } while (c.moveToNext()); 127 } 128 } finally { 129 c.close(); 130 } 131 } 132 return recipientEntries; 133 } 134 135 public RecipientAlternatesAdapter(Context context, long contactId, long currentId, int viewId, 136 OnCheckedItemChangedListener listener) { 137 this(context, contactId, currentId, viewId, QUERY_TYPE_EMAIL, listener); 138 } 139 140 public RecipientAlternatesAdapter(Context context, long contactId, long currentId, int viewId, 141 int queryMode, OnCheckedItemChangedListener listener) { 142 super(context, getCursorForConstruction(context, contactId, queryMode), 0); 143 mLayoutInflater = LayoutInflater.from(context); 144 mCurrentId = currentId; 145 mCheckedItemChangedListener = listener; 146 147 if (queryMode == QUERY_TYPE_EMAIL) { 148 mQuery = Queries.EMAIL; 149 } else if (queryMode == QUERY_TYPE_PHONE) { 150 mQuery = Queries.PHONE; 151 } else { 152 mQuery = Queries.EMAIL; 153 Log.e(TAG, "Unsupported query type: " + queryMode); 154 } 155 } 156 157 private static Cursor getCursorForConstruction(Context context, long contactId, int queryType) { 158 final Cursor cursor; 159 if (queryType == QUERY_TYPE_EMAIL) { 160 cursor = context.getContentResolver().query( 161 Queries.EMAIL.getContentUri(), 162 Queries.EMAIL.getProjection(), 163 Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] { 164 String.valueOf(contactId) 165 }, null); 166 } else { 167 cursor = context.getContentResolver().query( 168 Queries.PHONE.getContentUri(), 169 Queries.PHONE.getProjection(), 170 Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] { 171 String.valueOf(contactId) 172 }, null); 173 } 174 return removeDuplicateDestinations(cursor); 175 } 176 177 /** 178 * @return a new cursor based on the given cursor with all duplicate destinations removed. 179 * 180 * It's only intended to use for the alternate list, so... 181 * - This method ignores all other fields and dedupe solely on the destination. Normally, 182 * if a cursor contains multiple contacts and they have the same destination, we'd still want 183 * to show both. 184 * - This method creates a MatrixCursor, so all data will be kept in memory. We wouldn't want 185 * to do this if the original cursor is large, but it's okay here because the alternate list 186 * won't be that big. 187 */ 188 // Visible for testing 189 /* package */ static Cursor removeDuplicateDestinations(Cursor original) { 190 final MatrixCursor result = new MatrixCursor( 191 original.getColumnNames(), original.getCount()); 192 final HashSet<String> destinationsSeen = new HashSet<String>(); 193 194 original.moveToPosition(-1); 195 while (original.moveToNext()) { 196 final String destination = original.getString(Query.DESTINATION); 197 if (destinationsSeen.contains(destination)) { 198 continue; 199 } 200 destinationsSeen.add(destination); 201 202 result.addRow(new Object[] { 203 original.getString(Query.NAME), 204 original.getString(Query.DESTINATION), 205 original.getInt(Query.DESTINATION_TYPE), 206 original.getString(Query.DESTINATION_LABEL), 207 original.getLong(Query.CONTACT_ID), 208 original.getLong(Query.DATA_ID), 209 original.getString(Query.PHOTO_THUMBNAIL_URI), 210 original.getInt(Query.DISPLAY_NAME_SOURCE) 211 }); 212 } 213 214 return result; 215 } 216 217 @Override 218 public long getItemId(int position) { 219 Cursor c = getCursor(); 220 if (c.moveToPosition(position)) { 221 c.getLong(Queries.Query.DATA_ID); 222 } 223 return -1; 224 } 225 226 public RecipientEntry getRecipientEntry(int position) { 227 Cursor c = getCursor(); 228 c.moveToPosition(position); 229 return RecipientEntry.constructTopLevelEntry( 230 c.getString(Queries.Query.NAME), 231 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE), 232 c.getString(Queries.Query.DESTINATION), 233 c.getInt(Queries.Query.DESTINATION_TYPE), 234 c.getString(Queries.Query.DESTINATION_LABEL), 235 c.getLong(Queries.Query.CONTACT_ID), 236 c.getLong(Queries.Query.DATA_ID), 237 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI), 238 true); 239 } 240 241 @Override 242 public View getView(int position, View convertView, ViewGroup parent) { 243 Cursor cursor = getCursor(); 244 cursor.moveToPosition(position); 245 if (convertView == null) { 246 convertView = newView(); 247 } 248 if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) { 249 mCheckedItemPosition = position; 250 if (mCheckedItemChangedListener != null) { 251 mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition); 252 } 253 } 254 bindView(convertView, convertView.getContext(), cursor); 255 return convertView; 256 } 257 258 // TODO: this is VERY similar to the BaseRecipientAdapter. Can we combine 259 // somehow? 260 @Override 261 public void bindView(View view, Context context, Cursor cursor) { 262 int position = cursor.getPosition(); 263 264 TextView display = (TextView) view.findViewById(android.R.id.title); 265 ImageView imageView = (ImageView) view.findViewById(android.R.id.icon); 266 RecipientEntry entry = getRecipientEntry(position); 267 if (position == 0) { 268 display.setText(cursor.getString(Queries.Query.NAME)); 269 display.setVisibility(View.VISIBLE); 270 // TODO: see if this needs to be done outside the main thread 271 // as it may be too slow to get immediately. 272 imageView.setImageURI(entry.getPhotoThumbnailUri()); 273 imageView.setVisibility(View.VISIBLE); 274 } else { 275 display.setVisibility(View.GONE); 276 imageView.setVisibility(View.GONE); 277 } 278 TextView destination = (TextView) view.findViewById(android.R.id.text1); 279 destination.setText(cursor.getString(Queries.Query.DESTINATION)); 280 281 TextView destinationType = (TextView) view.findViewById(android.R.id.text2); 282 if (destinationType != null) { 283 destinationType.setText(mQuery.getTypeLabel(context.getResources(), 284 cursor.getInt(Queries.Query.DESTINATION_TYPE), 285 cursor.getString(Queries.Query.DESTINATION_LABEL)).toString().toUpperCase()); 286 } 287 } 288 289 @Override 290 public View newView(Context context, Cursor cursor, ViewGroup parent) { 291 return newView(); 292 } 293 294 private View newView() { 295 return mLayoutInflater.inflate(R.layout.chips_recipient_dropdown_item, null); 296 } 297 298 /*package*/ static interface OnCheckedItemChangedListener { 299 public void onCheckedItemChanged(int position); 300 } 301} 302