blob: e759efe15962748da9e9ed9afcb4288037546b0e [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;
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
Makoto Onuki69b4a882011-07-22 10:05:10 -0700295 public ArrayList<String> getInvitableAccontTypes() {
296 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,
423 RawContacts.DIRTY,
424 RawContacts.VERSION,
425 RawContacts.SOURCE_ID,
426 RawContacts.SYNC1,
427 RawContacts.SYNC2,
428 RawContacts.SYNC3,
429 RawContacts.SYNC4,
430 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700431 RawContacts.NAME_VERIFIED,
432
433 Contacts.Entity.DATA_ID,
434 Data.DATA1,
435 Data.DATA2,
436 Data.DATA3,
437 Data.DATA4,
438 Data.DATA5,
439 Data.DATA6,
440 Data.DATA7,
441 Data.DATA8,
442 Data.DATA9,
443 Data.DATA10,
444 Data.DATA11,
445 Data.DATA12,
446 Data.DATA13,
447 Data.DATA14,
448 Data.DATA15,
449 Data.SYNC1,
450 Data.SYNC2,
451 Data.SYNC3,
452 Data.SYNC4,
453 Data.DATA_VERSION,
454 Data.IS_PRIMARY,
455 Data.IS_SUPER_PRIMARY,
456 Data.MIMETYPE,
457 Data.RES_PACKAGE,
458
459 GroupMembership.GROUP_SOURCE_ID,
460
461 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700462 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700463 Data.STATUS,
464 Data.STATUS_RES_PACKAGE,
465 Data.STATUS_ICON,
466 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700467 Data.STATUS_TIMESTAMP,
468
469 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700470 Contacts.SEND_TO_VOICEMAIL,
471 Contacts.CUSTOM_RINGTONE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700472 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700473
474 public final static int NAME_RAW_CONTACT_ID = 0;
475 public final static int DISPLAY_NAME_SOURCE = 1;
476 public final static int LOOKUP_KEY = 2;
477 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700478 public final static int ALT_DISPLAY_NAME = 4;
479 public final static int PHONETIC_NAME = 5;
480 public final static int PHOTO_ID = 6;
481 public final static int STARRED = 7;
482 public final static int CONTACT_PRESENCE = 8;
483 public final static int CONTACT_STATUS = 9;
484 public final static int CONTACT_STATUS_TIMESTAMP = 10;
485 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
486 public final static int CONTACT_STATUS_LABEL = 12;
487 public final static int CONTACT_ID = 13;
488 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700489
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700490 public final static int ACCOUNT_NAME = 15;
491 public final static int ACCOUNT_TYPE = 16;
492 public final static int DIRTY = 17;
493 public final static int VERSION = 18;
494 public final static int SOURCE_ID = 19;
495 public final static int SYNC1 = 20;
496 public final static int SYNC2 = 21;
497 public final static int SYNC3 = 22;
498 public final static int SYNC4 = 23;
499 public final static int DELETED = 24;
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700500 public final static int NAME_VERIFIED = 25;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700501
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700502 public final static int DATA_ID = 26;
503 public final static int DATA1 = 27;
504 public final static int DATA2 = 28;
505 public final static int DATA3 = 29;
506 public final static int DATA4 = 30;
507 public final static int DATA5 = 31;
508 public final static int DATA6 = 32;
509 public final static int DATA7 = 33;
510 public final static int DATA8 = 34;
511 public final static int DATA9 = 35;
512 public final static int DATA10 = 36;
513 public final static int DATA11 = 37;
514 public final static int DATA12 = 38;
515 public final static int DATA13 = 39;
516 public final static int DATA14 = 40;
517 public final static int DATA15 = 41;
518 public final static int DATA_SYNC1 = 42;
519 public final static int DATA_SYNC2 = 43;
520 public final static int DATA_SYNC3 = 44;
521 public final static int DATA_SYNC4 = 45;
522 public final static int DATA_VERSION = 46;
523 public final static int IS_PRIMARY = 47;
524 public final static int IS_SUPERPRIMARY = 48;
525 public final static int MIMETYPE = 49;
526 public final static int RES_PACKAGE = 50;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700527
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700528 public final static int GROUP_SOURCE_ID = 51;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700529
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700530 public final static int PRESENCE = 52;
531 public final static int CHAT_CAPABILITY = 53;
532 public final static int STATUS = 54;
533 public final static int STATUS_RES_PACKAGE = 55;
534 public final static int STATUS_ICON = 56;
535 public final static int STATUS_LABEL = 57;
536 public final static int STATUS_TIMESTAMP = 58;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700537
Daniel Lehmann4ff31282011-07-11 14:38:36 -0700538 public final static int PHOTO_URI = 59;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700539 public final static int SEND_TO_VOICEMAIL = 60;
540 public final static int CUSTOM_RINGTONE = 61;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700541 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700542
Dave Santoro39156002011-07-19 01:18:14 -0700543 /**
544 * Projection used for the query that loads all data for the entire contact.
545 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700546 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700547 final static String[] COLUMNS = new String[] {
548 Directory.DISPLAY_NAME,
549 Directory.PACKAGE_NAME,
550 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700551 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700552 Directory.ACCOUNT_NAME,
553 Directory.EXPORT_SUPPORT,
554 };
555
556 public final static int DISPLAY_NAME = 0;
557 public final static int PACKAGE_NAME = 1;
558 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700559 public final static int ACCOUNT_TYPE = 3;
560 public final static int ACCOUNT_NAME = 4;
561 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700562 }
563
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700564 private static class GroupQuery {
565 final static String[] COLUMNS = new String[] {
566 Groups.ACCOUNT_NAME,
567 Groups.ACCOUNT_TYPE,
568 Groups._ID,
569 Groups.TITLE,
570 Groups.AUTO_ADD,
571 Groups.FAVORITES,
572 };
573
574 public final static int ACCOUNT_NAME = 0;
575 public final static int ACCOUNT_TYPE = 1;
576 public final static int ID = 2;
577 public final static int TITLE = 3;
578 public final static int AUTO_ADD = 4;
579 public final static int FAVORITES = 5;
580 }
581
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700582 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700583
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700584 @Override
585 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700586 try {
587 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700588 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700589 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700590 if (result != Result.NOT_FOUND) {
591 if (result.isDirectoryEntry()) {
592 loadDirectoryMetaData(result);
593 } else if (mLoadGroupMetaData) {
594 loadGroupMetaData(result);
595 }
Dave Santoro39156002011-07-19 01:18:14 -0700596 if (mLoadStreamItems) {
597 loadStreamItems(result);
598 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800599 loadPhotoBinaryData(result);
Makoto Onuki69b4a882011-07-22 10:05:10 -0700600 if (mLoadInvitableAccountTypes) {
601 loadInvitableAccountTypes(result);
602 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700603 }
604 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700605 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700606 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700607 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700608 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700609 }
610
611 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700612 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
613 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700614 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700615 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700616 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700617 if (uri == null) throw new IllegalArgumentException("uri must not be null");
618
619 final String authority = uri.getAuthority();
620
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700621 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700622 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700623 final String type = resolver.getType(uri);
624 // Contact-Uri? Good, return it
625 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
626 return uri;
627 }
628
629 // RawContact-Uri? Transform it to ContactUri
630 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
631 final long rawContactId = ContentUris.parseId(uri);
632 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
633 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
634 }
635
636 // Anything else? We don't know what this is
637 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700638 }
639
640 // Legacy Style? Convert to RawContact
641 final String OBSOLETE_AUTHORITY = "contacts";
642 if (OBSOLETE_AUTHORITY.equals(authority)) {
643 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
644 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700645 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700646 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
647 }
648
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700649 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700650 }
651
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700652 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
653 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
654 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
655 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700656 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700657 Log.e(TAG, "No cursor returned in loadContactEntity");
658 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700659 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700660
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700661 try {
662 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700663 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700664 return Result.NOT_FOUND;
665 }
666
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700667 long currentRawContactId = -1;
668 Entity entity = null;
669 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700670 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700671 HashMap<Long, DataStatus> statuses = result.getStatuses();
672 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
673 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
674 if (rawContactId != currentRawContactId) {
675 currentRawContactId = rawContactId;
676 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700677 entities.add(entity);
678 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700679 if (!cursor.isNull(ContactQuery.DATA_ID)) {
680 ContentValues data = loadData(cursor);
681 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
682
683 if (!cursor.isNull(ContactQuery.PRESENCE)
684 || !cursor.isNull(ContactQuery.STATUS)) {
685 final DataStatus status = new DataStatus(cursor);
686 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
687 statuses.put(dataId, status);
688 }
689 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700690 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700691
692 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700693 } finally {
694 cursor.close();
695 }
696 }
697
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700698 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800699 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
700 * not found, returns null
701 */
702 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700703
704 // If we have a photo URI, try loading that first.
705 String photoUri = contactData.getPhotoUri();
706 if (photoUri != null) {
707 try {
708 AssetFileDescriptor fd = getContext().getContentResolver()
709 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
710 byte[] buffer = new byte[16 * 1024];
711 FileInputStream fis = fd.createInputStream();
712 ByteArrayOutputStream baos = new ByteArrayOutputStream();
713 try {
714 int size;
715 while ((size = fis.read(buffer)) != -1) {
716 baos.write(buffer, 0, size);
717 }
718 contactData.setPhotoBinaryData(baos.toByteArray());
719 } finally {
720 fis.close();
721 fd.close();
722 }
723 return;
724 } catch (IOException ioe) {
725 // Just fall back to the case below.
726 }
727 }
728
729 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800730 final long photoId = contactData.getPhotoId();
731 if (photoId <= 0) {
732 // No photo ID
733 return;
734 }
735
736 for (Entity entity : contactData.getEntities()) {
737 for (NamedContentValues subValue : entity.getSubValues()) {
738 final ContentValues entryValues = subValue.values;
739 final long dataId = entryValues.getAsLong(Data._ID);
740 if (dataId == photoId) {
741 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
742 // Correct Data Id but incorrect MimeType? Don't load
743 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
744 return;
745 }
746 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
747 break;
748 }
749 }
750 }
751 }
752
Makoto Onuki69b4a882011-07-22 10:05:10 -0700753 private void loadInvitableAccountTypes(Result contactData) {
754 Map<String, AccountType> allInvitables =
755 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
756 if (allInvitables.isEmpty()) {
757 return;
758 }
759
760 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
761
762 // Remove the ones that already has a raw contact in the current contact
763 for (Entity entity : contactData.getEntities()) {
764 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
765 if (!TextUtils.isEmpty(type)) {
766 result.remove(type);
767 }
768 }
769
770 // Set to mInvitableAccountTypes
771 contactData.mInvitableAccountTypes.addAll(result.keySet());
772 }
773
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800774 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700775 * Extracts Contact level columns from the cursor.
776 */
777 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700778 final String directoryParameter =
779 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
780 final long directoryId = directoryParameter == null
781 ? Directory.DEFAULT
782 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700783 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
784 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
785 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
786 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
787 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700788 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700789 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
790 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700791 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700792 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
793 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
794 ? null
795 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700796 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
797 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700798
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700799 Uri lookupUri;
800 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
801 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700802 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700803 } else {
804 lookupUri = contactUri;
805 }
806
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700807 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700808 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700809 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
810 customRingtone);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700811 }
812
813 /**
814 * Extracts RawContact level columns from the cursor.
815 */
816 private ContentValues loadRawContact(Cursor cursor) {
817 ContentValues cv = new ContentValues();
818
819 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
820
821 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
822 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
823 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
824 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
825 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
826 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
827 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
828 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
829 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
830 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
831 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
832 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700833 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
834
835 return cv;
836 }
837
838 /**
839 * Extracts Data level columns from the cursor.
840 */
841 private ContentValues loadData(Cursor cursor) {
842 ContentValues cv = new ContentValues();
843
844 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
845
846 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
847 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
848 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
849 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
850 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
851 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
852 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
853 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
854 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
855 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
856 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
857 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
858 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
859 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
860 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
861 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
862 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
863 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
864 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
865 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
866 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
867 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
868 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
869 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
870 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700871 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700872
873 return cv;
874 }
875
876 private void cursorColumnToContentValues(
877 Cursor cursor, ContentValues values, int index) {
878 switch (cursor.getType(index)) {
879 case Cursor.FIELD_TYPE_NULL:
880 // don't put anything in the content values
881 break;
882 case Cursor.FIELD_TYPE_INTEGER:
883 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
884 break;
885 case Cursor.FIELD_TYPE_STRING:
886 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
887 break;
888 case Cursor.FIELD_TYPE_BLOB:
889 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
890 break;
891 default:
892 throw new IllegalStateException("Invalid or unhandled data type");
893 }
894 }
895
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700896 private void loadDirectoryMetaData(Result result) {
897 long directoryId = result.getDirectoryId();
898
899 Cursor cursor = getContext().getContentResolver().query(
900 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
901 DirectoryQuery.COLUMNS, null, null, null);
902 if (cursor == null) {
903 return;
904 }
905 try {
906 if (cursor.moveToFirst()) {
907 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
908 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
909 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700910 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700911 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
912 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
913 String directoryType = null;
914 if (!TextUtils.isEmpty(packageName)) {
915 PackageManager pm = getContext().getPackageManager();
916 try {
917 Resources resources = pm.getResourcesForApplication(packageName);
918 directoryType = resources.getString(typeResourceId);
919 } catch (NameNotFoundException e) {
920 Log.w(TAG, "Contact directory resource not found: "
921 + packageName + "." + typeResourceId);
922 }
923 }
924
925 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700926 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700927 }
928 } finally {
929 cursor.close();
930 }
931 }
932
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700933 /**
934 * Loads groups meta-data for all groups associated with all constituent raw contacts'
935 * accounts.
936 */
937 private void loadGroupMetaData(Result result) {
938 StringBuilder selection = new StringBuilder();
939 ArrayList<String> selectionArgs = new ArrayList<String>();
940 for (Entity entity : result.mEntities) {
941 ContentValues values = entity.getEntityValues();
942 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
943 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
944 if (accountName != null && accountType != null) {
945 if (selection.length() != 0) {
946 selection.append(" OR ");
947 }
948 selection.append(
949 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?)");
950 selectionArgs.add(accountName);
951 selectionArgs.add(accountType);
952 }
953 }
954 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
955 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
956 null);
957 try {
958 while (cursor.moveToNext()) {
959 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
960 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
961 final long groupId = cursor.getLong(GroupQuery.ID);
962 final String title = cursor.getString(GroupQuery.TITLE);
963 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
964 ? false
965 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
966 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
967 ? false
968 : cursor.getInt(GroupQuery.FAVORITES) != 0;
969
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700970 result.addGroupMetaData(new GroupMetaData(
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700971 accountName, accountType, groupId, title, defaultGroup, favorites));
972 }
973 } finally {
974 cursor.close();
975 }
976 }
977
Dave Santoro39156002011-07-19 01:18:14 -0700978 /**
979 * Loads all stream items and stream item photos belonging to this contact.
980 */
981 private void loadStreamItems(Result result) {
982 Cursor cursor = getContext().getContentResolver().query(
983 Contacts.CONTENT_LOOKUP_URI.buildUpon()
984 .appendPath(result.getLookupKey())
985 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
986 null, null, null, null);
987 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
988 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
989 try {
990 while (cursor.moveToNext()) {
991 StreamItemEntry streamItem = new StreamItemEntry(cursor);
992 streamItemsById.put(streamItem.getId(), streamItem);
993 streamItems.add(streamItem);
994 }
995 } finally {
996 cursor.close();
997 }
998
999 // Now retrieve any photo records associated with the stream items.
1000 String[] streamItemIdArr = new String[streamItems.size()];
1001 StringBuilder streamItemPhotoSelection = new StringBuilder();
1002 if (!streamItems.isEmpty()) {
1003 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1004 for (int i = 0; i < streamItems.size(); i++) {
1005 if (i > 0) {
1006 streamItemPhotoSelection.append(",");
1007 }
1008 streamItemPhotoSelection.append("?");
1009 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
1010 }
1011 streamItemPhotoSelection.append(")");
1012 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
1013 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1014 StreamItemPhotos.STREAM_ITEM_ID);
1015 try {
1016 while (cursor.moveToNext()) {
1017 long streamItemId = cursor.getLong(
1018 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1019 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1020 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1021 }
1022 } finally {
1023 cursor.close();
1024 }
1025 }
1026
1027 // Set the sorted stream items on the result.
1028 Collections.sort(streamItems);
1029 result.mStreamItems.addAll(streamItems);
1030 }
1031
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001032 @Override
1033 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001034 unregisterObserver();
1035
Daniel Lehmann1316b132010-04-13 15:08:53 -07001036 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001037 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001038 return;
1039 }
1040
1041 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001042
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001043 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1044 mLookupUri = result.getLookupUri();
1045
1046 if (!result.isDirectoryEntry()) {
1047 Log.i(TAG, "Registering content observer for " + mLookupUri);
1048 if (mObserver == null) {
1049 mObserver = new ForceLoadContentObserver();
1050 }
1051 getContext().getContentResolver().registerContentObserver(
1052 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001053 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001054
1055 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1056 mContact.setLoadingPhoto(true);
1057 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1058 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001059 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001060
1061 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001062 }
1063 }
1064
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001065 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1066
1067 private static final int BUFFER_SIZE = 1024*16;
1068
1069 @Override
1070 protected byte[] doInBackground(String... params) {
1071 Uri uri = Uri.parse(params[0]);
1072 byte[] data = null;
1073 try {
1074 InputStream is = getContext().getContentResolver().openInputStream(uri);
1075 if (is != null) {
1076 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1077 try {
1078 byte[] mBuffer = new byte[BUFFER_SIZE];
1079
1080 int size;
1081 while ((size = is.read(mBuffer)) != -1) {
1082 baos.write(mBuffer, 0, size);
1083 }
1084 data = baos.toByteArray();
1085 } finally {
1086 is.close();
1087 }
1088 } else {
1089 Log.v(TAG, "Cannot load photo " + uri);
1090 }
1091 } catch (IOException e) {
1092 Log.e(TAG, "Cannot load photo " + uri, e);
1093 }
1094
1095 return data;
1096 }
1097
1098 @Override
1099 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001100 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001101 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001102 mContact.setPhotoBinaryData(data);
1103 mContact.setLoadingPhoto(false);
1104 deliverResult(mContact);
1105 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001106 }
1107 }
1108
Daniel Lehmann3a120772010-06-21 16:21:35 -07001109 private void unregisterObserver() {
1110 if (mObserver != null) {
1111 getContext().getContentResolver().unregisterContentObserver(mObserver);
1112 mObserver = null;
1113 }
1114 }
1115
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001116 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001117 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001118 }
1119
Dave Santoro39156002011-07-19 01:18:14 -07001120 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001121 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001122 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001123 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001124 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001125 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001126 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001127 }
1128
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001129 public Uri getLookupUri() {
1130 return mLookupUri;
1131 }
1132
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001133 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001134 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001135 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001136 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001137 }
1138
1139 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001140 forceLoad();
1141 }
1142 }
1143
1144 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001145 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001146 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001147 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001148 }
1149
1150 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001151 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001152 unregisterObserver();
1153 mContact = null;
1154 mDestroyed = true;
1155 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001156}