1 package com.android.contacts.editor; 2 3 import android.content.AsyncTaskLoader; 4 import android.content.ContentResolver; 5 import android.content.Context; 6 import android.database.Cursor; 7 import android.net.Uri; 8 import android.os.Parcel; 9 import android.os.Parcelable; 10 import android.provider.ContactsContract; 11 import android.provider.ContactsContract.Contacts; 12 import android.provider.ContactsContract.Data; 13 import android.provider.ContactsContract.Profile; 14 import android.provider.ContactsContract.RawContacts; 15 16 import com.android.contacts.model.AccountTypeManager; 17 import com.android.contacts.model.account.AccountType; 18 19 import java.util.ArrayList; 20 import java.util.HashMap; 21 import java.util.Map; 22 23 /** 24 * Loader for the pick a raw contact to edit activity. Loads all raw contact metadata for the 25 * given Contact {@link Uri}. 26 */ 27 public class PickRawContactLoader extends 28 AsyncTaskLoader<PickRawContactLoader.RawContactsMetadata> { 29 private Uri mContactUri; 30 private RawContactsMetadata mCachedResult; 31 32 private static final String[] RAW_CONTACT_PROJECTION = new String[] { 33 RawContacts.ACCOUNT_NAME, 34 RawContacts.ACCOUNT_TYPE, 35 RawContacts.DATA_SET, 36 RawContacts._ID, 37 RawContacts.DISPLAY_NAME_PRIMARY, 38 RawContacts.DISPLAY_NAME_ALTERNATIVE 39 }; 40 41 private static final String RAW_CONTACT_SELECTION = RawContacts.CONTACT_ID + "=?"; 42 43 private static final int ACCOUNT_NAME = 0; 44 private static final int ACCOUNT_TYPE = 1; 45 private static final int DATA_SET = 2; 46 private static final int RAW_CONTACT_ID = 3; 47 private static final int DISPLAY_NAME_PRIMARY = 4; 48 private static final int DISPLAY_NAME_ALTERNATIVE = 5; 49 50 private static final String PHOTO_SELECTION_PREFIX = 51 ContactsContract.Data.RAW_CONTACT_ID + " IN ("; 52 private static final String PHOTO_SELECTION_SUFFIX = ") AND " + ContactsContract.Data.MIMETYPE 53 + "=\"" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "\""; 54 PickRawContactLoader(Context context, Uri contactUri)55 public PickRawContactLoader(Context context, Uri contactUri) { 56 super(context); 57 mContactUri = ensureIsContactUri(contactUri); 58 } 59 60 @Override loadInBackground()61 public RawContactsMetadata loadInBackground() { 62 final ContentResolver resolver = getContext().getContentResolver(); 63 // Get the id of the contact we're looking at. 64 final Cursor contactCursor = resolver.query( 65 mContactUri, new String[] {Contacts._ID, Contacts.IS_USER_PROFILE}, null, 66 null, null); 67 68 if (contactCursor == null) { 69 return null; 70 } 71 72 if (contactCursor.getCount() < 1) { 73 contactCursor.close(); 74 return null; 75 } 76 77 final RawContactsMetadata result = new RawContactsMetadata(); 78 try { 79 contactCursor.moveToFirst(); 80 result.contactId = contactCursor.getLong(/* Contacts._ID */ 0); 81 result.isUserProfile = contactCursor.getInt(/* Contacts.IS_USER_PROFILE */ 1) == 1; 82 } finally { 83 contactCursor.close(); 84 } 85 86 // Load RawContact data 87 final Uri rawContactUri; 88 if (result.isUserProfile) { 89 rawContactUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI; 90 } else { 91 rawContactUri = RawContacts.CONTENT_URI; 92 } 93 94 final Cursor rawContactCursor = resolver.query( 95 rawContactUri, RAW_CONTACT_PROJECTION, RAW_CONTACT_SELECTION, 96 new String[] {Long.toString(result.contactId)}, null); 97 98 if (rawContactCursor == null) { 99 return null; 100 } 101 102 if (rawContactCursor.getCount() < 1) { 103 rawContactCursor.close(); 104 return null; 105 } 106 107 rawContactCursor.moveToPosition(-1); 108 final StringBuilder photoSelection = new StringBuilder(PHOTO_SELECTION_PREFIX); 109 final Map<Long, RawContact> rawContactMap = new HashMap<>(); 110 try { 111 while (rawContactCursor.moveToNext()) { 112 RawContact rawContact = new RawContact(); 113 rawContact.id = rawContactCursor.getLong(RAW_CONTACT_ID); 114 photoSelection.append(rawContact.id).append(','); 115 rawContact.displayName = rawContactCursor.getString(DISPLAY_NAME_PRIMARY); 116 rawContact.displayNameAlt = rawContactCursor.getString(DISPLAY_NAME_ALTERNATIVE); 117 rawContact.accountName = rawContactCursor.getString(ACCOUNT_NAME); 118 rawContact.accountType = rawContactCursor.getString(ACCOUNT_TYPE); 119 rawContact.accountDataSet = rawContactCursor.getString(DATA_SET); 120 result.rawContacts.add(rawContact); 121 rawContactMap.put(rawContact.id, rawContact); 122 } 123 } finally { 124 rawContactCursor.close(); 125 } 126 127 // Remove the last ',' 128 if (photoSelection.length() > 0) { 129 photoSelection.deleteCharAt(photoSelection.length() - 1); 130 } 131 photoSelection.append(PHOTO_SELECTION_SUFFIX); 132 133 final Uri dataUri = result.isUserProfile 134 ? Uri.withAppendedPath(Profile.CONTENT_URI, Data.CONTENT_URI.getPath()) 135 : Data.CONTENT_URI; 136 final Cursor photoCursor = resolver.query( 137 dataUri, 138 new String[] {Data.RAW_CONTACT_ID, Contacts.Photo._ID}, 139 photoSelection.toString(), null, null); 140 141 if (photoCursor != null) { 142 try { 143 photoCursor.moveToPosition(-1); 144 while (photoCursor.moveToNext()) { 145 final long rawContactId = photoCursor.getLong(/* Data.RAW_CONTACT_ID */ 0); 146 rawContactMap.get(rawContactId).photoId = 147 photoCursor.getLong(/* PHOTO._ID */ 1); 148 } 149 } finally { 150 photoCursor.close(); 151 } 152 } 153 return result; 154 } 155 156 @Override deliverResult(RawContactsMetadata data)157 public void deliverResult(RawContactsMetadata data) { 158 mCachedResult = data; 159 if (isStarted()) { 160 super.deliverResult(data); 161 } 162 } 163 164 @Override onStartLoading()165 protected void onStartLoading() { 166 super.onStartLoading(); 167 if (mCachedResult == null) { 168 forceLoad(); 169 } else { 170 deliverResult(mCachedResult); 171 } 172 } 173 174 /** 175 * Ensures that this is a valid contact URI. If invalid, then an exception is 176 * thrown. Otherwise, the original URI is returned. 177 */ ensureIsContactUri(final Uri uri)178 private static Uri ensureIsContactUri(final Uri uri) { 179 if (uri == null) { 180 throw new IllegalArgumentException("Uri must not be null"); 181 } 182 if (!uri.toString().startsWith(Contacts.CONTENT_URI.toString())) { 183 throw new IllegalArgumentException("Invalid contact Uri: " + uri); 184 } 185 return uri; 186 } 187 188 public static class RawContactsMetadata implements Parcelable { 189 public static final Parcelable.Creator<RawContactsMetadata> CREATOR = 190 new Parcelable.Creator<RawContactsMetadata>() { 191 @Override 192 public RawContactsMetadata createFromParcel(Parcel source) { 193 return new RawContactsMetadata(source); 194 } 195 196 @Override 197 public RawContactsMetadata[] newArray(int size) { 198 return new RawContactsMetadata[size]; 199 } 200 }; 201 202 public long contactId; 203 public boolean isUserProfile; 204 public boolean showReadOnly = false; 205 public ArrayList<RawContact> rawContacts = new ArrayList<>(); 206 RawContactsMetadata()207 public RawContactsMetadata() {} 208 RawContactsMetadata(Parcel in)209 private RawContactsMetadata(Parcel in) { 210 contactId = in.readLong(); 211 isUserProfile = in.readInt() == 1; 212 showReadOnly = in.readInt() == 1; 213 in.readTypedList(rawContacts, RawContact.CREATOR); 214 } 215 216 /** 217 * Removes all read-only raw contacts. 218 */ trimReadOnly(AccountTypeManager accountManager)219 public void trimReadOnly(AccountTypeManager accountManager) { 220 for (int i = rawContacts.size() - 1; i >= 0 ; i--) { 221 final RawContact rawContact = rawContacts.get(i); 222 final AccountType account = accountManager.getAccountType( 223 rawContact.accountType, rawContact.accountDataSet); 224 if (!account.areContactsWritable()) { 225 rawContacts.remove(i); 226 } 227 } 228 } 229 230 /** 231 * Returns the index of the first writable account in this contact or -1 if none exist. 232 */ getIndexOfFirstWritableAccount(AccountTypeManager accountManager)233 public int getIndexOfFirstWritableAccount(AccountTypeManager accountManager) { 234 for (int i = 0; i < rawContacts.size(); i++) { 235 final RawContact rawContact = rawContacts.get(i); 236 final AccountType account = accountManager.getAccountType( 237 rawContact.accountType, rawContact.accountDataSet); 238 if (account.areContactsWritable()) { 239 return i; 240 } 241 } 242 243 return -1; 244 } 245 246 @Override describeContents()247 public int describeContents() { 248 return 0; 249 } 250 251 @Override writeToParcel(Parcel dest, int flags)252 public void writeToParcel(Parcel dest, int flags) { 253 dest.writeLong(contactId); 254 dest.writeInt(isUserProfile ? 1 : 0); 255 dest.writeInt(showReadOnly ? 1 : 0); 256 dest.writeTypedList(rawContacts); 257 } 258 } 259 260 public static class RawContact implements Parcelable { 261 public static final Parcelable.Creator<RawContact> CREATOR = 262 new Parcelable.Creator<RawContact>() { 263 @Override 264 public RawContact createFromParcel(Parcel source) { 265 return new RawContact(source); 266 } 267 268 @Override 269 public RawContact[] newArray(int size) { 270 return new RawContact[size]; 271 } 272 }; 273 274 public long id; 275 public long photoId; 276 public String displayName; 277 public String displayNameAlt; 278 public String accountName; 279 public String accountType; 280 public String accountDataSet; 281 RawContact()282 public RawContact() {} 283 RawContact(Parcel in)284 private RawContact(Parcel in) { 285 id = in.readLong(); 286 photoId = in.readLong(); 287 displayName = in.readString(); 288 displayNameAlt = in.readString(); 289 accountName = in.readString(); 290 accountType = in.readString(); 291 accountDataSet = in.readString(); 292 } 293 294 @Override describeContents()295 public int describeContents() { 296 return 0; 297 } 298 299 @Override writeToParcel(Parcel dest, int flags)300 public void writeToParcel(Parcel dest, int flags) { 301 dest.writeLong(id); 302 dest.writeLong(photoId); 303 dest.writeString(displayName); 304 dest.writeString(displayNameAlt); 305 dest.writeString(accountName); 306 dest.writeString(accountType); 307 dest.writeString(accountDataSet); 308 } 309 } 310 } 311