blob: 4b4d50d6adaca5e7041062146e52f34f89184ce4 [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;
Makoto Onukiaba2b832011-08-12 15:44:53 -070026import com.google.common.collect.Sets;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070027
28import android.content.ContentResolver;
29import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070030import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070031import android.content.Context;
32import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070033import android.content.Entity.NamedContentValues;
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -070034import android.content.Intent;
Jeff Hamilton3c462912010-05-15 02:20:01 -050035import android.content.Loader;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070036import android.content.pm.PackageManager;
37import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070038import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070039import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070040import android.database.Cursor;
41import android.net.Uri;
42import android.os.AsyncTask;
Daniel Lehmann1316b132010-04-13 15:08:53 -070043import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070044import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080045import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070046import android.provider.ContactsContract.Contacts;
47import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070048import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070049import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070050import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070051import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070052import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070053import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070054import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070055import android.util.Log;
56
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080057import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070058import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080059import java.io.IOException;
60import java.io.InputStream;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070061import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070062import java.util.Collections;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070063import java.util.HashMap;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070064import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070065import java.util.Map;
Makoto Onukiaba2b832011-08-12 15:44:53 -070066import java.util.Set;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070067
68/**
69 * Loads a single Contact and all it constituent RawContacts.
70 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070071public class ContactLoader extends Loader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070072 private static final String TAG = "ContactLoader";
73
Daniel Lehmann4cd94412010-04-08 16:44:36 -070074 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070075 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070076 private boolean mLoadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -070077 private final boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070078 private Result mContact;
79 private ForceLoadContentObserver mObserver;
80 private boolean mDestroyed;
Makoto Onukiaba2b832011-08-12 15:44:53 -070081 private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
Dmitri Plotnikove843f912010-09-16 15:21:48 -070082
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070083 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070084 public void onContactLoaded(Result contact);
85 }
86
87 /**
88 * The result of a load operation. Contains all data necessary to display the contact.
89 */
90 public static final class Result {
91 /**
92 * Singleton instance that represents "No Contact Found"
93 */
94 public static final Result NOT_FOUND = new Result();
95
Daniel Lehmann18f104f2010-05-07 15:41:11 -070096 /**
97 * Singleton instance that represents an error, e.g. because of an invalid Uri
98 * TODO: We should come up with something nicer here. Maybe use an Either type so
99 * that we can capture the Exception?
100 */
101 public static final Result ERROR = new Result();
102
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700103 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700104 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700105 private final long mDirectoryId;
106 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700107 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700108 private final long mNameRawContactId;
109 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700110 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700111 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700112 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700113 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700114 private final String mPhoneticName;
115 private final boolean mStarred;
116 private final Integer mPresence;
117 private final ArrayList<Entity> mEntities;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700118 private final ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700119 private final HashMap<Long, DataStatus> mStatuses;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700120 private final ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700121
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700122 private String mDirectoryDisplayName;
123 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700124 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700125 private String mDirectoryAccountName;
126 private int mDirectoryExportSupport;
127
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700128 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700129
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800130 private boolean mLoadingPhoto;
131 private byte[] mPhotoBinaryData;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700132 private final boolean mSendToVoicemail;
133 private final String mCustomRingtone;
134 private final boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800135
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700136 /**
137 * Constructor for case "no contact found". This must only be used for the
138 * final {@link Result#NOT_FOUND} singleton
139 */
140 private Result() {
141 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700142 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700143 mDirectoryId = -1;
144 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700145 mId = -1;
146 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700147 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700148 mStatuses = null;
149 mNameRawContactId = -1;
150 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700151 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700152 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700153 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700154 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700155 mPhoneticName = null;
156 mStarred = false;
157 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700158 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700159 mSendToVoicemail = false;
160 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700161 mIsUserProfile = false;
162
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700163 }
164
165 /**
166 * Constructor to call when contact was found
167 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700168 private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700169 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700170 String displayName, String altDisplayName, String phoneticName, boolean starred,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700171 Integer presence, boolean sendToVoicemail, String customRingtone,
172 boolean isUserProfile) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700173 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700174 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700175 mDirectoryId = directoryId;
176 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700177 mId = id;
178 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700179 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700180 mStatuses = new HashMap<Long, DataStatus>();
181 mNameRawContactId = nameRawContactId;
182 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700183 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700184 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700185 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700186 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700187 mPhoneticName = phoneticName;
188 mStarred = starred;
189 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700190 mInvitableAccountTypes = Lists.newArrayList();
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700191 mSendToVoicemail = sendToVoicemail;
192 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700193 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700194 }
195
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800196 private Result(Result from) {
197 mLookupUri = from.mLookupUri;
198 mUri = from.mUri;
199 mDirectoryId = from.mDirectoryId;
200 mLookupKey = from.mLookupKey;
201 mId = from.mId;
202 mNameRawContactId = from.mNameRawContactId;
203 mDisplayNameSource = from.mDisplayNameSource;
204 mPhotoId = from.mPhotoId;
205 mPhotoUri = from.mPhotoUri;
206 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700207 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800208 mPhoneticName = from.mPhoneticName;
209 mStarred = from.mStarred;
210 mPresence = from.mPresence;
211 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700212 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800213 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700214 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800215
216 mDirectoryDisplayName = from.mDirectoryDisplayName;
217 mDirectoryType = from.mDirectoryType;
218 mDirectoryAccountType = from.mDirectoryAccountType;
219 mDirectoryAccountName = from.mDirectoryAccountName;
220 mDirectoryExportSupport = from.mDirectoryExportSupport;
221
222 mGroups = from.mGroups;
223
224 mLoadingPhoto = from.mLoadingPhoto;
225 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700226 mSendToVoicemail = from.mSendToVoicemail;
227 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700228 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800229 }
230
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700231 /**
232 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
233 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700234 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700235 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700236 mDirectoryDisplayName = displayName;
237 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700238 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700239 mDirectoryAccountName = accountName;
240 mDirectoryExportSupport = exportSupport;
241 }
242
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800243 private void setLoadingPhoto(boolean flag) {
244 mLoadingPhoto = flag;
245 }
246
247 private void setPhotoBinaryData(byte[] photoBinaryData) {
248 mPhotoBinaryData = photoBinaryData;
249 }
250
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700251 public Uri getLookupUri() {
252 return mLookupUri;
253 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800254
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700255 public String getLookupKey() {
256 return mLookupKey;
257 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800258
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700259 public Uri getUri() {
260 return mUri;
261 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800262
Flavio Lerda37a26842011-06-27 11:36:52 +0100263 @VisibleForTesting
264 /*package*/ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700265 return mId;
266 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800267
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700268 public long getNameRawContactId() {
269 return mNameRawContactId;
270 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800271
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700272 public int getDisplayNameSource() {
273 return mDisplayNameSource;
274 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800275
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700276 public long getPhotoId() {
277 return mPhotoId;
278 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800279
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700280 public String getPhotoUri() {
281 return mPhotoUri;
282 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800283
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700284 public String getDisplayName() {
285 return mDisplayName;
286 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800287
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700288 public String getAltDisplayName() {
289 return mAltDisplayName;
290 }
291
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700292 public String getPhoneticName() {
293 return mPhoneticName;
294 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800295
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700296 public boolean getStarred() {
297 return mStarred;
298 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800299
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700300 public Integer getPresence() {
301 return mPresence;
302 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800303
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700304 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700305 return mInvitableAccountTypes;
306 }
307
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700308 public ArrayList<Entity> getEntities() {
309 return mEntities;
310 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800311
Dave Santoro39156002011-07-19 01:18:14 -0700312 public ArrayList<StreamItemEntry> getStreamItems() {
313 return mStreamItems;
314 }
315
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700316 public HashMap<Long, DataStatus> getStatuses() {
317 return mStatuses;
318 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700319
320 public long getDirectoryId() {
321 return mDirectoryId;
322 }
323
324 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700325 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
326 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700327 }
328
329 public int getDirectoryExportSupport() {
330 return mDirectoryExportSupport;
331 }
332
333 public String getDirectoryDisplayName() {
334 return mDirectoryDisplayName;
335 }
336
337 public String getDirectoryType() {
338 return mDirectoryType;
339 }
340
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700341 public String getDirectoryAccountType() {
342 return mDirectoryAccountType;
343 }
344
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700345 public String getDirectoryAccountName() {
346 return mDirectoryAccountName;
347 }
348
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800349 public boolean isLoadingPhoto() {
350 return mLoadingPhoto;
351 }
352
353 public byte[] getPhotoBinaryData() {
354 return mPhotoBinaryData;
355 }
356
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700357 public ArrayList<ContentValues> getContentValues() {
358 if (mEntities.size() != 1) {
359 throw new IllegalStateException(
360 "Cannot extract content values from an aggregated contact");
361 }
362
363 Entity entity = mEntities.get(0);
364 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
365 ArrayList<NamedContentValues> subValues = entity.getSubValues();
366 if (subValues != null) {
367 int size = subValues.size();
368 for (int i = 0; i < size; i++) {
369 NamedContentValues pair = subValues.get(i);
370 if (Data.CONTENT_URI.equals(pair.uri)) {
371 result.add(pair.values);
372 }
373 }
374 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800375
376 // If the photo was loaded using the URI, create an entry for the photo
377 // binary data.
378 if (mPhotoId == 0 && mPhotoBinaryData != null) {
379 ContentValues photo = new ContentValues();
380 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
381 photo.put(Photo.PHOTO, mPhotoBinaryData);
382 result.add(photo);
383 }
384
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700385 return result;
386 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700387
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700388 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700389 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700390 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700391 }
392 mGroups.add(group);
393 }
394
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700395 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700396 return mGroups;
397 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700398
399 public boolean isSendToVoicemail() {
400 return mSendToVoicemail;
401 }
402
403 public String getCustomRingtone() {
404 return mCustomRingtone;
405 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700406
407 public boolean isUserProfile() {
408 return mIsUserProfile;
409 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700410 }
411
Dave Santoro39156002011-07-19 01:18:14 -0700412 /**
413 * Projection used for the query that loads all data for the entire contact (except for
414 * social stream items).
415 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700416 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700417 final static String[] COLUMNS = new String[] {
418 Contacts.NAME_RAW_CONTACT_ID,
419 Contacts.DISPLAY_NAME_SOURCE,
420 Contacts.LOOKUP_KEY,
421 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700422 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700423 Contacts.PHONETIC_NAME,
424 Contacts.PHOTO_ID,
425 Contacts.STARRED,
426 Contacts.CONTACT_PRESENCE,
427 Contacts.CONTACT_STATUS,
428 Contacts.CONTACT_STATUS_TIMESTAMP,
429 Contacts.CONTACT_STATUS_RES_PACKAGE,
430 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700431 Contacts.Entity.CONTACT_ID,
432 Contacts.Entity.RAW_CONTACT_ID,
433
434 RawContacts.ACCOUNT_NAME,
435 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700436 RawContacts.DATA_SET,
437 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700438 RawContacts.DIRTY,
439 RawContacts.VERSION,
440 RawContacts.SOURCE_ID,
441 RawContacts.SYNC1,
442 RawContacts.SYNC2,
443 RawContacts.SYNC3,
444 RawContacts.SYNC4,
445 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700446 RawContacts.NAME_VERIFIED,
447
448 Contacts.Entity.DATA_ID,
449 Data.DATA1,
450 Data.DATA2,
451 Data.DATA3,
452 Data.DATA4,
453 Data.DATA5,
454 Data.DATA6,
455 Data.DATA7,
456 Data.DATA8,
457 Data.DATA9,
458 Data.DATA10,
459 Data.DATA11,
460 Data.DATA12,
461 Data.DATA13,
462 Data.DATA14,
463 Data.DATA15,
464 Data.SYNC1,
465 Data.SYNC2,
466 Data.SYNC3,
467 Data.SYNC4,
468 Data.DATA_VERSION,
469 Data.IS_PRIMARY,
470 Data.IS_SUPER_PRIMARY,
471 Data.MIMETYPE,
472 Data.RES_PACKAGE,
473
474 GroupMembership.GROUP_SOURCE_ID,
475
476 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700477 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700478 Data.STATUS,
479 Data.STATUS_RES_PACKAGE,
480 Data.STATUS_ICON,
481 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700482 Data.STATUS_TIMESTAMP,
483
484 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700485 Contacts.SEND_TO_VOICEMAIL,
486 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700487 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700488 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700489
490 public final static int NAME_RAW_CONTACT_ID = 0;
491 public final static int DISPLAY_NAME_SOURCE = 1;
492 public final static int LOOKUP_KEY = 2;
493 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700494 public final static int ALT_DISPLAY_NAME = 4;
495 public final static int PHONETIC_NAME = 5;
496 public final static int PHOTO_ID = 6;
497 public final static int STARRED = 7;
498 public final static int CONTACT_PRESENCE = 8;
499 public final static int CONTACT_STATUS = 9;
500 public final static int CONTACT_STATUS_TIMESTAMP = 10;
501 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
502 public final static int CONTACT_STATUS_LABEL = 12;
503 public final static int CONTACT_ID = 13;
504 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700505
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700506 public final static int ACCOUNT_NAME = 15;
507 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700508 public final static int DATA_SET = 17;
509 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
510 public final static int DIRTY = 19;
511 public final static int VERSION = 20;
512 public final static int SOURCE_ID = 21;
513 public final static int SYNC1 = 22;
514 public final static int SYNC2 = 23;
515 public final static int SYNC3 = 24;
516 public final static int SYNC4 = 25;
517 public final static int DELETED = 26;
518 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700519
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700520 public final static int DATA_ID = 28;
521 public final static int DATA1 = 29;
522 public final static int DATA2 = 30;
523 public final static int DATA3 = 31;
524 public final static int DATA4 = 32;
525 public final static int DATA5 = 33;
526 public final static int DATA6 = 34;
527 public final static int DATA7 = 35;
528 public final static int DATA8 = 36;
529 public final static int DATA9 = 37;
530 public final static int DATA10 = 38;
531 public final static int DATA11 = 39;
532 public final static int DATA12 = 40;
533 public final static int DATA13 = 41;
534 public final static int DATA14 = 42;
535 public final static int DATA15 = 43;
536 public final static int DATA_SYNC1 = 44;
537 public final static int DATA_SYNC2 = 45;
538 public final static int DATA_SYNC3 = 46;
539 public final static int DATA_SYNC4 = 47;
540 public final static int DATA_VERSION = 48;
541 public final static int IS_PRIMARY = 49;
542 public final static int IS_SUPERPRIMARY = 50;
543 public final static int MIMETYPE = 51;
544 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700545
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700546 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700547
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700548 public final static int PRESENCE = 54;
549 public final static int CHAT_CAPABILITY = 55;
550 public final static int STATUS = 56;
551 public final static int STATUS_RES_PACKAGE = 57;
552 public final static int STATUS_ICON = 58;
553 public final static int STATUS_LABEL = 59;
554 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700555
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700556 public final static int PHOTO_URI = 61;
557 public final static int SEND_TO_VOICEMAIL = 62;
558 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700559 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700560 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700561
Dave Santoro39156002011-07-19 01:18:14 -0700562 /**
563 * Projection used for the query that loads all data for the entire contact.
564 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700565 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700566 final static String[] COLUMNS = new String[] {
567 Directory.DISPLAY_NAME,
568 Directory.PACKAGE_NAME,
569 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700570 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700571 Directory.ACCOUNT_NAME,
572 Directory.EXPORT_SUPPORT,
573 };
574
575 public final static int DISPLAY_NAME = 0;
576 public final static int PACKAGE_NAME = 1;
577 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700578 public final static int ACCOUNT_TYPE = 3;
579 public final static int ACCOUNT_NAME = 4;
580 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700581 }
582
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700583 private static class GroupQuery {
584 final static String[] COLUMNS = new String[] {
585 Groups.ACCOUNT_NAME,
586 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700587 Groups.DATA_SET,
588 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700589 Groups._ID,
590 Groups.TITLE,
591 Groups.AUTO_ADD,
592 Groups.FAVORITES,
593 };
594
595 public final static int ACCOUNT_NAME = 0;
596 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700597 public final static int DATA_SET = 2;
598 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
599 public final static int ID = 4;
600 public final static int TITLE = 5;
601 public final static int AUTO_ADD = 6;
602 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700603 }
604
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700605 private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700606
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700607 @Override
608 protected Result doInBackground(Void... args) {
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700609 try {
610 final ContentResolver resolver = getContext().getContentResolver();
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700611 final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700612 Result result = loadContactEntity(resolver, uriCurrentFormat);
Dmitri Plotnikov217245c2010-09-18 13:04:50 -0700613 if (result != Result.NOT_FOUND) {
614 if (result.isDirectoryEntry()) {
615 loadDirectoryMetaData(result);
616 } else if (mLoadGroupMetaData) {
617 loadGroupMetaData(result);
618 }
Dave Santoro39156002011-07-19 01:18:14 -0700619 if (mLoadStreamItems) {
620 loadStreamItems(result);
621 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800622 loadPhotoBinaryData(result);
Makoto Onuki870a87e2011-08-12 13:40:31 -0700623
624 // Note ME profile should never have "Add connection"
625 if (mLoadInvitableAccountTypes && !result.isUserProfile()) {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700626 loadInvitableAccountTypes(result);
627 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700628 }
629 return result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700630 } catch (Exception e) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700631 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700632 return Result.ERROR;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700633 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700634 }
635
636 /**
Daniel Lehmann1316b132010-04-13 15:08:53 -0700637 * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
638 * For legacy contacts, a raw-contact lookup is performed.
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700639 * @param resolver
Daniel Lehmann1316b132010-04-13 15:08:53 -0700640 */
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700641 private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
Daniel Lehmann1316b132010-04-13 15:08:53 -0700642 if (uri == null) throw new IllegalArgumentException("uri must not be null");
643
644 final String authority = uri.getAuthority();
645
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700646 // Current Style Uri?
Daniel Lehmann1316b132010-04-13 15:08:53 -0700647 if (ContactsContract.AUTHORITY.equals(authority)) {
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700648 final String type = resolver.getType(uri);
649 // Contact-Uri? Good, return it
650 if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
651 return uri;
652 }
653
654 // RawContact-Uri? Transform it to ContactUri
655 if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
656 final long rawContactId = ContentUris.parseId(uri);
657 return RawContacts.getContactLookupUri(getContext().getContentResolver(),
658 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
659 }
660
661 // Anything else? We don't know what this is
662 throw new IllegalArgumentException("uri format is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700663 }
664
665 // Legacy Style? Convert to RawContact
666 final String OBSOLETE_AUTHORITY = "contacts";
667 if (OBSOLETE_AUTHORITY.equals(authority)) {
668 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
669 final long rawContactId = ContentUris.parseId(uri);
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700670 return RawContacts.getContactLookupUri(resolver,
Daniel Lehmann1316b132010-04-13 15:08:53 -0700671 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
672 }
673
Daniel Lehmanncdef2b62010-06-06 18:25:49 -0700674 throw new IllegalArgumentException("uri authority is unknown");
Daniel Lehmann1316b132010-04-13 15:08:53 -0700675 }
676
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700677 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
678 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
679 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
680 Contacts.Entity.RAW_CONTACT_ID);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700681 if (cursor == null) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700682 Log.e(TAG, "No cursor returned in loadContactEntity");
683 return Result.NOT_FOUND;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700684 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700685
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700686 try {
687 if (!cursor.moveToFirst()) {
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700688 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700689 return Result.NOT_FOUND;
690 }
691
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700692 long currentRawContactId = -1;
693 Entity entity = null;
694 Result result = loadContactHeaderData(cursor, contactUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700695 ArrayList<Entity> entities = result.getEntities();
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700696 HashMap<Long, DataStatus> statuses = result.getStatuses();
697 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
698 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
699 if (rawContactId != currentRawContactId) {
700 currentRawContactId = rawContactId;
701 entity = new android.content.Entity(loadRawContact(cursor));
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700702 entities.add(entity);
703 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700704 if (!cursor.isNull(ContactQuery.DATA_ID)) {
705 ContentValues data = loadData(cursor);
706 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
707
708 if (!cursor.isNull(ContactQuery.PRESENCE)
709 || !cursor.isNull(ContactQuery.STATUS)) {
710 final DataStatus status = new DataStatus(cursor);
711 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
712 statuses.put(dataId, status);
713 }
714 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700715 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700716
717 return result;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700718 } finally {
719 cursor.close();
720 }
721 }
722
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700723 /**
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800724 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
725 * not found, returns null
726 */
727 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700728
729 // If we have a photo URI, try loading that first.
730 String photoUri = contactData.getPhotoUri();
731 if (photoUri != null) {
732 try {
733 AssetFileDescriptor fd = getContext().getContentResolver()
734 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
735 byte[] buffer = new byte[16 * 1024];
736 FileInputStream fis = fd.createInputStream();
737 ByteArrayOutputStream baos = new ByteArrayOutputStream();
738 try {
739 int size;
740 while ((size = fis.read(buffer)) != -1) {
741 baos.write(buffer, 0, size);
742 }
743 contactData.setPhotoBinaryData(baos.toByteArray());
744 } finally {
745 fis.close();
746 fd.close();
747 }
748 return;
749 } catch (IOException ioe) {
750 // Just fall back to the case below.
751 }
752 }
753
754 // If we couldn't load from a file, fall back to the data blob.
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800755 final long photoId = contactData.getPhotoId();
756 if (photoId <= 0) {
757 // No photo ID
758 return;
759 }
760
761 for (Entity entity : contactData.getEntities()) {
762 for (NamedContentValues subValue : entity.getSubValues()) {
763 final ContentValues entryValues = subValue.values;
764 final long dataId = entryValues.getAsLong(Data._ID);
765 if (dataId == photoId) {
766 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
767 // Correct Data Id but incorrect MimeType? Don't load
768 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
769 return;
770 }
771 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
772 break;
773 }
774 }
775 }
776 }
777
Makoto Onuki3e6991e2011-07-24 14:51:20 -0700778 /**
779 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
780 *
781 * TODO Exclude the ones with no raw contacts in the database.
782 */
Makoto Onuki69b4a882011-07-22 10:05:10 -0700783 private void loadInvitableAccountTypes(Result contactData) {
784 Map<String, AccountType> allInvitables =
785 AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
786 if (allInvitables.isEmpty()) {
787 return;
788 }
789
790 HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
791
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -0700792 // Remove the ones that already have a raw contact in the current contact
Makoto Onuki69b4a882011-07-22 10:05:10 -0700793 for (Entity entity : contactData.getEntities()) {
794 final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
795 if (!TextUtils.isEmpty(type)) {
796 result.remove(type);
797 }
798 }
799
800 // Set to mInvitableAccountTypes
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700801 contactData.mInvitableAccountTypes.addAll(result.values());
Makoto Onuki69b4a882011-07-22 10:05:10 -0700802 }
803
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800804 /**
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700805 * Extracts Contact level columns from the cursor.
806 */
807 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700808 final String directoryParameter =
809 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
810 final long directoryId = directoryParameter == null
811 ? Directory.DEFAULT
812 : Long.parseLong(directoryParameter);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700813 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
814 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
815 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
816 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
817 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700818 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700819 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
820 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700821 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700822 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
823 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
824 ? null
825 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700826 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
827 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700828 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700829
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700830 Uri lookupUri;
831 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
832 lookupUri = ContentUris.withAppendedId(
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700833 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
Dmitri Plotnikov1536ea12010-10-29 11:51:05 -0700834 } else {
835 lookupUri = contactUri;
836 }
837
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700838 return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700839 nameRawContactId, displayNameSource, photoId, photoUri, displayName,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700840 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700841 customRingtone, isUserProfile);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700842 }
843
844 /**
845 * Extracts RawContact level columns from the cursor.
846 */
847 private ContentValues loadRawContact(Cursor cursor) {
848 ContentValues cv = new ContentValues();
849
850 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
851
852 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
853 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700854 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
855 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700856 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
857 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
858 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
859 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
860 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
861 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
862 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
863 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
864 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
865 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700866 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
867
868 return cv;
869 }
870
871 /**
872 * Extracts Data level columns from the cursor.
873 */
874 private ContentValues loadData(Cursor cursor) {
875 ContentValues cv = new ContentValues();
876
877 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
878
879 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
880 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
881 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
882 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
883 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
884 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
885 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
886 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
887 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
888 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
889 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
890 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
891 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
892 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
893 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
894 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
895 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
896 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
897 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
898 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
899 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
900 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
901 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
902 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
903 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700904 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700905
906 return cv;
907 }
908
909 private void cursorColumnToContentValues(
910 Cursor cursor, ContentValues values, int index) {
911 switch (cursor.getType(index)) {
912 case Cursor.FIELD_TYPE_NULL:
913 // don't put anything in the content values
914 break;
915 case Cursor.FIELD_TYPE_INTEGER:
916 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
917 break;
918 case Cursor.FIELD_TYPE_STRING:
919 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
920 break;
921 case Cursor.FIELD_TYPE_BLOB:
922 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
923 break;
924 default:
925 throw new IllegalStateException("Invalid or unhandled data type");
926 }
927 }
928
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700929 private void loadDirectoryMetaData(Result result) {
930 long directoryId = result.getDirectoryId();
931
932 Cursor cursor = getContext().getContentResolver().query(
933 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
934 DirectoryQuery.COLUMNS, null, null, null);
935 if (cursor == null) {
936 return;
937 }
938 try {
939 if (cursor.moveToFirst()) {
940 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
941 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
942 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700943 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700944 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
945 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
946 String directoryType = null;
947 if (!TextUtils.isEmpty(packageName)) {
948 PackageManager pm = getContext().getPackageManager();
949 try {
950 Resources resources = pm.getResourcesForApplication(packageName);
951 directoryType = resources.getString(typeResourceId);
952 } catch (NameNotFoundException e) {
953 Log.w(TAG, "Contact directory resource not found: "
954 + packageName + "." + typeResourceId);
955 }
956 }
957
958 result.setDirectoryMetaData(
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700959 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700960 }
961 } finally {
962 cursor.close();
963 }
964 }
965
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700966 /**
967 * Loads groups meta-data for all groups associated with all constituent raw contacts'
968 * accounts.
969 */
970 private void loadGroupMetaData(Result result) {
971 StringBuilder selection = new StringBuilder();
972 ArrayList<String> selectionArgs = new ArrayList<String>();
973 for (Entity entity : result.mEntities) {
974 ContentValues values = entity.getEntityValues();
975 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
976 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700977 String dataSet = values.getAsString(RawContacts.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700978 if (accountName != null && accountType != null) {
979 if (selection.length() != 0) {
980 selection.append(" OR ");
981 }
982 selection.append(
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700983 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700984 selectionArgs.add(accountName);
985 selectionArgs.add(accountType);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700986
987 if (dataSet != null) {
988 selection.append(" AND " + Groups.DATA_SET + "=?");
989 selectionArgs.add(dataSet);
990 } else {
991 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
992 }
993 selection.append(")");
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700994 }
995 }
996 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
997 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
998 null);
999 try {
1000 while (cursor.moveToNext()) {
1001 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1002 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -07001003 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -07001004 final long groupId = cursor.getLong(GroupQuery.ID);
1005 final String title = cursor.getString(GroupQuery.TITLE);
1006 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1007 ? false
1008 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1009 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1010 ? false
1011 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1012
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001013 result.addGroupMetaData(new GroupMetaData(
Dave Santoro2b3f3c52011-07-26 17:35:42 -07001014 accountName, accountType, dataSet, groupId, title, defaultGroup,
1015 favorites));
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -07001016 }
1017 } finally {
1018 cursor.close();
1019 }
1020 }
1021
Dave Santoro39156002011-07-19 01:18:14 -07001022 /**
1023 * Loads all stream items and stream item photos belonging to this contact.
1024 */
1025 private void loadStreamItems(Result result) {
1026 Cursor cursor = getContext().getContentResolver().query(
1027 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1028 .appendPath(result.getLookupKey())
1029 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1030 null, null, null, null);
1031 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
1032 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1033 try {
1034 while (cursor.moveToNext()) {
1035 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1036 streamItemsById.put(streamItem.getId(), streamItem);
1037 streamItems.add(streamItem);
1038 }
1039 } finally {
1040 cursor.close();
1041 }
1042
1043 // Now retrieve any photo records associated with the stream items.
1044 String[] streamItemIdArr = new String[streamItems.size()];
1045 StringBuilder streamItemPhotoSelection = new StringBuilder();
1046 if (!streamItems.isEmpty()) {
1047 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1048 for (int i = 0; i < streamItems.size(); i++) {
1049 if (i > 0) {
1050 streamItemPhotoSelection.append(",");
1051 }
1052 streamItemPhotoSelection.append("?");
1053 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
1054 }
1055 streamItemPhotoSelection.append(")");
1056 cursor = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
1057 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1058 StreamItemPhotos.STREAM_ITEM_ID);
1059 try {
1060 while (cursor.moveToNext()) {
1061 long streamItemId = cursor.getLong(
1062 cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1063 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1064 streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
1065 }
1066 } finally {
1067 cursor.close();
1068 }
1069 }
1070
1071 // Set the sorted stream items on the result.
1072 Collections.sort(streamItems);
1073 result.mStreamItems.addAll(streamItems);
1074 }
1075
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001076 @Override
1077 protected void onPostExecute(Result result) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001078 unregisterObserver();
1079
Daniel Lehmann1316b132010-04-13 15:08:53 -07001080 // The creator isn't interested in any further updates
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001081 if (mDestroyed || result == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001082 return;
1083 }
1084
1085 mContact = result;
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001086
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001087 if (result != Result.ERROR && result != Result.NOT_FOUND) {
1088 mLookupUri = result.getLookupUri();
1089
1090 if (!result.isDirectoryEntry()) {
1091 Log.i(TAG, "Registering content observer for " + mLookupUri);
1092 if (mObserver == null) {
1093 mObserver = new ForceLoadContentObserver();
1094 }
1095 getContext().getContentResolver().registerContentObserver(
1096 mLookupUri, true, mObserver);
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001097 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001098
1099 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
1100 mContact.setLoadingPhoto(true);
1101 new AsyncPhotoLoader().execute(mContact.getPhotoUri());
1102 }
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001103
1104 // inform the source of the data that this contact is being looked at
1105 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001106 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001107
1108 deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001109 }
1110 }
1111
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001112 /**
1113 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1114 * that the contact has just been loaded
1115 */
1116 private void postViewNotificationToSyncAdapter() {
1117 Context context = getContext();
1118 for (Entity entity : mContact.getEntities()) {
1119 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001120 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1121 if (mNotifiedRawContactIds.contains(rawContactId)) {
1122 continue; // Already notified for this raw contact.
1123 }
1124 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001125 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1126 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
1127 final AccountType accountType = AccountTypeManager.getInstance(context ).getAccountType(
1128 type, dataSet);
1129 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1130 final String resPackageName = accountType.resPackageName;
1131 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001132 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1133 final Intent intent = new Intent();
1134 intent.setClassName(resPackageName, serviceName);
1135 intent.setAction(Intent.ACTION_VIEW);
1136 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1137 try {
1138 context.startService(intent);
1139 } catch (Exception e) {
1140 Log.e(TAG, "Error sending message to source-app", e);
1141 }
1142 }
1143 }
1144 }
1145
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001146 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
1147
1148 private static final int BUFFER_SIZE = 1024*16;
1149
1150 @Override
1151 protected byte[] doInBackground(String... params) {
1152 Uri uri = Uri.parse(params[0]);
1153 byte[] data = null;
1154 try {
1155 InputStream is = getContext().getContentResolver().openInputStream(uri);
1156 if (is != null) {
1157 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1158 try {
1159 byte[] mBuffer = new byte[BUFFER_SIZE];
1160
1161 int size;
1162 while ((size = is.read(mBuffer)) != -1) {
1163 baos.write(mBuffer, 0, size);
1164 }
1165 data = baos.toByteArray();
1166 } finally {
1167 is.close();
1168 }
1169 } else {
1170 Log.v(TAG, "Cannot load photo " + uri);
1171 }
1172 } catch (IOException e) {
1173 Log.e(TAG, "Cannot load photo " + uri, e);
1174 }
1175
1176 return data;
1177 }
1178
1179 @Override
1180 protected void onPostExecute(byte[] data) {
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001181 if (mContact != null) {
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -08001182 mContact = new Result(mContact);
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001183 mContact.setPhotoBinaryData(data);
1184 mContact.setLoadingPhoto(false);
1185 deliverResult(mContact);
1186 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001187 }
1188 }
1189
Daniel Lehmann3a120772010-06-21 16:21:35 -07001190 private void unregisterObserver() {
1191 if (mObserver != null) {
1192 getContext().getContentResolver().unregisterContentObserver(mObserver);
1193 mObserver = null;
1194 }
1195 }
1196
Daniel Lehmanncdef2b62010-06-06 18:25:49 -07001197 public ContactLoader(Context context, Uri lookupUri) {
Makoto Onuki69b4a882011-07-22 10:05:10 -07001198 this(context, lookupUri, false, false, false);
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001199 }
1200
Dave Santoro39156002011-07-19 01:18:14 -07001201 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki69b4a882011-07-22 10:05:10 -07001202 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001203 super(context);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001204 mLookupUri = lookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -07001205 mLoadGroupMetaData = loadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -07001206 mLoadStreamItems = loadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -07001207 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001208 }
1209
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001210 public Uri getLookupUri() {
1211 return mLookupUri;
1212 }
1213
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001214 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001215 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001216 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001217 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001218 }
1219
1220 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001221 forceLoad();
1222 }
1223 }
1224
1225 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001226 protected void onForceLoad() {
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001227 final LoadContactTask task = new LoadContactTask();
Daniel Lehmann74a2dc52010-04-15 16:52:33 -07001228 task.execute((Void[])null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001229 }
1230
1231 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001232 protected void onReset() {
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001233 unregisterObserver();
1234 mContact = null;
1235 mDestroyed = true;
1236 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001237}