blob: 19ff611776eb93b79f5efd9964b8a590ce818ad1 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts.group;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.Contacts.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.contacts.R;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.group.SuggestedMemberListAdapter.SuggestedMember;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* This adapter provides suggested contacts that can be added to a group for an
* {@link AutoCompleteTextView} within the group editor.
*/
public class SuggestedMemberListAdapter extends ArrayAdapter<SuggestedMember> {
private static final String[] PROJECTION_FILTERED_MEMBERS = new String[] {
RawContacts._ID, // 0
RawContacts.CONTACT_ID, // 1
RawContacts.DISPLAY_NAME_PRIMARY // 2
};
private static final int RAW_CONTACT_ID_COLUMN_INDEX = 0;
private static final int CONTACT_ID_COLUMN_INDEX = 1;
private static final int DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 2;
private static final String[] PROJECTION_MEMBER_DATA = new String[] {
RawContacts._ID, // 0
RawContacts.CONTACT_ID, // 1
Data.MIMETYPE, // 2
Data.DATA1, // 3
Photo.PHOTO, // 4
};
private static final int MIMETYPE_COLUMN_INDEX = 2;
private static final int DATA_COLUMN_INDEX = 3;
private static final int PHOTO_COLUMN_INDEX = 4;
private Filter mFilter;
private ContentResolver mContentResolver;
private LayoutInflater mInflater;
private String mAccountType;
private String mAccountName;
private String mDataSet;
// TODO: Make this a Map for better performance when we check if a new contact is in the list
// or not
private final List<Long> mExistingMemberContactIds = new ArrayList<Long>();
private static final int SUGGESTIONS_LIMIT = 5;
public SuggestedMemberListAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setAccountType(String accountType) {
mAccountType = accountType;
}
public void setAccountName(String accountName) {
mAccountName = accountName;
}
public void setDataSet(String dataSet) {
mDataSet = dataSet;
}
public void setContentResolver(ContentResolver resolver) {
mContentResolver = resolver;
}
public void updateExistingMembersList(List<GroupEditorFragment.Member> list) {
mExistingMemberContactIds.clear();
for (GroupEditorFragment.Member member : list) {
mExistingMemberContactIds.add(member.getContactId());
}
}
public void addNewMember(long contactId) {
mExistingMemberContactIds.add(contactId);
}
public void removeMember(long contactId) {
if (mExistingMemberContactIds.contains(contactId)) {
mExistingMemberContactIds.remove(contactId);
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View result = convertView;
if (result == null) {
result = mInflater.inflate(R.layout.group_member_suggestion, parent, false);
}
// TODO: Use a viewholder
SuggestedMember member = getItem(position);
TextView text1 = (TextView) result.findViewById(R.id.text1);
TextView text2 = (TextView) result.findViewById(R.id.text2);
ImageView icon = (ImageView) result.findViewById(R.id.icon);
text1.setText(member.getDisplayName());
if (member.hasExtraInfo()) {
text2.setText(member.getExtraInfo());
} else {
text2.setVisibility(View.GONE);
}
byte[] byteArray = member.getPhotoByteArray();
if (byteArray == null) {
icon.setImageDrawable(ContactPhotoManager.getDefaultAvatarDrawableForContact(
icon.getResources(), false, null));
} else {
Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
icon.setImageBitmap(bitmap);
}
result.setTag(member);
return result;
}
@Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new SuggestedMemberFilter();
}
return mFilter;
}
/**
* This filter queries for raw contacts that match the given account name and account type,
* as well as the search query.
*/
public class SuggestedMemberFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mContentResolver == null || TextUtils.isEmpty(prefix)) {
return results;
}
// Create a list to store the suggested contacts (which will be alphabetically ordered),
// but also keep a map of raw contact IDs to {@link SuggestedMember}s to make it easier
// to add supplementary data to the contact (photo, phone, email) to the members based
// on raw contact IDs after the second query is completed.
List<SuggestedMember> suggestionsList = new ArrayList<SuggestedMember>();
HashMap<Long, SuggestedMember> suggestionsMap = new HashMap<Long, SuggestedMember>();
// First query for all the raw contacts that match the given search query
// and have the same account name and type as specified in this adapter
String searchQuery = prefix.toString() + "%";
String accountClause = RawContacts.ACCOUNT_NAME + "=? AND " +
RawContacts.ACCOUNT_TYPE + "=?";
String[] args;
if (mDataSet == null) {
accountClause += " AND " + RawContacts.DATA_SET + " IS NULL";
args = new String[] {mAccountName, mAccountType, searchQuery, searchQuery};
} else {
accountClause += " AND " + RawContacts.DATA_SET + "=?";
args = new String[] {
mAccountName, mAccountType, mDataSet, searchQuery, searchQuery
};
}
Cursor cursor = mContentResolver.query(
RawContacts.CONTENT_URI, PROJECTION_FILTERED_MEMBERS,
accountClause + " AND (" +
RawContacts.DISPLAY_NAME_PRIMARY + " LIKE ? OR " +
RawContacts.DISPLAY_NAME_ALTERNATIVE + " LIKE ? )",
args, RawContacts.DISPLAY_NAME_PRIMARY + " COLLATE LOCALIZED ASC");
if (cursor == null) {
return results;
}
// Read back the results from the cursor and filter out existing group members.
// For valid suggestions, add them to the hash map of suggested members.
try {
cursor.moveToPosition(-1);
while (cursor.moveToNext() && suggestionsMap.keySet().size() < SUGGESTIONS_LIMIT) {
long rawContactId = cursor.getLong(RAW_CONTACT_ID_COLUMN_INDEX);
long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
// Filter out contacts that have already been added to this group
if (mExistingMemberContactIds.contains(contactId)) {
continue;
}
// Otherwise, add the contact as a suggested new group member
String displayName = cursor.getString(DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
SuggestedMember member = new SuggestedMember(rawContactId, displayName,
contactId);
// Store the member in the list of suggestions and add it to the hash map too.
suggestionsList.add(member);
suggestionsMap.put(rawContactId, member);
}
} finally {
cursor.close();
}
int numSuggestions = suggestionsMap.keySet().size();
if (numSuggestions == 0) {
return results;
}
// Create a part of the selection string for the next query with the pattern (?, ?, ?)
// where the number of comma-separated question marks represent the number of raw
// contact IDs found in the previous query (while respective the SUGGESTION_LIMIT)
final StringBuilder rawContactIdSelectionBuilder = new StringBuilder();
final String[] questionMarks = new String[numSuggestions];
Arrays.fill(questionMarks, "?");
rawContactIdSelectionBuilder.append(RawContacts._ID + " IN (")
.append(TextUtils.join(",", questionMarks))
.append(")");
// Construct the selection args based on the raw contact IDs we're interested in
// (as well as the photo, email, and phone mimetypes)
List<String> selectionArgs = new ArrayList<String>();
selectionArgs.add(Photo.CONTENT_ITEM_TYPE);
selectionArgs.add(Email.CONTENT_ITEM_TYPE);
selectionArgs.add(Phone.CONTENT_ITEM_TYPE);
for (Long rawContactId : suggestionsMap.keySet()) {
selectionArgs.add(String.valueOf(rawContactId));
}
// Perform a second query to retrieve a photo and possibly a phone number or email
// address for the suggested contact
Cursor memberDataCursor = mContentResolver.query(
RawContactsEntity.CONTENT_URI, PROJECTION_MEMBER_DATA,
"(" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=? OR " + Data.MIMETYPE +
"=?) AND " + rawContactIdSelectionBuilder.toString(),
selectionArgs.toArray(new String[0]), null);
if (memberDataCursor != null) {
try {
memberDataCursor.moveToPosition(-1);
while (memberDataCursor.moveToNext()) {
long rawContactId = memberDataCursor.getLong(RAW_CONTACT_ID_COLUMN_INDEX);
SuggestedMember member = suggestionsMap.get(rawContactId);
if (member == null) {
continue;
}
String mimetype = memberDataCursor.getString(MIMETYPE_COLUMN_INDEX);
if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
// Set photo
byte[] bitmapArray = memberDataCursor.getBlob(PHOTO_COLUMN_INDEX);
member.setPhotoByteArray(bitmapArray);
} else if (Email.CONTENT_ITEM_TYPE.equals(mimetype) ||
Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
// Set at most 1 extra piece of contact info that can be a phone number or
// email
if (!member.hasExtraInfo()) {
String info = memberDataCursor.getString(DATA_COLUMN_INDEX);
member.setExtraInfo(info);
}
}
}
} finally {
memberDataCursor.close();
}
}
results.values = suggestionsList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
@SuppressWarnings("unchecked")
List<SuggestedMember> suggestionsList = (List<SuggestedMember>) results.values;
if (suggestionsList == null) {
return;
}
// Clear out the existing suggestions in this adapter
clear();
// Add all the suggested members to this adapter
for (SuggestedMember member : suggestionsList) {
add(member);
}
notifyDataSetChanged();
}
}
/**
* This represents a single contact that is a suggestion for the user to add to a group.
*/
// TODO: Merge this with the {@link GroupEditorFragment} Member class once we can find the
// lookup URI for this contact using the autocomplete filter queries
public class SuggestedMember {
private long mRawContactId;
private long mContactId;
private String mDisplayName;
private String mExtraInfo;
private byte[] mPhoto;
public SuggestedMember(long rawContactId, String displayName, long contactId) {
mRawContactId = rawContactId;
mDisplayName = displayName;
mContactId = contactId;
}
public String getDisplayName() {
return mDisplayName;
}
public String getExtraInfo() {
return mExtraInfo;
}
public long getRawContactId() {
return mRawContactId;
}
public long getContactId() {
return mContactId;
}
public byte[] getPhotoByteArray() {
return mPhoto;
}
public boolean hasExtraInfo() {
return mExtraInfo != null;
}
/**
* Set a phone number or email to distinguish this contact
*/
public void setExtraInfo(String info) {
mExtraInfo = info;
}
public void setPhotoByteArray(byte[] photo) {
mPhoto = photo;
}
@Override
public String toString() {
return getDisplayName();
}
}
}