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