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 !uri.toString().equals(Profile.CONTENT_URI.toString())) { 184 throw new IllegalArgumentException("Invalid contact Uri: " + uri); 185 } 186 return uri; 187 } 188 189 public static class RawContactsMetadata implements Parcelable { 190 public static final Parcelable.Creator<RawContactsMetadata> CREATOR = 191 new Parcelable.Creator<RawContactsMetadata>() { 192 @Override 193 public RawContactsMetadata createFromParcel(Parcel source) { 194 return new RawContactsMetadata(source); 195 } 196 197 @Override 198 public RawContactsMetadata[] newArray(int size) { 199 return new RawContactsMetadata[size]; 200 } 201 }; 202 203 public long contactId; 204 public boolean isUserProfile; 205 public boolean showReadOnly = false; 206 public ArrayList<RawContact> rawContacts = new ArrayList<>(); 207 RawContactsMetadata()208 public RawContactsMetadata() {} 209 RawContactsMetadata(Parcel in)210 private RawContactsMetadata(Parcel in) { 211 contactId = in.readLong(); 212 isUserProfile = in.readInt() == 1; 213 showReadOnly = in.readInt() == 1; 214 in.readTypedList(rawContacts, RawContact.CREATOR); 215 } 216 217 /** 218 * Removes all read-only raw contacts. 219 */ trimReadOnly(AccountTypeManager accountManager)220 public void trimReadOnly(AccountTypeManager accountManager) { 221 for (int i = rawContacts.size() - 1; i >= 0 ; i--) { 222 final RawContact rawContact = rawContacts.get(i); 223 final AccountType account = accountManager.getAccountType( 224 rawContact.accountType, rawContact.accountDataSet); 225 if (!account.areContactsWritable()) { 226 rawContacts.remove(i); 227 } 228 } 229 } 230 231 /** 232 * Returns the index of the first writable account in this contact or -1 if none exist. 233 */ getIndexOfFirstWritableAccount(AccountTypeManager accountManager)234 public int getIndexOfFirstWritableAccount(AccountTypeManager accountManager) { 235 for (int i = 0; i < rawContacts.size(); i++) { 236 final RawContact rawContact = rawContacts.get(i); 237 final AccountType account = accountManager.getAccountType( 238 rawContact.accountType, rawContact.accountDataSet); 239 if (account.areContactsWritable()) { 240 return i; 241 } 242 } 243 244 return -1; 245 } 246 247 @Override describeContents()248 public int describeContents() { 249 return 0; 250 } 251 252 @Override writeToParcel(Parcel dest, int flags)253 public void writeToParcel(Parcel dest, int flags) { 254 dest.writeLong(contactId); 255 dest.writeInt(isUserProfile ? 1 : 0); 256 dest.writeInt(showReadOnly ? 1 : 0); 257 dest.writeTypedList(rawContacts); 258 } 259 } 260 261 public static class RawContact implements Parcelable { 262 public static final Parcelable.Creator<RawContact> CREATOR = 263 new Parcelable.Creator<RawContact>() { 264 @Override 265 public RawContact createFromParcel(Parcel source) { 266 return new RawContact(source); 267 } 268 269 @Override 270 public RawContact[] newArray(int size) { 271 return new RawContact[size]; 272 } 273 }; 274 275 public long id; 276 public long photoId; 277 public String displayName; 278 public String displayNameAlt; 279 public String accountName; 280 public String accountType; 281 public String accountDataSet; 282 RawContact()283 public RawContact() {} 284 RawContact(Parcel in)285 private RawContact(Parcel in) { 286 id = in.readLong(); 287 photoId = in.readLong(); 288 displayName = in.readString(); 289 displayNameAlt = in.readString(); 290 accountName = in.readString(); 291 accountType = in.readString(); 292 accountDataSet = in.readString(); 293 } 294 295 @Override describeContents()296 public int describeContents() { 297 return 0; 298 } 299 300 @Override writeToParcel(Parcel dest, int flags)301 public void writeToParcel(Parcel dest, int flags) { 302 dest.writeLong(id); 303 dest.writeLong(photoId); 304 dest.writeString(displayName); 305 dest.writeString(displayNameAlt); 306 dest.writeString(accountName); 307 dest.writeString(accountType); 308 dest.writeString(accountDataSet); 309 } 310 } 311 } 312