blob: 7ed9d8605e89200397e8fdb47946df2d7c3cb0b0 [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,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700416 RawContacts.NAME_VERIFIED,
417
418 Contacts.Entity.DATA_ID,
419 Data.DATA1,
420 Data.DATA2,
421 Data.DATA3,
422 Data.DATA4,
423 Data.DATA5,
424 Data.DATA6,
425 Data.DATA7,
426 Data.DATA8,
427 Data.DATA9,
428 Data.DATA10,
429 Data.DATA11,
430 Data.DATA12,
431 Data.DATA13,
432 Data.DATA14,
433 Data.DATA15,
434 Data.SYNC1,
435 Data.SYNC2,
436 Data.SYNC3,
437 Data.SYNC4,
438 Data.DATA_VERSION,
439 Data.IS_PRIMARY,
440 Data.IS_SUPER_PRIMARY,
441 Data.MIMETYPE,
442 Data.RES_PACKAGE,
443
444 GroupMembership.GROUP_SOURCE_ID,
445
446 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700447 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700448 Data.STATUS,
449 Data.STATUS_RES_PACKAGE,
450 Data.STATUS_ICON,
451 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700452 Data.STATUS_TIMESTAMP,
453
454 Contacts.PHOTO_URI,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700455 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700456
457 public final static int NAME_RAW_CONTACT_ID = 0;
458 public final static int DISPLAY_NAME_SOURCE = 1;
459 public final static int LOOKUP_KEY = 2;
460 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700461 public final static int ALT_DISPLAY_NAME = 4;
462 public final static int PHONETIC_NAME = 5;
463 public final static int PHOTO_ID = 6;
464 public final static int STARRED = 7;
465 public final static int CONTACT_PRESENCE = 8;
466 public final static int CONTACT_STATUS = 9;
467 public final static int CONTACT_STATUS_TIMESTAMP = 10;
468 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
469 public final static int CONTACT_STATUS_LABEL = 12;
470 public final static int CONTACT_ID = 13;
471 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700472
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700473 public final static int ACCOUNT_NAME = 15;
474 public final static int ACCOUNT_TYPE = 16;
475 public final static int DIRTY = 17;
476 public final static int VERSION = 18;
477 public final static int SOURCE_ID = 19;
478 public final static int SYNC1 = 20;
479 public final static int SYNC2 = 21;
480 public final static int SYNC3 = 22;
481 public final static int SYNC4 = 23;
482 public final static int DELETED = 24;
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700483 public final static int NAME_VERIFIED = 25;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700484
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700485 public final static int DATA_ID = 26;
486 public final static int DATA1 = 27;
487 public final static int DATA2 = 28;
488 public final static int DATA3 = 29;
489 public final static int DATA4 = 30;
490 public final static int DATA5 = 31;
491 public final static int DATA6 = 32;
492 public final static int DATA7 = 33;
493 public final static int DATA8 = 34;
494 public final static int DATA9 = 35;
495 public final static int DATA10 = 36;
496 public final static int DATA11 = 37;
497 public final static int DATA12 = 38;
498 public final static int DATA13 = 39;
499 public final static int DATA14 = 40;
500 public final static int DATA15 = 41;
501 public final static int DATA_SYNC1 = 42;
502 public final static int DATA_SYNC2 = 43;
503 public final static int DATA_SYNC3 = 44;
504 public final static int DATA_SYNC4 = 45;
505 public final static int DATA_VERSION = 46;
506 public final static int IS_PRIMARY = 47;
507 public final static int IS_SUPERPRIMARY = 48;
508 public final static int MIMETYPE = 49;
509 public final static int RES_PACKAGE = 50;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700510
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700511 public final static int GROUP_SOURCE_ID = 51;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700512
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700513 public final static int PRESENCE = 52;
514 public final static int CHAT_CAPABILITY = 53;
515 public final static int STATUS = 54;
516 public final static int STATUS_RES_PACKAGE = 55;
517 public final static int STATUS_ICON = 56;
518 public final static int STATUS_LABEL = 57;
519 public final static int STATUS_TIMESTAMP = 58;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700520
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700521 public final static int PHOTO_URI = 59;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700522 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700523
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700524 private static class DirectoryQuery {
525 // Projection used for the query that loads all data for the entire contact.
526 final static String[] COLUMNS = new String[] {
527 Directory.DISPLAY_NAME,
528 Directory.PACKAGE_NAME,
529 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700530 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700531 Directory.ACCOUNT_NAME,
532 Directory.EXPORT_SUPPORT,
533 };
534
535 public final static int DISPLAY_NAME = 0;
536 public final static int PACKAGE_NAME = 1;
537 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700538 public final static int ACCOUNT_TYPE = 3;
539 public final static int ACCOUNT_NAME = 4;
540 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700541 }
542
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700543 private static class GroupQuery {
544 final static String[] COLUMNS = new String[] {
545 Groups.ACCOUNT_NAME,
546 Groups.ACCOUNT_TYPE,
547 Groups._ID,
548 Groups.TITLE,
549 Groups.AUTO_ADD,
550 Groups.FAVORITES,
551 };
552
553 public final static int ACCOUNT_NAME = 0;
554 public final static int ACCOUNT_TYPE = 1;
555 public final static int ID = 2;
556 public final static int TITLE = 3;
557 public final static int AUTO_ADD = 4;
558 public final static int FAVORITES = 5;
559 }
560
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700561 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700562
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700563 @Override
564 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700565 try {
566 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700567 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700568 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700569 if (result != Result.NOT_FOUND) {
570 if (result.isDirectoryEntry()) {
571 loadDirectoryMetaData(result);
572 } else if (mLoadGroupMetaData) {
573 loadGroupMetaData(result);
574 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800575 loadPhotoBinaryData(result);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700576 }
577 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700578 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700579 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700580 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700581 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700582 }
583
584 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700585 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
586 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700587 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700588 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700589 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700590 if (uri == null) throw new IllegalArgumentException("uri must not be null");
591
592 final String authority = uri.getAuthority();
593
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700594 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700595 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700596 final String type = resolver.getType(uri);
597 // Contact-Uri? Good, return it
598 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
599 return uri;
600 }
601
602 // RawContact-Uri? Transform it to ContactUri
603 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
604 final long rawContactId = ContentUris.parseId(uri);
605 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
606 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
607 }
608
609 // Anything else? We don't know what this is
610 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700611 }
612
613 // Legacy Style? Convert to RawContact
614 final String OBSOLETE_AUTHORITY = "contacts";
615 if (OBSOLETE_AUTHORITY.equals(authority)) {
616 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
617 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700618 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700619 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
620 }
621
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700622 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700623 }
624
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700625 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
626 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
627 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
628 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700629 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700630 Log.e(TAG, "No cursor returned in loadContactEntity");
631 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700632 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700633
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700634 try {
635 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700636 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700637 return Result.NOT_FOUND;
638 }
639
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700640 long currentRawContactId = -1;
641 Entity entity = null;
642 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700643 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700644 HashMap<Long, DataStatus> statuses = result.getStatuses();
645 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
646 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
647 if (rawContactId != currentRawContactId) {
648 currentRawContactId = rawContactId;
649 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700650 entities.add(entity);
651 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700652 if (!cursor.isNull(ContactQuery.DATA_ID)) {
653 ContentValues data = loadData(cursor);
654 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
655
656 if (!cursor.isNull(ContactQuery.PRESENCE)
657 || !cursor.isNull(ContactQuery.STATUS)) {
658 final DataStatus status = new DataStatus(cursor);
659 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
660 statuses.put(dataId, status);
661 }
662 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700663 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700664
665 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700666 } finally {
667 cursor.close();
668 }
669 }
670
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700671 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800672 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
673 * not found, returns null
674 */
675 private void loadPhotoBinaryData(Result contactData) {
676 final long photoId = contactData.getPhotoId();
677 if (photoId <= 0) {
678 // No photo ID
679 return;
680 }
681
682 for (Entity entity : contactData.getEntities()) {
683 for (NamedContentValues subValue : entity.getSubValues()) {
684 final ContentValues entryValues = subValue.values;
685 final long dataId = entryValues.getAsLong(Data._ID);
686 if (dataId == photoId) {
687 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
688 // Correct Data Id but incorrect MimeType? Don't load
689 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
690 return;
691 }
692 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
693 break;
694 }
695 }
696 }
697 }
698
699 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700700 * Extracts Contact level columns from the cursor.
701 */
702 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700703 final String directoryParameter =
704 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
705 final long directoryId = directoryParameter == null
706 ? Directory.DEFAULT
707 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700708 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
709 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
710 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
711 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
712 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700713 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700714 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
715 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700716 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700717 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
718 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
719 ? null
720 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
721 final String status = cursor.getString(ContactQuery.CONTACT_STATUS);
722 final Long statusTimestamp = cursor.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
723 ? null
724 : cursor.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
725 final Integer statusLabel = cursor.isNull(ContactQuery.CONTACT_STATUS_LABEL)
726 ? null
727 : cursor.getInt(ContactQuery.CONTACT_STATUS_LABEL);
728 final String statusResPackage = cursor.getString(
729 ContactQuery.CONTACT_STATUS_RES_PACKAGE);
730
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700731 Uri lookupUri;
732 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
733 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700734 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700735 } else {
736 lookupUri = contactUri;
737 }
738
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700739 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700740 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700741 altDisplayName, phoneticName, starred, presence, status, statusTimestamp,
742 statusLabel, statusResPackage);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700743 }
744
745 /**
746 * Extracts RawContact level columns from the cursor.
747 */
748 private ContentValues loadRawContact(Cursor cursor) {
749 ContentValues cv = new ContentValues();
750
751 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
752
753 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
754 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
755 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
756 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
757 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
758 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
759 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
760 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
761 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
762 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
763 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
764 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700765 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
766
767 return cv;
768 }
769
770 /**
771 * Extracts Data level columns from the cursor.
772 */
773 private ContentValues loadData(Cursor cursor) {
774 ContentValues cv = new ContentValues();
775
776 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
777
778 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
779 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
780 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
781 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
782 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
783 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
784 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
785 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
786 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
787 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
788 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
789 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
790 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
791 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
792 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
793 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
794 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
795 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
796 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
797 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
798 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
799 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
800 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
801 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
802 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700803 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700804
805 return cv;
806 }
807
808 private void cursorColumnToContentValues(
809 Cursor cursor, ContentValues values, int index) {
810 switch (cursor.getType(index)) {
811 case Cursor.FIELD_TYPE_NULL:
812 // don't put anything in the content values
813 break;
814 case Cursor.FIELD_TYPE_INTEGER:
815 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
816 break;
817 case Cursor.FIELD_TYPE_STRING:
818 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
819 break;
820 case Cursor.FIELD_TYPE_BLOB:
821 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
822 break;
823 default:
824 throw new IllegalStateException("Invalid or unhandled data type");
825 }
826 }
827
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700828 private void loadDirectoryMetaData(Result result) {
829 long directoryId = result.getDirectoryId();
830
831 Cursor cursor = getContext().getContentResolver().query(
832 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
833 DirectoryQuery.COLUMNS, null, null, null);
834 if (cursor == null) {
835 return;
836 }
837 try {
838 if (cursor.moveToFirst()) {
839 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
840 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
841 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700842 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700843 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
844 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
845 String directoryType = null;
846 if (!TextUtils.isEmpty(packageName)) {
847 PackageManager pm = getContext().getPackageManager();
848 try {
849 Resources resources = pm.getResourcesForApplication(packageName);
850 directoryType = resources.getString(typeResourceId);
851 } catch (NameNotFoundException e) {
852 Log.w(TAG, "Contact directory resource not found: "
853 + packageName + "." + typeResourceId);
854 }
855 }
856
857 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700858 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700859 }
860 } finally {
861 cursor.close();
862 }
863 }
864
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700865 /**
866 * Loads groups meta-data for all groups associated with all constituent raw contacts'
867 * accounts.
868 */
869 private void loadGroupMetaData(Result result) {
870 StringBuilder selection = new StringBuilder();
871 ArrayList<String> selectionArgs = new ArrayList<String>();
872 for (Entity entity : result.mEntities) {
873 ContentValues values = entity.getEntityValues();
874 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
875 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
876 if (accountName != null && accountType != null) {
877 if (selection.length() != 0) {
878 selection.append(" OR ");
879 }
880 selection.append(
881 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?)");
882 selectionArgs.add(accountName);
883 selectionArgs.add(accountType);
884 }
885 }
886 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
887 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
888 null);
889 try {
890 while (cursor.moveToNext()) {
891 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
892 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
893 final long groupId = cursor.getLong(GroupQuery.ID);
894 final String title = cursor.getString(GroupQuery.TITLE);
895 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
896 ? false
897 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
898 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
899 ? false
900 : cursor.getInt(GroupQuery.FAVORITES) != 0;
901
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700902 result.addGroupMetaData(new GroupMetaData(
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700903 accountName, accountType, groupId, title, defaultGroup, favorites));
904 }
905 } finally {
906 cursor.close();
907 }
908 }
909
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700910 @Override
911 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800912 unregisterObserver();
913
Daniel Lehmann1316b132010-04-13 15:08:53 -0700914 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800915 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700916 return;
917 }
918
919 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700920
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800921 if (result != Result.ERROR && result != Result.NOT_FOUND) {
922 mLookupUri = result.getLookupUri();
923
924 if (!result.isDirectoryEntry()) {
925 Log.i(TAG, "Registering content observer for " + mLookupUri);
926 if (mObserver == null) {
927 mObserver = new ForceLoadContentObserver();
928 }
929 getContext().getContentResolver().registerContentObserver(
930 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700931 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800932
933 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
934 mContact.setLoadingPhoto(true);
935 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
936 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700937 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800938
939 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700940 }
941 }
942
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800943 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
944
945 private static final int BUFFER_SIZE = 1024*16;
946
947 @Override
948 protected byte[] doInBackground(String... params) {
949 Uri uri = Uri.parse(params[0]);
950 byte[] data = null;
951 try {
952 InputStream is = getContext().getContentResolver().openInputStream(uri);
953 if (is != null) {
954 ByteArrayOutputStream baos = new ByteArrayOutputStream();
955 try {
956 byte[] mBuffer = new byte[BUFFER_SIZE];
957
958 int size;
959 while ((size = is.read(mBuffer)) != -1) {
960 baos.write(mBuffer, 0, size);
961 }
962 data = baos.toByteArray();
963 } finally {
964 is.close();
965 }
966 } else {
967 Log.v(TAG, "Cannot load photo " + uri);
968 }
969 } catch (IOException e) {
970 Log.e(TAG, "Cannot load photo " + uri, e);
971 }
972
973 return data;
974 }
975
976 @Override
977 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800978 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800979 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800980 mContact.setPhotoBinaryData(data);
981 mContact.setLoadingPhoto(false);
982 deliverResult(mContact);
983 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800984 }
985 }
986
Daniel Lehmann3a120772010-06-21 16:21:35 -0700987 private void unregisterObserver() {
988 if (mObserver != null) {
989 getContext().getContentResolver().unregisterContentObserver(mObserver);
990 mObserver = null;
991 }
992 }
993
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700994 public ContactLoader(Context context, Uri lookupUri) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700995 this(context, lookupUri, false);
996 }
997
998 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -0700999 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001000 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001001 mLoadGroupMetaData = loadGroupMetaData;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001002 }
1003
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001004 public Uri getLookupUri() {
1005 return mLookupUri;
1006 }
1007
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001008 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001009 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001010 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001011 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001012 }
1013
1014 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001015 forceLoad();
1016 }
1017 }
1018
1019 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001020 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001021 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001022 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001023 }
1024
1025 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001026 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001027 unregisterObserver();
1028 mContact = null;
1029 mDestroyed = true;
1030 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001031}