blob: 3463d3c356f8cb46e5198fc123dd4ab4fa0c96dc [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
Makoto Onuki69b4a882011-07-22 10:05:10 -070019import com.android.contacts.model.AccountType;
20import com.android.contacts.model.AccountTypeManager;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070021import com.android.contacts.util.DataStatus;
Dave Santoro39156002011-07-19 01:18:14 -070022import com.android.contacts.util.StreamItemEntry;
23import com.android.contacts.util.StreamItemPhotoEntry;
Makoto Onuki69b4a882011-07-22 10:05:10 -070024import com.google.android.collect.Lists;
Flavio Lerda37a26842011-06-27 11:36:52 +010025import com.google.common.annotations.VisibleForTesting;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070026
27import android.content.ContentResolver;
28import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070029import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070030import android.content.Context;
31import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070032import android.content.Entity.NamedContentValues;
Jeff Hamilton3c462912010-05-15 02:20:01 -050033import android.content.Loader;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070034import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070036import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070037import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070038import android.database.Cursor;
39import android.net.Uri;
40import android.os.AsyncTask;
Daniel Lehmann1316b132010-04-13 15:08:53 -070041import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070042import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080043import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070044import android.provider.ContactsContract.Contacts;
45import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070046import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070047import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070048import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070049import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070050import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070051import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070052import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070053import android.util.Log;
54
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080055import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070056import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080057import java.io.IOException;
58import java.io.InputStream;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070059import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070060import java.util.Collections;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070061import java.util.HashMap;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070062import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070063import java.util.Map;
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;
Makoto Onuki69b4a882011-07-22 10:05:10 -070074 private final boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070075 private Result mContact;
76 private ForceLoadContentObserver mObserver;
77 private boolean mDestroyed;
78
Dmitri Plotnikove843f912010-09-16 15:21:48 -070079
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070080 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070081 public void onContactLoaded(Result contact);
82 }
83
84 /**
85 * The result of a load operation. Contains all data necessary to display the contact.
86 */
87 public static final class Result {
88 /**
89 * Singleton instance that represents "No Contact Found"
90 */
91 public static final Result NOT_FOUND = new Result();
92
Daniel Lehmann18f104f2010-05-07 15:41:11 -070093 /**
94 * Singleton instance that represents an error, e.g. because of an invalid Uri
95 * TODO: We should come up with something nicer here. Maybe use an Either type so
96 * that we can capture the Exception?
97 */
98 public static final Result ERROR = new Result();
99
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700100 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700101 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700102 private final long mDirectoryId;
103 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700104 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700105 private final long mNameRawContactId;
106 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700107 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700108 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700109 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700110 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700111 private final String mPhoneticName;
112 private final boolean mStarred;
113 private final Integer mPresence;
114 private final ArrayList<Entity> mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700115 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700116 private final HashMap<Long, DataStatus> mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700117 private final ArrayList<String> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700118
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700119 private String mDirectoryDisplayName;
120 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700121 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700122 private String mDirectoryAccountName;
123 private int mDirectoryExportSupport;
124
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700125 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700126
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800127 private boolean mLoadingPhoto;
128 private byte[] mPhotoBinaryData;
129
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700130 /**
131 * Constructor for case "no contact found". This must only be used for the
132 * final {@link Result#NOT_FOUND} singleton
133 */
134 private Result() {
135 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700136 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700137 mDirectoryId = -1;
138 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700139 mId = -1;
140 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700141 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700142 mStatuses = null;
143 mNameRawContactId = -1;
144 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700145 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700146 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700147 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700148 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700149 mPhoneticName = null;
150 mStarred = false;
151 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700152 mInvitableAccountTypes = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700153 }
154
155 /**
156 * Constructor to call when contact was found
157 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700158 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700159 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700160 String displayName, String altDisplayName, String phoneticName, boolean starred,
Dave Santoro39156002011-07-19 01:18:14 -0700161 Integer presence) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700162 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700163 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700164 mDirectoryId = directoryId;
165 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700166 mId = id;
167 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700168 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700169 mStatuses = new HashMap<Long, DataStatus>();
170 mNameRawContactId = nameRawContactId;
171 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700172 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700173 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700174 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700175 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700176 mPhoneticName = phoneticName;
177 mStarred = starred;
178 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700179 mInvitableAccountTypes = Lists.newArrayList();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700180 }
181
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800182 private Result(Result from) {
183 mLookupUri = from.mLookupUri;
184 mUri = from.mUri;
185 mDirectoryId = from.mDirectoryId;
186 mLookupKey = from.mLookupKey;
187 mId = from.mId;
188 mNameRawContactId = from.mNameRawContactId;
189 mDisplayNameSource = from.mDisplayNameSource;
190 mPhotoId = from.mPhotoId;
191 mPhotoUri = from.mPhotoUri;
192 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700193 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800194 mPhoneticName = from.mPhoneticName;
195 mStarred = from.mStarred;
196 mPresence = from.mPresence;
197 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700198 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800199 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700200 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800201
202 mDirectoryDisplayName = from.mDirectoryDisplayName;
203 mDirectoryType = from.mDirectoryType;
204 mDirectoryAccountType = from.mDirectoryAccountType;
205 mDirectoryAccountName = from.mDirectoryAccountName;
206 mDirectoryExportSupport = from.mDirectoryExportSupport;
207
208 mGroups = from.mGroups;
209
210 mLoadingPhoto = from.mLoadingPhoto;
211 mPhotoBinaryData = from.mPhotoBinaryData;
212 }
213
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700214 /**
215 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
216 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700217 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700218 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700219 mDirectoryDisplayName = displayName;
220 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700221 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700222 mDirectoryAccountName = accountName;
223 mDirectoryExportSupport = exportSupport;
224 }
225
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800226 private void setLoadingPhoto(boolean flag) {
227 mLoadingPhoto = flag;
228 }
229
230 private void setPhotoBinaryData(byte[] photoBinaryData) {
231 mPhotoBinaryData = photoBinaryData;
232 }
233
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700234 public Uri getLookupUri() {
235 return mLookupUri;
236 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800237
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700238 public String getLookupKey() {
239 return mLookupKey;
240 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800241
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700242 public Uri getUri() {
243 return mUri;
244 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800245
Flavio Lerda37a26842011-06-27 11:36:52 +0100246 @VisibleForTesting
247 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700248 return mId;
249 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800250
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700251 public long getNameRawContactId() {
252 return mNameRawContactId;
253 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800254
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700255 public int getDisplayNameSource() {
256 return mDisplayNameSource;
257 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800258
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700259 public long getPhotoId() {
260 return mPhotoId;
261 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800262
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700263 public String getPhotoUri() {
264 return mPhotoUri;
265 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800266
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700267 public String getDisplayName() {
268 return mDisplayName;
269 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800270
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700271 public String getAltDisplayName() {
272 return mAltDisplayName;
273 }
274
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700275 public String getPhoneticName() {
276 return mPhoneticName;
277 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800278
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700279 public boolean getStarred() {
280 return mStarred;
281 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800282
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700283 public Integer getPresence() {
284 return mPresence;
285 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800286
Makoto Onuki69b4a882011-07-22 10:05:10 -0700287 public ArrayList<String> getInvitableAccontTypes() {
288 return mInvitableAccountTypes;
289 }
290
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700291 public ArrayList<Entity> getEntities() {
292 return mEntities;
293 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800294
Dave Santoro39156002011-07-19 01:18:14 -0700295 public ArrayList<StreamItemEntry> getStreamItems() {
296 return mStreamItems;
297 }
298
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700299 public HashMap<Long, DataStatus> getStatuses() {
300 return mStatuses;
301 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700302
303 public long getDirectoryId() {
304 return mDirectoryId;
305 }
306
307 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700308 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
309 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700310 }
311
312 public int getDirectoryExportSupport() {
313 return mDirectoryExportSupport;
314 }
315
316 public String getDirectoryDisplayName() {
317 return mDirectoryDisplayName;
318 }
319
320 public String getDirectoryType() {
321 return mDirectoryType;
322 }
323
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700324 public String getDirectoryAccountType() {
325 return mDirectoryAccountType;
326 }
327
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700328 public String getDirectoryAccountName() {
329 return mDirectoryAccountName;
330 }
331
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800332 public boolean isLoadingPhoto() {
333 return mLoadingPhoto;
334 }
335
336 public byte[] getPhotoBinaryData() {
337 return mPhotoBinaryData;
338 }
339
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700340 public ArrayList<ContentValues> getContentValues() {
341 if (mEntities.size() != 1) {
342 throw new IllegalStateException(
343 "Cannot extract content values from an aggregated contact");
344 }
345
346 Entity entity = mEntities.get(0);
347 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
348 ArrayList<NamedContentValues> subValues = entity.getSubValues();
349 if (subValues != null) {
350 int size = subValues.size();
351 for (int i = 0; i < size; i++) {
352 NamedContentValues pair = subValues.get(i);
353 if (Data.CONTENT_URI.equals(pair.uri)) {
354 result.add(pair.values);
355 }
356 }
357 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800358
359 // If the photo was loaded using the URI, create an entry for the photo
360 // binary data.
361 if (mPhotoId == 0 && mPhotoBinaryData != null) {
362 ContentValues photo = new ContentValues();
363 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
364 photo.put(Photo.PHOTO, mPhotoBinaryData);
365 result.add(photo);
366 }
367
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700368 return result;
369 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700370
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700371 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700372 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700373 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700374 }
375 mGroups.add(group);
376 }
377
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700378 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700379 return mGroups;
380 }
381 }
382
Dave Santoro39156002011-07-19 01:18:14 -0700383 /**
384 * Projection used for the query that loads all data for the entire contact (except for
385 * social stream items).
386 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700387 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700388 final static String[] COLUMNS = new String[] {
389 Contacts.NAME_RAW_CONTACT_ID,
390 Contacts.DISPLAY_NAME_SOURCE,
391 Contacts.LOOKUP_KEY,
392 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700393 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700394 Contacts.PHONETIC_NAME,
395 Contacts.PHOTO_ID,
396 Contacts.STARRED,
397 Contacts.CONTACT_PRESENCE,
398 Contacts.CONTACT_STATUS,
399 Contacts.CONTACT_STATUS_TIMESTAMP,
400 Contacts.CONTACT_STATUS_RES_PACKAGE,
401 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700402 Contacts.Entity.CONTACT_ID,
403 Contacts.Entity.RAW_CONTACT_ID,
404
405 RawContacts.ACCOUNT_NAME,
406 RawContacts.ACCOUNT_TYPE,
407 RawContacts.DIRTY,
408 RawContacts.VERSION,
409 RawContacts.SOURCE_ID,
410 RawContacts.SYNC1,
411 RawContacts.SYNC2,
412 RawContacts.SYNC3,
413 RawContacts.SYNC4,
414 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700415 RawContacts.NAME_VERIFIED,
416
417 Contacts.Entity.DATA_ID,
418 Data.DATA1,
419 Data.DATA2,
420 Data.DATA3,
421 Data.DATA4,
422 Data.DATA5,
423 Data.DATA6,
424 Data.DATA7,
425 Data.DATA8,
426 Data.DATA9,
427 Data.DATA10,
428 Data.DATA11,
429 Data.DATA12,
430 Data.DATA13,
431 Data.DATA14,
432 Data.DATA15,
433 Data.SYNC1,
434 Data.SYNC2,
435 Data.SYNC3,
436 Data.SYNC4,
437 Data.DATA_VERSION,
438 Data.IS_PRIMARY,
439 Data.IS_SUPER_PRIMARY,
440 Data.MIMETYPE,
441 Data.RES_PACKAGE,
442
443 GroupMembership.GROUP_SOURCE_ID,
444
445 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700446 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700447 Data.STATUS,
448 Data.STATUS_RES_PACKAGE,
449 Data.STATUS_ICON,
450 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700451 Data.STATUS_TIMESTAMP,
452
453 Contacts.PHOTO_URI,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700454 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700455
456 public final static int NAME_RAW_CONTACT_ID = 0;
457 public final static int DISPLAY_NAME_SOURCE = 1;
458 public final static int LOOKUP_KEY = 2;
459 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700460 public final static int ALT_DISPLAY_NAME = 4;
461 public final static int PHONETIC_NAME = 5;
462 public final static int PHOTO_ID = 6;
463 public final static int STARRED = 7;
464 public final static int CONTACT_PRESENCE = 8;
465 public final static int CONTACT_STATUS = 9;
466 public final static int CONTACT_STATUS_TIMESTAMP = 10;
467 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
468 public final static int CONTACT_STATUS_LABEL = 12;
469 public final static int CONTACT_ID = 13;
470 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700471
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700472 public final static int ACCOUNT_NAME = 15;
473 public final static int ACCOUNT_TYPE = 16;
474 public final static int DIRTY = 17;
475 public final static int VERSION = 18;
476 public final static int SOURCE_ID = 19;
477 public final static int SYNC1 = 20;
478 public final static int SYNC2 = 21;
479 public final static int SYNC3 = 22;
480 public final static int SYNC4 = 23;
481 public final static int DELETED = 24;
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700482 public final static int NAME_VERIFIED = 25;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700483
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700484 public final static int DATA_ID = 26;
485 public final static int DATA1 = 27;
486 public final static int DATA2 = 28;
487 public final static int DATA3 = 29;
488 public final static int DATA4 = 30;
489 public final static int DATA5 = 31;
490 public final static int DATA6 = 32;
491 public final static int DATA7 = 33;
492 public final static int DATA8 = 34;
493 public final static int DATA9 = 35;
494 public final static int DATA10 = 36;
495 public final static int DATA11 = 37;
496 public final static int DATA12 = 38;
497 public final static int DATA13 = 39;
498 public final static int DATA14 = 40;
499 public final static int DATA15 = 41;
500 public final static int DATA_SYNC1 = 42;
501 public final static int DATA_SYNC2 = 43;
502 public final static int DATA_SYNC3 = 44;
503 public final static int DATA_SYNC4 = 45;
504 public final static int DATA_VERSION = 46;
505 public final static int IS_PRIMARY = 47;
506 public final static int IS_SUPERPRIMARY = 48;
507 public final static int MIMETYPE = 49;
508 public final static int RES_PACKAGE = 50;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700509
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700510 public final static int GROUP_SOURCE_ID = 51;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700511
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700512 public final static int PRESENCE = 52;
513 public final static int CHAT_CAPABILITY = 53;
514 public final static int STATUS = 54;
515 public final static int STATUS_RES_PACKAGE = 55;
516 public final static int STATUS_ICON = 56;
517 public final static int STATUS_LABEL = 57;
518 public final static int STATUS_TIMESTAMP = 58;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700519
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700520 public final static int PHOTO_URI = 59;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700521 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700522
Dave Santoro39156002011-07-19 01:18:14 -0700523 /**
524 * Projection used for the query that loads all data for the entire contact.
525 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700526 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700527 final static String[] COLUMNS = new String[] {
528 Directory.DISPLAY_NAME,
529 Directory.PACKAGE_NAME,
530 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700531 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700532 Directory.ACCOUNT_NAME,
533 Directory.EXPORT_SUPPORT,
534 };
535
536 public final static int DISPLAY_NAME = 0;
537 public final static int PACKAGE_NAME = 1;
538 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700539 public final static int ACCOUNT_TYPE = 3;
540 public final static int ACCOUNT_NAME = 4;
541 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700542 }
543
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700544 private static class GroupQuery {
545 final static String[] COLUMNS = new String[] {
546 Groups.ACCOUNT_NAME,
547 Groups.ACCOUNT_TYPE,
548 Groups._ID,
549 Groups.TITLE,
550 Groups.AUTO_ADD,
551 Groups.FAVORITES,
552 };
553
554 public final static int ACCOUNT_NAME = 0;
555 public final static int ACCOUNT_TYPE = 1;
556 public final static int ID = 2;
557 public final static int TITLE = 3;
558 public final static int AUTO_ADD = 4;
559 public final static int FAVORITES = 5;
560 }
561
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700562 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700563
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700564 @Override
565 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700566 try {
567 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700568 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700569 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700570 if (result != Result.NOT_FOUND) {
571 if (result.isDirectoryEntry()) {
572 loadDirectoryMetaData(result);
573 } else if (mLoadGroupMetaData) {
574 loadGroupMetaData(result);
575 }
Dave Santoro39156002011-07-19 01:18:14 -0700576 if (mLoadStreamItems) {
577 loadStreamItems(result);
578 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800579 loadPhotoBinaryData(result);
Makoto Onuki69b4a882011-07-22 10:05:10 -0700580 if (mLoadInvitableAccountTypes) {
581 loadInvitableAccountTypes(result);
582 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700583 }
584 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700585 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700586 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700587 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700588 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700589 }
590
591 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700592 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
593 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700594 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700595 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700596 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700597 if (uri == null) throw new IllegalArgumentException("uri must not be null");
598
599 final String authority = uri.getAuthority();
600
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700601 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700602 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700603 final String type = resolver.getType(uri);
604 // Contact-Uri? Good, return it
605 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
606 return uri;
607 }
608
609 // RawContact-Uri? Transform it to ContactUri
610 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
611 final long rawContactId = ContentUris.parseId(uri);
612 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
613 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
614 }
615
616 // Anything else? We don't know what this is
617 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700618 }
619
620 // Legacy Style? Convert to RawContact
621 final String OBSOLETE_AUTHORITY = "contacts";
622 if (OBSOLETE_AUTHORITY.equals(authority)) {
623 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
624 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700625 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700626 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
627 }
628
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700629 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700630 }
631
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700632 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
633 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
634 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
635 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700636 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700637 Log.e(TAG, "No cursor returned in loadContactEntity");
638 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700639 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700640
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700641 try {
642 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700643 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700644 return Result.NOT_FOUND;
645 }
646
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700647 long currentRawContactId = -1;
648 Entity entity = null;
649 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700650 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700651 HashMap<Long, DataStatus> statuses = result.getStatuses();
652 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
653 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
654 if (rawContactId != currentRawContactId) {
655 currentRawContactId = rawContactId;
656 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700657 entities.add(entity);
658 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700659 if (!cursor.isNull(ContactQuery.DATA_ID)) {
660 ContentValues data = loadData(cursor);
661 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
662
663 if (!cursor.isNull(ContactQuery.PRESENCE)
664 || !cursor.isNull(ContactQuery.STATUS)) {
665 final DataStatus status = new DataStatus(cursor);
666 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
667 statuses.put(dataId, status);
668 }
669 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700670 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700671
672 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700673 } finally {
674 cursor.close();
675 }
676 }
677
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700678 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800679 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
680 * not found, returns null
681 */
682 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700683
684 // If we have a photo URI, try loading that first.
685 String photoUri = contactData.getPhotoUri();
686 if (photoUri != null) {
687 try {
688 AssetFileDescriptor fd = getContext().getContentResolver()
689 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
690 byte[] buffer = new byte[16 * 1024];
691 FileInputStream fis = fd.createInputStream();
692 ByteArrayOutputStream baos = new ByteArrayOutputStream();
693 try {
694 int size;
695 while ((size = fis.read(buffer)) != -1) {
696 baos.write(buffer, 0, size);
697 }
698 contactData.setPhotoBinaryData(baos.toByteArray());
699 } finally {
700 fis.close();
701 fd.close();
702 }
703 return;
704 } catch (IOException ioe) {
705 // Just fall back to the case below.
706 }
707 }
708
709 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800710 final long photoId = contactData.getPhotoId();
711 if (photoId <= 0) {
712 // No photo ID
713 return;
714 }
715
716 for (Entity entity : contactData.getEntities()) {
717 for (NamedContentValues subValue : entity.getSubValues()) {
718 final ContentValues entryValues = subValue.values;
719 final long dataId = entryValues.getAsLong(Data._ID);
720 if (dataId == photoId) {
721 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
722 // Correct Data Id but incorrect MimeType? Don't load
723 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
724 return;
725 }
726 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
727 break;
728 }
729 }
730 }
731 }
732
Makoto Onuki3e6991e2011-07-24 14:51:20 -0700733 /**
734 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
735 *
736 * TODO Exclude the ones with no raw contacts in the database.
737 */
Makoto Onuki69b4a882011-07-22 10:05:10 -0700738 private void loadInvitableAccountTypes(Result contactData) {
739 Map<String, AccountType> allInvitables =
740 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
741 if (allInvitables.isEmpty()) {
742 return;
743 }
744
745 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
746
747 // Remove the ones that already has a raw contact in the current contact
748 for (Entity entity : contactData.getEntities()) {
749 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
750 if (!TextUtils.isEmpty(type)) {
751 result.remove(type);
752 }
753 }
754
755 // Set to mInvitableAccountTypes
756 contactData.mInvitableAccountTypes.addAll(result.keySet());
757 }
758
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800759 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700760 * Extracts Contact level columns from the cursor.
761 */
762 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700763 final String directoryParameter =
764 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
765 final long directoryId = directoryParameter == null
766 ? Directory.DEFAULT
767 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700768 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
769 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
770 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
771 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
772 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700773 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700774 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
775 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700776 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700777 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
778 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
779 ? null
780 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700781
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700782 Uri lookupUri;
783 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
784 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700785 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700786 } else {
787 lookupUri = contactUri;
788 }
789
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700790 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700791 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Dave Santoro39156002011-07-19 01:18:14 -0700792 altDisplayName, phoneticName, starred, presence);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700793 }
794
795 /**
796 * Extracts RawContact level columns from the cursor.
797 */
798 private ContentValues loadRawContact(Cursor cursor) {
799 ContentValues cv = new ContentValues();
800
801 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
802
803 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
804 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
805 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
806 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
807 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
808 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
809 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
810 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
811 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
812 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
813 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
814 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700815 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
816
817 return cv;
818 }
819
820 /**
821 * Extracts Data level columns from the cursor.
822 */
823 private ContentValues loadData(Cursor cursor) {
824 ContentValues cv = new ContentValues();
825
826 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
827
828 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
829 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
830 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
831 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
832 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
833 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
834 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
835 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
836 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
837 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
838 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
839 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
840 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
841 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
842 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
843 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
844 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
845 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
846 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
847 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
848 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
849 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
850 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
851 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
852 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700853 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700854
855 return cv;
856 }
857
858 private void cursorColumnToContentValues(
859 Cursor cursor, ContentValues values, int index) {
860 switch (cursor.getType(index)) {
861 case Cursor.FIELD_TYPE_NULL:
862 // don't put anything in the content values
863 break;
864 case Cursor.FIELD_TYPE_INTEGER:
865 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
866 break;
867 case Cursor.FIELD_TYPE_STRING:
868 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
869 break;
870 case Cursor.FIELD_TYPE_BLOB:
871 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
872 break;
873 default:
874 throw new IllegalStateException("Invalid or unhandled data type");
875 }
876 }
877
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700878 private void loadDirectoryMetaData(Result result) {
879 long directoryId = result.getDirectoryId();
880
881 Cursor cursor = getContext().getContentResolver().query(
882 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
883 DirectoryQuery.COLUMNS, null, null, null);
884 if (cursor == null) {
885 return;
886 }
887 try {
888 if (cursor.moveToFirst()) {
889 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
890 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
891 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700892 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700893 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
894 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
895 String directoryType = null;
896 if (!TextUtils.isEmpty(packageName)) {
897 PackageManager pm = getContext().getPackageManager();
898 try {
899 Resources resources = pm.getResourcesForApplication(packageName);
900 directoryType = resources.getString(typeResourceId);
901 } catch (NameNotFoundException e) {
902 Log.w(TAG, "Contact directory resource not found: "
903 + packageName + "." + typeResourceId);
904 }
905 }
906
907 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700908 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700909 }
910 } finally {
911 cursor.close();
912 }
913 }
914
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700915 /**
916 * Loads groups meta-data for all groups associated with all constituent raw contacts'
917 * accounts.
918 */
919 private void loadGroupMetaData(Result result) {
920 StringBuilder selection = new StringBuilder();
921 ArrayList<String> selectionArgs = new ArrayList<String>();
922 for (Entity entity : result.mEntities) {
923 ContentValues values = entity.getEntityValues();
924 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
925 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
926 if (accountName != null && accountType != null) {
927 if (selection.length() != 0) {
928 selection.append(" OR ");
929 }
930 selection.append(
931 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?)");
932 selectionArgs.add(accountName);
933 selectionArgs.add(accountType);
934 }
935 }
936 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
937 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
938 null);
939 try {
940 while (cursor.moveToNext()) {
941 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
942 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
943 final long groupId = cursor.getLong(GroupQuery.ID);
944 final String title = cursor.getString(GroupQuery.TITLE);
945 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
946 ? false
947 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
948 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
949 ? false
950 : cursor.getInt(GroupQuery.FAVORITES) != 0;
951
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700952 result.addGroupMetaData(new GroupMetaData(
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700953 accountName, accountType, groupId, title, defaultGroup, favorites));
954 }
955 } finally {
956 cursor.close();
957 }
958 }
959
Dave Santoro39156002011-07-19 01:18:14 -0700960 /**
961 * Loads all stream items and stream item photos belonging to this contact.
962 */
963 private void loadStreamItems(Result result) {
964 Cursor cursor = getContext().getContentResolver().query(
965 Contacts.CONTENT_LOOKUP_URI.buildUpon()
966 .appendPath(result.getLookupKey())
967 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
968 null, null, null, null);
969 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
970 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
971 try {
972 while (cursor.moveToNext()) {
973 StreamItemEntry streamItem = new StreamItemEntry(cursor);
974 streamItemsById.put(streamItem.getId(), streamItem);
975 streamItems.add(streamItem);
976 }
977 } finally {
978 cursor.close();
979 }
980
981 // Now retrieve any photo records associated with the stream items.
982 String[] streamItemIdArr = new String[streamItems.size()];
983 StringBuilder streamItemPhotoSelection = new StringBuilder();
984 if (!streamItems.isEmpty()) {
985 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
986 for (int i = 0; i < streamItems.size(); i++) {
987 if (i > 0) {
988 streamItemPhotoSelection.append(",");
989 }
990 streamItemPhotoSelection.append("?");
991 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
992 }
993 streamItemPhotoSelection.append(")");
994 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
995 null, streamItemPhotoSelection.toString(), streamItemIdArr,
996 StreamItemPhotos.STREAM_ITEM_ID);
997 try {
998 while (cursor.moveToNext()) {
999 long streamItemId = cursor.getLong(
1000 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1001 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1002 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1003 }
1004 } finally {
1005 cursor.close();
1006 }
1007 }
1008
1009 // Set the sorted stream items on the result.
1010 Collections.sort(streamItems);
1011 result.mStreamItems.addAll(streamItems);
1012 }
1013
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001014 @Override
1015 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001016 unregisterObserver();
1017
Daniel Lehmann1316b132010-04-13 15:08:53 -07001018 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001019 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001020 return;
1021 }
1022
1023 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001024
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001025 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1026 mLookupUri = result.getLookupUri();
1027
1028 if (!result.isDirectoryEntry()) {
1029 Log.i(TAG, "Registering content observer for " + mLookupUri);
1030 if (mObserver == null) {
1031 mObserver = new ForceLoadContentObserver();
1032 }
1033 getContext().getContentResolver().registerContentObserver(
1034 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001035 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001036
1037 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1038 mContact.setLoadingPhoto(true);
1039 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1040 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001041 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001042
1043 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001044 }
1045 }
1046
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001047 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1048
1049 private static final int BUFFER_SIZE = 1024*16;
1050
1051 @Override
1052 protected byte[] doInBackground(String... params) {
1053 Uri uri = Uri.parse(params[0]);
1054 byte[] data = null;
1055 try {
1056 InputStream is = getContext().getContentResolver().openInputStream(uri);
1057 if (is != null) {
1058 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1059 try {
1060 byte[] mBuffer = new byte[BUFFER_SIZE];
1061
1062 int size;
1063 while ((size = is.read(mBuffer)) != -1) {
1064 baos.write(mBuffer, 0, size);
1065 }
1066 data = baos.toByteArray();
1067 } finally {
1068 is.close();
1069 }
1070 } else {
1071 Log.v(TAG, "Cannot load photo " + uri);
1072 }
1073 } catch (IOException e) {
1074 Log.e(TAG, "Cannot load photo " + uri, e);
1075 }
1076
1077 return data;
1078 }
1079
1080 @Override
1081 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001082 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001083 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001084 mContact.setPhotoBinaryData(data);
1085 mContact.setLoadingPhoto(false);
1086 deliverResult(mContact);
1087 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001088 }
1089 }
1090
Daniel Lehmann3a120772010-06-21 16:21:35 -07001091 private void unregisterObserver() {
1092 if (mObserver != null) {
1093 getContext().getContentResolver().unregisterContentObserver(mObserver);
1094 mObserver = null;
1095 }
1096 }
1097
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001098 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001099 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001100 }
1101
Dave Santoro39156002011-07-19 01:18:14 -07001102 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001103 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001104 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001105 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001106 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001107 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001108 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001109 }
1110
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001111 public Uri getLookupUri() {
1112 return mLookupUri;
1113 }
1114
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001115 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001116 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001117 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001118 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001119 }
1120
1121 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001122 forceLoad();
1123 }
1124 }
1125
1126 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001127 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001128 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001129 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001130 }
1131
1132 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001133 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001134 unregisterObserver();
1135 mContact = null;
1136 mDestroyed = true;
1137 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001138}