blob: fa0ffb2a6f1680f83464e54e3d834c6fe3933443 [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;
Dave Santoro39156002011-07-19 01:18:14 -070020import com.android.contacts.util.StreamItemEntry;
21import com.android.contacts.util.StreamItemPhotoEntry;
Flavio Lerda37a26842011-06-27 11:36:52 +010022import com.google.common.annotations.VisibleForTesting;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070023
24import android.content.ContentResolver;
25import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070026import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070027import android.content.Context;
28import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070029import android.content.Entity.NamedContentValues;
Jeff Hamilton3c462912010-05-15 02:20:01 -050030import android.content.Loader;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070031import android.content.pm.PackageManager;
32import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070033import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070034import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070035import android.database.Cursor;
36import android.net.Uri;
37import android.os.AsyncTask;
Daniel Lehmann1316b132010-04-13 15:08:53 -070038import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070039import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080040import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070041import android.provider.ContactsContract.Contacts;
42import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070043import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070044import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070045import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070046import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070047import android.provider.ContactsContract.StreamItems;
48import android.provider.ContactsContract.StreamItemPhotos;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070049import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070050import android.util.Log;
51
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080052import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070053import java.io.FileInputStream;
54import java.io.FileNotFoundException;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080055import java.io.IOException;
56import java.io.InputStream;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070057import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070058import java.util.Arrays;
59import java.util.Collections;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070060import java.util.HashMap;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070061import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070062import java.util.Map;
63import java.util.Set;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070064
65/**
66 * Loads a single Contact and all it constituent RawContacts.
67 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070068public class ContactLoader extends Loader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070069 private static final String TAG = "ContactLoader";
70
Daniel Lehmann4cd94412010-04-08 16:44:36 -070071 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070072 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070073 private boolean mLoadStreamItems;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070074 private Result mContact;
75 private ForceLoadContentObserver mObserver;
76 private boolean mDestroyed;
77
Dmitri Plotnikove843f912010-09-16 15:21:48 -070078
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070079 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070080 public void onContactLoaded(Result contact);
81 }
82
83 /**
84 * The result of a load operation. Contains all data necessary to display the contact.
85 */
86 public static final class Result {
87 /**
88 * Singleton instance that represents "No Contact Found"
89 */
90 public static final Result NOT_FOUND = new Result();
91
Daniel Lehmann18f104f2010-05-07 15:41:11 -070092 /**
93 * Singleton instance that represents an error, e.g. because of an invalid Uri
94 * TODO: We should come up with something nicer here. Maybe use an Either type so
95 * that we can capture the Exception?
96 */
97 public static final Result ERROR = new Result();
98
Daniel Lehmann4cd94412010-04-08 16:44:36 -070099 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700100 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700101 private final long mDirectoryId;
102 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700103 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700104 private final long mNameRawContactId;
105 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700106 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700107 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700108 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700109 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700110 private final String mPhoneticName;
111 private final boolean mStarred;
112 private final Integer mPresence;
113 private final ArrayList<Entity> mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700114 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700115 private final HashMap<Long, DataStatus> mStatuses;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700116
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700117 private String mDirectoryDisplayName;
118 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700119 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700120 private String mDirectoryAccountName;
121 private int mDirectoryExportSupport;
122
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700123 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700124
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800125 private boolean mLoadingPhoto;
126 private byte[] mPhotoBinaryData;
127
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700128 /**
129 * Constructor for case "no contact found". This must only be used for the
130 * final {@link Result#NOT_FOUND} singleton
131 */
132 private Result() {
133 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700134 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700135 mDirectoryId = -1;
136 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700137 mId = -1;
138 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700139 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700140 mStatuses = null;
141 mNameRawContactId = -1;
142 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700143 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700144 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700145 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700146 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700147 mPhoneticName = null;
148 mStarred = false;
149 mPresence = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700150 }
151
152 /**
153 * Constructor to call when contact was found
154 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700155 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700156 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700157 String displayName, String altDisplayName, String phoneticName, boolean starred,
Dave Santoro39156002011-07-19 01:18:14 -0700158 Integer presence) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700159 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700160 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700161 mDirectoryId = directoryId;
162 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700163 mId = id;
164 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700165 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700166 mStatuses = new HashMap<Long, DataStatus>();
167 mNameRawContactId = nameRawContactId;
168 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700169 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700170 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700171 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700172 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700173 mPhoneticName = phoneticName;
174 mStarred = starred;
175 mPresence = presence;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700176 }
177
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800178 private Result(Result from) {
179 mLookupUri = from.mLookupUri;
180 mUri = from.mUri;
181 mDirectoryId = from.mDirectoryId;
182 mLookupKey = from.mLookupKey;
183 mId = from.mId;
184 mNameRawContactId = from.mNameRawContactId;
185 mDisplayNameSource = from.mDisplayNameSource;
186 mPhotoId = from.mPhotoId;
187 mPhotoUri = from.mPhotoUri;
188 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700189 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800190 mPhoneticName = from.mPhoneticName;
191 mStarred = from.mStarred;
192 mPresence = from.mPresence;
193 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700194 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800195 mStatuses = from.mStatuses;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800196
197 mDirectoryDisplayName = from.mDirectoryDisplayName;
198 mDirectoryType = from.mDirectoryType;
199 mDirectoryAccountType = from.mDirectoryAccountType;
200 mDirectoryAccountName = from.mDirectoryAccountName;
201 mDirectoryExportSupport = from.mDirectoryExportSupport;
202
203 mGroups = from.mGroups;
204
205 mLoadingPhoto = from.mLoadingPhoto;
206 mPhotoBinaryData = from.mPhotoBinaryData;
207 }
208
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700209 /**
210 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
211 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700212 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700213 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700214 mDirectoryDisplayName = displayName;
215 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700216 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700217 mDirectoryAccountName = accountName;
218 mDirectoryExportSupport = exportSupport;
219 }
220
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800221 private void setLoadingPhoto(boolean flag) {
222 mLoadingPhoto = flag;
223 }
224
225 private void setPhotoBinaryData(byte[] photoBinaryData) {
226 mPhotoBinaryData = photoBinaryData;
227 }
228
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700229 public Uri getLookupUri() {
230 return mLookupUri;
231 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800232
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700233 public String getLookupKey() {
234 return mLookupKey;
235 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800236
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700237 public Uri getUri() {
238 return mUri;
239 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800240
Flavio Lerda37a26842011-06-27 11:36:52 +0100241 @VisibleForTesting
242 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700243 return mId;
244 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800245
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700246 public long getNameRawContactId() {
247 return mNameRawContactId;
248 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800249
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700250 public int getDisplayNameSource() {
251 return mDisplayNameSource;
252 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800253
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700254 public long getPhotoId() {
255 return mPhotoId;
256 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800257
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700258 public String getPhotoUri() {
259 return mPhotoUri;
260 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800261
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700262 public String getDisplayName() {
263 return mDisplayName;
264 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800265
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700266 public String getAltDisplayName() {
267 return mAltDisplayName;
268 }
269
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700270 public String getPhoneticName() {
271 return mPhoneticName;
272 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800273
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700274 public boolean getStarred() {
275 return mStarred;
276 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800277
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700278 public Integer getPresence() {
279 return mPresence;
280 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800281
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700282 public ArrayList<Entity> getEntities() {
283 return mEntities;
284 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800285
Dave Santoro39156002011-07-19 01:18:14 -0700286 public ArrayList<StreamItemEntry> getStreamItems() {
287 return mStreamItems;
288 }
289
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700290 public HashMap<Long, DataStatus> getStatuses() {
291 return mStatuses;
292 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700293
294 public long getDirectoryId() {
295 return mDirectoryId;
296 }
297
298 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700299 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
300 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700301 }
302
303 public int getDirectoryExportSupport() {
304 return mDirectoryExportSupport;
305 }
306
307 public String getDirectoryDisplayName() {
308 return mDirectoryDisplayName;
309 }
310
311 public String getDirectoryType() {
312 return mDirectoryType;
313 }
314
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700315 public String getDirectoryAccountType() {
316 return mDirectoryAccountType;
317 }
318
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700319 public String getDirectoryAccountName() {
320 return mDirectoryAccountName;
321 }
322
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800323 public boolean isLoadingPhoto() {
324 return mLoadingPhoto;
325 }
326
327 public byte[] getPhotoBinaryData() {
328 return mPhotoBinaryData;
329 }
330
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700331 public ArrayList<ContentValues> getContentValues() {
332 if (mEntities.size() != 1) {
333 throw new IllegalStateException(
334 "Cannot extract content values from an aggregated contact");
335 }
336
337 Entity entity = mEntities.get(0);
338 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
339 ArrayList<NamedContentValues> subValues = entity.getSubValues();
340 if (subValues != null) {
341 int size = subValues.size();
342 for (int i = 0; i < size; i++) {
343 NamedContentValues pair = subValues.get(i);
344 if (Data.CONTENT_URI.equals(pair.uri)) {
345 result.add(pair.values);
346 }
347 }
348 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800349
350 // If the photo was loaded using the URI, create an entry for the photo
351 // binary data.
352 if (mPhotoId == 0 && mPhotoBinaryData != null) {
353 ContentValues photo = new ContentValues();
354 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
355 photo.put(Photo.PHOTO, mPhotoBinaryData);
356 result.add(photo);
357 }
358
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700359 return result;
360 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700361
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700362 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700363 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700364 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700365 }
366 mGroups.add(group);
367 }
368
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700369 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700370 return mGroups;
371 }
372 }
373
Dave Santoro39156002011-07-19 01:18:14 -0700374 /**
375 * Projection used for the query that loads all data for the entire contact (except for
376 * social stream items).
377 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700378 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700379 final static String[] COLUMNS = new String[] {
380 Contacts.NAME_RAW_CONTACT_ID,
381 Contacts.DISPLAY_NAME_SOURCE,
382 Contacts.LOOKUP_KEY,
383 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700384 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700385 Contacts.PHONETIC_NAME,
386 Contacts.PHOTO_ID,
387 Contacts.STARRED,
388 Contacts.CONTACT_PRESENCE,
389 Contacts.CONTACT_STATUS,
390 Contacts.CONTACT_STATUS_TIMESTAMP,
391 Contacts.CONTACT_STATUS_RES_PACKAGE,
392 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700393 Contacts.Entity.CONTACT_ID,
394 Contacts.Entity.RAW_CONTACT_ID,
395
396 RawContacts.ACCOUNT_NAME,
397 RawContacts.ACCOUNT_TYPE,
398 RawContacts.DIRTY,
399 RawContacts.VERSION,
400 RawContacts.SOURCE_ID,
401 RawContacts.SYNC1,
402 RawContacts.SYNC2,
403 RawContacts.SYNC3,
404 RawContacts.SYNC4,
405 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700406 RawContacts.NAME_VERIFIED,
407
408 Contacts.Entity.DATA_ID,
409 Data.DATA1,
410 Data.DATA2,
411 Data.DATA3,
412 Data.DATA4,
413 Data.DATA5,
414 Data.DATA6,
415 Data.DATA7,
416 Data.DATA8,
417 Data.DATA9,
418 Data.DATA10,
419 Data.DATA11,
420 Data.DATA12,
421 Data.DATA13,
422 Data.DATA14,
423 Data.DATA15,
424 Data.SYNC1,
425 Data.SYNC2,
426 Data.SYNC3,
427 Data.SYNC4,
428 Data.DATA_VERSION,
429 Data.IS_PRIMARY,
430 Data.IS_SUPER_PRIMARY,
431 Data.MIMETYPE,
432 Data.RES_PACKAGE,
433
434 GroupMembership.GROUP_SOURCE_ID,
435
436 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700437 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700438 Data.STATUS,
439 Data.STATUS_RES_PACKAGE,
440 Data.STATUS_ICON,
441 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700442 Data.STATUS_TIMESTAMP,
443
444 Contacts.PHOTO_URI,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700445 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700446
447 public final static int NAME_RAW_CONTACT_ID = 0;
448 public final static int DISPLAY_NAME_SOURCE = 1;
449 public final static int LOOKUP_KEY = 2;
450 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700451 public final static int ALT_DISPLAY_NAME = 4;
452 public final static int PHONETIC_NAME = 5;
453 public final static int PHOTO_ID = 6;
454 public final static int STARRED = 7;
455 public final static int CONTACT_PRESENCE = 8;
456 public final static int CONTACT_STATUS = 9;
457 public final static int CONTACT_STATUS_TIMESTAMP = 10;
458 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
459 public final static int CONTACT_STATUS_LABEL = 12;
460 public final static int CONTACT_ID = 13;
461 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700462
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700463 public final static int ACCOUNT_NAME = 15;
464 public final static int ACCOUNT_TYPE = 16;
465 public final static int DIRTY = 17;
466 public final static int VERSION = 18;
467 public final static int SOURCE_ID = 19;
468 public final static int SYNC1 = 20;
469 public final static int SYNC2 = 21;
470 public final static int SYNC3 = 22;
471 public final static int SYNC4 = 23;
472 public final static int DELETED = 24;
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700473 public final static int NAME_VERIFIED = 25;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700474
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700475 public final static int DATA_ID = 26;
476 public final static int DATA1 = 27;
477 public final static int DATA2 = 28;
478 public final static int DATA3 = 29;
479 public final static int DATA4 = 30;
480 public final static int DATA5 = 31;
481 public final static int DATA6 = 32;
482 public final static int DATA7 = 33;
483 public final static int DATA8 = 34;
484 public final static int DATA9 = 35;
485 public final static int DATA10 = 36;
486 public final static int DATA11 = 37;
487 public final static int DATA12 = 38;
488 public final static int DATA13 = 39;
489 public final static int DATA14 = 40;
490 public final static int DATA15 = 41;
491 public final static int DATA_SYNC1 = 42;
492 public final static int DATA_SYNC2 = 43;
493 public final static int DATA_SYNC3 = 44;
494 public final static int DATA_SYNC4 = 45;
495 public final static int DATA_VERSION = 46;
496 public final static int IS_PRIMARY = 47;
497 public final static int IS_SUPERPRIMARY = 48;
498 public final static int MIMETYPE = 49;
499 public final static int RES_PACKAGE = 50;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700500
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700501 public final static int GROUP_SOURCE_ID = 51;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700502
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700503 public final static int PRESENCE = 52;
504 public final static int CHAT_CAPABILITY = 53;
505 public final static int STATUS = 54;
506 public final static int STATUS_RES_PACKAGE = 55;
507 public final static int STATUS_ICON = 56;
508 public final static int STATUS_LABEL = 57;
509 public final static int STATUS_TIMESTAMP = 58;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700510
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700511 public final static int PHOTO_URI = 59;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700512 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700513
Dave Santoro39156002011-07-19 01:18:14 -0700514 /**
515 * Projection used for the query that loads all data for the entire contact.
516 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700517 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700518 final static String[] COLUMNS = new String[] {
519 Directory.DISPLAY_NAME,
520 Directory.PACKAGE_NAME,
521 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700522 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700523 Directory.ACCOUNT_NAME,
524 Directory.EXPORT_SUPPORT,
525 };
526
527 public final static int DISPLAY_NAME = 0;
528 public final static int PACKAGE_NAME = 1;
529 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700530 public final static int ACCOUNT_TYPE = 3;
531 public final static int ACCOUNT_NAME = 4;
532 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700533 }
534
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700535 private static class GroupQuery {
536 final static String[] COLUMNS = new String[] {
537 Groups.ACCOUNT_NAME,
538 Groups.ACCOUNT_TYPE,
539 Groups._ID,
540 Groups.TITLE,
541 Groups.AUTO_ADD,
542 Groups.FAVORITES,
543 };
544
545 public final static int ACCOUNT_NAME = 0;
546 public final static int ACCOUNT_TYPE = 1;
547 public final static int ID = 2;
548 public final static int TITLE = 3;
549 public final static int AUTO_ADD = 4;
550 public final static int FAVORITES = 5;
551 }
552
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700553 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700554
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700555 @Override
556 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700557 try {
558 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700559 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700560 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700561 if (result != Result.NOT_FOUND) {
562 if (result.isDirectoryEntry()) {
563 loadDirectoryMetaData(result);
564 } else if (mLoadGroupMetaData) {
565 loadGroupMetaData(result);
566 }
Dave Santoro39156002011-07-19 01:18:14 -0700567 if (mLoadStreamItems) {
568 loadStreamItems(result);
569 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800570 loadPhotoBinaryData(result);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700571 }
572 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700573 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700574 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700575 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700576 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700577 }
578
579 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700580 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
581 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700582 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700583 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700584 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700585 if (uri == null) throw new IllegalArgumentException("uri must not be null");
586
587 final String authority = uri.getAuthority();
588
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700589 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700590 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700591 final String type = resolver.getType(uri);
592 // Contact-Uri? Good, return it
593 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
594 return uri;
595 }
596
597 // RawContact-Uri? Transform it to ContactUri
598 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
599 final long rawContactId = ContentUris.parseId(uri);
600 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
601 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
602 }
603
604 // Anything else? We don't know what this is
605 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700606 }
607
608 // Legacy Style? Convert to RawContact
609 final String OBSOLETE_AUTHORITY = "contacts";
610 if (OBSOLETE_AUTHORITY.equals(authority)) {
611 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
612 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700613 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700614 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
615 }
616
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700617 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700618 }
619
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700620 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
621 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
622 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
623 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700624 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700625 Log.e(TAG, "No cursor returned in loadContactEntity");
626 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700627 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700628
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700629 try {
630 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700631 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700632 return Result.NOT_FOUND;
633 }
634
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700635 long currentRawContactId = -1;
636 Entity entity = null;
637 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700638 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700639 HashMap<Long, DataStatus> statuses = result.getStatuses();
640 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
641 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
642 if (rawContactId != currentRawContactId) {
643 currentRawContactId = rawContactId;
644 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700645 entities.add(entity);
646 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700647 if (!cursor.isNull(ContactQuery.DATA_ID)) {
648 ContentValues data = loadData(cursor);
649 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
650
651 if (!cursor.isNull(ContactQuery.PRESENCE)
652 || !cursor.isNull(ContactQuery.STATUS)) {
653 final DataStatus status = new DataStatus(cursor);
654 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
655 statuses.put(dataId, status);
656 }
657 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700658 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700659
660 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700661 } finally {
662 cursor.close();
663 }
664 }
665
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700666 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800667 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
668 * not found, returns null
669 */
670 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700671
672 // If we have a photo URI, try loading that first.
673 String photoUri = contactData.getPhotoUri();
674 if (photoUri != null) {
675 try {
676 AssetFileDescriptor fd = getContext().getContentResolver()
677 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
678 byte[] buffer = new byte[16 * 1024];
679 FileInputStream fis = fd.createInputStream();
680 ByteArrayOutputStream baos = new ByteArrayOutputStream();
681 try {
682 int size;
683 while ((size = fis.read(buffer)) != -1) {
684 baos.write(buffer, 0, size);
685 }
686 contactData.setPhotoBinaryData(baos.toByteArray());
687 } finally {
688 fis.close();
689 fd.close();
690 }
691 return;
692 } catch (IOException ioe) {
693 // Just fall back to the case below.
694 }
695 }
696
697 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800698 final long photoId = contactData.getPhotoId();
699 if (photoId <= 0) {
700 // No photo ID
701 return;
702 }
703
704 for (Entity entity : contactData.getEntities()) {
705 for (NamedContentValues subValue : entity.getSubValues()) {
706 final ContentValues entryValues = subValue.values;
707 final long dataId = entryValues.getAsLong(Data._ID);
708 if (dataId == photoId) {
709 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
710 // Correct Data Id but incorrect MimeType? Don't load
711 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
712 return;
713 }
714 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
715 break;
716 }
717 }
718 }
719 }
720
721 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700722 * Extracts Contact level columns from the cursor.
723 */
724 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700725 final String directoryParameter =
726 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
727 final long directoryId = directoryParameter == null
728 ? Directory.DEFAULT
729 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700730 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
731 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
732 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
733 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
734 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700735 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700736 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
737 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700738 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700739 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
740 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
741 ? null
742 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700743
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700744 Uri lookupUri;
745 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
746 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700747 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700748 } else {
749 lookupUri = contactUri;
750 }
751
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700752 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700753 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Dave Santoro39156002011-07-19 01:18:14 -0700754 altDisplayName, phoneticName, starred, presence);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700755 }
756
757 /**
758 * Extracts RawContact level columns from the cursor.
759 */
760 private ContentValues loadRawContact(Cursor cursor) {
761 ContentValues cv = new ContentValues();
762
763 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
764
765 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
766 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
767 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
768 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
769 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
770 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
771 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
772 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
773 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
774 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
775 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
776 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700777 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
778
779 return cv;
780 }
781
782 /**
783 * Extracts Data level columns from the cursor.
784 */
785 private ContentValues loadData(Cursor cursor) {
786 ContentValues cv = new ContentValues();
787
788 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
789
790 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
791 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
792 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
793 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
794 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
795 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
796 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
797 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
798 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
799 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
800 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
801 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
802 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
803 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
804 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
805 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
806 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
807 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
808 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
809 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
810 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
811 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
812 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
813 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
814 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700815 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700816
817 return cv;
818 }
819
820 private void cursorColumnToContentValues(
821 Cursor cursor, ContentValues values, int index) {
822 switch (cursor.getType(index)) {
823 case Cursor.FIELD_TYPE_NULL:
824 // don't put anything in the content values
825 break;
826 case Cursor.FIELD_TYPE_INTEGER:
827 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
828 break;
829 case Cursor.FIELD_TYPE_STRING:
830 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
831 break;
832 case Cursor.FIELD_TYPE_BLOB:
833 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
834 break;
835 default:
836 throw new IllegalStateException("Invalid or unhandled data type");
837 }
838 }
839
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700840 private void loadDirectoryMetaData(Result result) {
841 long directoryId = result.getDirectoryId();
842
843 Cursor cursor = getContext().getContentResolver().query(
844 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
845 DirectoryQuery.COLUMNS, null, null, null);
846 if (cursor == null) {
847 return;
848 }
849 try {
850 if (cursor.moveToFirst()) {
851 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
852 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
853 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700854 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700855 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
856 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
857 String directoryType = null;
858 if (!TextUtils.isEmpty(packageName)) {
859 PackageManager pm = getContext().getPackageManager();
860 try {
861 Resources resources = pm.getResourcesForApplication(packageName);
862 directoryType = resources.getString(typeResourceId);
863 } catch (NameNotFoundException e) {
864 Log.w(TAG, "Contact directory resource not found: "
865 + packageName + "." + typeResourceId);
866 }
867 }
868
869 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700870 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700871 }
872 } finally {
873 cursor.close();
874 }
875 }
876
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700877 /**
878 * Loads groups meta-data for all groups associated with all constituent raw contacts'
879 * accounts.
880 */
881 private void loadGroupMetaData(Result result) {
882 StringBuilder selection = new StringBuilder();
883 ArrayList<String> selectionArgs = new ArrayList<String>();
884 for (Entity entity : result.mEntities) {
885 ContentValues values = entity.getEntityValues();
886 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
887 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
888 if (accountName != null && accountType != null) {
889 if (selection.length() != 0) {
890 selection.append(" OR ");
891 }
892 selection.append(
893 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?)");
894 selectionArgs.add(accountName);
895 selectionArgs.add(accountType);
896 }
897 }
898 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
899 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
900 null);
901 try {
902 while (cursor.moveToNext()) {
903 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
904 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
905 final long groupId = cursor.getLong(GroupQuery.ID);
906 final String title = cursor.getString(GroupQuery.TITLE);
907 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
908 ? false
909 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
910 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
911 ? false
912 : cursor.getInt(GroupQuery.FAVORITES) != 0;
913
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700914 result.addGroupMetaData(new GroupMetaData(
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700915 accountName, accountType, groupId, title, defaultGroup, favorites));
916 }
917 } finally {
918 cursor.close();
919 }
920 }
921
Dave Santoro39156002011-07-19 01:18:14 -0700922 /**
923 * Loads all stream items and stream item photos belonging to this contact.
924 */
925 private void loadStreamItems(Result result) {
926 Cursor cursor = getContext().getContentResolver().query(
927 Contacts.CONTENT_LOOKUP_URI.buildUpon()
928 .appendPath(result.getLookupKey())
929 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
930 null, null, null, null);
931 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
932 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
933 try {
934 while (cursor.moveToNext()) {
935 StreamItemEntry streamItem = new StreamItemEntry(cursor);
936 streamItemsById.put(streamItem.getId(), streamItem);
937 streamItems.add(streamItem);
938 }
939 } finally {
940 cursor.close();
941 }
942
943 // Now retrieve any photo records associated with the stream items.
944 String[] streamItemIdArr = new String[streamItems.size()];
945 StringBuilder streamItemPhotoSelection = new StringBuilder();
946 if (!streamItems.isEmpty()) {
947 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
948 for (int i = 0; i < streamItems.size(); i++) {
949 if (i > 0) {
950 streamItemPhotoSelection.append(",");
951 }
952 streamItemPhotoSelection.append("?");
953 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
954 }
955 streamItemPhotoSelection.append(")");
956 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
957 null, streamItemPhotoSelection.toString(), streamItemIdArr,
958 StreamItemPhotos.STREAM_ITEM_ID);
959 try {
960 while (cursor.moveToNext()) {
961 long streamItemId = cursor.getLong(
962 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
963 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
964 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
965 }
966 } finally {
967 cursor.close();
968 }
969 }
970
971 // Set the sorted stream items on the result.
972 Collections.sort(streamItems);
973 result.mStreamItems.addAll(streamItems);
974 }
975
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700976 @Override
977 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800978 unregisterObserver();
979
Daniel Lehmann1316b132010-04-13 15:08:53 -0700980 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800981 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700982 return;
983 }
984
985 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700986
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -0800987 if (result != Result.ERROR && result != Result.NOT_FOUND) {
988 mLookupUri = result.getLookupUri();
989
990 if (!result.isDirectoryEntry()) {
991 Log.i(TAG, "Registering content observer for " + mLookupUri);
992 if (mObserver == null) {
993 mObserver = new ForceLoadContentObserver();
994 }
995 getContext().getContentResolver().registerContentObserver(
996 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700997 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800998
999 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1000 mContact.setLoadingPhoto(true);
1001 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1002 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001003 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001004
1005 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001006 }
1007 }
1008
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001009 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1010
1011 private static final int BUFFER_SIZE = 1024*16;
1012
1013 @Override
1014 protected byte[] doInBackground(String... params) {
1015 Uri uri = Uri.parse(params[0]);
1016 byte[] data = null;
1017 try {
1018 InputStream is = getContext().getContentResolver().openInputStream(uri);
1019 if (is != null) {
1020 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1021 try {
1022 byte[] mBuffer = new byte[BUFFER_SIZE];
1023
1024 int size;
1025 while ((size = is.read(mBuffer)) != -1) {
1026 baos.write(mBuffer, 0, size);
1027 }
1028 data = baos.toByteArray();
1029 } finally {
1030 is.close();
1031 }
1032 } else {
1033 Log.v(TAG, "Cannot load photo " + uri);
1034 }
1035 } catch (IOException e) {
1036 Log.e(TAG, "Cannot load photo " + uri, e);
1037 }
1038
1039 return data;
1040 }
1041
1042 @Override
1043 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001044 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001045 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001046 mContact.setPhotoBinaryData(data);
1047 mContact.setLoadingPhoto(false);
1048 deliverResult(mContact);
1049 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001050 }
1051 }
1052
Daniel Lehmann3a120772010-06-21 16:21:35 -07001053 private void unregisterObserver() {
1054 if (mObserver != null) {
1055 getContext().getContentResolver().unregisterContentObserver(mObserver);
1056 mObserver = null;
1057 }
1058 }
1059
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001060 public ContactLoader(Context context, Uri lookupUri) {
Dave Santoro39156002011-07-19 01:18:14 -07001061 this(context, lookupUri, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001062 }
1063
Dave Santoro39156002011-07-19 01:18:14 -07001064 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
1065 boolean loadStreamItems) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001066 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001067 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001068 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001069 mLoadStreamItems = loadStreamItems;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001070 }
1071
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001072 public Uri getLookupUri() {
1073 return mLookupUri;
1074 }
1075
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001076 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001077 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001078 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001079 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001080 }
1081
1082 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001083 forceLoad();
1084 }
1085 }
1086
1087 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001088 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001089 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001090 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001091 }
1092
1093 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001094 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001095 unregisterObserver();
1096 mContact = null;
1097 mDestroyed = true;
1098 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001099}