blob: 2ec1cbfc9450a4e38b6945f6d75111ff6ad451d1 [file] [log] [blame]
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001/*
Daniel Lehmannef3f8f02010-07-26 18:55:25 -07002 * Copyright (C) 2010 The Android Open Source Project
Daniel Lehmann4cd94412010-04-08 16:44:36 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070018
Daniel Lehmann4cd94412010-04-08 16:44:36 -070019import com.android.contacts.util.DataStatus;
Flavio Lerda37a26842011-06-27 11:36:52 +010020import com.google.common.annotations.VisibleForTesting;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070021
22import android.content.ContentResolver;
23import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070024import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070025import android.content.Context;
26import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070027import android.content.Entity.NamedContentValues;
Jeff Hamilton3c462912010-05-15 02:20:01 -050028import android.content.Loader;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070029import android.content.pm.PackageManager;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070032import android.database.Cursor;
33import android.net.Uri;
34import android.os.AsyncTask;
Daniel Lehmann1316b132010-04-13 15:08:53 -070035import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070036import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080037import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070038import android.provider.ContactsContract.Contacts;
39import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070040import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070041import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070042import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070043import android.provider.ContactsContract.RawContacts;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070044import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070045import android.util.Log;
46
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080047import java.io.ByteArrayOutputStream;
48import java.io.IOException;
49import java.io.InputStream;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070050import java.util.ArrayList;
51import java.util.HashMap;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070052import java.util.List;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070053
54/**
55 * Loads a single Contact and all it constituent RawContacts.
56 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070057public class ContactLoader extends Loader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070058 private static final String TAG = "ContactLoader";
59
Daniel Lehmann4cd94412010-04-08 16:44:36 -070060 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070061 private boolean mLoadGroupMetaData;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070062 private Result mContact;
63 private ForceLoadContentObserver mObserver;
64 private boolean mDestroyed;
65
Dmitri Plotnikove843f912010-09-16 15:21:48 -070066
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070067 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070068 public void onContactLoaded(Result contact);
69 }
70
71 /**
72 * The result of a load operation. Contains all data necessary to display the contact.
73 */
74 public static final class Result {
75 /**
76 * Singleton instance that represents "No Contact Found"
77 */
78 public static final Result NOT_FOUND = new Result();
79
Daniel Lehmann18f104f2010-05-07 15:41:11 -070080 /**
81 * Singleton instance that represents an error, e.g. because of an invalid Uri
82 * TODO: We should come up with something nicer here. Maybe use an Either type so
83 * that we can capture the Exception?
84 */
85 public static final Result ERROR = new Result();
86
Daniel Lehmann4cd94412010-04-08 16:44:36 -070087 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070088 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070089 private final long mDirectoryId;
90 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070091 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070092 private final long mNameRawContactId;
93 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -070094 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -070095 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -070096 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -070097 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -070098 private final String mPhoneticName;
99 private final boolean mStarred;
100 private final Integer mPresence;
101 private final ArrayList<Entity> mEntities;
102 private final HashMap<Long, DataStatus> mStatuses;
103 private final String mStatus;
104 private final Long mStatusTimestamp;
105 private final Integer mStatusLabel;
106 private final String mStatusResPackage;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700107
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700108 private String mDirectoryDisplayName;
109 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700110 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700111 private String mDirectoryAccountName;
112 private int mDirectoryExportSupport;
113
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700114 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700115
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800116 private boolean mLoadingPhoto;
117 private byte[] mPhotoBinaryData;
118
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700119 /**
120 * Constructor for case "no contact found". This must only be used for the
121 * final {@link Result#NOT_FOUND} singleton
122 */
123 private Result() {
124 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700125 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700126 mDirectoryId = -1;
127 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700128 mId = -1;
129 mEntities = null;
130 mStatuses = null;
131 mNameRawContactId = -1;
132 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700133 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700134 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700135 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700136 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700137 mPhoneticName = null;
138 mStarred = false;
139 mPresence = null;
140 mStatus = null;
141 mStatusTimestamp = null;
142 mStatusLabel = null;
143 mStatusResPackage = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700144 }
145
146 /**
147 * Constructor to call when contact was found
148 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700149 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700150 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700151 String displayName, String altDisplayName, String phoneticName, boolean starred,
152 Integer presence, String status, Long statusTimestamp, Integer statusLabel,
153 String statusResPackage) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700154 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700155 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700156 mDirectoryId = directoryId;
157 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700158 mId = id;
159 mEntities = new ArrayList<Entity>();
160 mStatuses = new HashMap<Long, DataStatus>();
161 mNameRawContactId = nameRawContactId;
162 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700163 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700164 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700165 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700166 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700167 mPhoneticName = phoneticName;
168 mStarred = starred;
169 mPresence = presence;
170 mStatus = status;
171 mStatusTimestamp = statusTimestamp;
172 mStatusLabel = statusLabel;
173 mStatusResPackage = statusResPackage;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700174 }
175
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800176 private Result(Result from) {
177 mLookupUri = from.mLookupUri;
178 mUri = from.mUri;
179 mDirectoryId = from.mDirectoryId;
180 mLookupKey = from.mLookupKey;
181 mId = from.mId;
182 mNameRawContactId = from.mNameRawContactId;
183 mDisplayNameSource = from.mDisplayNameSource;
184 mPhotoId = from.mPhotoId;
185 mPhotoUri = from.mPhotoUri;
186 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700187 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800188 mPhoneticName = from.mPhoneticName;
189 mStarred = from.mStarred;
190 mPresence = from.mPresence;
191 mEntities = from.mEntities;
192 mStatuses = from.mStatuses;
193 mStatus = from.mStatus;
194 mStatusTimestamp = from.mStatusTimestamp;
195 mStatusLabel = from.mStatusLabel;
196 mStatusResPackage = from.mStatusResPackage;
197
198 mDirectoryDisplayName = from.mDirectoryDisplayName;
199 mDirectoryType = from.mDirectoryType;
200 mDirectoryAccountType = from.mDirectoryAccountType;
201 mDirectoryAccountName = from.mDirectoryAccountName;
202 mDirectoryExportSupport = from.mDirectoryExportSupport;
203
204 mGroups = from.mGroups;
205
206 mLoadingPhoto = from.mLoadingPhoto;
207 mPhotoBinaryData = from.mPhotoBinaryData;
208 }
209
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700210 /**
211 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
212 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700213 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700214 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700215 mDirectoryDisplayName = displayName;
216 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700217 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700218 mDirectoryAccountName = accountName;
219 mDirectoryExportSupport = exportSupport;
220 }
221
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800222 private void setLoadingPhoto(boolean flag) {
223 mLoadingPhoto = flag;
224 }
225
226 private void setPhotoBinaryData(byte[] photoBinaryData) {
227 mPhotoBinaryData = photoBinaryData;
228 }
229
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700230 public Uri getLookupUri() {
231 return mLookupUri;
232 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800233
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700234 public String getLookupKey() {
235 return mLookupKey;
236 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800237
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700238 public Uri getUri() {
239 return mUri;
240 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800241
Flavio Lerda37a26842011-06-27 11:36:52 +0100242 @VisibleForTesting
243 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700244 return mId;
245 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800246
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700247 public long getNameRawContactId() {
248 return mNameRawContactId;
249 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800250
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700251 public int getDisplayNameSource() {
252 return mDisplayNameSource;
253 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800254
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700255 public long getPhotoId() {
256 return mPhotoId;
257 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800258
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700259 public String getPhotoUri() {
260 return mPhotoUri;
261 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800262
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700263 public String getDisplayName() {
264 return mDisplayName;
265 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800266
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700267 public String getAltDisplayName() {
268 return mAltDisplayName;
269 }
270
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700271 public String getPhoneticName() {
272 return mPhoneticName;
273 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800274
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700275 public boolean getStarred() {
276 return mStarred;
277 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800278
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700279 public Integer getPresence() {
280 return mPresence;
281 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800282
Daniel Lehmann2d4f7592010-10-12 23:31:31 -0700283 public String getSocialSnippet() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700284 return mStatus;
285 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800286
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700287 public Long getStatusTimestamp() {
288 return mStatusTimestamp;
289 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800290
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700291 public Integer getStatusLabel() {
292 return mStatusLabel;
293 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800294
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700295 public String getStatusResPackage() {
296 return mStatusResPackage;
297 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800298
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700299 public ArrayList<Entity> getEntities() {
300 return mEntities;
301 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800302
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700303 public HashMap<Long, DataStatus> getStatuses() {
304 return mStatuses;
305 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700306
307 public long getDirectoryId() {
308 return mDirectoryId;
309 }
310
311 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700312 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
313 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700314 }
315
316 public int getDirectoryExportSupport() {
317 return mDirectoryExportSupport;
318 }
319
320 public String getDirectoryDisplayName() {
321 return mDirectoryDisplayName;
322 }
323
324 public String getDirectoryType() {
325 return mDirectoryType;
326 }
327
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700328 public String getDirectoryAccountType() {
329 return mDirectoryAccountType;
330 }
331
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700332 public String getDirectoryAccountName() {
333 return mDirectoryAccountName;
334 }
335
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800336 public boolean isLoadingPhoto() {
337 return mLoadingPhoto;
338 }
339
340 public byte[] getPhotoBinaryData() {
341 return mPhotoBinaryData;
342 }
343
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700344 public ArrayList<ContentValues> getContentValues() {
345 if (mEntities.size() != 1) {
346 throw new IllegalStateException(
347 "Cannot extract content values from an aggregated contact");
348 }
349
350 Entity entity = mEntities.get(0);
351 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
352 ArrayList<NamedContentValues> subValues = entity.getSubValues();
353 if (subValues != null) {
354 int size = subValues.size();
355 for (int i = 0; i < size; i++) {
356 NamedContentValues pair = subValues.get(i);
357 if (Data.CONTENT_URI.equals(pair.uri)) {
358 result.add(pair.values);
359 }
360 }
361 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800362
363 // If the photo was loaded using the URI, create an entry for the photo
364 // binary data.
365 if (mPhotoId == 0 && mPhotoBinaryData != null) {
366 ContentValues photo = new ContentValues();
367 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
368 photo.put(Photo.PHOTO, mPhotoBinaryData);
369 result.add(photo);
370 }
371
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700372 return result;
373 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700374
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700375 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700376 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700377 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700378 }
379 mGroups.add(group);
380 }
381
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700382 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700383 return mGroups;
384 }
385 }
386
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700387 private static class ContactQuery {
388 // Projection used for the query that loads all data for the entire contact.
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700389 final static String[] COLUMNS = new String[] {
390 Contacts.NAME_RAW_CONTACT_ID,
391 Contacts.DISPLAY_NAME_SOURCE,
392 Contacts.LOOKUP_KEY,
393 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700394 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700395 Contacts.PHONETIC_NAME,
396 Contacts.PHOTO_ID,
397 Contacts.STARRED,
398 Contacts.CONTACT_PRESENCE,
399 Contacts.CONTACT_STATUS,
400 Contacts.CONTACT_STATUS_TIMESTAMP,
401 Contacts.CONTACT_STATUS_RES_PACKAGE,
402 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700403 Contacts.Entity.CONTACT_ID,
404 Contacts.Entity.RAW_CONTACT_ID,
405
406 RawContacts.ACCOUNT_NAME,
407 RawContacts.ACCOUNT_TYPE,
408 RawContacts.DIRTY,
409 RawContacts.VERSION,
410 RawContacts.SOURCE_ID,
411 RawContacts.SYNC1,
412 RawContacts.SYNC2,
413 RawContacts.SYNC3,
414 RawContacts.SYNC4,
415 RawContacts.DELETED,
416 RawContacts.IS_RESTRICTED,
417 RawContacts.NAME_VERIFIED,
418
419 Contacts.Entity.DATA_ID,
420 Data.DATA1,
421 Data.DATA2,
422 Data.DATA3,
423 Data.DATA4,
424 Data.DATA5,
425 Data.DATA6,
426 Data.DATA7,
427 Data.DATA8,
428 Data.DATA9,
429 Data.DATA10,
430 Data.DATA11,
431 Data.DATA12,
432 Data.DATA13,
433 Data.DATA14,
434 Data.DATA15,
435 Data.SYNC1,
436 Data.SYNC2,
437 Data.SYNC3,
438 Data.SYNC4,
439 Data.DATA_VERSION,
440 Data.IS_PRIMARY,
441 Data.IS_SUPER_PRIMARY,
442 Data.MIMETYPE,
443 Data.RES_PACKAGE,
444
445 GroupMembership.GROUP_SOURCE_ID,
446
447 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700448 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700449 Data.STATUS,
450 Data.STATUS_RES_PACKAGE,
451 Data.STATUS_ICON,
452 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700453 Data.STATUS_TIMESTAMP,
454
455 Contacts.PHOTO_URI,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700456 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700457
458 public final static int NAME_RAW_CONTACT_ID = 0;
459 public final static int DISPLAY_NAME_SOURCE = 1;
460 public final static int LOOKUP_KEY = 2;
461 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700462 public final static int ALT_DISPLAY_NAME = 4;
463 public final static int PHONETIC_NAME = 5;
464 public final static int PHOTO_ID = 6;
465 public final static int STARRED = 7;
466 public final static int CONTACT_PRESENCE = 8;
467 public final static int CONTACT_STATUS = 9;
468 public final static int CONTACT_STATUS_TIMESTAMP = 10;
469 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
470 public final static int CONTACT_STATUS_LABEL = 12;
471 public final static int CONTACT_ID = 13;
472 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700473
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700474 public final static int ACCOUNT_NAME = 15;
475 public final static int ACCOUNT_TYPE = 16;
476 public final static int DIRTY = 17;
477 public final static int VERSION = 18;
478 public final static int SOURCE_ID = 19;
479 public final static int SYNC1 = 20;
480 public final static int SYNC2 = 21;
481 public final static int SYNC3 = 22;
482 public final static int SYNC4 = 23;
483 public final static int DELETED = 24;
484 public final static int IS_RESTRICTED = 25;
485 public final static int NAME_VERIFIED = 26;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700486
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700487 public final static int DATA_ID = 27;
488 public final static int DATA1 = 28;
489 public final static int DATA2 = 29;
490 public final static int DATA3 = 30;
491 public final static int DATA4 = 31;
492 public final static int DATA5 = 32;
493 public final static int DATA6 = 33;
494 public final static int DATA7 = 34;
495 public final static int DATA8 = 35;
496 public final static int DATA9 = 36;
497 public final static int DATA10 = 37;
498 public final static int DATA11 = 38;
499 public final static int DATA12 = 39;
500 public final static int DATA13 = 40;
501 public final static int DATA14 = 41;
502 public final static int DATA15 = 42;
503 public final static int DATA_SYNC1 = 43;
504 public final static int DATA_SYNC2 = 44;
505 public final static int DATA_SYNC3 = 45;
506 public final static int DATA_SYNC4 = 46;
507 public final static int DATA_VERSION = 47;
508 public final static int IS_PRIMARY = 48;
509 public final static int IS_SUPERPRIMARY = 49;
510 public final static int MIMETYPE = 50;
511 public final static int RES_PACKAGE = 51;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700512
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700513 public final static int GROUP_SOURCE_ID = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700514
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700515 public final static int PRESENCE = 53;
516 public final static int CHAT_CAPABILITY = 54;
517 public final static int STATUS = 55;
518 public final static int STATUS_RES_PACKAGE = 56;
519 public final static int STATUS_ICON = 57;
520 public final static int STATUS_LABEL = 58;
521 public final static int STATUS_TIMESTAMP = 59;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700522
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700523 public final static int PHOTO_URI = 60;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700524 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700525
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700526 private static class DirectoryQuery {
527 // Projection used for the query that loads all data for the entire contact.
528 final static String[] COLUMNS = new String[] {
529 Directory.DISPLAY_NAME,
530 Directory.PACKAGE_NAME,
531 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700532 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700533 Directory.ACCOUNT_NAME,
534 Directory.EXPORT_SUPPORT,
535 };
536
537 public final static int DISPLAY_NAME = 0;
538 public final static int PACKAGE_NAME = 1;
539 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700540 public final static int ACCOUNT_TYPE = 3;
541 public final static int ACCOUNT_NAME = 4;
542 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700543 }
544
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700545 private static class GroupQuery {
546 final static String[] COLUMNS = new String[] {
547 Groups.ACCOUNT_NAME,
548 Groups.ACCOUNT_TYPE,
549 Groups._ID,
550 Groups.TITLE,
551 Groups.AUTO_ADD,
552 Groups.FAVORITES,
553 };
554
555 public final static int ACCOUNT_NAME = 0;
556 public final static int ACCOUNT_TYPE = 1;
557 public final static int ID = 2;
558 public final static int TITLE = 3;
559 public final static int AUTO_ADD = 4;
560 public final static int FAVORITES = 5;
561 }
562
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700563 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700564
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700565 @Override
566 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700567 try {
568 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700569 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700570 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700571 if (result != Result.NOT_FOUND) {
572 if (result.isDirectoryEntry()) {
573 loadDirectoryMetaData(result);
574 } else if (mLoadGroupMetaData) {
575 loadGroupMetaData(result);
576 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800577 loadPhotoBinaryData(result);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700578 }
579 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700580 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700581 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700582 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700583 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700584 }
585
586 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700587 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
588 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700589 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700590 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700591 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700592 if (uri == null) throw new IllegalArgumentException("uri must not be null");
593
594 final String authority = uri.getAuthority();
595
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700596 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700597 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700598 final String type = resolver.getType(uri);
599 // Contact-Uri? Good, return it
600 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
601 return uri;
602 }
603
604 // RawContact-Uri? Transform it to ContactUri
605 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
606 final long rawContactId = ContentUris.parseId(uri);
607 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
608 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
609 }
610
611 // Anything else? We don't know what this is
612 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700613 }
614
615 // Legacy Style? Convert to RawContact
616 final String OBSOLETE_AUTHORITY = "contacts";
617 if (OBSOLETE_AUTHORITY.equals(authority)) {
618 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
619 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700620 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700621 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
622 }
623
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700624 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700625 }
626
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700627 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
628 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
629 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
630 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700631 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700632 Log.e(TAG, "No cursor returned in loadContactEntity");
633 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700634 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700635
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700636 try {
637 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700638 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700639 return Result.NOT_FOUND;
640 }
641
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700642 long currentRawContactId = -1;
643 Entity entity = null;
644 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700645 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700646 HashMap<Long, DataStatus> statuses = result.getStatuses();
647 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
648 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
649 if (rawContactId != currentRawContactId) {
650 currentRawContactId = rawContactId;
651 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700652 entities.add(entity);
653 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700654 if (!cursor.isNull(ContactQuery.DATA_ID)) {
655 ContentValues data = loadData(cursor);
656 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
657
658 if (!cursor.isNull(ContactQuery.PRESENCE)
659 || !cursor.isNull(ContactQuery.STATUS)) {
660 final DataStatus status = new DataStatus(cursor);
661 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
662 statuses.put(dataId, status);
663 }
664 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700665 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700666
667 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700668 } finally {
669 cursor.close();
670 }
671 }
672
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700673 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800674 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
675 * not found, returns null
676 */
677 private void loadPhotoBinaryData(Result contactData) {
678 final long photoId = contactData.getPhotoId();
679 if (photoId <= 0) {
680 // No photo ID
681 return;
682 }
683
684 for (Entity entity : contactData.getEntities()) {
685 for (NamedContentValues subValue : entity.getSubValues()) {
686 final ContentValues entryValues = subValue.values;
687 final long dataId = entryValues.getAsLong(Data._ID);
688 if (dataId == photoId) {
689 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
690 // Correct Data Id but incorrect MimeType? Don't load
691 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
692 return;
693 }
694 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
695 break;
696 }
697 }
698 }
699 }
700
701 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700702 * Extracts Contact level columns from the cursor.
703 */
704 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700705 final String directoryParameter =
706 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
707 final long directoryId = directoryParameter == null
708 ? Directory.DEFAULT
709 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700710 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
711 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
712 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
713 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
714 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700715 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700716 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
717 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700718 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700719 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
720 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
721 ? null
722 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
723 final String status = cursor.getString(ContactQuery.CONTACT_STATUS);
724 final Long statusTimestamp = cursor.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
725 ? null
726 : cursor.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
727 final Integer statusLabel = cursor.isNull(ContactQuery.CONTACT_STATUS_LABEL)
728 ? null
729 : cursor.getInt(ContactQuery.CONTACT_STATUS_LABEL);
730 final String statusResPackage = cursor.getString(
731 ContactQuery.CONTACT_STATUS_RES_PACKAGE);
732
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700733 Uri lookupUri;
734 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
735 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700736 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700737 } else {
738 lookupUri = contactUri;
739 }
740
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700741 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700742 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700743 altDisplayName, phoneticName, starred, presence, status, statusTimestamp,
744 statusLabel, statusResPackage);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700745 }
746
747 /**
748 * Extracts RawContact level columns from the cursor.
749 */
750 private ContentValues loadRawContact(Cursor cursor) {
751 ContentValues cv = new ContentValues();
752
753 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
754
755 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
756 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
757 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
758 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
759 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
760 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
761 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
762 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
763 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
764 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
765 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
766 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
767 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_RESTRICTED);
768 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
769
770 return cv;
771 }
772
773 /**
774 * Extracts Data level columns from the cursor.
775 */
776 private ContentValues loadData(Cursor cursor) {
777 ContentValues cv = new ContentValues();
778
779 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
780
781 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
782 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
783 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
784 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
785 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
786 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
787 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
788 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
789 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
790 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
791 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
792 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
793 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
794 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
795 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
796 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
797 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
798 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
799 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
800 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
801 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
802 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
803 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
804 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
805 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700806 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700807
808 return cv;
809 }
810
811 private void cursorColumnToContentValues(
812 Cursor cursor, ContentValues values, int index) {
813 switch (cursor.getType(index)) {
814 case Cursor.FIELD_TYPE_NULL:
815 // don't put anything in the content values
816 break;
817 case Cursor.FIELD_TYPE_INTEGER:
818 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
819 break;
820 case Cursor.FIELD_TYPE_STRING:
821 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
822 break;
823 case Cursor.FIELD_TYPE_BLOB:
824 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
825 break;
826 default:
827 throw new IllegalStateException("Invalid or unhandled data type");
828 }
829 }
830
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700831 private void loadDirectoryMetaData(Result result) {
832 long directoryId = result.getDirectoryId();
833
834 Cursor cursor = getContext().getContentResolver().query(
835 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
836 DirectoryQuery.COLUMNS, null, null, null);
837 if (cursor == null) {
838 return;
839 }
840 try {
841 if (cursor.moveToFirst()) {
842 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
843 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
844 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700845 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700846 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
847 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
848 String directoryType = null;
849 if (!TextUtils.isEmpty(packageName)) {
850 PackageManager pm = getContext().getPackageManager();
851 try {
852 Resources resources = pm.getResourcesForApplication(packageName);
853 directoryType = resources.getString(typeResourceId);
854 } catch (NameNotFoundException e) {
855 Log.w(TAG, "Contact directory resource not found: "
856 + packageName + "." + typeResourceId);
857 }
858 }
859
860 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700861 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700862 }
863 } finally {
864 cursor.close();
865 }
866 }
867
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700868 /**
869 * Loads groups meta-data for all groups associated with all constituent raw contacts'
870 * accounts.
871 */
872 private void loadGroupMetaData(Result result) {
873 StringBuilder selection = new StringBuilder();
874 ArrayList<String> selectionArgs = new ArrayList<String>();
875 for (Entity entity : result.mEntities) {
876 ContentValues values = entity.getEntityValues();
877 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
878 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
879 if (accountName != null && accountType != null) {
880 if (selection.length() != 0) {
881 selection.append(" OR ");
882 }
883 selection.append(
884 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?)");
885 selectionArgs.add(accountName);
886 selectionArgs.add(accountType);
887 }
888 }
889 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
890 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
891 null);
892 try {
893 while (cursor.moveToNext()) {
894 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
895 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
896 final long groupId = cursor.getLong(GroupQuery.ID);
897 final String title = cursor.getString(GroupQuery.TITLE);
898 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
899 ? false
900 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
901 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
902 ? false
903 : cursor.getInt(GroupQuery.FAVORITES) != 0;
904
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700905 result.addGroupMetaData(new GroupMetaData(
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700906 accountName, accountType, groupId, title, defaultGroup, favorites));
907 }
908 } finally {
909 cursor.close();
910 }
911 }
912
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700913 @Override
914 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800915 unregisterObserver();
916
Daniel Lehmann1316b132010-04-13 15:08:53 -0700917 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800918 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700919 return;
920 }
921
922 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700923
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800924 if (result != Result.ERROR && result != Result.NOT_FOUND) {
925 mLookupUri = result.getLookupUri();
926
927 if (!result.isDirectoryEntry()) {
928 Log.i(TAG, "Registering content observer for " + mLookupUri);
929 if (mObserver == null) {
930 mObserver = new ForceLoadContentObserver();
931 }
932 getContext().getContentResolver().registerContentObserver(
933 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700934 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800935
936 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
937 mContact.setLoadingPhoto(true);
938 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
939 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700940 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800941
942 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700943 }
944 }
945
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800946 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
947
948 private static final int BUFFER_SIZE = 1024*16;
949
950 @Override
951 protected byte[] doInBackground(String... params) {
952 Uri uri = Uri.parse(params[0]);
953 byte[] data = null;
954 try {
955 InputStream is = getContext().getContentResolver().openInputStream(uri);
956 if (is != null) {
957 ByteArrayOutputStream baos = new ByteArrayOutputStream();
958 try {
959 byte[] mBuffer = new byte[BUFFER_SIZE];
960
961 int size;
962 while ((size = is.read(mBuffer)) != -1) {
963 baos.write(mBuffer, 0, size);
964 }
965 data = baos.toByteArray();
966 } finally {
967 is.close();
968 }
969 } else {
970 Log.v(TAG, "Cannot load photo " + uri);
971 }
972 } catch (IOException e) {
973 Log.e(TAG, "Cannot load photo " + uri, e);
974 }
975
976 return data;
977 }
978
979 @Override
980 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800981 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800982 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800983 mContact.setPhotoBinaryData(data);
984 mContact.setLoadingPhoto(false);
985 deliverResult(mContact);
986 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800987 }
988 }
989
Daniel Lehmann3a120772010-06-21 16:21:35 -0700990 private void unregisterObserver() {
991 if (mObserver != null) {
992 getContext().getContentResolver().unregisterContentObserver(mObserver);
993 mObserver = null;
994 }
995 }
996
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700997 public ContactLoader(Context context, Uri lookupUri) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700998 this(context, lookupUri, false);
999 }
1000
1001 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001002 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001003 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001004 mLoadGroupMetaData = loadGroupMetaData;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001005 }
1006
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001007 public Uri getLookupUri() {
1008 return mLookupUri;
1009 }
1010
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001011 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001012 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001013 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001014 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001015 }
1016
1017 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001018 forceLoad();
1019 }
1020 }
1021
1022 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001023 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001024 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001025 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001026 }
1027
1028 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001029 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001030 unregisterObserver();
1031 mContact = null;
1032 mDestroyed = true;
1033 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001034}