blob: 064deb5a7d614bdf2c50819e67526656354bafd7 [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;
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -070033import android.content.Intent;
Jeff Hamilton3c462912010-05-15 02:20:01 -050034import android.content.Loader;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070035import android.content.pm.PackageManager;
36import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070037import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070038import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070039import android.database.Cursor;
40import android.net.Uri;
41import android.os.AsyncTask;
Daniel Lehmann1316b132010-04-13 15:08:53 -070042import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070043import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080044import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070045import android.provider.ContactsContract.Contacts;
46import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070047import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070048import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070049import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070050import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070051import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070052import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070053import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070054import android.util.Log;
55
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080056import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070057import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080058import java.io.IOException;
59import java.io.InputStream;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070060import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070061import java.util.Collections;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070062import java.util.HashMap;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070063import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070064import java.util.Map;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070065
66/**
67 * Loads a single Contact and all it constituent RawContacts.
68 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070069public class ContactLoader extends Loader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070070 private static final String TAG = "ContactLoader";
71
Daniel Lehmann4cd94412010-04-08 16:44:36 -070072 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070073 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070074 private boolean mLoadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -070075 private final boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070076 private Result mContact;
77 private ForceLoadContentObserver mObserver;
78 private boolean mDestroyed;
79
Dmitri Plotnikove843f912010-09-16 15:21:48 -070080
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070081 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070082 public void onContactLoaded(Result contact);
83 }
84
85 /**
86 * The result of a load operation. Contains all data necessary to display the contact.
87 */
88 public static final class Result {
89 /**
90 * Singleton instance that represents "No Contact Found"
91 */
92 public static final Result NOT_FOUND = new Result();
93
Daniel Lehmann18f104f2010-05-07 15:41:11 -070094 /**
95 * Singleton instance that represents an error, e.g. because of an invalid Uri
96 * TODO: We should come up with something nicer here. Maybe use an Either type so
97 * that we can capture the Exception?
98 */
99 public static final Result ERROR = new Result();
100
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700101 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700102 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700103 private final long mDirectoryId;
104 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700105 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700106 private final long mNameRawContactId;
107 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700108 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700109 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700110 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700111 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700112 private final String mPhoneticName;
113 private final boolean mStarred;
114 private final Integer mPresence;
115 private final ArrayList<Entity> mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700116 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700117 private final HashMap<Long, DataStatus> mStatuses;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700118 private final ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700119
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700120 private String mDirectoryDisplayName;
121 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700122 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700123 private String mDirectoryAccountName;
124 private int mDirectoryExportSupport;
125
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700126 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700127
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800128 private boolean mLoadingPhoto;
129 private byte[] mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700130 private boolean mSendToVoicemail;
131 private String mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700132 private boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800133
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700134 /**
135 * Constructor for case "no contact found". This must only be used for the
136 * final {@link Result#NOT_FOUND} singleton
137 */
138 private Result() {
139 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700140 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700141 mDirectoryId = -1;
142 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700143 mId = -1;
144 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700145 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700146 mStatuses = null;
147 mNameRawContactId = -1;
148 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700149 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700150 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700151 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700152 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700153 mPhoneticName = null;
154 mStarred = false;
155 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700156 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700157 mSendToVoicemail = false;
158 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700159 mIsUserProfile = false;
160
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700161 }
162
163 /**
164 * Constructor to call when contact was found
165 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700166 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700167 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700168 String displayName, String altDisplayName, String phoneticName, boolean starred,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700169 Integer presence, boolean sendToVoicemail, String customRingtone,
170 boolean isUserProfile) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700171 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700172 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700173 mDirectoryId = directoryId;
174 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700175 mId = id;
176 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700177 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700178 mStatuses = new HashMap<Long, DataStatus>();
179 mNameRawContactId = nameRawContactId;
180 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700181 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700182 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700183 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700184 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700185 mPhoneticName = phoneticName;
186 mStarred = starred;
187 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700188 mInvitableAccountTypes = Lists.newArrayList();
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700189 mSendToVoicemail = sendToVoicemail;
190 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700191 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700192 }
193
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800194 private Result(Result from) {
195 mLookupUri = from.mLookupUri;
196 mUri = from.mUri;
197 mDirectoryId = from.mDirectoryId;
198 mLookupKey = from.mLookupKey;
199 mId = from.mId;
200 mNameRawContactId = from.mNameRawContactId;
201 mDisplayNameSource = from.mDisplayNameSource;
202 mPhotoId = from.mPhotoId;
203 mPhotoUri = from.mPhotoUri;
204 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700205 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800206 mPhoneticName = from.mPhoneticName;
207 mStarred = from.mStarred;
208 mPresence = from.mPresence;
209 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700210 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800211 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700212 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800213
214 mDirectoryDisplayName = from.mDirectoryDisplayName;
215 mDirectoryType = from.mDirectoryType;
216 mDirectoryAccountType = from.mDirectoryAccountType;
217 mDirectoryAccountName = from.mDirectoryAccountName;
218 mDirectoryExportSupport = from.mDirectoryExportSupport;
219
220 mGroups = from.mGroups;
221
222 mLoadingPhoto = from.mLoadingPhoto;
223 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700224 mSendToVoicemail = from.mSendToVoicemail;
225 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700226 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800227 }
228
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700229 /**
230 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
231 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700232 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700233 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700234 mDirectoryDisplayName = displayName;
235 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700236 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700237 mDirectoryAccountName = accountName;
238 mDirectoryExportSupport = exportSupport;
239 }
240
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800241 private void setLoadingPhoto(boolean flag) {
242 mLoadingPhoto = flag;
243 }
244
245 private void setPhotoBinaryData(byte[] photoBinaryData) {
246 mPhotoBinaryData = photoBinaryData;
247 }
248
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700249 public Uri getLookupUri() {
250 return mLookupUri;
251 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800252
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700253 public String getLookupKey() {
254 return mLookupKey;
255 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800256
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700257 public Uri getUri() {
258 return mUri;
259 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800260
Flavio Lerda37a26842011-06-27 11:36:52 +0100261 @VisibleForTesting
262 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700263 return mId;
264 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800265
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700266 public long getNameRawContactId() {
267 return mNameRawContactId;
268 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800269
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700270 public int getDisplayNameSource() {
271 return mDisplayNameSource;
272 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800273
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700274 public long getPhotoId() {
275 return mPhotoId;
276 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800277
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700278 public String getPhotoUri() {
279 return mPhotoUri;
280 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800281
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700282 public String getDisplayName() {
283 return mDisplayName;
284 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800285
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700286 public String getAltDisplayName() {
287 return mAltDisplayName;
288 }
289
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700290 public String getPhoneticName() {
291 return mPhoneticName;
292 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800293
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700294 public boolean getStarred() {
295 return mStarred;
296 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800297
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700298 public Integer getPresence() {
299 return mPresence;
300 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800301
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700302 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700303 return mInvitableAccountTypes;
304 }
305
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700306 public ArrayList<Entity> getEntities() {
307 return mEntities;
308 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800309
Dave Santoro39156002011-07-19 01:18:14 -0700310 public ArrayList<StreamItemEntry> getStreamItems() {
311 return mStreamItems;
312 }
313
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700314 public HashMap<Long, DataStatus> getStatuses() {
315 return mStatuses;
316 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700317
318 public long getDirectoryId() {
319 return mDirectoryId;
320 }
321
322 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700323 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
324 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700325 }
326
327 public int getDirectoryExportSupport() {
328 return mDirectoryExportSupport;
329 }
330
331 public String getDirectoryDisplayName() {
332 return mDirectoryDisplayName;
333 }
334
335 public String getDirectoryType() {
336 return mDirectoryType;
337 }
338
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700339 public String getDirectoryAccountType() {
340 return mDirectoryAccountType;
341 }
342
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700343 public String getDirectoryAccountName() {
344 return mDirectoryAccountName;
345 }
346
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800347 public boolean isLoadingPhoto() {
348 return mLoadingPhoto;
349 }
350
351 public byte[] getPhotoBinaryData() {
352 return mPhotoBinaryData;
353 }
354
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700355 public ArrayList<ContentValues> getContentValues() {
356 if (mEntities.size() != 1) {
357 throw new IllegalStateException(
358 "Cannot extract content values from an aggregated contact");
359 }
360
361 Entity entity = mEntities.get(0);
362 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
363 ArrayList<NamedContentValues> subValues = entity.getSubValues();
364 if (subValues != null) {
365 int size = subValues.size();
366 for (int i = 0; i < size; i++) {
367 NamedContentValues pair = subValues.get(i);
368 if (Data.CONTENT_URI.equals(pair.uri)) {
369 result.add(pair.values);
370 }
371 }
372 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800373
374 // If the photo was loaded using the URI, create an entry for the photo
375 // binary data.
376 if (mPhotoId == 0 && mPhotoBinaryData != null) {
377 ContentValues photo = new ContentValues();
378 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
379 photo.put(Photo.PHOTO, mPhotoBinaryData);
380 result.add(photo);
381 }
382
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700383 return result;
384 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700385
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700386 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700387 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700388 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700389 }
390 mGroups.add(group);
391 }
392
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700393 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700394 return mGroups;
395 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700396
397 public boolean isSendToVoicemail() {
398 return mSendToVoicemail;
399 }
400
401 public String getCustomRingtone() {
402 return mCustomRingtone;
403 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700404
405 public boolean isUserProfile() {
406 return mIsUserProfile;
407 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700408 }
409
Dave Santoro39156002011-07-19 01:18:14 -0700410 /**
411 * Projection used for the query that loads all data for the entire contact (except for
412 * social stream items).
413 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700414 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700415 final static String[] COLUMNS = new String[] {
416 Contacts.NAME_RAW_CONTACT_ID,
417 Contacts.DISPLAY_NAME_SOURCE,
418 Contacts.LOOKUP_KEY,
419 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700420 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700421 Contacts.PHONETIC_NAME,
422 Contacts.PHOTO_ID,
423 Contacts.STARRED,
424 Contacts.CONTACT_PRESENCE,
425 Contacts.CONTACT_STATUS,
426 Contacts.CONTACT_STATUS_TIMESTAMP,
427 Contacts.CONTACT_STATUS_RES_PACKAGE,
428 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700429 Contacts.Entity.CONTACT_ID,
430 Contacts.Entity.RAW_CONTACT_ID,
431
432 RawContacts.ACCOUNT_NAME,
433 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700434 RawContacts.DATA_SET,
435 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700436 RawContacts.DIRTY,
437 RawContacts.VERSION,
438 RawContacts.SOURCE_ID,
439 RawContacts.SYNC1,
440 RawContacts.SYNC2,
441 RawContacts.SYNC3,
442 RawContacts.SYNC4,
443 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700444 RawContacts.NAME_VERIFIED,
445
446 Contacts.Entity.DATA_ID,
447 Data.DATA1,
448 Data.DATA2,
449 Data.DATA3,
450 Data.DATA4,
451 Data.DATA5,
452 Data.DATA6,
453 Data.DATA7,
454 Data.DATA8,
455 Data.DATA9,
456 Data.DATA10,
457 Data.DATA11,
458 Data.DATA12,
459 Data.DATA13,
460 Data.DATA14,
461 Data.DATA15,
462 Data.SYNC1,
463 Data.SYNC2,
464 Data.SYNC3,
465 Data.SYNC4,
466 Data.DATA_VERSION,
467 Data.IS_PRIMARY,
468 Data.IS_SUPER_PRIMARY,
469 Data.MIMETYPE,
470 Data.RES_PACKAGE,
471
472 GroupMembership.GROUP_SOURCE_ID,
473
474 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700475 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700476 Data.STATUS,
477 Data.STATUS_RES_PACKAGE,
478 Data.STATUS_ICON,
479 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700480 Data.STATUS_TIMESTAMP,
481
482 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700483 Contacts.SEND_TO_VOICEMAIL,
484 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700485 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700486 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700487
488 public final static int NAME_RAW_CONTACT_ID = 0;
489 public final static int DISPLAY_NAME_SOURCE = 1;
490 public final static int LOOKUP_KEY = 2;
491 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700492 public final static int ALT_DISPLAY_NAME = 4;
493 public final static int PHONETIC_NAME = 5;
494 public final static int PHOTO_ID = 6;
495 public final static int STARRED = 7;
496 public final static int CONTACT_PRESENCE = 8;
497 public final static int CONTACT_STATUS = 9;
498 public final static int CONTACT_STATUS_TIMESTAMP = 10;
499 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
500 public final static int CONTACT_STATUS_LABEL = 12;
501 public final static int CONTACT_ID = 13;
502 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700503
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700504 public final static int ACCOUNT_NAME = 15;
505 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700506 public final static int DATA_SET = 17;
507 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
508 public final static int DIRTY = 19;
509 public final static int VERSION = 20;
510 public final static int SOURCE_ID = 21;
511 public final static int SYNC1 = 22;
512 public final static int SYNC2 = 23;
513 public final static int SYNC3 = 24;
514 public final static int SYNC4 = 25;
515 public final static int DELETED = 26;
516 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700517
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700518 public final static int DATA_ID = 28;
519 public final static int DATA1 = 29;
520 public final static int DATA2 = 30;
521 public final static int DATA3 = 31;
522 public final static int DATA4 = 32;
523 public final static int DATA5 = 33;
524 public final static int DATA6 = 34;
525 public final static int DATA7 = 35;
526 public final static int DATA8 = 36;
527 public final static int DATA9 = 37;
528 public final static int DATA10 = 38;
529 public final static int DATA11 = 39;
530 public final static int DATA12 = 40;
531 public final static int DATA13 = 41;
532 public final static int DATA14 = 42;
533 public final static int DATA15 = 43;
534 public final static int DATA_SYNC1 = 44;
535 public final static int DATA_SYNC2 = 45;
536 public final static int DATA_SYNC3 = 46;
537 public final static int DATA_SYNC4 = 47;
538 public final static int DATA_VERSION = 48;
539 public final static int IS_PRIMARY = 49;
540 public final static int IS_SUPERPRIMARY = 50;
541 public final static int MIMETYPE = 51;
542 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700543
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700544 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700545
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700546 public final static int PRESENCE = 54;
547 public final static int CHAT_CAPABILITY = 55;
548 public final static int STATUS = 56;
549 public final static int STATUS_RES_PACKAGE = 57;
550 public final static int STATUS_ICON = 58;
551 public final static int STATUS_LABEL = 59;
552 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700553
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700554 public final static int PHOTO_URI = 61;
555 public final static int SEND_TO_VOICEMAIL = 62;
556 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700557 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700558 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700559
Dave Santoro39156002011-07-19 01:18:14 -0700560 /**
561 * Projection used for the query that loads all data for the entire contact.
562 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700563 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700564 final static String[] COLUMNS = new String[] {
565 Directory.DISPLAY_NAME,
566 Directory.PACKAGE_NAME,
567 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700568 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700569 Directory.ACCOUNT_NAME,
570 Directory.EXPORT_SUPPORT,
571 };
572
573 public final static int DISPLAY_NAME = 0;
574 public final static int PACKAGE_NAME = 1;
575 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700576 public final static int ACCOUNT_TYPE = 3;
577 public final static int ACCOUNT_NAME = 4;
578 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700579 }
580
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700581 private static class GroupQuery {
582 final static String[] COLUMNS = new String[] {
583 Groups.ACCOUNT_NAME,
584 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700585 Groups.DATA_SET,
586 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700587 Groups._ID,
588 Groups.TITLE,
589 Groups.AUTO_ADD,
590 Groups.FAVORITES,
591 };
592
593 public final static int ACCOUNT_NAME = 0;
594 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700595 public final static int DATA_SET = 2;
596 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
597 public final static int ID = 4;
598 public final static int TITLE = 5;
599 public final static int AUTO_ADD = 6;
600 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700601 }
602
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700603 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700604
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700605 @Override
606 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700607 try {
608 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700609 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700610 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700611 if (result != Result.NOT_FOUND) {
612 if (result.isDirectoryEntry()) {
613 loadDirectoryMetaData(result);
614 } else if (mLoadGroupMetaData) {
615 loadGroupMetaData(result);
616 }
Dave Santoro39156002011-07-19 01:18:14 -0700617 if (mLoadStreamItems) {
618 loadStreamItems(result);
619 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800620 loadPhotoBinaryData(result);
Makoto Onuki69b4a882011-07-22 10:05:10 -0700621 if (mLoadInvitableAccountTypes) {
622 loadInvitableAccountTypes(result);
623 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700624 }
625 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700626 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700627 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700628 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700629 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700630 }
631
632 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700633 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
634 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700635 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700636 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700637 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700638 if (uri == null) throw new IllegalArgumentException("uri must not be null");
639
640 final String authority = uri.getAuthority();
641
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700642 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700643 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700644 final String type = resolver.getType(uri);
645 // Contact-Uri? Good, return it
646 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
647 return uri;
648 }
649
650 // RawContact-Uri? Transform it to ContactUri
651 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
652 final long rawContactId = ContentUris.parseId(uri);
653 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
654 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
655 }
656
657 // Anything else? We don't know what this is
658 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700659 }
660
661 // Legacy Style? Convert to RawContact
662 final String OBSOLETE_AUTHORITY = "contacts";
663 if (OBSOLETE_AUTHORITY.equals(authority)) {
664 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
665 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700666 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700667 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
668 }
669
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700670 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700671 }
672
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700673 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
674 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
675 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
676 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700677 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700678 Log.e(TAG, "No cursor returned in loadContactEntity");
679 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700680 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700681
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700682 try {
683 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700684 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700685 return Result.NOT_FOUND;
686 }
687
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700688 long currentRawContactId = -1;
689 Entity entity = null;
690 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700691 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700692 HashMap<Long, DataStatus> statuses = result.getStatuses();
693 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
694 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
695 if (rawContactId != currentRawContactId) {
696 currentRawContactId = rawContactId;
697 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700698 entities.add(entity);
699 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700700 if (!cursor.isNull(ContactQuery.DATA_ID)) {
701 ContentValues data = loadData(cursor);
702 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
703
704 if (!cursor.isNull(ContactQuery.PRESENCE)
705 || !cursor.isNull(ContactQuery.STATUS)) {
706 final DataStatus status = new DataStatus(cursor);
707 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
708 statuses.put(dataId, status);
709 }
710 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700711 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700712
713 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700714 } finally {
715 cursor.close();
716 }
717 }
718
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700719 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800720 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
721 * not found, returns null
722 */
723 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700724
725 // If we have a photo URI, try loading that first.
726 String photoUri = contactData.getPhotoUri();
727 if (photoUri != null) {
728 try {
729 AssetFileDescriptor fd = getContext().getContentResolver()
730 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
731 byte[] buffer = new byte[16 * 1024];
732 FileInputStream fis = fd.createInputStream();
733 ByteArrayOutputStream baos = new ByteArrayOutputStream();
734 try {
735 int size;
736 while ((size = fis.read(buffer)) != -1) {
737 baos.write(buffer, 0, size);
738 }
739 contactData.setPhotoBinaryData(baos.toByteArray());
740 } finally {
741 fis.close();
742 fd.close();
743 }
744 return;
745 } catch (IOException ioe) {
746 // Just fall back to the case below.
747 }
748 }
749
750 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800751 final long photoId = contactData.getPhotoId();
752 if (photoId <= 0) {
753 // No photo ID
754 return;
755 }
756
757 for (Entity entity : contactData.getEntities()) {
758 for (NamedContentValues subValue : entity.getSubValues()) {
759 final ContentValues entryValues = subValue.values;
760 final long dataId = entryValues.getAsLong(Data._ID);
761 if (dataId == photoId) {
762 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
763 // Correct Data Id but incorrect MimeType? Don't load
764 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
765 return;
766 }
767 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
768 break;
769 }
770 }
771 }
772 }
773
Makoto Onuki3e6991e2011-07-24 14:51:20 -0700774 /**
775 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
776 *
777 * TODO Exclude the ones with no raw contacts in the database.
778 */
Makoto Onuki69b4a882011-07-22 10:05:10 -0700779 private void loadInvitableAccountTypes(Result contactData) {
780 Map<String, AccountType> allInvitables =
781 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
782 if (allInvitables.isEmpty()) {
783 return;
784 }
785
786 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
787
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -0700788 // Remove the ones that already have a raw contact in the current contact
Makoto Onuki69b4a882011-07-22 10:05:10 -0700789 for (Entity entity : contactData.getEntities()) {
790 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
791 if (!TextUtils.isEmpty(type)) {
792 result.remove(type);
793 }
794 }
795
796 // Set to mInvitableAccountTypes
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700797 contactData.mInvitableAccountTypes.addAll(result.values());
Makoto Onuki69b4a882011-07-22 10:05:10 -0700798 }
799
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800800 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700801 * Extracts Contact level columns from the cursor.
802 */
803 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700804 final String directoryParameter =
805 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
806 final long directoryId = directoryParameter == null
807 ? Directory.DEFAULT
808 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700809 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
810 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
811 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
812 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
813 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700814 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700815 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
816 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700817 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700818 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
819 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
820 ? null
821 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700822 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
823 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700824 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700825
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700826 Uri lookupUri;
827 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
828 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700829 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700830 } else {
831 lookupUri = contactUri;
832 }
833
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700834 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700835 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700836 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700837 customRingtone, isUserProfile);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700838 }
839
840 /**
841 * Extracts RawContact level columns from the cursor.
842 */
843 private ContentValues loadRawContact(Cursor cursor) {
844 ContentValues cv = new ContentValues();
845
846 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
847
848 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
849 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700850 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
851 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700852 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
853 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
854 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
855 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
856 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
857 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
858 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
859 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
860 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
861 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700862 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
863
864 return cv;
865 }
866
867 /**
868 * Extracts Data level columns from the cursor.
869 */
870 private ContentValues loadData(Cursor cursor) {
871 ContentValues cv = new ContentValues();
872
873 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
874
875 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
876 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
877 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
878 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
879 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
880 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
881 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
882 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
883 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
884 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
885 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
886 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
887 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
888 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
889 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
890 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
891 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
892 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
893 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
894 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
895 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
896 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
897 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
898 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
899 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700900 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700901
902 return cv;
903 }
904
905 private void cursorColumnToContentValues(
906 Cursor cursor, ContentValues values, int index) {
907 switch (cursor.getType(index)) {
908 case Cursor.FIELD_TYPE_NULL:
909 // don't put anything in the content values
910 break;
911 case Cursor.FIELD_TYPE_INTEGER:
912 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
913 break;
914 case Cursor.FIELD_TYPE_STRING:
915 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
916 break;
917 case Cursor.FIELD_TYPE_BLOB:
918 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
919 break;
920 default:
921 throw new IllegalStateException("Invalid or unhandled data type");
922 }
923 }
924
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700925 private void loadDirectoryMetaData(Result result) {
926 long directoryId = result.getDirectoryId();
927
928 Cursor cursor = getContext().getContentResolver().query(
929 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
930 DirectoryQuery.COLUMNS, null, null, null);
931 if (cursor == null) {
932 return;
933 }
934 try {
935 if (cursor.moveToFirst()) {
936 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
937 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
938 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700939 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700940 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
941 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
942 String directoryType = null;
943 if (!TextUtils.isEmpty(packageName)) {
944 PackageManager pm = getContext().getPackageManager();
945 try {
946 Resources resources = pm.getResourcesForApplication(packageName);
947 directoryType = resources.getString(typeResourceId);
948 } catch (NameNotFoundException e) {
949 Log.w(TAG, "Contact directory resource not found: "
950 + packageName + "." + typeResourceId);
951 }
952 }
953
954 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700955 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700956 }
957 } finally {
958 cursor.close();
959 }
960 }
961
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700962 /**
963 * Loads groups meta-data for all groups associated with all constituent raw contacts'
964 * accounts.
965 */
966 private void loadGroupMetaData(Result result) {
967 StringBuilder selection = new StringBuilder();
968 ArrayList<String> selectionArgs = new ArrayList<String>();
969 for (Entity entity : result.mEntities) {
970 ContentValues values = entity.getEntityValues();
971 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
972 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700973 String dataSet = values.getAsString(RawContacts.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700974 if (accountName != null && accountType != null) {
975 if (selection.length() != 0) {
976 selection.append(" OR ");
977 }
978 selection.append(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700979 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700980 selectionArgs.add(accountName);
981 selectionArgs.add(accountType);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700982
983 if (dataSet != null) {
984 selection.append(" AND " + Groups.DATA_SET + "=?");
985 selectionArgs.add(dataSet);
986 } else {
987 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
988 }
989 selection.append(")");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700990 }
991 }
992 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
993 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
994 null);
995 try {
996 while (cursor.moveToNext()) {
997 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
998 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700999 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -07001000 final long groupId = cursor.getLong(GroupQuery.ID);
1001 final String title = cursor.getString(GroupQuery.TITLE);
1002 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1003 ? false
1004 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1005 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1006 ? false
1007 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1008
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001009 result.addGroupMetaData(new GroupMetaData(
Dave Santoro2b3f3c52011-07-26 17:35:42 -07001010 accountName, accountType, dataSet, groupId, title, defaultGroup,
1011 favorites));
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -07001012 }
1013 } finally {
1014 cursor.close();
1015 }
1016 }
1017
Dave Santoro39156002011-07-19 01:18:14 -07001018 /**
1019 * Loads all stream items and stream item photos belonging to this contact.
1020 */
1021 private void loadStreamItems(Result result) {
1022 Cursor cursor = getContext().getContentResolver().query(
1023 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1024 .appendPath(result.getLookupKey())
1025 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1026 null, null, null, null);
1027 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
1028 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1029 try {
1030 while (cursor.moveToNext()) {
1031 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1032 streamItemsById.put(streamItem.getId(), streamItem);
1033 streamItems.add(streamItem);
1034 }
1035 } finally {
1036 cursor.close();
1037 }
1038
1039 // Now retrieve any photo records associated with the stream items.
1040 String[] streamItemIdArr = new String[streamItems.size()];
1041 StringBuilder streamItemPhotoSelection = new StringBuilder();
1042 if (!streamItems.isEmpty()) {
1043 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1044 for (int i = 0; i < streamItems.size(); i++) {
1045 if (i > 0) {
1046 streamItemPhotoSelection.append(",");
1047 }
1048 streamItemPhotoSelection.append("?");
1049 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
1050 }
1051 streamItemPhotoSelection.append(")");
1052 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
1053 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1054 StreamItemPhotos.STREAM_ITEM_ID);
1055 try {
1056 while (cursor.moveToNext()) {
1057 long streamItemId = cursor.getLong(
1058 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1059 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1060 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1061 }
1062 } finally {
1063 cursor.close();
1064 }
1065 }
1066
1067 // Set the sorted stream items on the result.
1068 Collections.sort(streamItems);
1069 result.mStreamItems.addAll(streamItems);
1070 }
1071
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001072 @Override
1073 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001074 unregisterObserver();
1075
Daniel Lehmann1316b132010-04-13 15:08:53 -07001076 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001077 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001078 return;
1079 }
1080
1081 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001082
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001083 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1084 mLookupUri = result.getLookupUri();
1085
1086 if (!result.isDirectoryEntry()) {
1087 Log.i(TAG, "Registering content observer for " + mLookupUri);
1088 if (mObserver == null) {
1089 mObserver = new ForceLoadContentObserver();
1090 }
1091 getContext().getContentResolver().registerContentObserver(
1092 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001093 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001094
1095 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1096 mContact.setLoadingPhoto(true);
1097 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1098 }
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001099
1100 // inform the source of the data that this contact is being looked at
1101 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001102 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001103
1104 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001105 }
1106 }
1107
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001108 /**
1109 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1110 * that the contact has just been loaded
1111 */
1112 private void postViewNotificationToSyncAdapter() {
1113 Context context = getContext();
1114 for (Entity entity : mContact.getEntities()) {
1115 final ContentValues entityValues = entity.getEntityValues();
1116 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1117 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
1118 final AccountType accountType = AccountTypeManager.getInstance(context ).getAccountType(
1119 type, dataSet);
1120 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1121 final String resPackageName = accountType.resPackageName;
1122 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
1123 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1124 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1125 final Intent intent = new Intent();
1126 intent.setClassName(resPackageName, serviceName);
1127 intent.setAction(Intent.ACTION_VIEW);
1128 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1129 try {
1130 context.startService(intent);
1131 } catch (Exception e) {
1132 Log.e(TAG, "Error sending message to source-app", e);
1133 }
1134 }
1135 }
1136 }
1137
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001138 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1139
1140 private static final int BUFFER_SIZE = 1024*16;
1141
1142 @Override
1143 protected byte[] doInBackground(String... params) {
1144 Uri uri = Uri.parse(params[0]);
1145 byte[] data = null;
1146 try {
1147 InputStream is = getContext().getContentResolver().openInputStream(uri);
1148 if (is != null) {
1149 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1150 try {
1151 byte[] mBuffer = new byte[BUFFER_SIZE];
1152
1153 int size;
1154 while ((size = is.read(mBuffer)) != -1) {
1155 baos.write(mBuffer, 0, size);
1156 }
1157 data = baos.toByteArray();
1158 } finally {
1159 is.close();
1160 }
1161 } else {
1162 Log.v(TAG, "Cannot load photo " + uri);
1163 }
1164 } catch (IOException e) {
1165 Log.e(TAG, "Cannot load photo " + uri, e);
1166 }
1167
1168 return data;
1169 }
1170
1171 @Override
1172 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001173 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001174 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001175 mContact.setPhotoBinaryData(data);
1176 mContact.setLoadingPhoto(false);
1177 deliverResult(mContact);
1178 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001179 }
1180 }
1181
Daniel Lehmann3a120772010-06-21 16:21:35 -07001182 private void unregisterObserver() {
1183 if (mObserver != null) {
1184 getContext().getContentResolver().unregisterContentObserver(mObserver);
1185 mObserver = null;
1186 }
1187 }
1188
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001189 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001190 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001191 }
1192
Dave Santoro39156002011-07-19 01:18:14 -07001193 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001194 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001195 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001196 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001197 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001198 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001199 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001200 }
1201
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001202 public Uri getLookupUri() {
1203 return mLookupUri;
1204 }
1205
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001206 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001207 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001208 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001209 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001210 }
1211
1212 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001213 forceLoad();
1214 }
1215 }
1216
1217 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001218 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001219 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001220 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001221 }
1222
1223 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001224 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001225 unregisterObserver();
1226 mContact = null;
1227 mDestroyed = true;
1228 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001229}