blob: 8416721625c53b08b2a5b5bde77ba0391c492013 [file] [log] [blame]
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001/*
Daniel Lehmannef3f8f02010-07-26 18:55:25 -07002 * Copyright (C) 2010 The Android Open Source Project
Daniel Lehmann4cd94412010-04-08 16:44:36 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070018
Makoto Onuki69b4a882011-07-22 10:05:10 -070019import com.android.contacts.model.AccountType;
20import com.android.contacts.model.AccountTypeManager;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070021import com.android.contacts.util.DataStatus;
Dave Santoro39156002011-07-19 01:18:14 -070022import com.android.contacts.util.StreamItemEntry;
23import com.android.contacts.util.StreamItemPhotoEntry;
Makoto Onuki69b4a882011-07-22 10:05:10 -070024import com.google.android.collect.Lists;
Flavio Lerda37a26842011-06-27 11:36:52 +010025import com.google.common.annotations.VisibleForTesting;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070026
27import android.content.ContentResolver;
28import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070029import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070030import android.content.Context;
31import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070032import android.content.Entity.NamedContentValues;
Jeff Hamilton3c462912010-05-15 02:20:01 -050033import android.content.Loader;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070034import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070036import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070037import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070038import android.database.Cursor;
39import android.net.Uri;
40import android.os.AsyncTask;
Daniel Lehmann1316b132010-04-13 15:08:53 -070041import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070042import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080043import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070044import android.provider.ContactsContract.Contacts;
45import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070046import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070047import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070048import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070049import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070050import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070051import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070052import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070053import android.util.Log;
54
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080055import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070056import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080057import java.io.IOException;
58import java.io.InputStream;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070059import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070060import java.util.Collections;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070061import java.util.HashMap;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070062import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070063import java.util.Map;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070064
65/**
66 * Loads a single Contact and all it constituent RawContacts.
67 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070068public class ContactLoader extends Loader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070069 private static final String TAG = "ContactLoader";
70
Daniel Lehmann4cd94412010-04-08 16:44:36 -070071 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070072 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070073 private boolean mLoadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -070074 private final boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070075 private Result mContact;
76 private ForceLoadContentObserver mObserver;
77 private boolean mDestroyed;
78
Dmitri Plotnikove843f912010-09-16 15:21:48 -070079
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070080 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070081 public void onContactLoaded(Result contact);
82 }
83
84 /**
85 * The result of a load operation. Contains all data necessary to display the contact.
86 */
87 public static final class Result {
88 /**
89 * Singleton instance that represents "No Contact Found"
90 */
91 public static final Result NOT_FOUND = new Result();
92
Daniel Lehmann18f104f2010-05-07 15:41:11 -070093 /**
94 * Singleton instance that represents an error, e.g. because of an invalid Uri
95 * TODO: We should come up with something nicer here. Maybe use an Either type so
96 * that we can capture the Exception?
97 */
98 public static final Result ERROR = new Result();
99
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700100 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700101 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700102 private final long mDirectoryId;
103 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700104 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700105 private final long mNameRawContactId;
106 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700107 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700108 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700109 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700110 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700111 private final String mPhoneticName;
112 private final boolean mStarred;
113 private final Integer mPresence;
114 private final ArrayList<Entity> mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700115 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700116 private final HashMap<Long, DataStatus> mStatuses;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700117 private final ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700118
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700119 private String mDirectoryDisplayName;
120 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700121 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700122 private String mDirectoryAccountName;
123 private int mDirectoryExportSupport;
124
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700125 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700126
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800127 private boolean mLoadingPhoto;
128 private byte[] mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700129 private boolean mSendToVoicemail;
130 private String mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700131 private boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800132
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700133 /**
134 * Constructor for case "no contact found". This must only be used for the
135 * final {@link Result#NOT_FOUND} singleton
136 */
137 private Result() {
138 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700139 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700140 mDirectoryId = -1;
141 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700142 mId = -1;
143 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700144 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700145 mStatuses = null;
146 mNameRawContactId = -1;
147 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700148 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700149 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700150 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700151 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700152 mPhoneticName = null;
153 mStarred = false;
154 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700155 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700156 mSendToVoicemail = false;
157 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700158 mIsUserProfile = false;
159
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700160 }
161
162 /**
163 * Constructor to call when contact was found
164 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700165 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700166 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700167 String displayName, String altDisplayName, String phoneticName, boolean starred,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700168 Integer presence, boolean sendToVoicemail, String customRingtone,
169 boolean isUserProfile) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700170 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700171 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700172 mDirectoryId = directoryId;
173 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700174 mId = id;
175 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700176 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700177 mStatuses = new HashMap<Long, DataStatus>();
178 mNameRawContactId = nameRawContactId;
179 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700180 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700181 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700182 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700183 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700184 mPhoneticName = phoneticName;
185 mStarred = starred;
186 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700187 mInvitableAccountTypes = Lists.newArrayList();
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700188 mSendToVoicemail = sendToVoicemail;
189 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700190 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700191 }
192
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800193 private Result(Result from) {
194 mLookupUri = from.mLookupUri;
195 mUri = from.mUri;
196 mDirectoryId = from.mDirectoryId;
197 mLookupKey = from.mLookupKey;
198 mId = from.mId;
199 mNameRawContactId = from.mNameRawContactId;
200 mDisplayNameSource = from.mDisplayNameSource;
201 mPhotoId = from.mPhotoId;
202 mPhotoUri = from.mPhotoUri;
203 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700204 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800205 mPhoneticName = from.mPhoneticName;
206 mStarred = from.mStarred;
207 mPresence = from.mPresence;
208 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700209 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800210 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700211 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800212
213 mDirectoryDisplayName = from.mDirectoryDisplayName;
214 mDirectoryType = from.mDirectoryType;
215 mDirectoryAccountType = from.mDirectoryAccountType;
216 mDirectoryAccountName = from.mDirectoryAccountName;
217 mDirectoryExportSupport = from.mDirectoryExportSupport;
218
219 mGroups = from.mGroups;
220
221 mLoadingPhoto = from.mLoadingPhoto;
222 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700223 mSendToVoicemail = from.mSendToVoicemail;
224 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700225 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800226 }
227
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700228 /**
229 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
230 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700231 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700232 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700233 mDirectoryDisplayName = displayName;
234 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700235 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700236 mDirectoryAccountName = accountName;
237 mDirectoryExportSupport = exportSupport;
238 }
239
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800240 private void setLoadingPhoto(boolean flag) {
241 mLoadingPhoto = flag;
242 }
243
244 private void setPhotoBinaryData(byte[] photoBinaryData) {
245 mPhotoBinaryData = photoBinaryData;
246 }
247
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700248 public Uri getLookupUri() {
249 return mLookupUri;
250 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800251
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700252 public String getLookupKey() {
253 return mLookupKey;
254 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800255
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700256 public Uri getUri() {
257 return mUri;
258 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800259
Flavio Lerda37a26842011-06-27 11:36:52 +0100260 @VisibleForTesting
261 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700262 return mId;
263 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800264
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700265 public long getNameRawContactId() {
266 return mNameRawContactId;
267 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800268
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700269 public int getDisplayNameSource() {
270 return mDisplayNameSource;
271 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800272
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700273 public long getPhotoId() {
274 return mPhotoId;
275 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800276
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700277 public String getPhotoUri() {
278 return mPhotoUri;
279 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800280
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700281 public String getDisplayName() {
282 return mDisplayName;
283 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800284
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700285 public String getAltDisplayName() {
286 return mAltDisplayName;
287 }
288
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700289 public String getPhoneticName() {
290 return mPhoneticName;
291 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800292
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700293 public boolean getStarred() {
294 return mStarred;
295 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800296
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700297 public Integer getPresence() {
298 return mPresence;
299 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800300
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700301 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700302 return mInvitableAccountTypes;
303 }
304
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700305 public ArrayList<Entity> getEntities() {
306 return mEntities;
307 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800308
Dave Santoro39156002011-07-19 01:18:14 -0700309 public ArrayList<StreamItemEntry> getStreamItems() {
310 return mStreamItems;
311 }
312
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700313 public HashMap<Long, DataStatus> getStatuses() {
314 return mStatuses;
315 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700316
317 public long getDirectoryId() {
318 return mDirectoryId;
319 }
320
321 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700322 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
323 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700324 }
325
326 public int getDirectoryExportSupport() {
327 return mDirectoryExportSupport;
328 }
329
330 public String getDirectoryDisplayName() {
331 return mDirectoryDisplayName;
332 }
333
334 public String getDirectoryType() {
335 return mDirectoryType;
336 }
337
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700338 public String getDirectoryAccountType() {
339 return mDirectoryAccountType;
340 }
341
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700342 public String getDirectoryAccountName() {
343 return mDirectoryAccountName;
344 }
345
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800346 public boolean isLoadingPhoto() {
347 return mLoadingPhoto;
348 }
349
350 public byte[] getPhotoBinaryData() {
351 return mPhotoBinaryData;
352 }
353
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700354 public ArrayList<ContentValues> getContentValues() {
355 if (mEntities.size() != 1) {
356 throw new IllegalStateException(
357 "Cannot extract content values from an aggregated contact");
358 }
359
360 Entity entity = mEntities.get(0);
361 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
362 ArrayList<NamedContentValues> subValues = entity.getSubValues();
363 if (subValues != null) {
364 int size = subValues.size();
365 for (int i = 0; i < size; i++) {
366 NamedContentValues pair = subValues.get(i);
367 if (Data.CONTENT_URI.equals(pair.uri)) {
368 result.add(pair.values);
369 }
370 }
371 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800372
373 // If the photo was loaded using the URI, create an entry for the photo
374 // binary data.
375 if (mPhotoId == 0 && mPhotoBinaryData != null) {
376 ContentValues photo = new ContentValues();
377 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
378 photo.put(Photo.PHOTO, mPhotoBinaryData);
379 result.add(photo);
380 }
381
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700382 return result;
383 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700384
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700385 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700386 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700387 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700388 }
389 mGroups.add(group);
390 }
391
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700392 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700393 return mGroups;
394 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700395
396 public boolean isSendToVoicemail() {
397 return mSendToVoicemail;
398 }
399
400 public String getCustomRingtone() {
401 return mCustomRingtone;
402 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700403
404 public boolean isUserProfile() {
405 return mIsUserProfile;
406 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700407 }
408
Dave Santoro39156002011-07-19 01:18:14 -0700409 /**
410 * Projection used for the query that loads all data for the entire contact (except for
411 * social stream items).
412 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700413 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700414 final static String[] COLUMNS = new String[] {
415 Contacts.NAME_RAW_CONTACT_ID,
416 Contacts.DISPLAY_NAME_SOURCE,
417 Contacts.LOOKUP_KEY,
418 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700419 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700420 Contacts.PHONETIC_NAME,
421 Contacts.PHOTO_ID,
422 Contacts.STARRED,
423 Contacts.CONTACT_PRESENCE,
424 Contacts.CONTACT_STATUS,
425 Contacts.CONTACT_STATUS_TIMESTAMP,
426 Contacts.CONTACT_STATUS_RES_PACKAGE,
427 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700428 Contacts.Entity.CONTACT_ID,
429 Contacts.Entity.RAW_CONTACT_ID,
430
431 RawContacts.ACCOUNT_NAME,
432 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700433 RawContacts.DATA_SET,
434 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700435 RawContacts.DIRTY,
436 RawContacts.VERSION,
437 RawContacts.SOURCE_ID,
438 RawContacts.SYNC1,
439 RawContacts.SYNC2,
440 RawContacts.SYNC3,
441 RawContacts.SYNC4,
442 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700443 RawContacts.NAME_VERIFIED,
444
445 Contacts.Entity.DATA_ID,
446 Data.DATA1,
447 Data.DATA2,
448 Data.DATA3,
449 Data.DATA4,
450 Data.DATA5,
451 Data.DATA6,
452 Data.DATA7,
453 Data.DATA8,
454 Data.DATA9,
455 Data.DATA10,
456 Data.DATA11,
457 Data.DATA12,
458 Data.DATA13,
459 Data.DATA14,
460 Data.DATA15,
461 Data.SYNC1,
462 Data.SYNC2,
463 Data.SYNC3,
464 Data.SYNC4,
465 Data.DATA_VERSION,
466 Data.IS_PRIMARY,
467 Data.IS_SUPER_PRIMARY,
468 Data.MIMETYPE,
469 Data.RES_PACKAGE,
470
471 GroupMembership.GROUP_SOURCE_ID,
472
473 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700474 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700475 Data.STATUS,
476 Data.STATUS_RES_PACKAGE,
477 Data.STATUS_ICON,
478 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700479 Data.STATUS_TIMESTAMP,
480
481 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700482 Contacts.SEND_TO_VOICEMAIL,
483 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700484 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700485 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700486
487 public final static int NAME_RAW_CONTACT_ID = 0;
488 public final static int DISPLAY_NAME_SOURCE = 1;
489 public final static int LOOKUP_KEY = 2;
490 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700491 public final static int ALT_DISPLAY_NAME = 4;
492 public final static int PHONETIC_NAME = 5;
493 public final static int PHOTO_ID = 6;
494 public final static int STARRED = 7;
495 public final static int CONTACT_PRESENCE = 8;
496 public final static int CONTACT_STATUS = 9;
497 public final static int CONTACT_STATUS_TIMESTAMP = 10;
498 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
499 public final static int CONTACT_STATUS_LABEL = 12;
500 public final static int CONTACT_ID = 13;
501 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700502
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700503 public final static int ACCOUNT_NAME = 15;
504 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700505 public final static int DATA_SET = 17;
506 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
507 public final static int DIRTY = 19;
508 public final static int VERSION = 20;
509 public final static int SOURCE_ID = 21;
510 public final static int SYNC1 = 22;
511 public final static int SYNC2 = 23;
512 public final static int SYNC3 = 24;
513 public final static int SYNC4 = 25;
514 public final static int DELETED = 26;
515 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700516
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700517 public final static int DATA_ID = 28;
518 public final static int DATA1 = 29;
519 public final static int DATA2 = 30;
520 public final static int DATA3 = 31;
521 public final static int DATA4 = 32;
522 public final static int DATA5 = 33;
523 public final static int DATA6 = 34;
524 public final static int DATA7 = 35;
525 public final static int DATA8 = 36;
526 public final static int DATA9 = 37;
527 public final static int DATA10 = 38;
528 public final static int DATA11 = 39;
529 public final static int DATA12 = 40;
530 public final static int DATA13 = 41;
531 public final static int DATA14 = 42;
532 public final static int DATA15 = 43;
533 public final static int DATA_SYNC1 = 44;
534 public final static int DATA_SYNC2 = 45;
535 public final static int DATA_SYNC3 = 46;
536 public final static int DATA_SYNC4 = 47;
537 public final static int DATA_VERSION = 48;
538 public final static int IS_PRIMARY = 49;
539 public final static int IS_SUPERPRIMARY = 50;
540 public final static int MIMETYPE = 51;
541 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700542
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700543 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700544
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700545 public final static int PRESENCE = 54;
546 public final static int CHAT_CAPABILITY = 55;
547 public final static int STATUS = 56;
548 public final static int STATUS_RES_PACKAGE = 57;
549 public final static int STATUS_ICON = 58;
550 public final static int STATUS_LABEL = 59;
551 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700552
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700553 public final static int PHOTO_URI = 61;
554 public final static int SEND_TO_VOICEMAIL = 62;
555 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700556 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700557 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700558
Dave Santoro39156002011-07-19 01:18:14 -0700559 /**
560 * Projection used for the query that loads all data for the entire contact.
561 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700562 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700563 final static String[] COLUMNS = new String[] {
564 Directory.DISPLAY_NAME,
565 Directory.PACKAGE_NAME,
566 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700567 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700568 Directory.ACCOUNT_NAME,
569 Directory.EXPORT_SUPPORT,
570 };
571
572 public final static int DISPLAY_NAME = 0;
573 public final static int PACKAGE_NAME = 1;
574 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700575 public final static int ACCOUNT_TYPE = 3;
576 public final static int ACCOUNT_NAME = 4;
577 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700578 }
579
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700580 private static class GroupQuery {
581 final static String[] COLUMNS = new String[] {
582 Groups.ACCOUNT_NAME,
583 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700584 Groups.DATA_SET,
585 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700586 Groups._ID,
587 Groups.TITLE,
588 Groups.AUTO_ADD,
589 Groups.FAVORITES,
590 };
591
592 public final static int ACCOUNT_NAME = 0;
593 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700594 public final static int DATA_SET = 2;
595 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
596 public final static int ID = 4;
597 public final static int TITLE = 5;
598 public final static int AUTO_ADD = 6;
599 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700600 }
601
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700602 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700603
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700604 @Override
605 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700606 try {
607 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700608 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700609 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700610 if (result != Result.NOT_FOUND) {
611 if (result.isDirectoryEntry()) {
612 loadDirectoryMetaData(result);
613 } else if (mLoadGroupMetaData) {
614 loadGroupMetaData(result);
615 }
Dave Santoro39156002011-07-19 01:18:14 -0700616 if (mLoadStreamItems) {
617 loadStreamItems(result);
618 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800619 loadPhotoBinaryData(result);
Makoto Onuki69b4a882011-07-22 10:05:10 -0700620 if (mLoadInvitableAccountTypes) {
621 loadInvitableAccountTypes(result);
622 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700623 }
624 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700625 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700626 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700627 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700628 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700629 }
630
631 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700632 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
633 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700634 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700635 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700636 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700637 if (uri == null) throw new IllegalArgumentException("uri must not be null");
638
639 final String authority = uri.getAuthority();
640
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700641 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700642 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700643 final String type = resolver.getType(uri);
644 // Contact-Uri? Good, return it
645 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
646 return uri;
647 }
648
649 // RawContact-Uri? Transform it to ContactUri
650 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
651 final long rawContactId = ContentUris.parseId(uri);
652 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
653 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
654 }
655
656 // Anything else? We don't know what this is
657 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700658 }
659
660 // Legacy Style? Convert to RawContact
661 final String OBSOLETE_AUTHORITY = "contacts";
662 if (OBSOLETE_AUTHORITY.equals(authority)) {
663 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
664 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700665 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700666 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
667 }
668
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700669 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700670 }
671
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700672 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
673 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
674 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
675 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700676 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700677 Log.e(TAG, "No cursor returned in loadContactEntity");
678 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700679 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700680
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700681 try {
682 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700683 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700684 return Result.NOT_FOUND;
685 }
686
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700687 long currentRawContactId = -1;
688 Entity entity = null;
689 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700690 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700691 HashMap<Long, DataStatus> statuses = result.getStatuses();
692 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
693 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
694 if (rawContactId != currentRawContactId) {
695 currentRawContactId = rawContactId;
696 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700697 entities.add(entity);
698 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700699 if (!cursor.isNull(ContactQuery.DATA_ID)) {
700 ContentValues data = loadData(cursor);
701 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
702
703 if (!cursor.isNull(ContactQuery.PRESENCE)
704 || !cursor.isNull(ContactQuery.STATUS)) {
705 final DataStatus status = new DataStatus(cursor);
706 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
707 statuses.put(dataId, status);
708 }
709 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700710 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700711
712 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700713 } finally {
714 cursor.close();
715 }
716 }
717
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700718 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800719 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
720 * not found, returns null
721 */
722 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700723
724 // If we have a photo URI, try loading that first.
725 String photoUri = contactData.getPhotoUri();
726 if (photoUri != null) {
727 try {
728 AssetFileDescriptor fd = getContext().getContentResolver()
729 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
730 byte[] buffer = new byte[16 * 1024];
731 FileInputStream fis = fd.createInputStream();
732 ByteArrayOutputStream baos = new ByteArrayOutputStream();
733 try {
734 int size;
735 while ((size = fis.read(buffer)) != -1) {
736 baos.write(buffer, 0, size);
737 }
738 contactData.setPhotoBinaryData(baos.toByteArray());
739 } finally {
740 fis.close();
741 fd.close();
742 }
743 return;
744 } catch (IOException ioe) {
745 // Just fall back to the case below.
746 }
747 }
748
749 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800750 final long photoId = contactData.getPhotoId();
751 if (photoId <= 0) {
752 // No photo ID
753 return;
754 }
755
756 for (Entity entity : contactData.getEntities()) {
757 for (NamedContentValues subValue : entity.getSubValues()) {
758 final ContentValues entryValues = subValue.values;
759 final long dataId = entryValues.getAsLong(Data._ID);
760 if (dataId == photoId) {
761 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
762 // Correct Data Id but incorrect MimeType? Don't load
763 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
764 return;
765 }
766 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
767 break;
768 }
769 }
770 }
771 }
772
Makoto Onuki3e6991e2011-07-24 14:51:20 -0700773 /**
774 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
775 *
776 * TODO Exclude the ones with no raw contacts in the database.
777 */
Makoto Onuki69b4a882011-07-22 10:05:10 -0700778 private void loadInvitableAccountTypes(Result contactData) {
779 Map<String, AccountType> allInvitables =
780 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
781 if (allInvitables.isEmpty()) {
782 return;
783 }
784
785 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
786
787 // Remove the ones that already has a raw contact in the current contact
788 for (Entity entity : contactData.getEntities()) {
789 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
790 if (!TextUtils.isEmpty(type)) {
791 result.remove(type);
792 }
793 }
794
795 // Set to mInvitableAccountTypes
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700796 contactData.mInvitableAccountTypes.addAll(result.values());
Makoto Onuki69b4a882011-07-22 10:05:10 -0700797 }
798
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800799 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700800 * Extracts Contact level columns from the cursor.
801 */
802 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700803 final String directoryParameter =
804 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
805 final long directoryId = directoryParameter == null
806 ? Directory.DEFAULT
807 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700808 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
809 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
810 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
811 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
812 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700813 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700814 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
815 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700816 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700817 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
818 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
819 ? null
820 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700821 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
822 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700823 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700824
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700825 Uri lookupUri;
826 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
827 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700828 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700829 } else {
830 lookupUri = contactUri;
831 }
832
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700833 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700834 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700835 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700836 customRingtone, isUserProfile);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700837 }
838
839 /**
840 * Extracts RawContact level columns from the cursor.
841 */
842 private ContentValues loadRawContact(Cursor cursor) {
843 ContentValues cv = new ContentValues();
844
845 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
846
847 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
848 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700849 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
850 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700851 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
852 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
853 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
854 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
855 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
856 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
857 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
858 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
859 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
860 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700861 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
862
863 return cv;
864 }
865
866 /**
867 * Extracts Data level columns from the cursor.
868 */
869 private ContentValues loadData(Cursor cursor) {
870 ContentValues cv = new ContentValues();
871
872 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
873
874 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
875 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
876 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
877 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
878 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
879 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
880 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
881 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
882 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
883 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
884 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
885 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
886 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
887 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
888 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
889 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
890 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
891 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
892 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
893 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
894 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
895 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
896 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
897 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
898 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700899 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700900
901 return cv;
902 }
903
904 private void cursorColumnToContentValues(
905 Cursor cursor, ContentValues values, int index) {
906 switch (cursor.getType(index)) {
907 case Cursor.FIELD_TYPE_NULL:
908 // don't put anything in the content values
909 break;
910 case Cursor.FIELD_TYPE_INTEGER:
911 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
912 break;
913 case Cursor.FIELD_TYPE_STRING:
914 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
915 break;
916 case Cursor.FIELD_TYPE_BLOB:
917 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
918 break;
919 default:
920 throw new IllegalStateException("Invalid or unhandled data type");
921 }
922 }
923
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700924 private void loadDirectoryMetaData(Result result) {
925 long directoryId = result.getDirectoryId();
926
927 Cursor cursor = getContext().getContentResolver().query(
928 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
929 DirectoryQuery.COLUMNS, null, null, null);
930 if (cursor == null) {
931 return;
932 }
933 try {
934 if (cursor.moveToFirst()) {
935 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
936 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
937 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700938 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700939 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
940 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
941 String directoryType = null;
942 if (!TextUtils.isEmpty(packageName)) {
943 PackageManager pm = getContext().getPackageManager();
944 try {
945 Resources resources = pm.getResourcesForApplication(packageName);
946 directoryType = resources.getString(typeResourceId);
947 } catch (NameNotFoundException e) {
948 Log.w(TAG, "Contact directory resource not found: "
949 + packageName + "." + typeResourceId);
950 }
951 }
952
953 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700954 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700955 }
956 } finally {
957 cursor.close();
958 }
959 }
960
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700961 /**
962 * Loads groups meta-data for all groups associated with all constituent raw contacts'
963 * accounts.
964 */
965 private void loadGroupMetaData(Result result) {
966 StringBuilder selection = new StringBuilder();
967 ArrayList<String> selectionArgs = new ArrayList<String>();
968 for (Entity entity : result.mEntities) {
969 ContentValues values = entity.getEntityValues();
970 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
971 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700972 String dataSet = values.getAsString(RawContacts.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700973 if (accountName != null && accountType != null) {
974 if (selection.length() != 0) {
975 selection.append(" OR ");
976 }
977 selection.append(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700978 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700979 selectionArgs.add(accountName);
980 selectionArgs.add(accountType);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700981
982 if (dataSet != null) {
983 selection.append(" AND " + Groups.DATA_SET + "=?");
984 selectionArgs.add(dataSet);
985 } else {
986 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
987 }
988 selection.append(")");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700989 }
990 }
991 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
992 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
993 null);
994 try {
995 while (cursor.moveToNext()) {
996 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
997 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700998 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700999 final long groupId = cursor.getLong(GroupQuery.ID);
1000 final String title = cursor.getString(GroupQuery.TITLE);
1001 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1002 ? false
1003 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1004 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1005 ? false
1006 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1007
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001008 result.addGroupMetaData(new GroupMetaData(
Dave Santoro2b3f3c52011-07-26 17:35:42 -07001009 accountName, accountType, dataSet, groupId, title, defaultGroup,
1010 favorites));
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -07001011 }
1012 } finally {
1013 cursor.close();
1014 }
1015 }
1016
Dave Santoro39156002011-07-19 01:18:14 -07001017 /**
1018 * Loads all stream items and stream item photos belonging to this contact.
1019 */
1020 private void loadStreamItems(Result result) {
1021 Cursor cursor = getContext().getContentResolver().query(
1022 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1023 .appendPath(result.getLookupKey())
1024 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1025 null, null, null, null);
1026 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
1027 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1028 try {
1029 while (cursor.moveToNext()) {
1030 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1031 streamItemsById.put(streamItem.getId(), streamItem);
1032 streamItems.add(streamItem);
1033 }
1034 } finally {
1035 cursor.close();
1036 }
1037
1038 // Now retrieve any photo records associated with the stream items.
1039 String[] streamItemIdArr = new String[streamItems.size()];
1040 StringBuilder streamItemPhotoSelection = new StringBuilder();
1041 if (!streamItems.isEmpty()) {
1042 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1043 for (int i = 0; i < streamItems.size(); i++) {
1044 if (i > 0) {
1045 streamItemPhotoSelection.append(",");
1046 }
1047 streamItemPhotoSelection.append("?");
1048 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
1049 }
1050 streamItemPhotoSelection.append(")");
1051 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
1052 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1053 StreamItemPhotos.STREAM_ITEM_ID);
1054 try {
1055 while (cursor.moveToNext()) {
1056 long streamItemId = cursor.getLong(
1057 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1058 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1059 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1060 }
1061 } finally {
1062 cursor.close();
1063 }
1064 }
1065
1066 // Set the sorted stream items on the result.
1067 Collections.sort(streamItems);
1068 result.mStreamItems.addAll(streamItems);
1069 }
1070
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001071 @Override
1072 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001073 unregisterObserver();
1074
Daniel Lehmann1316b132010-04-13 15:08:53 -07001075 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001076 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001077 return;
1078 }
1079
1080 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001081
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001082 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1083 mLookupUri = result.getLookupUri();
1084
1085 if (!result.isDirectoryEntry()) {
1086 Log.i(TAG, "Registering content observer for " + mLookupUri);
1087 if (mObserver == null) {
1088 mObserver = new ForceLoadContentObserver();
1089 }
1090 getContext().getContentResolver().registerContentObserver(
1091 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001092 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001093
1094 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1095 mContact.setLoadingPhoto(true);
1096 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1097 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001098 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001099
1100 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001101 }
1102 }
1103
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001104 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1105
1106 private static final int BUFFER_SIZE = 1024*16;
1107
1108 @Override
1109 protected byte[] doInBackground(String... params) {
1110 Uri uri = Uri.parse(params[0]);
1111 byte[] data = null;
1112 try {
1113 InputStream is = getContext().getContentResolver().openInputStream(uri);
1114 if (is != null) {
1115 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1116 try {
1117 byte[] mBuffer = new byte[BUFFER_SIZE];
1118
1119 int size;
1120 while ((size = is.read(mBuffer)) != -1) {
1121 baos.write(mBuffer, 0, size);
1122 }
1123 data = baos.toByteArray();
1124 } finally {
1125 is.close();
1126 }
1127 } else {
1128 Log.v(TAG, "Cannot load photo " + uri);
1129 }
1130 } catch (IOException e) {
1131 Log.e(TAG, "Cannot load photo " + uri, e);
1132 }
1133
1134 return data;
1135 }
1136
1137 @Override
1138 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001139 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001140 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001141 mContact.setPhotoBinaryData(data);
1142 mContact.setLoadingPhoto(false);
1143 deliverResult(mContact);
1144 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001145 }
1146 }
1147
Daniel Lehmann3a120772010-06-21 16:21:35 -07001148 private void unregisterObserver() {
1149 if (mObserver != null) {
1150 getContext().getContentResolver().unregisterContentObserver(mObserver);
1151 mObserver = null;
1152 }
1153 }
1154
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001155 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001156 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001157 }
1158
Dave Santoro39156002011-07-19 01:18:14 -07001159 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001160 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001161 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001162 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001163 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001164 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001165 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001166 }
1167
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001168 public Uri getLookupUri() {
1169 return mLookupUri;
1170 }
1171
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001172 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001173 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001174 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001175 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001176 }
1177
1178 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001179 forceLoad();
1180 }
1181 }
1182
1183 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001184 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001185 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001186 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001187 }
1188
1189 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001190 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001191 unregisterObserver();
1192 mContact = null;
1193 mDestroyed = true;
1194 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001195}