blob: d8874dca0ac6139731e082643be6c1a1d45a6cb5 [file] [log] [blame]
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001/*
Daniel Lehmannef3f8f02010-07-26 18:55:25 -07002 * Copyright (C) 2010 The Android Open Source Project
Daniel Lehmann4cd94412010-04-08 16:44:36 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070018
Makoto Onuki69b4a882011-07-22 10:05:10 -070019import com.android.contacts.model.AccountType;
20import com.android.contacts.model.AccountTypeManager;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070021import com.android.contacts.util.DataStatus;
Dave Santoro39156002011-07-19 01:18:14 -070022import com.android.contacts.util.StreamItemEntry;
23import com.android.contacts.util.StreamItemPhotoEntry;
Makoto Onuki69b4a882011-07-22 10:05:10 -070024import com.google.android.collect.Lists;
Flavio Lerda37a26842011-06-27 11:36:52 +010025import com.google.common.annotations.VisibleForTesting;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070026
27import android.content.ContentResolver;
28import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070029import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070030import android.content.Context;
31import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070032import android.content.Entity.NamedContentValues;
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -070033import android.content.Intent;
Jeff Hamilton3c462912010-05-15 02:20:01 -050034import android.content.Loader;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070035import android.content.pm.PackageManager;
36import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070037import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070038import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070039import android.database.Cursor;
40import android.net.Uri;
41import android.os.AsyncTask;
Daniel Lehmann1316b132010-04-13 15:08:53 -070042import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070043import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080044import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070045import android.provider.ContactsContract.Contacts;
46import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070047import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070048import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070049import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070050import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070051import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070052import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070053import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070054import android.util.Log;
55
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080056import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070057import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080058import java.io.IOException;
59import java.io.InputStream;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070060import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070061import java.util.Collections;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070062import java.util.HashMap;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070063import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070064import java.util.Map;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070065
66/**
67 * Loads a single Contact and all it constituent RawContacts.
68 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070069public class ContactLoader extends Loader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070070 private static final String TAG = "ContactLoader";
71
Daniel Lehmann4cd94412010-04-08 16:44:36 -070072 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070073 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070074 private boolean mLoadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -070075 private final boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070076 private Result mContact;
77 private ForceLoadContentObserver mObserver;
78 private boolean mDestroyed;
79
Dmitri Plotnikove843f912010-09-16 15:21:48 -070080
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070081 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070082 public void onContactLoaded(Result contact);
83 }
84
85 /**
86 * The result of a load operation. Contains all data necessary to display the contact.
87 */
88 public static final class Result {
89 /**
90 * Singleton instance that represents "No Contact Found"
91 */
92 public static final Result NOT_FOUND = new Result();
93
Daniel Lehmann18f104f2010-05-07 15:41:11 -070094 /**
95 * Singleton instance that represents an error, e.g. because of an invalid Uri
96 * TODO: We should come up with something nicer here. Maybe use an Either type so
97 * that we can capture the Exception?
98 */
99 public static final Result ERROR = new Result();
100
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700101 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700102 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700103 private final long mDirectoryId;
104 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700105 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700106 private final long mNameRawContactId;
107 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700108 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700109 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700110 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700111 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700112 private final String mPhoneticName;
113 private final boolean mStarred;
114 private final Integer mPresence;
115 private final ArrayList<Entity> mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700116 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700117 private final HashMap<Long, DataStatus> mStatuses;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700118 private final ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700119
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700120 private String mDirectoryDisplayName;
121 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700122 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700123 private String mDirectoryAccountName;
124 private int mDirectoryExportSupport;
125
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700126 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700127
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800128 private boolean mLoadingPhoto;
129 private byte[] mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700130 private boolean mSendToVoicemail;
131 private String mCustomRingtone;
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;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700158 }
159
160 /**
161 * Constructor to call when contact was found
162 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700163 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700164 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700165 String displayName, String altDisplayName, String phoneticName, boolean starred,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700166 Integer presence, boolean sendToVoicemail, String customRingtone) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700167 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700168 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700169 mDirectoryId = directoryId;
170 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700171 mId = id;
172 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700173 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700174 mStatuses = new HashMap<Long, DataStatus>();
175 mNameRawContactId = nameRawContactId;
176 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700177 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700178 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700179 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700180 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700181 mPhoneticName = phoneticName;
182 mStarred = starred;
183 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700184 mInvitableAccountTypes = Lists.newArrayList();
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700185 mSendToVoicemail = sendToVoicemail;
186 mCustomRingtone = customRingtone;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700187 }
188
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800189 private Result(Result from) {
190 mLookupUri = from.mLookupUri;
191 mUri = from.mUri;
192 mDirectoryId = from.mDirectoryId;
193 mLookupKey = from.mLookupKey;
194 mId = from.mId;
195 mNameRawContactId = from.mNameRawContactId;
196 mDisplayNameSource = from.mDisplayNameSource;
197 mPhotoId = from.mPhotoId;
198 mPhotoUri = from.mPhotoUri;
199 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700200 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800201 mPhoneticName = from.mPhoneticName;
202 mStarred = from.mStarred;
203 mPresence = from.mPresence;
204 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700205 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800206 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700207 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800208
209 mDirectoryDisplayName = from.mDirectoryDisplayName;
210 mDirectoryType = from.mDirectoryType;
211 mDirectoryAccountType = from.mDirectoryAccountType;
212 mDirectoryAccountName = from.mDirectoryAccountName;
213 mDirectoryExportSupport = from.mDirectoryExportSupport;
214
215 mGroups = from.mGroups;
216
217 mLoadingPhoto = from.mLoadingPhoto;
218 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700219 mSendToVoicemail = from.mSendToVoicemail;
220 mCustomRingtone = from.mCustomRingtone;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800221 }
222
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700223 /**
224 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
225 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700226 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700227 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700228 mDirectoryDisplayName = displayName;
229 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700230 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700231 mDirectoryAccountName = accountName;
232 mDirectoryExportSupport = exportSupport;
233 }
234
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800235 private void setLoadingPhoto(boolean flag) {
236 mLoadingPhoto = flag;
237 }
238
239 private void setPhotoBinaryData(byte[] photoBinaryData) {
240 mPhotoBinaryData = photoBinaryData;
241 }
242
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700243 public Uri getLookupUri() {
244 return mLookupUri;
245 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800246
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700247 public String getLookupKey() {
248 return mLookupKey;
249 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800250
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700251 public Uri getUri() {
252 return mUri;
253 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800254
Flavio Lerda37a26842011-06-27 11:36:52 +0100255 @VisibleForTesting
256 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700257 return mId;
258 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800259
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700260 public long getNameRawContactId() {
261 return mNameRawContactId;
262 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800263
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700264 public int getDisplayNameSource() {
265 return mDisplayNameSource;
266 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800267
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700268 public long getPhotoId() {
269 return mPhotoId;
270 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800271
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700272 public String getPhotoUri() {
273 return mPhotoUri;
274 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800275
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700276 public String getDisplayName() {
277 return mDisplayName;
278 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800279
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700280 public String getAltDisplayName() {
281 return mAltDisplayName;
282 }
283
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700284 public String getPhoneticName() {
285 return mPhoneticName;
286 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800287
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700288 public boolean getStarred() {
289 return mStarred;
290 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800291
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700292 public Integer getPresence() {
293 return mPresence;
294 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800295
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700296 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700297 return mInvitableAccountTypes;
298 }
299
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700300 public ArrayList<Entity> getEntities() {
301 return mEntities;
302 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800303
Dave Santoro39156002011-07-19 01:18:14 -0700304 public ArrayList<StreamItemEntry> getStreamItems() {
305 return mStreamItems;
306 }
307
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700308 public HashMap<Long, DataStatus> getStatuses() {
309 return mStatuses;
310 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700311
312 public long getDirectoryId() {
313 return mDirectoryId;
314 }
315
316 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700317 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
318 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700319 }
320
321 public int getDirectoryExportSupport() {
322 return mDirectoryExportSupport;
323 }
324
325 public String getDirectoryDisplayName() {
326 return mDirectoryDisplayName;
327 }
328
329 public String getDirectoryType() {
330 return mDirectoryType;
331 }
332
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700333 public String getDirectoryAccountType() {
334 return mDirectoryAccountType;
335 }
336
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700337 public String getDirectoryAccountName() {
338 return mDirectoryAccountName;
339 }
340
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800341 public boolean isLoadingPhoto() {
342 return mLoadingPhoto;
343 }
344
345 public byte[] getPhotoBinaryData() {
346 return mPhotoBinaryData;
347 }
348
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700349 public ArrayList<ContentValues> getContentValues() {
350 if (mEntities.size() != 1) {
351 throw new IllegalStateException(
352 "Cannot extract content values from an aggregated contact");
353 }
354
355 Entity entity = mEntities.get(0);
356 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
357 ArrayList<NamedContentValues> subValues = entity.getSubValues();
358 if (subValues != null) {
359 int size = subValues.size();
360 for (int i = 0; i < size; i++) {
361 NamedContentValues pair = subValues.get(i);
362 if (Data.CONTENT_URI.equals(pair.uri)) {
363 result.add(pair.values);
364 }
365 }
366 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800367
368 // If the photo was loaded using the URI, create an entry for the photo
369 // binary data.
370 if (mPhotoId == 0 && mPhotoBinaryData != null) {
371 ContentValues photo = new ContentValues();
372 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
373 photo.put(Photo.PHOTO, mPhotoBinaryData);
374 result.add(photo);
375 }
376
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700377 return result;
378 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700379
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700380 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700381 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700382 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700383 }
384 mGroups.add(group);
385 }
386
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700387 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700388 return mGroups;
389 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700390
391 public boolean isSendToVoicemail() {
392 return mSendToVoicemail;
393 }
394
395 public String getCustomRingtone() {
396 return mCustomRingtone;
397 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700398 }
399
Dave Santoro39156002011-07-19 01:18:14 -0700400 /**
401 * Projection used for the query that loads all data for the entire contact (except for
402 * social stream items).
403 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700404 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700405 final static String[] COLUMNS = new String[] {
406 Contacts.NAME_RAW_CONTACT_ID,
407 Contacts.DISPLAY_NAME_SOURCE,
408 Contacts.LOOKUP_KEY,
409 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700410 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700411 Contacts.PHONETIC_NAME,
412 Contacts.PHOTO_ID,
413 Contacts.STARRED,
414 Contacts.CONTACT_PRESENCE,
415 Contacts.CONTACT_STATUS,
416 Contacts.CONTACT_STATUS_TIMESTAMP,
417 Contacts.CONTACT_STATUS_RES_PACKAGE,
418 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700419 Contacts.Entity.CONTACT_ID,
420 Contacts.Entity.RAW_CONTACT_ID,
421
422 RawContacts.ACCOUNT_NAME,
423 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700424 RawContacts.DATA_SET,
425 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700426 RawContacts.DIRTY,
427 RawContacts.VERSION,
428 RawContacts.SOURCE_ID,
429 RawContacts.SYNC1,
430 RawContacts.SYNC2,
431 RawContacts.SYNC3,
432 RawContacts.SYNC4,
433 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700434 RawContacts.NAME_VERIFIED,
435
436 Contacts.Entity.DATA_ID,
437 Data.DATA1,
438 Data.DATA2,
439 Data.DATA3,
440 Data.DATA4,
441 Data.DATA5,
442 Data.DATA6,
443 Data.DATA7,
444 Data.DATA8,
445 Data.DATA9,
446 Data.DATA10,
447 Data.DATA11,
448 Data.DATA12,
449 Data.DATA13,
450 Data.DATA14,
451 Data.DATA15,
452 Data.SYNC1,
453 Data.SYNC2,
454 Data.SYNC3,
455 Data.SYNC4,
456 Data.DATA_VERSION,
457 Data.IS_PRIMARY,
458 Data.IS_SUPER_PRIMARY,
459 Data.MIMETYPE,
460 Data.RES_PACKAGE,
461
462 GroupMembership.GROUP_SOURCE_ID,
463
464 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700465 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700466 Data.STATUS,
467 Data.STATUS_RES_PACKAGE,
468 Data.STATUS_ICON,
469 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700470 Data.STATUS_TIMESTAMP,
471
472 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700473 Contacts.SEND_TO_VOICEMAIL,
474 Contacts.CUSTOM_RINGTONE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700475 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700476
477 public final static int NAME_RAW_CONTACT_ID = 0;
478 public final static int DISPLAY_NAME_SOURCE = 1;
479 public final static int LOOKUP_KEY = 2;
480 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700481 public final static int ALT_DISPLAY_NAME = 4;
482 public final static int PHONETIC_NAME = 5;
483 public final static int PHOTO_ID = 6;
484 public final static int STARRED = 7;
485 public final static int CONTACT_PRESENCE = 8;
486 public final static int CONTACT_STATUS = 9;
487 public final static int CONTACT_STATUS_TIMESTAMP = 10;
488 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
489 public final static int CONTACT_STATUS_LABEL = 12;
490 public final static int CONTACT_ID = 13;
491 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700492
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700493 public final static int ACCOUNT_NAME = 15;
494 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700495 public final static int DATA_SET = 17;
496 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
497 public final static int DIRTY = 19;
498 public final static int VERSION = 20;
499 public final static int SOURCE_ID = 21;
500 public final static int SYNC1 = 22;
501 public final static int SYNC2 = 23;
502 public final static int SYNC3 = 24;
503 public final static int SYNC4 = 25;
504 public final static int DELETED = 26;
505 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700506
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700507 public final static int DATA_ID = 28;
508 public final static int DATA1 = 29;
509 public final static int DATA2 = 30;
510 public final static int DATA3 = 31;
511 public final static int DATA4 = 32;
512 public final static int DATA5 = 33;
513 public final static int DATA6 = 34;
514 public final static int DATA7 = 35;
515 public final static int DATA8 = 36;
516 public final static int DATA9 = 37;
517 public final static int DATA10 = 38;
518 public final static int DATA11 = 39;
519 public final static int DATA12 = 40;
520 public final static int DATA13 = 41;
521 public final static int DATA14 = 42;
522 public final static int DATA15 = 43;
523 public final static int DATA_SYNC1 = 44;
524 public final static int DATA_SYNC2 = 45;
525 public final static int DATA_SYNC3 = 46;
526 public final static int DATA_SYNC4 = 47;
527 public final static int DATA_VERSION = 48;
528 public final static int IS_PRIMARY = 49;
529 public final static int IS_SUPERPRIMARY = 50;
530 public final static int MIMETYPE = 51;
531 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700532
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700533 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700534
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700535 public final static int PRESENCE = 54;
536 public final static int CHAT_CAPABILITY = 55;
537 public final static int STATUS = 56;
538 public final static int STATUS_RES_PACKAGE = 57;
539 public final static int STATUS_ICON = 58;
540 public final static int STATUS_LABEL = 59;
541 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700542
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700543 public final static int PHOTO_URI = 61;
544 public final static int SEND_TO_VOICEMAIL = 62;
545 public final static int CUSTOM_RINGTONE = 63;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700546 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700547
Dave Santoro39156002011-07-19 01:18:14 -0700548 /**
549 * Projection used for the query that loads all data for the entire contact.
550 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700551 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700552 final static String[] COLUMNS = new String[] {
553 Directory.DISPLAY_NAME,
554 Directory.PACKAGE_NAME,
555 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700556 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700557 Directory.ACCOUNT_NAME,
558 Directory.EXPORT_SUPPORT,
559 };
560
561 public final static int DISPLAY_NAME = 0;
562 public final static int PACKAGE_NAME = 1;
563 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700564 public final static int ACCOUNT_TYPE = 3;
565 public final static int ACCOUNT_NAME = 4;
566 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700567 }
568
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700569 private static class GroupQuery {
570 final static String[] COLUMNS = new String[] {
571 Groups.ACCOUNT_NAME,
572 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700573 Groups.DATA_SET,
574 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700575 Groups._ID,
576 Groups.TITLE,
577 Groups.AUTO_ADD,
578 Groups.FAVORITES,
579 };
580
581 public final static int ACCOUNT_NAME = 0;
582 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700583 public final static int DATA_SET = 2;
584 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
585 public final static int ID = 4;
586 public final static int TITLE = 5;
587 public final static int AUTO_ADD = 6;
588 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700589 }
590
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700591 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700592
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700593 @Override
594 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700595 try {
596 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700597 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700598 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700599 if (result != Result.NOT_FOUND) {
600 if (result.isDirectoryEntry()) {
601 loadDirectoryMetaData(result);
602 } else if (mLoadGroupMetaData) {
603 loadGroupMetaData(result);
604 }
Dave Santoro39156002011-07-19 01:18:14 -0700605 if (mLoadStreamItems) {
606 loadStreamItems(result);
607 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800608 loadPhotoBinaryData(result);
Makoto Onuki69b4a882011-07-22 10:05:10 -0700609 if (mLoadInvitableAccountTypes) {
610 loadInvitableAccountTypes(result);
611 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700612 }
613 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700614 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700615 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700616 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700617 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700618 }
619
620 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700621 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
622 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700623 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700624 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700625 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700626 if (uri == null) throw new IllegalArgumentException("uri must not be null");
627
628 final String authority = uri.getAuthority();
629
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700630 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700631 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700632 final String type = resolver.getType(uri);
633 // Contact-Uri? Good, return it
634 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
635 return uri;
636 }
637
638 // RawContact-Uri? Transform it to ContactUri
639 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
640 final long rawContactId = ContentUris.parseId(uri);
641 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
642 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
643 }
644
645 // Anything else? We don't know what this is
646 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700647 }
648
649 // Legacy Style? Convert to RawContact
650 final String OBSOLETE_AUTHORITY = "contacts";
651 if (OBSOLETE_AUTHORITY.equals(authority)) {
652 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
653 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700654 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700655 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
656 }
657
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700658 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700659 }
660
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700661 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
662 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
663 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
664 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700665 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700666 Log.e(TAG, "No cursor returned in loadContactEntity");
667 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700668 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700669
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700670 try {
671 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700672 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700673 return Result.NOT_FOUND;
674 }
675
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700676 long currentRawContactId = -1;
677 Entity entity = null;
678 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700679 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700680 HashMap<Long, DataStatus> statuses = result.getStatuses();
681 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
682 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
683 if (rawContactId != currentRawContactId) {
684 currentRawContactId = rawContactId;
685 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700686 entities.add(entity);
687 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700688 if (!cursor.isNull(ContactQuery.DATA_ID)) {
689 ContentValues data = loadData(cursor);
690 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
691
692 if (!cursor.isNull(ContactQuery.PRESENCE)
693 || !cursor.isNull(ContactQuery.STATUS)) {
694 final DataStatus status = new DataStatus(cursor);
695 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
696 statuses.put(dataId, status);
697 }
698 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700699 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700700
701 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700702 } finally {
703 cursor.close();
704 }
705 }
706
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700707 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800708 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
709 * not found, returns null
710 */
711 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700712
713 // If we have a photo URI, try loading that first.
714 String photoUri = contactData.getPhotoUri();
715 if (photoUri != null) {
716 try {
717 AssetFileDescriptor fd = getContext().getContentResolver()
718 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
719 byte[] buffer = new byte[16 * 1024];
720 FileInputStream fis = fd.createInputStream();
721 ByteArrayOutputStream baos = new ByteArrayOutputStream();
722 try {
723 int size;
724 while ((size = fis.read(buffer)) != -1) {
725 baos.write(buffer, 0, size);
726 }
727 contactData.setPhotoBinaryData(baos.toByteArray());
728 } finally {
729 fis.close();
730 fd.close();
731 }
732 return;
733 } catch (IOException ioe) {
734 // Just fall back to the case below.
735 }
736 }
737
738 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800739 final long photoId = contactData.getPhotoId();
740 if (photoId <= 0) {
741 // No photo ID
742 return;
743 }
744
745 for (Entity entity : contactData.getEntities()) {
746 for (NamedContentValues subValue : entity.getSubValues()) {
747 final ContentValues entryValues = subValue.values;
748 final long dataId = entryValues.getAsLong(Data._ID);
749 if (dataId == photoId) {
750 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
751 // Correct Data Id but incorrect MimeType? Don't load
752 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
753 return;
754 }
755 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
756 break;
757 }
758 }
759 }
760 }
761
Makoto Onuki3e6991e2011-07-24 14:51:20 -0700762 /**
763 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
764 *
765 * TODO Exclude the ones with no raw contacts in the database.
766 */
Makoto Onuki69b4a882011-07-22 10:05:10 -0700767 private void loadInvitableAccountTypes(Result contactData) {
768 Map<String, AccountType> allInvitables =
769 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
770 if (allInvitables.isEmpty()) {
771 return;
772 }
773
774 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
775
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -0700776 // Remove the ones that already have a raw contact in the current contact
Makoto Onuki69b4a882011-07-22 10:05:10 -0700777 for (Entity entity : contactData.getEntities()) {
778 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
779 if (!TextUtils.isEmpty(type)) {
780 result.remove(type);
781 }
782 }
783
784 // Set to mInvitableAccountTypes
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700785 contactData.mInvitableAccountTypes.addAll(result.values());
Makoto Onuki69b4a882011-07-22 10:05:10 -0700786 }
787
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800788 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700789 * Extracts Contact level columns from the cursor.
790 */
791 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700792 final String directoryParameter =
793 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
794 final long directoryId = directoryParameter == null
795 ? Directory.DEFAULT
796 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700797 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
798 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
799 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
800 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
801 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700802 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700803 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
804 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700805 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700806 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
807 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
808 ? null
809 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700810 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
811 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700812
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700813 Uri lookupUri;
814 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
815 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700816 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700817 } else {
818 lookupUri = contactUri;
819 }
820
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700821 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700822 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700823 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
824 customRingtone);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700825 }
826
827 /**
828 * Extracts RawContact level columns from the cursor.
829 */
830 private ContentValues loadRawContact(Cursor cursor) {
831 ContentValues cv = new ContentValues();
832
833 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
834
835 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
836 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700837 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
838 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700839 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
840 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
841 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
842 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
843 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
844 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
845 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
846 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
847 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
848 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700849 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
850
851 return cv;
852 }
853
854 /**
855 * Extracts Data level columns from the cursor.
856 */
857 private ContentValues loadData(Cursor cursor) {
858 ContentValues cv = new ContentValues();
859
860 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
861
862 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
863 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
864 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
865 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
866 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
867 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
868 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
869 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
870 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
871 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
872 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
873 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
874 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
875 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
876 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
877 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
878 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
879 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
880 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
881 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
882 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
883 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
884 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
885 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
886 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700887 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700888
889 return cv;
890 }
891
892 private void cursorColumnToContentValues(
893 Cursor cursor, ContentValues values, int index) {
894 switch (cursor.getType(index)) {
895 case Cursor.FIELD_TYPE_NULL:
896 // don't put anything in the content values
897 break;
898 case Cursor.FIELD_TYPE_INTEGER:
899 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
900 break;
901 case Cursor.FIELD_TYPE_STRING:
902 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
903 break;
904 case Cursor.FIELD_TYPE_BLOB:
905 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
906 break;
907 default:
908 throw new IllegalStateException("Invalid or unhandled data type");
909 }
910 }
911
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700912 private void loadDirectoryMetaData(Result result) {
913 long directoryId = result.getDirectoryId();
914
915 Cursor cursor = getContext().getContentResolver().query(
916 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
917 DirectoryQuery.COLUMNS, null, null, null);
918 if (cursor == null) {
919 return;
920 }
921 try {
922 if (cursor.moveToFirst()) {
923 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
924 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
925 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700926 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700927 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
928 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
929 String directoryType = null;
930 if (!TextUtils.isEmpty(packageName)) {
931 PackageManager pm = getContext().getPackageManager();
932 try {
933 Resources resources = pm.getResourcesForApplication(packageName);
934 directoryType = resources.getString(typeResourceId);
935 } catch (NameNotFoundException e) {
936 Log.w(TAG, "Contact directory resource not found: "
937 + packageName + "." + typeResourceId);
938 }
939 }
940
941 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700942 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700943 }
944 } finally {
945 cursor.close();
946 }
947 }
948
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700949 /**
950 * Loads groups meta-data for all groups associated with all constituent raw contacts'
951 * accounts.
952 */
953 private void loadGroupMetaData(Result result) {
954 StringBuilder selection = new StringBuilder();
955 ArrayList<String> selectionArgs = new ArrayList<String>();
956 for (Entity entity : result.mEntities) {
957 ContentValues values = entity.getEntityValues();
958 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
959 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700960 String dataSet = values.getAsString(RawContacts.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700961 if (accountName != null && accountType != null) {
962 if (selection.length() != 0) {
963 selection.append(" OR ");
964 }
965 selection.append(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700966 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700967 selectionArgs.add(accountName);
968 selectionArgs.add(accountType);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700969
970 if (dataSet != null) {
971 selection.append(" AND " + Groups.DATA_SET + "=?");
972 selectionArgs.add(dataSet);
973 } else {
974 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
975 }
976 selection.append(")");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700977 }
978 }
979 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
980 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
981 null);
982 try {
983 while (cursor.moveToNext()) {
984 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
985 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700986 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700987 final long groupId = cursor.getLong(GroupQuery.ID);
988 final String title = cursor.getString(GroupQuery.TITLE);
989 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
990 ? false
991 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
992 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
993 ? false
994 : cursor.getInt(GroupQuery.FAVORITES) != 0;
995
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700996 result.addGroupMetaData(new GroupMetaData(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700997 accountName, accountType, dataSet, groupId, title, defaultGroup,
998 favorites));
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700999 }
1000 } finally {
1001 cursor.close();
1002 }
1003 }
1004
Dave Santoro39156002011-07-19 01:18:14 -07001005 /**
1006 * Loads all stream items and stream item photos belonging to this contact.
1007 */
1008 private void loadStreamItems(Result result) {
1009 Cursor cursor = getContext().getContentResolver().query(
1010 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1011 .appendPath(result.getLookupKey())
1012 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1013 null, null, null, null);
1014 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
1015 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1016 try {
1017 while (cursor.moveToNext()) {
1018 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1019 streamItemsById.put(streamItem.getId(), streamItem);
1020 streamItems.add(streamItem);
1021 }
1022 } finally {
1023 cursor.close();
1024 }
1025
1026 // Now retrieve any photo records associated with the stream items.
1027 String[] streamItemIdArr = new String[streamItems.size()];
1028 StringBuilder streamItemPhotoSelection = new StringBuilder();
1029 if (!streamItems.isEmpty()) {
1030 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1031 for (int i = 0; i < streamItems.size(); i++) {
1032 if (i > 0) {
1033 streamItemPhotoSelection.append(",");
1034 }
1035 streamItemPhotoSelection.append("?");
1036 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
1037 }
1038 streamItemPhotoSelection.append(")");
1039 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
1040 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1041 StreamItemPhotos.STREAM_ITEM_ID);
1042 try {
1043 while (cursor.moveToNext()) {
1044 long streamItemId = cursor.getLong(
1045 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1046 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1047 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1048 }
1049 } finally {
1050 cursor.close();
1051 }
1052 }
1053
1054 // Set the sorted stream items on the result.
1055 Collections.sort(streamItems);
1056 result.mStreamItems.addAll(streamItems);
1057 }
1058
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001059 @Override
1060 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001061 unregisterObserver();
1062
Daniel Lehmann1316b132010-04-13 15:08:53 -07001063 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001064 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001065 return;
1066 }
1067
1068 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001069
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001070 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1071 mLookupUri = result.getLookupUri();
1072
1073 if (!result.isDirectoryEntry()) {
1074 Log.i(TAG, "Registering content observer for " + mLookupUri);
1075 if (mObserver == null) {
1076 mObserver = new ForceLoadContentObserver();
1077 }
1078 getContext().getContentResolver().registerContentObserver(
1079 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001080 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001081
1082 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1083 mContact.setLoadingPhoto(true);
1084 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1085 }
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001086
1087 // inform the source of the data that this contact is being looked at
1088 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001089 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001090
1091 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001092 }
1093 }
1094
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001095 /**
1096 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1097 * that the contact has just been loaded
1098 */
1099 private void postViewNotificationToSyncAdapter() {
1100 Context context = getContext();
1101 for (Entity entity : mContact.getEntities()) {
1102 final ContentValues entityValues = entity.getEntityValues();
1103 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1104 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
1105 final AccountType accountType = AccountTypeManager.getInstance(context ).getAccountType(
1106 type, dataSet);
1107 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1108 final String resPackageName = accountType.resPackageName;
1109 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
1110 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1111 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1112 final Intent intent = new Intent();
1113 intent.setClassName(resPackageName, serviceName);
1114 intent.setAction(Intent.ACTION_VIEW);
1115 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1116 try {
1117 context.startService(intent);
1118 } catch (Exception e) {
1119 Log.e(TAG, "Error sending message to source-app", e);
1120 }
1121 }
1122 }
1123 }
1124
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001125 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1126
1127 private static final int BUFFER_SIZE = 1024*16;
1128
1129 @Override
1130 protected byte[] doInBackground(String... params) {
1131 Uri uri = Uri.parse(params[0]);
1132 byte[] data = null;
1133 try {
1134 InputStream is = getContext().getContentResolver().openInputStream(uri);
1135 if (is != null) {
1136 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1137 try {
1138 byte[] mBuffer = new byte[BUFFER_SIZE];
1139
1140 int size;
1141 while ((size = is.read(mBuffer)) != -1) {
1142 baos.write(mBuffer, 0, size);
1143 }
1144 data = baos.toByteArray();
1145 } finally {
1146 is.close();
1147 }
1148 } else {
1149 Log.v(TAG, "Cannot load photo " + uri);
1150 }
1151 } catch (IOException e) {
1152 Log.e(TAG, "Cannot load photo " + uri, e);
1153 }
1154
1155 return data;
1156 }
1157
1158 @Override
1159 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001160 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001161 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001162 mContact.setPhotoBinaryData(data);
1163 mContact.setLoadingPhoto(false);
1164 deliverResult(mContact);
1165 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001166 }
1167 }
1168
Daniel Lehmann3a120772010-06-21 16:21:35 -07001169 private void unregisterObserver() {
1170 if (mObserver != null) {
1171 getContext().getContentResolver().unregisterContentObserver(mObserver);
1172 mObserver = null;
1173 }
1174 }
1175
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001176 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001177 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001178 }
1179
Dave Santoro39156002011-07-19 01:18:14 -07001180 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001181 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001182 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001183 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001184 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001185 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001186 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001187 }
1188
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001189 public Uri getLookupUri() {
1190 return mLookupUri;
1191 }
1192
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001193 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001194 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001195 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001196 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001197 }
1198
1199 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001200 forceLoad();
1201 }
1202 }
1203
1204 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001205 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001206 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001207 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001208 }
1209
1210 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001211 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001212 unregisterObserver();
1213 mContact = null;
1214 mDestroyed = true;
1215 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001216}