package com.android.contacts.editor; import android.content.AsyncTaskLoader; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; import com.android.contacts.model.AccountTypeManager; import com.android.contacts.model.account.AccountType; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * Loader for the pick a raw contact to edit activity. Loads all raw contact metadata for the * given Contact {@link Uri}. */ public class PickRawContactLoader extends AsyncTaskLoader { private Uri mContactUri; private RawContactsMetadata mCachedResult; private static final String[] RAW_CONTACT_PROJECTION = new String[] { RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET, RawContacts._ID, RawContacts.DISPLAY_NAME_PRIMARY, RawContacts.DISPLAY_NAME_ALTERNATIVE }; private static final String RAW_CONTACT_SELECTION = RawContacts.CONTACT_ID + "=?"; private static final int ACCOUNT_NAME = 0; private static final int ACCOUNT_TYPE = 1; private static final int DATA_SET = 2; private static final int RAW_CONTACT_ID = 3; private static final int DISPLAY_NAME_PRIMARY = 4; private static final int DISPLAY_NAME_ALTERNATIVE = 5; private static final String PHOTO_SELECTION_PREFIX = ContactsContract.Data.RAW_CONTACT_ID + " IN ("; private static final String PHOTO_SELECTION_SUFFIX = ") AND " + ContactsContract.Data.MIMETYPE + "=\"" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "\""; public PickRawContactLoader(Context context, Uri contactUri) { super(context); mContactUri = ensureIsContactUri(contactUri); } @Override public RawContactsMetadata loadInBackground() { final ContentResolver resolver = getContext().getContentResolver(); // Get the id of the contact we're looking at. final Cursor contactCursor = resolver.query( mContactUri, new String[] {Contacts._ID, Contacts.IS_USER_PROFILE}, null, null, null); if (contactCursor == null) { return null; } if (contactCursor.getCount() < 1) { contactCursor.close(); return null; } final RawContactsMetadata result = new RawContactsMetadata(); try { contactCursor.moveToFirst(); result.contactId = contactCursor.getLong(/* Contacts._ID */ 0); result.isUserProfile = contactCursor.getInt(/* Contacts.IS_USER_PROFILE */ 1) == 1; } finally { contactCursor.close(); } // Load RawContact data final Uri rawContactUri; if (result.isUserProfile) { rawContactUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI; } else { rawContactUri = RawContacts.CONTENT_URI; } final Cursor rawContactCursor = resolver.query( rawContactUri, RAW_CONTACT_PROJECTION, RAW_CONTACT_SELECTION, new String[] {Long.toString(result.contactId)}, null); if (rawContactCursor == null) { return null; } if (rawContactCursor.getCount() < 1) { rawContactCursor.close(); return null; } rawContactCursor.moveToPosition(-1); final StringBuilder photoSelection = new StringBuilder(PHOTO_SELECTION_PREFIX); final Map rawContactMap = new HashMap<>(); try { while (rawContactCursor.moveToNext()) { RawContact rawContact = new RawContact(); rawContact.id = rawContactCursor.getLong(RAW_CONTACT_ID); photoSelection.append(rawContact.id).append(','); rawContact.displayName = rawContactCursor.getString(DISPLAY_NAME_PRIMARY); rawContact.displayNameAlt = rawContactCursor.getString(DISPLAY_NAME_ALTERNATIVE); rawContact.accountName = rawContactCursor.getString(ACCOUNT_NAME); rawContact.accountType = rawContactCursor.getString(ACCOUNT_TYPE); rawContact.accountDataSet = rawContactCursor.getString(DATA_SET); result.rawContacts.add(rawContact); rawContactMap.put(rawContact.id, rawContact); } } finally { rawContactCursor.close(); } // Remove the last ',' if (photoSelection.length() > 0) { photoSelection.deleteCharAt(photoSelection.length() - 1); } photoSelection.append(PHOTO_SELECTION_SUFFIX); final Uri dataUri = result.isUserProfile ? Uri.withAppendedPath(Profile.CONTENT_URI, Data.CONTENT_URI.getPath()) : Data.CONTENT_URI; final Cursor photoCursor = resolver.query( dataUri, new String[] {Data.RAW_CONTACT_ID, Contacts.Photo._ID}, photoSelection.toString(), null, null); if (photoCursor != null) { try { photoCursor.moveToPosition(-1); while (photoCursor.moveToNext()) { final long rawContactId = photoCursor.getLong(/* Data.RAW_CONTACT_ID */ 0); rawContactMap.get(rawContactId).photoId = photoCursor.getLong(/* PHOTO._ID */ 1); } } finally { photoCursor.close(); } } return result; } @Override public void deliverResult(RawContactsMetadata data) { mCachedResult = data; if (isStarted()) { super.deliverResult(data); } } @Override protected void onStartLoading() { super.onStartLoading(); if (mCachedResult == null) { forceLoad(); } else { deliverResult(mCachedResult); } } /** * Ensures that this is a valid contact URI. If invalid, then an exception is * thrown. Otherwise, the original URI is returned. */ private static Uri ensureIsContactUri(final Uri uri) { if (uri == null) { throw new IllegalArgumentException("Uri must not be null"); } if (!uri.toString().startsWith(Contacts.CONTENT_URI.toString())) { throw new IllegalArgumentException("Invalid contact Uri: " + uri); } return uri; } public static class RawContactsMetadata implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public RawContactsMetadata createFromParcel(Parcel source) { return new RawContactsMetadata(source); } @Override public RawContactsMetadata[] newArray(int size) { return new RawContactsMetadata[size]; } }; public long contactId; public boolean isUserProfile; public boolean showReadOnly = false; public ArrayList rawContacts = new ArrayList<>(); public RawContactsMetadata() {} private RawContactsMetadata(Parcel in) { contactId = in.readLong(); isUserProfile = in.readInt() == 1; showReadOnly = in.readInt() == 1; in.readTypedList(rawContacts, RawContact.CREATOR); } /** * Removes all read-only raw contacts. */ public void trimReadOnly(AccountTypeManager accountManager) { for (int i = rawContacts.size() - 1; i >= 0 ; i--) { final RawContact rawContact = rawContacts.get(i); final AccountType account = accountManager.getAccountType( rawContact.accountType, rawContact.accountDataSet); if (!account.areContactsWritable()) { rawContacts.remove(i); } } } /** * Returns the index of the first writable account in this contact or -1 if none exist. */ public int getIndexOfFirstWritableAccount(AccountTypeManager accountManager) { for (int i = 0; i < rawContacts.size(); i++) { final RawContact rawContact = rawContacts.get(i); final AccountType account = accountManager.getAccountType( rawContact.accountType, rawContact.accountDataSet); if (account.areContactsWritable()) { return i; } } return -1; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(contactId); dest.writeInt(isUserProfile ? 1 : 0); dest.writeInt(showReadOnly ? 1 : 0); dest.writeTypedList(rawContacts); } } public static class RawContact implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public RawContact createFromParcel(Parcel source) { return new RawContact(source); } @Override public RawContact[] newArray(int size) { return new RawContact[size]; } }; public long id; public long photoId; public String displayName; public String displayNameAlt; public String accountName; public String accountType; public String accountDataSet; public RawContact() {} private RawContact(Parcel in) { id = in.readLong(); photoId = in.readLong(); displayName = in.readString(); displayNameAlt = in.readString(); accountName = in.readString(); accountType = in.readString(); accountDataSet = in.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(id); dest.writeLong(photoId); dest.writeString(displayName); dest.writeString(displayNameAlt); dest.writeString(accountName); dest.writeString(accountType); dest.writeString(accountDataSet); } } }