blob: 4078598b5b9e11056e3d4acbe1ffed8a58a63cca [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 Onuki69b4a882011-07-22 10:05:10 -0700733 private void loadInvitableAccountTypes(Result contactData) {
734 Map<String, AccountType> allInvitables =
735 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
736 if (allInvitables.isEmpty()) {
737 return;
738 }
739
740 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
741
742 // Remove the ones that already has a raw contact in the current contact
743 for (Entity entity : contactData.getEntities()) {
744 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
745 if (!TextUtils.isEmpty(type)) {
746 result.remove(type);
747 }
748 }
749
750 // Set to mInvitableAccountTypes
751 contactData.mInvitableAccountTypes.addAll(result.keySet());
752 }
753
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800754 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700755 * Extracts Contact level columns from the cursor.
756 */
757 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700758 final String directoryParameter =
759 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
760 final long directoryId = directoryParameter == null
761 ? Directory.DEFAULT
762 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700763 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
764 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
765 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
766 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
767 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700768 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700769 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
770 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700771 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700772 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
773 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
774 ? null
775 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700776
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700777 Uri lookupUri;
778 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
779 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700780 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700781 } else {
782 lookupUri = contactUri;
783 }
784
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700785 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700786 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Dave Santoro39156002011-07-19 01:18:14 -0700787 altDisplayName, phoneticName, starred, presence);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700788 }
789
790 /**
791 * Extracts RawContact level columns from the cursor.
792 */
793 private ContentValues loadRawContact(Cursor cursor) {
794 ContentValues cv = new ContentValues();
795
796 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
797
798 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
799 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
800 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
801 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
802 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
803 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
804 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
805 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
806 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
807 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
808 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
809 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700810 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
811
812 return cv;
813 }
814
815 /**
816 * Extracts Data level columns from the cursor.
817 */
818 private ContentValues loadData(Cursor cursor) {
819 ContentValues cv = new ContentValues();
820
821 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
822
823 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
824 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
825 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
826 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
827 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
828 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
829 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
830 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
831 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
832 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
833 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
834 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
835 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
836 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
837 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
838 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
839 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
840 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
841 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
842 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
843 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
844 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
845 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
846 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
847 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700848 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700849
850 return cv;
851 }
852
853 private void cursorColumnToContentValues(
854 Cursor cursor, ContentValues values, int index) {
855 switch (cursor.getType(index)) {
856 case Cursor.FIELD_TYPE_NULL:
857 // don't put anything in the content values
858 break;
859 case Cursor.FIELD_TYPE_INTEGER:
860 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
861 break;
862 case Cursor.FIELD_TYPE_STRING:
863 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
864 break;
865 case Cursor.FIELD_TYPE_BLOB:
866 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
867 break;
868 default:
869 throw new IllegalStateException("Invalid or unhandled data type");
870 }
871 }
872
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700873 private void loadDirectoryMetaData(Result result) {
874 long directoryId = result.getDirectoryId();
875
876 Cursor cursor = getContext().getContentResolver().query(
877 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
878 DirectoryQuery.COLUMNS, null, null, null);
879 if (cursor == null) {
880 return;
881 }
882 try {
883 if (cursor.moveToFirst()) {
884 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
885 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
886 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700887 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700888 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
889 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
890 String directoryType = null;
891 if (!TextUtils.isEmpty(packageName)) {
892 PackageManager pm = getContext().getPackageManager();
893 try {
894 Resources resources = pm.getResourcesForApplication(packageName);
895 directoryType = resources.getString(typeResourceId);
896 } catch (NameNotFoundException e) {
897 Log.w(TAG, "Contact directory resource not found: "
898 + packageName + "." + typeResourceId);
899 }
900 }
901
902 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700903 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700904 }
905 } finally {
906 cursor.close();
907 }
908 }
909
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700910 /**
911 * Loads groups meta-data for all groups associated with all constituent raw contacts'
912 * accounts.
913 */
914 private void loadGroupMetaData(Result result) {
915 StringBuilder selection = new StringBuilder();
916 ArrayList<String> selectionArgs = new ArrayList<String>();
917 for (Entity entity : result.mEntities) {
918 ContentValues values = entity.getEntityValues();
919 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
920 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
921 if (accountName != null && accountType != null) {
922 if (selection.length() != 0) {
923 selection.append(" OR ");
924 }
925 selection.append(
926 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?)");
927 selectionArgs.add(accountName);
928 selectionArgs.add(accountType);
929 }
930 }
931 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
932 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
933 null);
934 try {
935 while (cursor.moveToNext()) {
936 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
937 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
938 final long groupId = cursor.getLong(GroupQuery.ID);
939 final String title = cursor.getString(GroupQuery.TITLE);
940 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
941 ? false
942 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
943 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
944 ? false
945 : cursor.getInt(GroupQuery.FAVORITES) != 0;
946
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700947 result.addGroupMetaData(new GroupMetaData(
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700948 accountName, accountType, groupId, title, defaultGroup, favorites));
949 }
950 } finally {
951 cursor.close();
952 }
953 }
954
Dave Santoro39156002011-07-19 01:18:14 -0700955 /**
956 * Loads all stream items and stream item photos belonging to this contact.
957 */
958 private void loadStreamItems(Result result) {
959 Cursor cursor = getContext().getContentResolver().query(
960 Contacts.CONTENT_LOOKUP_URI.buildUpon()
961 .appendPath(result.getLookupKey())
962 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
963 null, null, null, null);
964 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
965 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
966 try {
967 while (cursor.moveToNext()) {
968 StreamItemEntry streamItem = new StreamItemEntry(cursor);
969 streamItemsById.put(streamItem.getId(), streamItem);
970 streamItems.add(streamItem);
971 }
972 } finally {
973 cursor.close();
974 }
975
976 // Now retrieve any photo records associated with the stream items.
977 String[] streamItemIdArr = new String[streamItems.size()];
978 StringBuilder streamItemPhotoSelection = new StringBuilder();
979 if (!streamItems.isEmpty()) {
980 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
981 for (int i = 0; i < streamItems.size(); i++) {
982 if (i > 0) {
983 streamItemPhotoSelection.append(",");
984 }
985 streamItemPhotoSelection.append("?");
986 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
987 }
988 streamItemPhotoSelection.append(")");
989 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
990 null, streamItemPhotoSelection.toString(), streamItemIdArr,
991 StreamItemPhotos.STREAM_ITEM_ID);
992 try {
993 while (cursor.moveToNext()) {
994 long streamItemId = cursor.getLong(
995 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
996 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
997 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
998 }
999 } finally {
1000 cursor.close();
1001 }
1002 }
1003
1004 // Set the sorted stream items on the result.
1005 Collections.sort(streamItems);
1006 result.mStreamItems.addAll(streamItems);
1007 }
1008
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001009 @Override
1010 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001011 unregisterObserver();
1012
Daniel Lehmann1316b132010-04-13 15:08:53 -07001013 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001014 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001015 return;
1016 }
1017
1018 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001019
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001020 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1021 mLookupUri = result.getLookupUri();
1022
1023 if (!result.isDirectoryEntry()) {
1024 Log.i(TAG, "Registering content observer for " + mLookupUri);
1025 if (mObserver == null) {
1026 mObserver = new ForceLoadContentObserver();
1027 }
1028 getContext().getContentResolver().registerContentObserver(
1029 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001030 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001031
1032 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1033 mContact.setLoadingPhoto(true);
1034 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1035 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001036 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001037
1038 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001039 }
1040 }
1041
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001042 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1043
1044 private static final int BUFFER_SIZE = 1024*16;
1045
1046 @Override
1047 protected byte[] doInBackground(String... params) {
1048 Uri uri = Uri.parse(params[0]);
1049 byte[] data = null;
1050 try {
1051 InputStream is = getContext().getContentResolver().openInputStream(uri);
1052 if (is != null) {
1053 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1054 try {
1055 byte[] mBuffer = new byte[BUFFER_SIZE];
1056
1057 int size;
1058 while ((size = is.read(mBuffer)) != -1) {
1059 baos.write(mBuffer, 0, size);
1060 }
1061 data = baos.toByteArray();
1062 } finally {
1063 is.close();
1064 }
1065 } else {
1066 Log.v(TAG, "Cannot load photo " + uri);
1067 }
1068 } catch (IOException e) {
1069 Log.e(TAG, "Cannot load photo " + uri, e);
1070 }
1071
1072 return data;
1073 }
1074
1075 @Override
1076 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001077 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001078 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001079 mContact.setPhotoBinaryData(data);
1080 mContact.setLoadingPhoto(false);
1081 deliverResult(mContact);
1082 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001083 }
1084 }
1085
Daniel Lehmann3a120772010-06-21 16:21:35 -07001086 private void unregisterObserver() {
1087 if (mObserver != null) {
1088 getContext().getContentResolver().unregisterContentObserver(mObserver);
1089 mObserver = null;
1090 }
1091 }
1092
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001093 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001094 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001095 }
1096
Dave Santoro39156002011-07-19 01:18:14 -07001097 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001098 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001099 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001100 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001101 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001102 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001103 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001104 }
1105
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001106 public Uri getLookupUri() {
1107 return mLookupUri;
1108 }
1109
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001110 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001111 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001112 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001113 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001114 }
1115
1116 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001117 forceLoad();
1118 }
1119 }
1120
1121 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001122 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001123 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001124 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001125 }
1126
1127 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001128 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001129 unregisterObserver();
1130 mContact = null;
1131 mDestroyed = true;
1132 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001133}