blob: 9daa1e0e0caf99a33e83d937cbbacea5ea36390c [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;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700117 private final ArrayList<AccountType> 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;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700129 private boolean mSendToVoicemail;
130 private String mCustomRingtone;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800131
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700132 /**
133 * Constructor for case "no contact found". This must only be used for the
134 * final {@link Result#NOT_FOUND} singleton
135 */
136 private Result() {
137 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700138 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700139 mDirectoryId = -1;
140 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700141 mId = -1;
142 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700143 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700144 mStatuses = null;
145 mNameRawContactId = -1;
146 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700147 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700148 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700149 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700150 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700151 mPhoneticName = null;
152 mStarred = false;
153 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700154 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700155 mSendToVoicemail = false;
156 mCustomRingtone = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700157 }
158
159 /**
160 * Constructor to call when contact was found
161 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700162 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700163 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700164 String displayName, String altDisplayName, String phoneticName, boolean starred,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700165 Integer presence, boolean sendToVoicemail, String customRingtone) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700166 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700167 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700168 mDirectoryId = directoryId;
169 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700170 mId = id;
171 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700172 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700173 mStatuses = new HashMap<Long, DataStatus>();
174 mNameRawContactId = nameRawContactId;
175 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700176 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700177 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700178 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700179 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700180 mPhoneticName = phoneticName;
181 mStarred = starred;
182 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700183 mInvitableAccountTypes = Lists.newArrayList();
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700184 mSendToVoicemail = sendToVoicemail;
185 mCustomRingtone = customRingtone;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700186 }
187
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800188 private Result(Result from) {
189 mLookupUri = from.mLookupUri;
190 mUri = from.mUri;
191 mDirectoryId = from.mDirectoryId;
192 mLookupKey = from.mLookupKey;
193 mId = from.mId;
194 mNameRawContactId = from.mNameRawContactId;
195 mDisplayNameSource = from.mDisplayNameSource;
196 mPhotoId = from.mPhotoId;
197 mPhotoUri = from.mPhotoUri;
198 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700199 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800200 mPhoneticName = from.mPhoneticName;
201 mStarred = from.mStarred;
202 mPresence = from.mPresence;
203 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700204 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800205 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700206 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800207
208 mDirectoryDisplayName = from.mDirectoryDisplayName;
209 mDirectoryType = from.mDirectoryType;
210 mDirectoryAccountType = from.mDirectoryAccountType;
211 mDirectoryAccountName = from.mDirectoryAccountName;
212 mDirectoryExportSupport = from.mDirectoryExportSupport;
213
214 mGroups = from.mGroups;
215
216 mLoadingPhoto = from.mLoadingPhoto;
217 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700218 mSendToVoicemail = from.mSendToVoicemail;
219 mCustomRingtone = from.mCustomRingtone;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800220 }
221
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700222 /**
223 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
224 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700225 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700226 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700227 mDirectoryDisplayName = displayName;
228 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700229 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700230 mDirectoryAccountName = accountName;
231 mDirectoryExportSupport = exportSupport;
232 }
233
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800234 private void setLoadingPhoto(boolean flag) {
235 mLoadingPhoto = flag;
236 }
237
238 private void setPhotoBinaryData(byte[] photoBinaryData) {
239 mPhotoBinaryData = photoBinaryData;
240 }
241
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700242 public Uri getLookupUri() {
243 return mLookupUri;
244 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800245
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700246 public String getLookupKey() {
247 return mLookupKey;
248 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800249
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700250 public Uri getUri() {
251 return mUri;
252 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800253
Flavio Lerda37a26842011-06-27 11:36:52 +0100254 @VisibleForTesting
255 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700256 return mId;
257 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800258
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700259 public long getNameRawContactId() {
260 return mNameRawContactId;
261 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800262
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700263 public int getDisplayNameSource() {
264 return mDisplayNameSource;
265 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800266
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700267 public long getPhotoId() {
268 return mPhotoId;
269 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800270
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700271 public String getPhotoUri() {
272 return mPhotoUri;
273 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800274
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700275 public String getDisplayName() {
276 return mDisplayName;
277 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800278
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700279 public String getAltDisplayName() {
280 return mAltDisplayName;
281 }
282
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700283 public String getPhoneticName() {
284 return mPhoneticName;
285 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800286
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700287 public boolean getStarred() {
288 return mStarred;
289 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800290
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700291 public Integer getPresence() {
292 return mPresence;
293 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800294
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700295 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700296 return mInvitableAccountTypes;
297 }
298
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700299 public ArrayList<Entity> getEntities() {
300 return mEntities;
301 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800302
Dave Santoro39156002011-07-19 01:18:14 -0700303 public ArrayList<StreamItemEntry> getStreamItems() {
304 return mStreamItems;
305 }
306
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700307 public HashMap<Long, DataStatus> getStatuses() {
308 return mStatuses;
309 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700310
311 public long getDirectoryId() {
312 return mDirectoryId;
313 }
314
315 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700316 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
317 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700318 }
319
320 public int getDirectoryExportSupport() {
321 return mDirectoryExportSupport;
322 }
323
324 public String getDirectoryDisplayName() {
325 return mDirectoryDisplayName;
326 }
327
328 public String getDirectoryType() {
329 return mDirectoryType;
330 }
331
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700332 public String getDirectoryAccountType() {
333 return mDirectoryAccountType;
334 }
335
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700336 public String getDirectoryAccountName() {
337 return mDirectoryAccountName;
338 }
339
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800340 public boolean isLoadingPhoto() {
341 return mLoadingPhoto;
342 }
343
344 public byte[] getPhotoBinaryData() {
345 return mPhotoBinaryData;
346 }
347
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700348 public ArrayList<ContentValues> getContentValues() {
349 if (mEntities.size() != 1) {
350 throw new IllegalStateException(
351 "Cannot extract content values from an aggregated contact");
352 }
353
354 Entity entity = mEntities.get(0);
355 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
356 ArrayList<NamedContentValues> subValues = entity.getSubValues();
357 if (subValues != null) {
358 int size = subValues.size();
359 for (int i = 0; i < size; i++) {
360 NamedContentValues pair = subValues.get(i);
361 if (Data.CONTENT_URI.equals(pair.uri)) {
362 result.add(pair.values);
363 }
364 }
365 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800366
367 // If the photo was loaded using the URI, create an entry for the photo
368 // binary data.
369 if (mPhotoId == 0 && mPhotoBinaryData != null) {
370 ContentValues photo = new ContentValues();
371 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
372 photo.put(Photo.PHOTO, mPhotoBinaryData);
373 result.add(photo);
374 }
375
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700376 return result;
377 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700378
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700379 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700380 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700381 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700382 }
383 mGroups.add(group);
384 }
385
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700386 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700387 return mGroups;
388 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700389
390 public boolean isSendToVoicemail() {
391 return mSendToVoicemail;
392 }
393
394 public String getCustomRingtone() {
395 return mCustomRingtone;
396 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700397 }
398
Dave Santoro39156002011-07-19 01:18:14 -0700399 /**
400 * Projection used for the query that loads all data for the entire contact (except for
401 * social stream items).
402 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700403 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700404 final static String[] COLUMNS = new String[] {
405 Contacts.NAME_RAW_CONTACT_ID,
406 Contacts.DISPLAY_NAME_SOURCE,
407 Contacts.LOOKUP_KEY,
408 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700409 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700410 Contacts.PHONETIC_NAME,
411 Contacts.PHOTO_ID,
412 Contacts.STARRED,
413 Contacts.CONTACT_PRESENCE,
414 Contacts.CONTACT_STATUS,
415 Contacts.CONTACT_STATUS_TIMESTAMP,
416 Contacts.CONTACT_STATUS_RES_PACKAGE,
417 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700418 Contacts.Entity.CONTACT_ID,
419 Contacts.Entity.RAW_CONTACT_ID,
420
421 RawContacts.ACCOUNT_NAME,
422 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700423 RawContacts.DATA_SET,
424 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700425 RawContacts.DIRTY,
426 RawContacts.VERSION,
427 RawContacts.SOURCE_ID,
428 RawContacts.SYNC1,
429 RawContacts.SYNC2,
430 RawContacts.SYNC3,
431 RawContacts.SYNC4,
432 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700433 RawContacts.NAME_VERIFIED,
434
435 Contacts.Entity.DATA_ID,
436 Data.DATA1,
437 Data.DATA2,
438 Data.DATA3,
439 Data.DATA4,
440 Data.DATA5,
441 Data.DATA6,
442 Data.DATA7,
443 Data.DATA8,
444 Data.DATA9,
445 Data.DATA10,
446 Data.DATA11,
447 Data.DATA12,
448 Data.DATA13,
449 Data.DATA14,
450 Data.DATA15,
451 Data.SYNC1,
452 Data.SYNC2,
453 Data.SYNC3,
454 Data.SYNC4,
455 Data.DATA_VERSION,
456 Data.IS_PRIMARY,
457 Data.IS_SUPER_PRIMARY,
458 Data.MIMETYPE,
459 Data.RES_PACKAGE,
460
461 GroupMembership.GROUP_SOURCE_ID,
462
463 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700464 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700465 Data.STATUS,
466 Data.STATUS_RES_PACKAGE,
467 Data.STATUS_ICON,
468 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700469 Data.STATUS_TIMESTAMP,
470
471 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700472 Contacts.SEND_TO_VOICEMAIL,
473 Contacts.CUSTOM_RINGTONE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700474 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700475
476 public final static int NAME_RAW_CONTACT_ID = 0;
477 public final static int DISPLAY_NAME_SOURCE = 1;
478 public final static int LOOKUP_KEY = 2;
479 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700480 public final static int ALT_DISPLAY_NAME = 4;
481 public final static int PHONETIC_NAME = 5;
482 public final static int PHOTO_ID = 6;
483 public final static int STARRED = 7;
484 public final static int CONTACT_PRESENCE = 8;
485 public final static int CONTACT_STATUS = 9;
486 public final static int CONTACT_STATUS_TIMESTAMP = 10;
487 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
488 public final static int CONTACT_STATUS_LABEL = 12;
489 public final static int CONTACT_ID = 13;
490 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700491
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700492 public final static int ACCOUNT_NAME = 15;
493 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700494 public final static int DATA_SET = 17;
495 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
496 public final static int DIRTY = 19;
497 public final static int VERSION = 20;
498 public final static int SOURCE_ID = 21;
499 public final static int SYNC1 = 22;
500 public final static int SYNC2 = 23;
501 public final static int SYNC3 = 24;
502 public final static int SYNC4 = 25;
503 public final static int DELETED = 26;
504 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700505
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700506 public final static int DATA_ID = 28;
507 public final static int DATA1 = 29;
508 public final static int DATA2 = 30;
509 public final static int DATA3 = 31;
510 public final static int DATA4 = 32;
511 public final static int DATA5 = 33;
512 public final static int DATA6 = 34;
513 public final static int DATA7 = 35;
514 public final static int DATA8 = 36;
515 public final static int DATA9 = 37;
516 public final static int DATA10 = 38;
517 public final static int DATA11 = 39;
518 public final static int DATA12 = 40;
519 public final static int DATA13 = 41;
520 public final static int DATA14 = 42;
521 public final static int DATA15 = 43;
522 public final static int DATA_SYNC1 = 44;
523 public final static int DATA_SYNC2 = 45;
524 public final static int DATA_SYNC3 = 46;
525 public final static int DATA_SYNC4 = 47;
526 public final static int DATA_VERSION = 48;
527 public final static int IS_PRIMARY = 49;
528 public final static int IS_SUPERPRIMARY = 50;
529 public final static int MIMETYPE = 51;
530 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700531
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700532 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700533
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700534 public final static int PRESENCE = 54;
535 public final static int CHAT_CAPABILITY = 55;
536 public final static int STATUS = 56;
537 public final static int STATUS_RES_PACKAGE = 57;
538 public final static int STATUS_ICON = 58;
539 public final static int STATUS_LABEL = 59;
540 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700541
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700542 public final static int PHOTO_URI = 61;
543 public final static int SEND_TO_VOICEMAIL = 62;
544 public final static int CUSTOM_RINGTONE = 63;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700545 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700546
Dave Santoro39156002011-07-19 01:18:14 -0700547 /**
548 * Projection used for the query that loads all data for the entire contact.
549 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700550 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700551 final static String[] COLUMNS = new String[] {
552 Directory.DISPLAY_NAME,
553 Directory.PACKAGE_NAME,
554 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700555 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700556 Directory.ACCOUNT_NAME,
557 Directory.EXPORT_SUPPORT,
558 };
559
560 public final static int DISPLAY_NAME = 0;
561 public final static int PACKAGE_NAME = 1;
562 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700563 public final static int ACCOUNT_TYPE = 3;
564 public final static int ACCOUNT_NAME = 4;
565 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700566 }
567
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700568 private static class GroupQuery {
569 final static String[] COLUMNS = new String[] {
570 Groups.ACCOUNT_NAME,
571 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700572 Groups.DATA_SET,
573 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700574 Groups._ID,
575 Groups.TITLE,
576 Groups.AUTO_ADD,
577 Groups.FAVORITES,
578 };
579
580 public final static int ACCOUNT_NAME = 0;
581 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700582 public final static int DATA_SET = 2;
583 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
584 public final static int ID = 4;
585 public final static int TITLE = 5;
586 public final static int AUTO_ADD = 6;
587 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700588 }
589
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700590 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700591
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700592 @Override
593 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700594 try {
595 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700596 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700597 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700598 if (result != Result.NOT_FOUND) {
599 if (result.isDirectoryEntry()) {
600 loadDirectoryMetaData(result);
601 } else if (mLoadGroupMetaData) {
602 loadGroupMetaData(result);
603 }
Dave Santoro39156002011-07-19 01:18:14 -0700604 if (mLoadStreamItems) {
605 loadStreamItems(result);
606 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800607 loadPhotoBinaryData(result);
Makoto Onuki69b4a882011-07-22 10:05:10 -0700608 if (mLoadInvitableAccountTypes) {
609 loadInvitableAccountTypes(result);
610 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700611 }
612 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700613 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700614 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700615 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700616 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700617 }
618
619 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700620 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
621 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700622 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700623 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700624 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700625 if (uri == null) throw new IllegalArgumentException("uri must not be null");
626
627 final String authority = uri.getAuthority();
628
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700629 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700630 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700631 final String type = resolver.getType(uri);
632 // Contact-Uri? Good, return it
633 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
634 return uri;
635 }
636
637 // RawContact-Uri? Transform it to ContactUri
638 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
639 final long rawContactId = ContentUris.parseId(uri);
640 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
641 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
642 }
643
644 // Anything else? We don't know what this is
645 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700646 }
647
648 // Legacy Style? Convert to RawContact
649 final String OBSOLETE_AUTHORITY = "contacts";
650 if (OBSOLETE_AUTHORITY.equals(authority)) {
651 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
652 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700653 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700654 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
655 }
656
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700657 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700658 }
659
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700660 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
661 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
662 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
663 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700664 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700665 Log.e(TAG, "No cursor returned in loadContactEntity");
666 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700667 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700668
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700669 try {
670 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700671 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700672 return Result.NOT_FOUND;
673 }
674
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700675 long currentRawContactId = -1;
676 Entity entity = null;
677 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700678 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700679 HashMap<Long, DataStatus> statuses = result.getStatuses();
680 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
681 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
682 if (rawContactId != currentRawContactId) {
683 currentRawContactId = rawContactId;
684 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700685 entities.add(entity);
686 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700687 if (!cursor.isNull(ContactQuery.DATA_ID)) {
688 ContentValues data = loadData(cursor);
689 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
690
691 if (!cursor.isNull(ContactQuery.PRESENCE)
692 || !cursor.isNull(ContactQuery.STATUS)) {
693 final DataStatus status = new DataStatus(cursor);
694 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
695 statuses.put(dataId, status);
696 }
697 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700698 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700699
700 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700701 } finally {
702 cursor.close();
703 }
704 }
705
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700706 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800707 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
708 * not found, returns null
709 */
710 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700711
712 // If we have a photo URI, try loading that first.
713 String photoUri = contactData.getPhotoUri();
714 if (photoUri != null) {
715 try {
716 AssetFileDescriptor fd = getContext().getContentResolver()
717 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
718 byte[] buffer = new byte[16 * 1024];
719 FileInputStream fis = fd.createInputStream();
720 ByteArrayOutputStream baos = new ByteArrayOutputStream();
721 try {
722 int size;
723 while ((size = fis.read(buffer)) != -1) {
724 baos.write(buffer, 0, size);
725 }
726 contactData.setPhotoBinaryData(baos.toByteArray());
727 } finally {
728 fis.close();
729 fd.close();
730 }
731 return;
732 } catch (IOException ioe) {
733 // Just fall back to the case below.
734 }
735 }
736
737 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800738 final long photoId = contactData.getPhotoId();
739 if (photoId <= 0) {
740 // No photo ID
741 return;
742 }
743
744 for (Entity entity : contactData.getEntities()) {
745 for (NamedContentValues subValue : entity.getSubValues()) {
746 final ContentValues entryValues = subValue.values;
747 final long dataId = entryValues.getAsLong(Data._ID);
748 if (dataId == photoId) {
749 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
750 // Correct Data Id but incorrect MimeType? Don't load
751 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
752 return;
753 }
754 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
755 break;
756 }
757 }
758 }
759 }
760
Makoto Onuki3e6991e2011-07-24 14:51:20 -0700761 /**
762 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
763 *
764 * TODO Exclude the ones with no raw contacts in the database.
765 */
Makoto Onuki69b4a882011-07-22 10:05:10 -0700766 private void loadInvitableAccountTypes(Result contactData) {
767 Map<String, AccountType> allInvitables =
768 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
769 if (allInvitables.isEmpty()) {
770 return;
771 }
772
773 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
774
775 // Remove the ones that already has a raw contact in the current contact
776 for (Entity entity : contactData.getEntities()) {
777 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
778 if (!TextUtils.isEmpty(type)) {
779 result.remove(type);
780 }
781 }
782
783 // Set to mInvitableAccountTypes
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700784 contactData.mInvitableAccountTypes.addAll(result.values());
Makoto Onuki69b4a882011-07-22 10:05:10 -0700785 }
786
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800787 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700788 * Extracts Contact level columns from the cursor.
789 */
790 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700791 final String directoryParameter =
792 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
793 final long directoryId = directoryParameter == null
794 ? Directory.DEFAULT
795 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700796 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
797 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
798 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
799 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
800 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700801 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700802 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
803 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700804 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700805 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
806 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
807 ? null
808 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700809 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
810 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700811
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700812 Uri lookupUri;
813 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
814 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700815 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700816 } else {
817 lookupUri = contactUri;
818 }
819
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700820 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700821 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700822 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
823 customRingtone);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700824 }
825
826 /**
827 * Extracts RawContact level columns from the cursor.
828 */
829 private ContentValues loadRawContact(Cursor cursor) {
830 ContentValues cv = new ContentValues();
831
832 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
833
834 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
835 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700836 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
837 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700838 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
839 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
840 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
841 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
842 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
843 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
844 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
845 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
846 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
847 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700848 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
849
850 return cv;
851 }
852
853 /**
854 * Extracts Data level columns from the cursor.
855 */
856 private ContentValues loadData(Cursor cursor) {
857 ContentValues cv = new ContentValues();
858
859 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
860
861 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
862 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
863 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
864 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
865 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
866 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
867 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
868 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
869 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
870 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
871 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
872 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
873 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
874 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
875 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
876 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
877 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
878 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
879 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
880 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
881 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
882 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
883 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
884 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
885 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700886 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700887
888 return cv;
889 }
890
891 private void cursorColumnToContentValues(
892 Cursor cursor, ContentValues values, int index) {
893 switch (cursor.getType(index)) {
894 case Cursor.FIELD_TYPE_NULL:
895 // don't put anything in the content values
896 break;
897 case Cursor.FIELD_TYPE_INTEGER:
898 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
899 break;
900 case Cursor.FIELD_TYPE_STRING:
901 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
902 break;
903 case Cursor.FIELD_TYPE_BLOB:
904 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
905 break;
906 default:
907 throw new IllegalStateException("Invalid or unhandled data type");
908 }
909 }
910
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700911 private void loadDirectoryMetaData(Result result) {
912 long directoryId = result.getDirectoryId();
913
914 Cursor cursor = getContext().getContentResolver().query(
915 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
916 DirectoryQuery.COLUMNS, null, null, null);
917 if (cursor == null) {
918 return;
919 }
920 try {
921 if (cursor.moveToFirst()) {
922 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
923 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
924 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700925 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700926 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
927 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
928 String directoryType = null;
929 if (!TextUtils.isEmpty(packageName)) {
930 PackageManager pm = getContext().getPackageManager();
931 try {
932 Resources resources = pm.getResourcesForApplication(packageName);
933 directoryType = resources.getString(typeResourceId);
934 } catch (NameNotFoundException e) {
935 Log.w(TAG, "Contact directory resource not found: "
936 + packageName + "." + typeResourceId);
937 }
938 }
939
940 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700941 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700942 }
943 } finally {
944 cursor.close();
945 }
946 }
947
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700948 /**
949 * Loads groups meta-data for all groups associated with all constituent raw contacts'
950 * accounts.
951 */
952 private void loadGroupMetaData(Result result) {
953 StringBuilder selection = new StringBuilder();
954 ArrayList<String> selectionArgs = new ArrayList<String>();
955 for (Entity entity : result.mEntities) {
956 ContentValues values = entity.getEntityValues();
957 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
958 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700959 String dataSet = values.getAsString(RawContacts.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700960 if (accountName != null && accountType != null) {
961 if (selection.length() != 0) {
962 selection.append(" OR ");
963 }
964 selection.append(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700965 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700966 selectionArgs.add(accountName);
967 selectionArgs.add(accountType);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700968
969 if (dataSet != null) {
970 selection.append(" AND " + Groups.DATA_SET + "=?");
971 selectionArgs.add(dataSet);
972 } else {
973 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
974 }
975 selection.append(")");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700976 }
977 }
978 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
979 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
980 null);
981 try {
982 while (cursor.moveToNext()) {
983 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
984 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700985 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700986 final long groupId = cursor.getLong(GroupQuery.ID);
987 final String title = cursor.getString(GroupQuery.TITLE);
988 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
989 ? false
990 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
991 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
992 ? false
993 : cursor.getInt(GroupQuery.FAVORITES) != 0;
994
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700995 result.addGroupMetaData(new GroupMetaData(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700996 accountName, accountType, dataSet, groupId, title, defaultGroup,
997 favorites));
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700998 }
999 } finally {
1000 cursor.close();
1001 }
1002 }
1003
Dave Santoro39156002011-07-19 01:18:14 -07001004 /**
1005 * Loads all stream items and stream item photos belonging to this contact.
1006 */
1007 private void loadStreamItems(Result result) {
1008 Cursor cursor = getContext().getContentResolver().query(
1009 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1010 .appendPath(result.getLookupKey())
1011 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1012 null, null, null, null);
1013 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
1014 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1015 try {
1016 while (cursor.moveToNext()) {
1017 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1018 streamItemsById.put(streamItem.getId(), streamItem);
1019 streamItems.add(streamItem);
1020 }
1021 } finally {
1022 cursor.close();
1023 }
1024
1025 // Now retrieve any photo records associated with the stream items.
1026 String[] streamItemIdArr = new String[streamItems.size()];
1027 StringBuilder streamItemPhotoSelection = new StringBuilder();
1028 if (!streamItems.isEmpty()) {
1029 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1030 for (int i = 0; i < streamItems.size(); i++) {
1031 if (i > 0) {
1032 streamItemPhotoSelection.append(",");
1033 }
1034 streamItemPhotoSelection.append("?");
1035 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
1036 }
1037 streamItemPhotoSelection.append(")");
1038 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
1039 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1040 StreamItemPhotos.STREAM_ITEM_ID);
1041 try {
1042 while (cursor.moveToNext()) {
1043 long streamItemId = cursor.getLong(
1044 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1045 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1046 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1047 }
1048 } finally {
1049 cursor.close();
1050 }
1051 }
1052
1053 // Set the sorted stream items on the result.
1054 Collections.sort(streamItems);
1055 result.mStreamItems.addAll(streamItems);
1056 }
1057
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001058 @Override
1059 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001060 unregisterObserver();
1061
Daniel Lehmann1316b132010-04-13 15:08:53 -07001062 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001063 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001064 return;
1065 }
1066
1067 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001068
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001069 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1070 mLookupUri = result.getLookupUri();
1071
1072 if (!result.isDirectoryEntry()) {
1073 Log.i(TAG, "Registering content observer for " + mLookupUri);
1074 if (mObserver == null) {
1075 mObserver = new ForceLoadContentObserver();
1076 }
1077 getContext().getContentResolver().registerContentObserver(
1078 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001079 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001080
1081 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1082 mContact.setLoadingPhoto(true);
1083 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1084 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001085 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001086
1087 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001088 }
1089 }
1090
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001091 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1092
1093 private static final int BUFFER_SIZE = 1024*16;
1094
1095 @Override
1096 protected byte[] doInBackground(String... params) {
1097 Uri uri = Uri.parse(params[0]);
1098 byte[] data = null;
1099 try {
1100 InputStream is = getContext().getContentResolver().openInputStream(uri);
1101 if (is != null) {
1102 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1103 try {
1104 byte[] mBuffer = new byte[BUFFER_SIZE];
1105
1106 int size;
1107 while ((size = is.read(mBuffer)) != -1) {
1108 baos.write(mBuffer, 0, size);
1109 }
1110 data = baos.toByteArray();
1111 } finally {
1112 is.close();
1113 }
1114 } else {
1115 Log.v(TAG, "Cannot load photo " + uri);
1116 }
1117 } catch (IOException e) {
1118 Log.e(TAG, "Cannot load photo " + uri, e);
1119 }
1120
1121 return data;
1122 }
1123
1124 @Override
1125 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001126 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001127 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001128 mContact.setPhotoBinaryData(data);
1129 mContact.setLoadingPhoto(false);
1130 deliverResult(mContact);
1131 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001132 }
1133 }
1134
Daniel Lehmann3a120772010-06-21 16:21:35 -07001135 private void unregisterObserver() {
1136 if (mObserver != null) {
1137 getContext().getContentResolver().unregisterContentObserver(mObserver);
1138 mObserver = null;
1139 }
1140 }
1141
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001142 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001143 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001144 }
1145
Dave Santoro39156002011-07-19 01:18:14 -07001146 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001147 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001148 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001149 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001150 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001151 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001152 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001153 }
1154
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001155 public Uri getLookupUri() {
1156 return mLookupUri;
1157 }
1158
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001159 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001160 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001161 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001162 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001163 }
1164
1165 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001166 forceLoad();
1167 }
1168 }
1169
1170 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001171 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001172 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001173 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001174 }
1175
1176 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001177 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001178 unregisterObserver();
1179 mContact = null;
1180 mDestroyed = true;
1181 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001182}