| /* |
| * Copyright (C) 2010 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; |
| |
| import com.android.contacts.util.DataStatus; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Entity; |
| import android.content.Entity.NamedContentValues; |
| import android.content.Loader; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.AsyncTask; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.CommonDataKinds.GroupMembership; |
| import android.provider.ContactsContract.CommonDataKinds.Photo; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.Data; |
| import android.provider.ContactsContract.Directory; |
| import android.provider.ContactsContract.DisplayNameSources; |
| import android.provider.ContactsContract.Groups; |
| import android.provider.ContactsContract.RawContacts; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * Loads a single Contact and all it constituent RawContacts. |
| */ |
| public class ContactLoader extends Loader<ContactLoader.Result> { |
| private static final String TAG = "ContactLoader"; |
| |
| private Uri mLookupUri; |
| private boolean mLoadGroupMetaData; |
| private Result mContact; |
| private ForceLoadContentObserver mObserver; |
| private boolean mDestroyed; |
| |
| |
| public interface Listener { |
| public void onContactLoaded(Result contact); |
| } |
| |
| /** |
| * The result of a load operation. Contains all data necessary to display the contact. |
| */ |
| public static final class Result { |
| /** |
| * Singleton instance that represents "No Contact Found" |
| */ |
| public static final Result NOT_FOUND = new Result(); |
| |
| /** |
| * Singleton instance that represents an error, e.g. because of an invalid Uri |
| * TODO: We should come up with something nicer here. Maybe use an Either type so |
| * that we can capture the Exception? |
| */ |
| public static final Result ERROR = new Result(); |
| |
| private final Uri mLookupUri; |
| private final Uri mUri; |
| private final long mDirectoryId; |
| private final String mLookupKey; |
| private final long mId; |
| private final long mNameRawContactId; |
| private final int mDisplayNameSource; |
| private final long mPhotoId; |
| private final String mPhotoUri; |
| private final String mDisplayName; |
| private final String mPhoneticName; |
| private final boolean mStarred; |
| private final Integer mPresence; |
| private final ArrayList<Entity> mEntities; |
| private final HashMap<Long, DataStatus> mStatuses; |
| private final String mStatus; |
| private final Long mStatusTimestamp; |
| private final Integer mStatusLabel; |
| private final String mStatusResPackage; |
| |
| private String mDirectoryDisplayName; |
| private String mDirectoryType; |
| private String mDirectoryAccountType; |
| private String mDirectoryAccountName; |
| private int mDirectoryExportSupport; |
| |
| private ArrayList<GroupMetaData> mGroups; |
| |
| private boolean mLoadingPhoto; |
| private byte[] mPhotoBinaryData; |
| |
| /** |
| * Constructor for case "no contact found". This must only be used for the |
| * final {@link Result#NOT_FOUND} singleton |
| */ |
| private Result() { |
| mLookupUri = null; |
| mUri = null; |
| mDirectoryId = -1; |
| mLookupKey = null; |
| mId = -1; |
| mEntities = null; |
| mStatuses = null; |
| mNameRawContactId = -1; |
| mDisplayNameSource = DisplayNameSources.UNDEFINED; |
| mPhotoId = -1; |
| mPhotoUri = null; |
| mDisplayName = null; |
| mPhoneticName = null; |
| mStarred = false; |
| mPresence = null; |
| mStatus = null; |
| mStatusTimestamp = null; |
| mStatusLabel = null; |
| mStatusResPackage = null; |
| } |
| |
| /** |
| * Constructor to call when contact was found |
| */ |
| private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id, |
| long nameRawContactId, int displayNameSource, long photoId, String photoUri, |
| String displayName, String phoneticName, boolean starred, Integer presence, |
| String status, Long statusTimestamp, Integer statusLabel, String statusResPackage) { |
| mLookupUri = lookupUri; |
| mUri = uri; |
| mDirectoryId = directoryId; |
| mLookupKey = lookupKey; |
| mId = id; |
| mEntities = new ArrayList<Entity>(); |
| mStatuses = new HashMap<Long, DataStatus>(); |
| mNameRawContactId = nameRawContactId; |
| mDisplayNameSource = displayNameSource; |
| mPhotoId = photoId; |
| mPhotoUri = photoUri; |
| mDisplayName = displayName; |
| mPhoneticName = phoneticName; |
| mStarred = starred; |
| mPresence = presence; |
| mStatus = status; |
| mStatusTimestamp = statusTimestamp; |
| mStatusLabel = statusLabel; |
| mStatusResPackage = statusResPackage; |
| } |
| |
| /** |
| * @param exportSupport See {@link Directory#EXPORT_SUPPORT}. |
| */ |
| private void setDirectoryMetaData(String displayName, String directoryType, |
| String accountType, String accountName, int exportSupport) { |
| mDirectoryDisplayName = displayName; |
| mDirectoryType = directoryType; |
| mDirectoryAccountType = accountType; |
| mDirectoryAccountName = accountName; |
| mDirectoryExportSupport = exportSupport; |
| } |
| |
| private void setLoadingPhoto(boolean flag) { |
| mLoadingPhoto = flag; |
| } |
| |
| private void setPhotoBinaryData(byte[] photoBinaryData) { |
| mPhotoBinaryData = photoBinaryData; |
| } |
| |
| public Uri getLookupUri() { |
| return mLookupUri; |
| } |
| |
| public String getLookupKey() { |
| return mLookupKey; |
| } |
| |
| public Uri getUri() { |
| return mUri; |
| } |
| |
| public long getId() { |
| return mId; |
| } |
| |
| public long getNameRawContactId() { |
| return mNameRawContactId; |
| } |
| |
| public int getDisplayNameSource() { |
| return mDisplayNameSource; |
| } |
| |
| public long getPhotoId() { |
| return mPhotoId; |
| } |
| |
| public String getPhotoUri() { |
| return mPhotoUri; |
| } |
| |
| public String getDisplayName() { |
| return mDisplayName; |
| } |
| |
| public String getPhoneticName() { |
| return mPhoneticName; |
| } |
| |
| public boolean getStarred() { |
| return mStarred; |
| } |
| |
| public Integer getPresence() { |
| return mPresence; |
| } |
| |
| public String getSocialSnippet() { |
| return mStatus; |
| } |
| |
| public Long getStatusTimestamp() { |
| return mStatusTimestamp; |
| } |
| |
| public Integer getStatusLabel() { |
| return mStatusLabel; |
| } |
| |
| public String getStatusResPackage() { |
| return mStatusResPackage; |
| } |
| |
| public ArrayList<Entity> getEntities() { |
| return mEntities; |
| } |
| |
| public HashMap<Long, DataStatus> getStatuses() { |
| return mStatuses; |
| } |
| |
| public long getDirectoryId() { |
| return mDirectoryId; |
| } |
| |
| public boolean isDirectoryEntry() { |
| return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT |
| && mDirectoryId != Directory.LOCAL_INVISIBLE; |
| } |
| |
| public int getDirectoryExportSupport() { |
| return mDirectoryExportSupport; |
| } |
| |
| public String getDirectoryDisplayName() { |
| return mDirectoryDisplayName; |
| } |
| |
| public String getDirectoryType() { |
| return mDirectoryType; |
| } |
| |
| public String getDirectoryAccountType() { |
| return mDirectoryAccountType; |
| } |
| |
| public String getDirectoryAccountName() { |
| return mDirectoryAccountName; |
| } |
| |
| public boolean isLoadingPhoto() { |
| return mLoadingPhoto; |
| } |
| |
| public byte[] getPhotoBinaryData() { |
| return mPhotoBinaryData; |
| } |
| |
| public ArrayList<ContentValues> getContentValues() { |
| if (mEntities.size() != 1) { |
| throw new IllegalStateException( |
| "Cannot extract content values from an aggregated contact"); |
| } |
| |
| Entity entity = mEntities.get(0); |
| ArrayList<ContentValues> result = new ArrayList<ContentValues>(); |
| ArrayList<NamedContentValues> subValues = entity.getSubValues(); |
| if (subValues != null) { |
| int size = subValues.size(); |
| for (int i = 0; i < size; i++) { |
| NamedContentValues pair = subValues.get(i); |
| if (Data.CONTENT_URI.equals(pair.uri)) { |
| result.add(pair.values); |
| } |
| } |
| } |
| |
| // If the photo was loaded using the URI, create an entry for the photo |
| // binary data. |
| if (mPhotoId == 0 && mPhotoBinaryData != null) { |
| ContentValues photo = new ContentValues(); |
| photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); |
| photo.put(Photo.PHOTO, mPhotoBinaryData); |
| result.add(photo); |
| } |
| |
| return result; |
| } |
| |
| private void addGroupMetaData(GroupMetaData group) { |
| if (mGroups == null) { |
| mGroups = new ArrayList<GroupMetaData>(); |
| } |
| mGroups.add(group); |
| } |
| |
| public List<GroupMetaData> getGroupMetaData() { |
| return mGroups; |
| } |
| } |
| |
| private static class ContactQuery { |
| // Projection used for the query that loads all data for the entire contact. |
| final static String[] COLUMNS = new String[] { |
| Contacts.NAME_RAW_CONTACT_ID, |
| Contacts.DISPLAY_NAME_SOURCE, |
| Contacts.LOOKUP_KEY, |
| Contacts.DISPLAY_NAME, |
| Contacts.PHONETIC_NAME, |
| Contacts.PHOTO_ID, |
| Contacts.STARRED, |
| Contacts.CONTACT_PRESENCE, |
| Contacts.CONTACT_STATUS, |
| Contacts.CONTACT_STATUS_TIMESTAMP, |
| Contacts.CONTACT_STATUS_RES_PACKAGE, |
| Contacts.CONTACT_STATUS_LABEL, |
| Contacts.Entity.CONTACT_ID, |
| Contacts.Entity.RAW_CONTACT_ID, |
| |
| RawContacts.ACCOUNT_NAME, |
| RawContacts.ACCOUNT_TYPE, |
| RawContacts.DIRTY, |
| RawContacts.VERSION, |
| RawContacts.SOURCE_ID, |
| RawContacts.SYNC1, |
| RawContacts.SYNC2, |
| RawContacts.SYNC3, |
| RawContacts.SYNC4, |
| RawContacts.DELETED, |
| RawContacts.IS_RESTRICTED, |
| RawContacts.NAME_VERIFIED, |
| |
| Contacts.Entity.DATA_ID, |
| Data.DATA1, |
| Data.DATA2, |
| Data.DATA3, |
| Data.DATA4, |
| Data.DATA5, |
| Data.DATA6, |
| Data.DATA7, |
| Data.DATA8, |
| Data.DATA9, |
| Data.DATA10, |
| Data.DATA11, |
| Data.DATA12, |
| Data.DATA13, |
| Data.DATA14, |
| Data.DATA15, |
| Data.SYNC1, |
| Data.SYNC2, |
| Data.SYNC3, |
| Data.SYNC4, |
| Data.DATA_VERSION, |
| Data.IS_PRIMARY, |
| Data.IS_SUPER_PRIMARY, |
| Data.MIMETYPE, |
| Data.RES_PACKAGE, |
| |
| GroupMembership.GROUP_SOURCE_ID, |
| |
| Data.PRESENCE, |
| Data.CHAT_CAPABILITY, |
| Data.STATUS, |
| Data.STATUS_RES_PACKAGE, |
| Data.STATUS_ICON, |
| Data.STATUS_LABEL, |
| Data.STATUS_TIMESTAMP, |
| |
| Contacts.PHOTO_URI, |
| }; |
| |
| public final static int NAME_RAW_CONTACT_ID = 0; |
| public final static int DISPLAY_NAME_SOURCE = 1; |
| public final static int LOOKUP_KEY = 2; |
| public final static int DISPLAY_NAME = 3; |
| public final static int PHONETIC_NAME = 4; |
| public final static int PHOTO_ID = 5; |
| public final static int STARRED = 6; |
| public final static int CONTACT_PRESENCE = 7; |
| public final static int CONTACT_STATUS = 8; |
| public final static int CONTACT_STATUS_TIMESTAMP = 9; |
| public final static int CONTACT_STATUS_RES_PACKAGE = 10; |
| public final static int CONTACT_STATUS_LABEL = 11; |
| public final static int CONTACT_ID = 12; |
| public final static int RAW_CONTACT_ID = 13; |
| |
| public final static int ACCOUNT_NAME = 14; |
| public final static int ACCOUNT_TYPE = 15; |
| public final static int DIRTY = 16; |
| public final static int VERSION = 17; |
| public final static int SOURCE_ID = 18; |
| public final static int SYNC1 = 19; |
| public final static int SYNC2 = 20; |
| public final static int SYNC3 = 21; |
| public final static int SYNC4 = 22; |
| public final static int DELETED = 23; |
| public final static int IS_RESTRICTED = 24; |
| public final static int NAME_VERIFIED = 25; |
| |
| public final static int DATA_ID = 26; |
| public final static int DATA1 = 27; |
| public final static int DATA2 = 28; |
| public final static int DATA3 = 29; |
| public final static int DATA4 = 30; |
| public final static int DATA5 = 31; |
| public final static int DATA6 = 32; |
| public final static int DATA7 = 33; |
| public final static int DATA8 = 34; |
| public final static int DATA9 = 35; |
| public final static int DATA10 = 36; |
| public final static int DATA11 = 37; |
| public final static int DATA12 = 38; |
| public final static int DATA13 = 39; |
| public final static int DATA14 = 40; |
| public final static int DATA15 = 41; |
| public final static int DATA_SYNC1 = 42; |
| public final static int DATA_SYNC2 = 43; |
| public final static int DATA_SYNC3 = 44; |
| public final static int DATA_SYNC4 = 45; |
| public final static int DATA_VERSION = 46; |
| public final static int IS_PRIMARY = 47; |
| public final static int IS_SUPERPRIMARY = 48; |
| public final static int MIMETYPE = 49; |
| public final static int RES_PACKAGE = 50; |
| |
| public final static int GROUP_SOURCE_ID = 51; |
| |
| public final static int PRESENCE = 52; |
| public final static int CHAT_CAPABILITY = 53; |
| public final static int STATUS = 54; |
| public final static int STATUS_RES_PACKAGE = 55; |
| public final static int STATUS_ICON = 56; |
| public final static int STATUS_LABEL = 57; |
| public final static int STATUS_TIMESTAMP = 58; |
| |
| public final static int PHOTO_URI = 59; |
| } |
| |
| private static class DirectoryQuery { |
| // Projection used for the query that loads all data for the entire contact. |
| final static String[] COLUMNS = new String[] { |
| Directory.DISPLAY_NAME, |
| Directory.PACKAGE_NAME, |
| Directory.TYPE_RESOURCE_ID, |
| Directory.ACCOUNT_TYPE, |
| Directory.ACCOUNT_NAME, |
| Directory.EXPORT_SUPPORT, |
| }; |
| |
| public final static int DISPLAY_NAME = 0; |
| public final static int PACKAGE_NAME = 1; |
| public final static int TYPE_RESOURCE_ID = 2; |
| public final static int ACCOUNT_TYPE = 3; |
| public final static int ACCOUNT_NAME = 4; |
| public final static int EXPORT_SUPPORT = 5; |
| } |
| |
| private static class GroupQuery { |
| final static String[] COLUMNS = new String[] { |
| Groups.ACCOUNT_NAME, |
| Groups.ACCOUNT_TYPE, |
| Groups._ID, |
| Groups.TITLE, |
| Groups.AUTO_ADD, |
| Groups.FAVORITES, |
| }; |
| |
| public final static int ACCOUNT_NAME = 0; |
| public final static int ACCOUNT_TYPE = 1; |
| public final static int ID = 2; |
| public final static int TITLE = 3; |
| public final static int AUTO_ADD = 4; |
| public final static int FAVORITES = 5; |
| } |
| |
| private final class LoadContactTask extends AsyncTask<Void, Void, Result> { |
| |
| @Override |
| protected Result doInBackground(Void... args) { |
| try { |
| final ContentResolver resolver = getContext().getContentResolver(); |
| final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri); |
| Result result = loadContactEntity(resolver, uriCurrentFormat); |
| if (result != Result.NOT_FOUND) { |
| if (result.isDirectoryEntry()) { |
| loadDirectoryMetaData(result); |
| } else if (mLoadGroupMetaData) { |
| loadGroupMetaData(result); |
| } |
| loadPhotoBinaryData(result); |
| } |
| return result; |
| } catch (Exception e) { |
| Log.e(TAG, "Error loading the contact: " + mLookupUri, e); |
| return Result.ERROR; |
| } |
| } |
| |
| /** |
| * Transforms the given Uri and returns a Lookup-Uri that represents the contact. |
| * For legacy contacts, a raw-contact lookup is performed. |
| * @param resolver |
| */ |
| private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) { |
| if (uri == null) throw new IllegalArgumentException("uri must not be null"); |
| |
| final String authority = uri.getAuthority(); |
| |
| // Current Style Uri? |
| if (ContactsContract.AUTHORITY.equals(authority)) { |
| final String type = resolver.getType(uri); |
| // Contact-Uri? Good, return it |
| if (Contacts.CONTENT_ITEM_TYPE.equals(type)) { |
| return uri; |
| } |
| |
| // RawContact-Uri? Transform it to ContactUri |
| if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) { |
| final long rawContactId = ContentUris.parseId(uri); |
| return RawContacts.getContactLookupUri(getContext().getContentResolver(), |
| ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId)); |
| } |
| |
| // Anything else? We don't know what this is |
| throw new IllegalArgumentException("uri format is unknown"); |
| } |
| |
| // Legacy Style? Convert to RawContact |
| final String OBSOLETE_AUTHORITY = "contacts"; |
| if (OBSOLETE_AUTHORITY.equals(authority)) { |
| // Legacy Format. Convert to RawContact-Uri and then lookup the contact |
| final long rawContactId = ContentUris.parseId(uri); |
| return RawContacts.getContactLookupUri(resolver, |
| ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId)); |
| } |
| |
| throw new IllegalArgumentException("uri authority is unknown"); |
| } |
| |
| private Result loadContactEntity(ContentResolver resolver, Uri contactUri) { |
| Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY); |
| Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null, |
| Contacts.Entity.RAW_CONTACT_ID); |
| if (cursor == null) { |
| Log.e(TAG, "No cursor returned in loadContactEntity"); |
| return Result.NOT_FOUND; |
| } |
| |
| try { |
| if (!cursor.moveToFirst()) { |
| cursor.close(); |
| return Result.NOT_FOUND; |
| } |
| |
| long currentRawContactId = -1; |
| Entity entity = null; |
| Result result = loadContactHeaderData(cursor, contactUri); |
| ArrayList<Entity> entities = result.getEntities(); |
| HashMap<Long, DataStatus> statuses = result.getStatuses(); |
| for (; !cursor.isAfterLast(); cursor.moveToNext()) { |
| long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID); |
| if (rawContactId != currentRawContactId) { |
| currentRawContactId = rawContactId; |
| entity = new android.content.Entity(loadRawContact(cursor)); |
| entities.add(entity); |
| } |
| if (!cursor.isNull(ContactQuery.DATA_ID)) { |
| ContentValues data = loadData(cursor); |
| entity.addSubValue(ContactsContract.Data.CONTENT_URI, data); |
| |
| if (!cursor.isNull(ContactQuery.PRESENCE) |
| || !cursor.isNull(ContactQuery.STATUS)) { |
| final DataStatus status = new DataStatus(cursor); |
| final long dataId = cursor.getLong(ContactQuery.DATA_ID); |
| statuses.put(dataId, status); |
| } |
| } |
| } |
| |
| return result; |
| } finally { |
| cursor.close(); |
| } |
| } |
| |
| /** |
| * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If |
| * not found, returns null |
| */ |
| private void loadPhotoBinaryData(Result contactData) { |
| final long photoId = contactData.getPhotoId(); |
| if (photoId <= 0) { |
| // No photo ID |
| return; |
| } |
| |
| for (Entity entity : contactData.getEntities()) { |
| for (NamedContentValues subValue : entity.getSubValues()) { |
| final ContentValues entryValues = subValue.values; |
| final long dataId = entryValues.getAsLong(Data._ID); |
| if (dataId == photoId) { |
| final String mimeType = entryValues.getAsString(Data.MIMETYPE); |
| // Correct Data Id but incorrect MimeType? Don't load |
| if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { |
| return; |
| } |
| contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO)); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Extracts Contact level columns from the cursor. |
| */ |
| private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) { |
| final String directoryParameter = |
| contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY); |
| final long directoryId = directoryParameter == null |
| ? Directory.DEFAULT |
| : Long.parseLong(directoryParameter); |
| final long contactId = cursor.getLong(ContactQuery.CONTACT_ID); |
| final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY); |
| final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID); |
| final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE); |
| final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME); |
| final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME); |
| final long photoId = cursor.getLong(ContactQuery.PHOTO_ID); |
| final String photoUri = cursor.getString(ContactQuery.PHOTO_URI); |
| final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0; |
| final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE) |
| ? null |
| : cursor.getInt(ContactQuery.CONTACT_PRESENCE); |
| final String status = cursor.getString(ContactQuery.CONTACT_STATUS); |
| final Long statusTimestamp = cursor.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP) |
| ? null |
| : cursor.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP); |
| final Integer statusLabel = cursor.isNull(ContactQuery.CONTACT_STATUS_LABEL) |
| ? null |
| : cursor.getInt(ContactQuery.CONTACT_STATUS_LABEL); |
| final String statusResPackage = cursor.getString( |
| ContactQuery.CONTACT_STATUS_RES_PACKAGE); |
| |
| Uri lookupUri; |
| if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) { |
| lookupUri = ContentUris.withAppendedId( |
| Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId); |
| } else { |
| lookupUri = contactUri; |
| } |
| |
| return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId, |
| nameRawContactId, displayNameSource, photoId, photoUri, displayName, |
| phoneticName, starred, presence, status, statusTimestamp, statusLabel, |
| statusResPackage); |
| } |
| |
| /** |
| * Extracts RawContact level columns from the cursor. |
| */ |
| private ContentValues loadRawContact(Cursor cursor) { |
| ContentValues cv = new ContentValues(); |
| |
| cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID)); |
| |
| cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.IS_RESTRICTED); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED); |
| |
| return cv; |
| } |
| |
| /** |
| * Extracts Data level columns from the cursor. |
| */ |
| private ContentValues loadData(Cursor cursor) { |
| ContentValues cv = new ContentValues(); |
| |
| cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID)); |
| |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID); |
| cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY); |
| |
| return cv; |
| } |
| |
| private void cursorColumnToContentValues( |
| Cursor cursor, ContentValues values, int index) { |
| switch (cursor.getType(index)) { |
| case Cursor.FIELD_TYPE_NULL: |
| // don't put anything in the content values |
| break; |
| case Cursor.FIELD_TYPE_INTEGER: |
| values.put(ContactQuery.COLUMNS[index], cursor.getLong(index)); |
| break; |
| case Cursor.FIELD_TYPE_STRING: |
| values.put(ContactQuery.COLUMNS[index], cursor.getString(index)); |
| break; |
| case Cursor.FIELD_TYPE_BLOB: |
| values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index)); |
| break; |
| default: |
| throw new IllegalStateException("Invalid or unhandled data type"); |
| } |
| } |
| |
| private void loadDirectoryMetaData(Result result) { |
| long directoryId = result.getDirectoryId(); |
| |
| Cursor cursor = getContext().getContentResolver().query( |
| ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId), |
| DirectoryQuery.COLUMNS, null, null, null); |
| if (cursor == null) { |
| return; |
| } |
| try { |
| if (cursor.moveToFirst()) { |
| final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME); |
| final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME); |
| final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID); |
| final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE); |
| final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME); |
| final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT); |
| String directoryType = null; |
| if (!TextUtils.isEmpty(packageName)) { |
| PackageManager pm = getContext().getPackageManager(); |
| try { |
| Resources resources = pm.getResourcesForApplication(packageName); |
| directoryType = resources.getString(typeResourceId); |
| } catch (NameNotFoundException e) { |
| Log.w(TAG, "Contact directory resource not found: " |
| + packageName + "." + typeResourceId); |
| } |
| } |
| |
| result.setDirectoryMetaData( |
| displayName, directoryType, accountType, accountName, exportSupport); |
| } |
| } finally { |
| cursor.close(); |
| } |
| } |
| |
| /** |
| * Loads groups meta-data for all groups associated with all constituent raw contacts' |
| * accounts. |
| */ |
| private void loadGroupMetaData(Result result) { |
| StringBuilder selection = new StringBuilder(); |
| ArrayList<String> selectionArgs = new ArrayList<String>(); |
| for (Entity entity : result.mEntities) { |
| ContentValues values = entity.getEntityValues(); |
| String accountName = values.getAsString(RawContacts.ACCOUNT_NAME); |
| String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE); |
| if (accountName != null && accountType != null) { |
| if (selection.length() != 0) { |
| selection.append(" OR "); |
| } |
| selection.append( |
| "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?)"); |
| selectionArgs.add(accountName); |
| selectionArgs.add(accountType); |
| } |
| } |
| Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI, |
| GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]), |
| null); |
| try { |
| while (cursor.moveToNext()) { |
| final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME); |
| final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE); |
| final long groupId = cursor.getLong(GroupQuery.ID); |
| final String title = cursor.getString(GroupQuery.TITLE); |
| final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD) |
| ? false |
| : cursor.getInt(GroupQuery.AUTO_ADD) != 0; |
| final boolean favorites = cursor.isNull(GroupQuery.FAVORITES) |
| ? false |
| : cursor.getInt(GroupQuery.FAVORITES) != 0; |
| |
| result.addGroupMetaData(new GroupMetaData( |
| accountName, accountType, groupId, title, defaultGroup, favorites)); |
| } |
| } finally { |
| cursor.close(); |
| } |
| } |
| |
| @Override |
| protected void onPostExecute(Result result) { |
| unregisterObserver(); |
| |
| // The creator isn't interested in any further updates |
| if (mDestroyed || result == null) { |
| return; |
| } |
| |
| mContact = result; |
| |
| if (result != Result.ERROR && result != Result.NOT_FOUND) { |
| mLookupUri = result.getLookupUri(); |
| |
| if (!result.isDirectoryEntry()) { |
| Log.i(TAG, "Registering content observer for " + mLookupUri); |
| if (mObserver == null) { |
| mObserver = new ForceLoadContentObserver(); |
| } |
| getContext().getContentResolver().registerContentObserver( |
| mLookupUri, true, mObserver); |
| } |
| |
| if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) { |
| mContact.setLoadingPhoto(true); |
| new AsyncPhotoLoader().execute(mContact.getPhotoUri()); |
| } |
| } |
| |
| deliverResult(mContact); |
| } |
| } |
| |
| private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> { |
| |
| private static final int BUFFER_SIZE = 1024*16; |
| |
| @Override |
| protected byte[] doInBackground(String... params) { |
| Uri uri = Uri.parse(params[0]); |
| byte[] data = null; |
| try { |
| InputStream is = getContext().getContentResolver().openInputStream(uri); |
| if (is != null) { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| try { |
| byte[] mBuffer = new byte[BUFFER_SIZE]; |
| |
| int size; |
| while ((size = is.read(mBuffer)) != -1) { |
| baos.write(mBuffer, 0, size); |
| } |
| data = baos.toByteArray(); |
| } finally { |
| is.close(); |
| } |
| } else { |
| Log.v(TAG, "Cannot load photo " + uri); |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "Cannot load photo " + uri, e); |
| } |
| |
| return data; |
| } |
| |
| @Override |
| protected void onPostExecute(byte[] data) { |
| if (mContact != null) { |
| mContact.setPhotoBinaryData(data); |
| mContact.setLoadingPhoto(false); |
| deliverResult(mContact); |
| } |
| } |
| } |
| |
| private void unregisterObserver() { |
| if (mObserver != null) { |
| getContext().getContentResolver().unregisterContentObserver(mObserver); |
| mObserver = null; |
| } |
| } |
| |
| public ContactLoader(Context context, Uri lookupUri) { |
| this(context, lookupUri, false); |
| } |
| |
| public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData) { |
| super(context); |
| mLookupUri = lookupUri; |
| mLoadGroupMetaData = loadGroupMetaData; |
| } |
| |
| public Uri getLookupUri() { |
| return mLookupUri; |
| } |
| |
| @Override |
| public void startLoading() { |
| if (mContact != null) { |
| deliverResult(mContact); |
| } else { |
| forceLoad(); |
| } |
| } |
| |
| @Override |
| public void forceLoad() { |
| final LoadContactTask task = new LoadContactTask(); |
| task.execute((Void[])null); |
| } |
| |
| @Override |
| public void stopLoading() { |
| unregisterObserver(); |
| mContact = null; |
| } |
| |
| @Override |
| public void destroy() { |
| unregisterObserver(); |
| mContact = null; |
| mDestroyed = true; |
| } |
| } |