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