blob: ceaa2463b5d3a928e0947cfa98c6891c878eef82 [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;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700116 private final 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;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700130 private final boolean mSendToVoicemail;
131 private final String mCustomRingtone;
132 private final 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 Onuki870a87e2011-08-12 13:40:31 -0700621
622 // Note ME profile should never have "Add connection"
623 if (mLoadInvitableAccountTypes && !result.isUserProfile()) {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700624 loadInvitableAccountTypes(result);
625 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700626 }
627 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700628 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700629 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700630 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700631 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700632 }
633
634 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700635 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
636 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700637 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700638 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700639 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700640 if (uri == null) throw new IllegalArgumentException("uri must not be null");
641
642 final String authority = uri.getAuthority();
643
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700644 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700645 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700646 final String type = resolver.getType(uri);
647 // Contact-Uri? Good, return it
648 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
649 return uri;
650 }
651
652 // RawContact-Uri? Transform it to ContactUri
653 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
654 final long rawContactId = ContentUris.parseId(uri);
655 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
656 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
657 }
658
659 // Anything else? We don't know what this is
660 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700661 }
662
663 // Legacy Style? Convert to RawContact
664 final String OBSOLETE_AUTHORITY = "contacts";
665 if (OBSOLETE_AUTHORITY.equals(authority)) {
666 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
667 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700668 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700669 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
670 }
671
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700672 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700673 }
674
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700675 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
676 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
677 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
678 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700679 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700680 Log.e(TAG, "No cursor returned in loadContactEntity");
681 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700682 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700683
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700684 try {
685 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700686 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700687 return Result.NOT_FOUND;
688 }
689
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700690 long currentRawContactId = -1;
691 Entity entity = null;
692 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700693 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700694 HashMap<Long, DataStatus> statuses = result.getStatuses();
695 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
696 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
697 if (rawContactId != currentRawContactId) {
698 currentRawContactId = rawContactId;
699 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700700 entities.add(entity);
701 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700702 if (!cursor.isNull(ContactQuery.DATA_ID)) {
703 ContentValues data = loadData(cursor);
704 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
705
706 if (!cursor.isNull(ContactQuery.PRESENCE)
707 || !cursor.isNull(ContactQuery.STATUS)) {
708 final DataStatus status = new DataStatus(cursor);
709 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
710 statuses.put(dataId, status);
711 }
712 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700713 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700714
715 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700716 } finally {
717 cursor.close();
718 }
719 }
720
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700721 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800722 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
723 * not found, returns null
724 */
725 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700726
727 // If we have a photo URI, try loading that first.
728 String photoUri = contactData.getPhotoUri();
729 if (photoUri != null) {
730 try {
731 AssetFileDescriptor fd = getContext().getContentResolver()
732 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
733 byte[] buffer = new byte[16 * 1024];
734 FileInputStream fis = fd.createInputStream();
735 ByteArrayOutputStream baos = new ByteArrayOutputStream();
736 try {
737 int size;
738 while ((size = fis.read(buffer)) != -1) {
739 baos.write(buffer, 0, size);
740 }
741 contactData.setPhotoBinaryData(baos.toByteArray());
742 } finally {
743 fis.close();
744 fd.close();
745 }
746 return;
747 } catch (IOException ioe) {
748 // Just fall back to the case below.
749 }
750 }
751
752 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800753 final long photoId = contactData.getPhotoId();
754 if (photoId <= 0) {
755 // No photo ID
756 return;
757 }
758
759 for (Entity entity : contactData.getEntities()) {
760 for (NamedContentValues subValue : entity.getSubValues()) {
761 final ContentValues entryValues = subValue.values;
762 final long dataId = entryValues.getAsLong(Data._ID);
763 if (dataId == photoId) {
764 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
765 // Correct Data Id but incorrect MimeType? Don't load
766 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
767 return;
768 }
769 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
770 break;
771 }
772 }
773 }
774 }
775
Makoto Onuki3e6991e2011-07-24 14:51:20 -0700776 /**
777 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
778 *
779 * TODO Exclude the ones with no raw contacts in the database.
780 */
Makoto Onuki69b4a882011-07-22 10:05:10 -0700781 private void loadInvitableAccountTypes(Result contactData) {
782 Map<String, AccountType> allInvitables =
783 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
784 if (allInvitables.isEmpty()) {
785 return;
786 }
787
788 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
789
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -0700790 // Remove the ones that already have a raw contact in the current contact
Makoto Onuki69b4a882011-07-22 10:05:10 -0700791 for (Entity entity : contactData.getEntities()) {
792 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
793 if (!TextUtils.isEmpty(type)) {
794 result.remove(type);
795 }
796 }
797
798 // Set to mInvitableAccountTypes
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700799 contactData.mInvitableAccountTypes.addAll(result.values());
Makoto Onuki69b4a882011-07-22 10:05:10 -0700800 }
801
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800802 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700803 * Extracts Contact level columns from the cursor.
804 */
805 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700806 final String directoryParameter =
807 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
808 final long directoryId = directoryParameter == null
809 ? Directory.DEFAULT
810 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700811 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
812 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
813 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
814 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
815 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700816 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700817 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
818 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700819 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700820 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
821 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
822 ? null
823 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700824 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
825 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700826 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700827
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700828 Uri lookupUri;
829 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
830 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700831 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700832 } else {
833 lookupUri = contactUri;
834 }
835
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700836 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700837 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700838 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700839 customRingtone, isUserProfile);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700840 }
841
842 /**
843 * Extracts RawContact level columns from the cursor.
844 */
845 private ContentValues loadRawContact(Cursor cursor) {
846 ContentValues cv = new ContentValues();
847
848 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
849
850 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
851 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700852 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
853 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700854 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
855 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
856 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
857 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
858 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
859 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
860 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
861 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
862 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
863 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700864 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
865
866 return cv;
867 }
868
869 /**
870 * Extracts Data level columns from the cursor.
871 */
872 private ContentValues loadData(Cursor cursor) {
873 ContentValues cv = new ContentValues();
874
875 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
876
877 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
878 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
879 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
880 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
881 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
882 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
883 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
884 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
885 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
886 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
887 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
888 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
889 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
890 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
891 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
892 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
893 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
894 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
895 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
896 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
897 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
898 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
899 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
900 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
901 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700902 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700903
904 return cv;
905 }
906
907 private void cursorColumnToContentValues(
908 Cursor cursor, ContentValues values, int index) {
909 switch (cursor.getType(index)) {
910 case Cursor.FIELD_TYPE_NULL:
911 // don't put anything in the content values
912 break;
913 case Cursor.FIELD_TYPE_INTEGER:
914 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
915 break;
916 case Cursor.FIELD_TYPE_STRING:
917 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
918 break;
919 case Cursor.FIELD_TYPE_BLOB:
920 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
921 break;
922 default:
923 throw new IllegalStateException("Invalid or unhandled data type");
924 }
925 }
926
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700927 private void loadDirectoryMetaData(Result result) {
928 long directoryId = result.getDirectoryId();
929
930 Cursor cursor = getContext().getContentResolver().query(
931 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
932 DirectoryQuery.COLUMNS, null, null, null);
933 if (cursor == null) {
934 return;
935 }
936 try {
937 if (cursor.moveToFirst()) {
938 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
939 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
940 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700941 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700942 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
943 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
944 String directoryType = null;
945 if (!TextUtils.isEmpty(packageName)) {
946 PackageManager pm = getContext().getPackageManager();
947 try {
948 Resources resources = pm.getResourcesForApplication(packageName);
949 directoryType = resources.getString(typeResourceId);
950 } catch (NameNotFoundException e) {
951 Log.w(TAG, "Contact directory resource not found: "
952 + packageName + "." + typeResourceId);
953 }
954 }
955
956 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700957 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700958 }
959 } finally {
960 cursor.close();
961 }
962 }
963
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700964 /**
965 * Loads groups meta-data for all groups associated with all constituent raw contacts'
966 * accounts.
967 */
968 private void loadGroupMetaData(Result result) {
969 StringBuilder selection = new StringBuilder();
970 ArrayList<String> selectionArgs = new ArrayList<String>();
971 for (Entity entity : result.mEntities) {
972 ContentValues values = entity.getEntityValues();
973 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
974 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700975 String dataSet = values.getAsString(RawContacts.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700976 if (accountName != null && accountType != null) {
977 if (selection.length() != 0) {
978 selection.append(" OR ");
979 }
980 selection.append(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700981 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700982 selectionArgs.add(accountName);
983 selectionArgs.add(accountType);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700984
985 if (dataSet != null) {
986 selection.append(" AND " + Groups.DATA_SET + "=?");
987 selectionArgs.add(dataSet);
988 } else {
989 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
990 }
991 selection.append(")");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700992 }
993 }
994 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
995 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
996 null);
997 try {
998 while (cursor.moveToNext()) {
999 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1000 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -07001001 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -07001002 final long groupId = cursor.getLong(GroupQuery.ID);
1003 final String title = cursor.getString(GroupQuery.TITLE);
1004 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1005 ? false
1006 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1007 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1008 ? false
1009 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1010
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001011 result.addGroupMetaData(new GroupMetaData(
Dave Santoro2b3f3c52011-07-26 17:35:42 -07001012 accountName, accountType, dataSet, groupId, title, defaultGroup,
1013 favorites));
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -07001014 }
1015 } finally {
1016 cursor.close();
1017 }
1018 }
1019
Dave Santoro39156002011-07-19 01:18:14 -07001020 /**
1021 * Loads all stream items and stream item photos belonging to this contact.
1022 */
1023 private void loadStreamItems(Result result) {
1024 Cursor cursor = getContext().getContentResolver().query(
1025 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1026 .appendPath(result.getLookupKey())
1027 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1028 null, null, null, null);
1029 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
1030 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1031 try {
1032 while (cursor.moveToNext()) {
1033 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1034 streamItemsById.put(streamItem.getId(), streamItem);
1035 streamItems.add(streamItem);
1036 }
1037 } finally {
1038 cursor.close();
1039 }
1040
1041 // Now retrieve any photo records associated with the stream items.
1042 String[] streamItemIdArr = new String[streamItems.size()];
1043 StringBuilder streamItemPhotoSelection = new StringBuilder();
1044 if (!streamItems.isEmpty()) {
1045 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1046 for (int i = 0; i < streamItems.size(); i++) {
1047 if (i > 0) {
1048 streamItemPhotoSelection.append(",");
1049 }
1050 streamItemPhotoSelection.append("?");
1051 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
1052 }
1053 streamItemPhotoSelection.append(")");
1054 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
1055 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1056 StreamItemPhotos.STREAM_ITEM_ID);
1057 try {
1058 while (cursor.moveToNext()) {
1059 long streamItemId = cursor.getLong(
1060 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1061 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1062 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1063 }
1064 } finally {
1065 cursor.close();
1066 }
1067 }
1068
1069 // Set the sorted stream items on the result.
1070 Collections.sort(streamItems);
1071 result.mStreamItems.addAll(streamItems);
1072 }
1073
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001074 @Override
1075 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001076 unregisterObserver();
1077
Daniel Lehmann1316b132010-04-13 15:08:53 -07001078 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001079 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001080 return;
1081 }
1082
1083 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001084
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001085 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1086 mLookupUri = result.getLookupUri();
1087
1088 if (!result.isDirectoryEntry()) {
1089 Log.i(TAG, "Registering content observer for " + mLookupUri);
1090 if (mObserver == null) {
1091 mObserver = new ForceLoadContentObserver();
1092 }
1093 getContext().getContentResolver().registerContentObserver(
1094 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001095 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001096
1097 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1098 mContact.setLoadingPhoto(true);
1099 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1100 }
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001101
1102 // inform the source of the data that this contact is being looked at
1103 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001104 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001105
1106 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001107 }
1108 }
1109
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001110 /**
1111 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1112 * that the contact has just been loaded
1113 */
1114 private void postViewNotificationToSyncAdapter() {
1115 Context context = getContext();
1116 for (Entity entity : mContact.getEntities()) {
1117 final ContentValues entityValues = entity.getEntityValues();
1118 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1119 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
1120 final AccountType accountType = AccountTypeManager.getInstance(context ).getAccountType(
1121 type, dataSet);
1122 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1123 final String resPackageName = accountType.resPackageName;
1124 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
1125 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1126 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1127 final Intent intent = new Intent();
1128 intent.setClassName(resPackageName, serviceName);
1129 intent.setAction(Intent.ACTION_VIEW);
1130 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1131 try {
1132 context.startService(intent);
1133 } catch (Exception e) {
1134 Log.e(TAG, "Error sending message to source-app", e);
1135 }
1136 }
1137 }
1138 }
1139
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001140 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1141
1142 private static final int BUFFER_SIZE = 1024*16;
1143
1144 @Override
1145 protected byte[] doInBackground(String... params) {
1146 Uri uri = Uri.parse(params[0]);
1147 byte[] data = null;
1148 try {
1149 InputStream is = getContext().getContentResolver().openInputStream(uri);
1150 if (is != null) {
1151 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1152 try {
1153 byte[] mBuffer = new byte[BUFFER_SIZE];
1154
1155 int size;
1156 while ((size = is.read(mBuffer)) != -1) {
1157 baos.write(mBuffer, 0, size);
1158 }
1159 data = baos.toByteArray();
1160 } finally {
1161 is.close();
1162 }
1163 } else {
1164 Log.v(TAG, "Cannot load photo " + uri);
1165 }
1166 } catch (IOException e) {
1167 Log.e(TAG, "Cannot load photo " + uri, e);
1168 }
1169
1170 return data;
1171 }
1172
1173 @Override
1174 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001175 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001176 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001177 mContact.setPhotoBinaryData(data);
1178 mContact.setLoadingPhoto(false);
1179 deliverResult(mContact);
1180 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001181 }
1182 }
1183
Daniel Lehmann3a120772010-06-21 16:21:35 -07001184 private void unregisterObserver() {
1185 if (mObserver != null) {
1186 getContext().getContentResolver().unregisterContentObserver(mObserver);
1187 mObserver = null;
1188 }
1189 }
1190
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001191 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001192 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001193 }
1194
Dave Santoro39156002011-07-19 01:18:14 -07001195 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001196 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001197 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001198 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001199 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001200 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001201 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001202 }
1203
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001204 public Uri getLookupUri() {
1205 return mLookupUri;
1206 }
1207
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001208 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001209 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001210 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001211 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001212 }
1213
1214 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001215 forceLoad();
1216 }
1217 }
1218
1219 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001220 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001221 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001222 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001223 }
1224
1225 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001226 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001227 unregisterObserver();
1228 mContact = null;
1229 mDestroyed = true;
1230 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001231}