blob: bc0f3f4a1825cf3d69d766891ada70b887dd4b2e [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;
Makoto Onuki6ad227f2011-08-15 13:46:59 -070021import com.android.contacts.model.AccountTypeWithDataSet;
Katherine Kuan6cd5b0a2011-09-16 11:46:01 -070022import com.android.contacts.util.ContactLoaderUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070023import com.android.contacts.util.DataStatus;
Dave Santoro39156002011-07-19 01:18:14 -070024import com.android.contacts.util.StreamItemEntry;
25import com.android.contacts.util.StreamItemPhotoEntry;
Makoto Onuki69b4a882011-07-22 10:05:10 -070026import com.google.android.collect.Lists;
Flavio Lerda37a26842011-06-27 11:36:52 +010027import com.google.common.annotations.VisibleForTesting;
Makoto Onuki6ad227f2011-08-15 13:46:59 -070028import com.google.common.collect.Maps;
Makoto Onukiaba2b832011-08-12 15:44:53 -070029import com.google.common.collect.Sets;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070030
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080031import android.content.AsyncTaskLoader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070032import android.content.ContentResolver;
33import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070034import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070035import android.content.Context;
36import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070037import android.content.Entity.NamedContentValues;
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -070038import android.content.Intent;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070039import android.content.pm.PackageManager;
40import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070041import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070042import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070043import android.database.Cursor;
44import android.net.Uri;
Daniel Lehmann1316b132010-04-13 15:08:53 -070045import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070046import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080047import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070048import android.provider.ContactsContract.Contacts;
49import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070050import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070051import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070052import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070053import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070054import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070055import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070056import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070057import android.util.Log;
Daniel Lehmann18958a22012-02-28 17:45:25 -080058import android.util.LongSparseArray;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070059
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080060import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070061import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080062import java.io.IOException;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070063import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070064import java.util.Collections;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070065import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070066import java.util.Map;
Makoto Onukiaba2b832011-08-12 15:44:53 -070067import java.util.Set;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070068
69/**
70 * Loads a single Contact and all it constituent RawContacts.
71 */
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080072public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070073 private static final String TAG = "ContactLoader";
74
Makoto Onukida9cdc12012-02-27 16:11:50 -080075 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
76
Daniel Lehmann685157e2011-08-29 21:07:01 -070077 private final Uri mRequestedUri;
Makoto Onuki2621c5b2011-10-03 12:56:16 -070078 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070079 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070080 private boolean mLoadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -070081 private final boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070082 private Result mContact;
83 private ForceLoadContentObserver mObserver;
Makoto Onukiaba2b832011-08-12 15:44:53 -070084 private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
Dmitri Plotnikove843f912010-09-16 15:21:48 -070085
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070086 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070087 public void onContactLoaded(Result contact);
88 }
89
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080090 public ContactLoader(Context context, Uri lookupUri) {
91 this(context, lookupUri, false, false, false);
92 }
93
94 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
95 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
96 super(context);
97 mLookupUri = lookupUri;
98 mRequestedUri = lookupUri;
99 mLoadGroupMetaData = loadGroupMetaData;
100 mLoadStreamItems = loadStreamItems;
101 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
102 }
103
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700104 /**
105 * The result of a load operation. Contains all data necessary to display the contact.
106 */
107 public static final class Result {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700108 private enum Status {
109 /** Contact is successfully loaded */
110 LOADED,
111 /** There was an error loading the contact */
112 ERROR,
113 /** Contact is not found */
114 NOT_FOUND,
115 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700116
Daniel Lehmann685157e2011-08-29 21:07:01 -0700117 private final Uri mRequestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700118 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700119 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700120 private final long mDirectoryId;
121 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700122 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700123 private final long mNameRawContactId;
124 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700125 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700126 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700127 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700128 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700129 private final String mPhoneticName;
130 private final boolean mStarred;
131 private final Integer mPresence;
132 private final ArrayList<Entity> mEntities;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700133 private final ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800134 private final LongSparseArray<DataStatus> mStatuses;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700135 private final ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700136
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700137 private String mDirectoryDisplayName;
138 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700139 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700140 private String mDirectoryAccountName;
141 private int mDirectoryExportSupport;
142
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700143 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700144
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800145 private byte[] mPhotoBinaryData;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700146 private final boolean mSendToVoicemail;
147 private final String mCustomRingtone;
148 private final boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800149
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700150 private final Status mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700151 private final Exception mException;
152
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700153 /**
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700154 * Constructor for special results, namely "no contact found" and "error".
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700155 */
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700156 private Result(Uri requestedUri, Status status, Exception exception) {
157 if (status == Status.ERROR && exception == null) {
158 throw new IllegalArgumentException("ERROR result must have exception");
159 }
160 mStatus = status;
161 mException = exception;
162 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700163 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700164 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700165 mDirectoryId = -1;
166 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700167 mId = -1;
168 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700169 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700170 mStatuses = null;
171 mNameRawContactId = -1;
172 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700173 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700174 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700175 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700176 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700177 mPhoneticName = null;
178 mStarred = false;
179 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700180 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700181 mSendToVoicemail = false;
182 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700183 mIsUserProfile = false;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700184 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700185
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700186 private static Result forError(Uri requestedUri, Exception exception) {
187 return new Result(requestedUri, Status.ERROR, exception);
188 }
189
190 private static Result forNotFound(Uri requestedUri) {
191 return new Result(requestedUri, Status.NOT_FOUND, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700192 }
193
194 /**
195 * Constructor to call when contact was found
196 */
Daniel Lehmann685157e2011-08-29 21:07:01 -0700197 private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
198 long id, long nameRawContactId, int displayNameSource, long photoId,
199 String photoUri, String displayName, String altDisplayName, String phoneticName,
200 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700201 boolean isUserProfile) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700202 mStatus = Status.LOADED;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700203 mException = null;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700204 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700205 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700206 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700207 mDirectoryId = directoryId;
208 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700209 mId = id;
210 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700211 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann18958a22012-02-28 17:45:25 -0800212 mStatuses = new LongSparseArray<DataStatus>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700213 mNameRawContactId = nameRawContactId;
214 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700215 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700216 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700217 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700218 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700219 mPhoneticName = phoneticName;
220 mStarred = starred;
221 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700222 mInvitableAccountTypes = Lists.newArrayList();
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700223 mSendToVoicemail = sendToVoicemail;
224 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700225 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700226 }
227
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800228 private Result(Result from) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700229 mStatus = from.mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700230 mException = from.mException;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700231 mRequestedUri = from.mRequestedUri;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800232 mLookupUri = from.mLookupUri;
233 mUri = from.mUri;
234 mDirectoryId = from.mDirectoryId;
235 mLookupKey = from.mLookupKey;
236 mId = from.mId;
237 mNameRawContactId = from.mNameRawContactId;
238 mDisplayNameSource = from.mDisplayNameSource;
239 mPhotoId = from.mPhotoId;
240 mPhotoUri = from.mPhotoUri;
241 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700242 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800243 mPhoneticName = from.mPhoneticName;
244 mStarred = from.mStarred;
245 mPresence = from.mPresence;
246 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700247 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800248 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700249 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800250
251 mDirectoryDisplayName = from.mDirectoryDisplayName;
252 mDirectoryType = from.mDirectoryType;
253 mDirectoryAccountType = from.mDirectoryAccountType;
254 mDirectoryAccountName = from.mDirectoryAccountName;
255 mDirectoryExportSupport = from.mDirectoryExportSupport;
256
257 mGroups = from.mGroups;
258
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800259 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700260 mSendToVoicemail = from.mSendToVoicemail;
261 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700262 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800263 }
264
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700265 /**
266 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
267 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700268 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700269 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700270 mDirectoryDisplayName = displayName;
271 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700272 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700273 mDirectoryAccountName = accountName;
274 mDirectoryExportSupport = exportSupport;
275 }
276
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800277 private void setPhotoBinaryData(byte[] photoBinaryData) {
278 mPhotoBinaryData = photoBinaryData;
279 }
280
Daniel Lehmann685157e2011-08-29 21:07:01 -0700281 /**
282 * Returns the URI for the contact that contains both the lookup key and the ID. This is
283 * the best URI to reference a contact.
284 * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
285 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700286 public Uri getLookupUri() {
287 return mLookupUri;
288 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800289
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700290 public String getLookupKey() {
291 return mLookupKey;
292 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800293
Daniel Lehmann685157e2011-08-29 21:07:01 -0700294 /**
295 * Returns the contact Uri that was passed to the provider to make the query. This is
296 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
297 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
298 * always reference the full aggregate contact.
299 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700300 public Uri getUri() {
301 return mUri;
302 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800303
Daniel Lehmann685157e2011-08-29 21:07:01 -0700304 /**
305 * Returns the URI for which this {@link ContactLoader) was initially requested.
306 */
307 public Uri getRequestedUri() {
308 return mRequestedUri;
309 }
310
Dave Santoro6fa73842011-09-28 14:37:06 -0700311 /**
312 * Returns the contact ID.
313 */
Makoto Onuki98306102011-11-28 15:16:58 -0800314 @VisibleForTesting
315 /* package */ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700316 return mId;
317 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800318
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700319 /**
320 * @return true when an exception happened during loading, in which case
321 * {@link #getException} returns the actual exception object.
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700322 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
323 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
324 * and vice versa.
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700325 */
326 public boolean isError() {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700327 return mStatus == Status.ERROR;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700328 }
329
330 public Exception getException() {
331 return mException;
332 }
333
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700334 /**
335 * @return true when the specified contact is not found.
336 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
337 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
338 * and vice versa.
339 */
340 public boolean isNotFound() {
341 return mStatus == Status.NOT_FOUND;
342 }
343
344 /**
345 * @return true if the specified contact is successfully loaded.
346 * i.e. neither {@link #isError()} nor {@link #isNotFound()}.
347 */
348 public boolean isLoaded() {
349 return mStatus == Status.LOADED;
350 }
351
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700352 public long getNameRawContactId() {
353 return mNameRawContactId;
354 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800355
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700356 public int getDisplayNameSource() {
357 return mDisplayNameSource;
358 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800359
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700360 public long getPhotoId() {
361 return mPhotoId;
362 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800363
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700364 public String getPhotoUri() {
365 return mPhotoUri;
366 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800367
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700368 public String getDisplayName() {
369 return mDisplayName;
370 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800371
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700372 public String getAltDisplayName() {
373 return mAltDisplayName;
374 }
375
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700376 public String getPhoneticName() {
377 return mPhoneticName;
378 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800379
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700380 public boolean getStarred() {
381 return mStarred;
382 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800383
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700384 public Integer getPresence() {
385 return mPresence;
386 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800387
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700388 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700389 return mInvitableAccountTypes;
390 }
391
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700392 public ArrayList<Entity> getEntities() {
393 return mEntities;
394 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800395
Dave Santoro39156002011-07-19 01:18:14 -0700396 public ArrayList<StreamItemEntry> getStreamItems() {
397 return mStreamItems;
398 }
399
Daniel Lehmann18958a22012-02-28 17:45:25 -0800400 public LongSparseArray<DataStatus> getStatuses() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700401 return mStatuses;
402 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700403
404 public long getDirectoryId() {
405 return mDirectoryId;
406 }
407
408 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700409 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
410 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700411 }
412
Josh Gargus187c8162012-03-13 17:06:53 -0700413 /**
414 * @return true if this is a contact (not group, etc.) with at least one
415 * writeable raw-contact, and false otherwise.
416 */
417 public boolean isWritableContact(Context context) {
418 if (isDirectoryEntry()) return false;
419 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
420 for (Entity rawContact : getEntities()) {
421 final ContentValues rawValues = rawContact.getEntityValues();
422 final String accountType = rawValues.getAsString(RawContacts.ACCOUNT_TYPE);
423 final String dataSet = rawValues.getAsString(RawContacts.DATA_SET);
424 final AccountType type = accountTypes.getAccountType(accountType, dataSet);
425 if (type != null && type.areContactsWritable()) return true;
426 }
427 return false;
428 }
429
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700430 public int getDirectoryExportSupport() {
431 return mDirectoryExportSupport;
432 }
433
434 public String getDirectoryDisplayName() {
435 return mDirectoryDisplayName;
436 }
437
438 public String getDirectoryType() {
439 return mDirectoryType;
440 }
441
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700442 public String getDirectoryAccountType() {
443 return mDirectoryAccountType;
444 }
445
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700446 public String getDirectoryAccountName() {
447 return mDirectoryAccountName;
448 }
449
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800450 public byte[] getPhotoBinaryData() {
451 return mPhotoBinaryData;
452 }
453
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700454 public ArrayList<ContentValues> getContentValues() {
455 if (mEntities.size() != 1) {
456 throw new IllegalStateException(
457 "Cannot extract content values from an aggregated contact");
458 }
459
460 Entity entity = mEntities.get(0);
461 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
462 ArrayList<NamedContentValues> subValues = entity.getSubValues();
463 if (subValues != null) {
464 int size = subValues.size();
465 for (int i = 0; i < size; i++) {
466 NamedContentValues pair = subValues.get(i);
467 if (Data.CONTENT_URI.equals(pair.uri)) {
468 result.add(pair.values);
469 }
470 }
471 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800472
473 // If the photo was loaded using the URI, create an entry for the photo
474 // binary data.
475 if (mPhotoId == 0 && mPhotoBinaryData != null) {
476 ContentValues photo = new ContentValues();
477 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
478 photo.put(Photo.PHOTO, mPhotoBinaryData);
479 result.add(photo);
480 }
481
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700482 return result;
483 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700484
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700485 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700486 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700487 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700488 }
489 mGroups.add(group);
490 }
491
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700492 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700493 return mGroups;
494 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700495
496 public boolean isSendToVoicemail() {
497 return mSendToVoicemail;
498 }
499
500 public String getCustomRingtone() {
501 return mCustomRingtone;
502 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700503
504 public boolean isUserProfile() {
505 return mIsUserProfile;
506 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700507 }
508
Dave Santoro39156002011-07-19 01:18:14 -0700509 /**
510 * Projection used for the query that loads all data for the entire contact (except for
511 * social stream items).
512 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700513 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700514 final static String[] COLUMNS = new String[] {
515 Contacts.NAME_RAW_CONTACT_ID,
516 Contacts.DISPLAY_NAME_SOURCE,
517 Contacts.LOOKUP_KEY,
518 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700519 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700520 Contacts.PHONETIC_NAME,
521 Contacts.PHOTO_ID,
522 Contacts.STARRED,
523 Contacts.CONTACT_PRESENCE,
524 Contacts.CONTACT_STATUS,
525 Contacts.CONTACT_STATUS_TIMESTAMP,
526 Contacts.CONTACT_STATUS_RES_PACKAGE,
527 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700528 Contacts.Entity.CONTACT_ID,
529 Contacts.Entity.RAW_CONTACT_ID,
530
531 RawContacts.ACCOUNT_NAME,
532 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700533 RawContacts.DATA_SET,
534 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700535 RawContacts.DIRTY,
536 RawContacts.VERSION,
537 RawContacts.SOURCE_ID,
538 RawContacts.SYNC1,
539 RawContacts.SYNC2,
540 RawContacts.SYNC3,
541 RawContacts.SYNC4,
542 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700543 RawContacts.NAME_VERIFIED,
544
545 Contacts.Entity.DATA_ID,
546 Data.DATA1,
547 Data.DATA2,
548 Data.DATA3,
549 Data.DATA4,
550 Data.DATA5,
551 Data.DATA6,
552 Data.DATA7,
553 Data.DATA8,
554 Data.DATA9,
555 Data.DATA10,
556 Data.DATA11,
557 Data.DATA12,
558 Data.DATA13,
559 Data.DATA14,
560 Data.DATA15,
561 Data.SYNC1,
562 Data.SYNC2,
563 Data.SYNC3,
564 Data.SYNC4,
565 Data.DATA_VERSION,
566 Data.IS_PRIMARY,
567 Data.IS_SUPER_PRIMARY,
568 Data.MIMETYPE,
569 Data.RES_PACKAGE,
570
571 GroupMembership.GROUP_SOURCE_ID,
572
573 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700574 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700575 Data.STATUS,
576 Data.STATUS_RES_PACKAGE,
577 Data.STATUS_ICON,
578 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700579 Data.STATUS_TIMESTAMP,
580
581 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700582 Contacts.SEND_TO_VOICEMAIL,
583 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700584 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700585 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700586
587 public final static int NAME_RAW_CONTACT_ID = 0;
588 public final static int DISPLAY_NAME_SOURCE = 1;
589 public final static int LOOKUP_KEY = 2;
590 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700591 public final static int ALT_DISPLAY_NAME = 4;
592 public final static int PHONETIC_NAME = 5;
593 public final static int PHOTO_ID = 6;
594 public final static int STARRED = 7;
595 public final static int CONTACT_PRESENCE = 8;
596 public final static int CONTACT_STATUS = 9;
597 public final static int CONTACT_STATUS_TIMESTAMP = 10;
598 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
599 public final static int CONTACT_STATUS_LABEL = 12;
600 public final static int CONTACT_ID = 13;
601 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700602
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700603 public final static int ACCOUNT_NAME = 15;
604 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700605 public final static int DATA_SET = 17;
606 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
607 public final static int DIRTY = 19;
608 public final static int VERSION = 20;
609 public final static int SOURCE_ID = 21;
610 public final static int SYNC1 = 22;
611 public final static int SYNC2 = 23;
612 public final static int SYNC3 = 24;
613 public final static int SYNC4 = 25;
614 public final static int DELETED = 26;
615 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700616
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700617 public final static int DATA_ID = 28;
618 public final static int DATA1 = 29;
619 public final static int DATA2 = 30;
620 public final static int DATA3 = 31;
621 public final static int DATA4 = 32;
622 public final static int DATA5 = 33;
623 public final static int DATA6 = 34;
624 public final static int DATA7 = 35;
625 public final static int DATA8 = 36;
626 public final static int DATA9 = 37;
627 public final static int DATA10 = 38;
628 public final static int DATA11 = 39;
629 public final static int DATA12 = 40;
630 public final static int DATA13 = 41;
631 public final static int DATA14 = 42;
632 public final static int DATA15 = 43;
633 public final static int DATA_SYNC1 = 44;
634 public final static int DATA_SYNC2 = 45;
635 public final static int DATA_SYNC3 = 46;
636 public final static int DATA_SYNC4 = 47;
637 public final static int DATA_VERSION = 48;
638 public final static int IS_PRIMARY = 49;
639 public final static int IS_SUPERPRIMARY = 50;
640 public final static int MIMETYPE = 51;
641 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700642
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700643 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700644
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700645 public final static int PRESENCE = 54;
646 public final static int CHAT_CAPABILITY = 55;
647 public final static int STATUS = 56;
648 public final static int STATUS_RES_PACKAGE = 57;
649 public final static int STATUS_ICON = 58;
650 public final static int STATUS_LABEL = 59;
651 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700652
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700653 public final static int PHOTO_URI = 61;
654 public final static int SEND_TO_VOICEMAIL = 62;
655 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700656 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700657 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700658
Dave Santoro39156002011-07-19 01:18:14 -0700659 /**
660 * Projection used for the query that loads all data for the entire contact.
661 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700662 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700663 final static String[] COLUMNS = new String[] {
664 Directory.DISPLAY_NAME,
665 Directory.PACKAGE_NAME,
666 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700667 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700668 Directory.ACCOUNT_NAME,
669 Directory.EXPORT_SUPPORT,
670 };
671
672 public final static int DISPLAY_NAME = 0;
673 public final static int PACKAGE_NAME = 1;
674 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700675 public final static int ACCOUNT_TYPE = 3;
676 public final static int ACCOUNT_NAME = 4;
677 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700678 }
679
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700680 private static class GroupQuery {
681 final static String[] COLUMNS = new String[] {
682 Groups.ACCOUNT_NAME,
683 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700684 Groups.DATA_SET,
685 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700686 Groups._ID,
687 Groups.TITLE,
688 Groups.AUTO_ADD,
689 Groups.FAVORITES,
690 };
691
692 public final static int ACCOUNT_NAME = 0;
693 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700694 public final static int DATA_SET = 2;
695 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
696 public final static int ID = 4;
697 public final static int TITLE = 5;
698 public final static int AUTO_ADD = 6;
699 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700700 }
701
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800702 @Override
703 public Result loadInBackground() {
704 try {
705 final ContentResolver resolver = getContext().getContentResolver();
706 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
707 resolver, mLookupUri);
708 Result result = loadContactEntity(resolver, uriCurrentFormat);
709 if (!result.isNotFound()) {
710 if (result.isDirectoryEntry()) {
711 loadDirectoryMetaData(result);
712 } else if (mLoadGroupMetaData) {
713 loadGroupMetaData(result);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700714 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800715 if (mLoadStreamItems) {
716 loadStreamItems(result);
717 }
718 loadPhotoBinaryData(result);
719
720 // Note ME profile should never have "Add connection"
721 if (mLoadInvitableAccountTypes && !result.isUserProfile()) {
722 loadInvitableAccountTypes(result);
723 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700724 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800725 return result;
726 } catch (Exception e) {
727 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
728 return Result.forError(mRequestedUri, e);
729 }
730 }
731
732 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
733 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
734 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
735 Contacts.Entity.RAW_CONTACT_ID);
736 if (cursor == null) {
737 Log.e(TAG, "No cursor returned in loadContactEntity");
738 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700739 }
740
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800741 try {
742 if (!cursor.moveToFirst()) {
743 cursor.close();
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700744 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700745 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700746
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800747 long currentRawContactId = -1;
748 Entity entity = null;
749 Result result = loadContactHeaderData(cursor, contactUri);
750 ArrayList<Entity> entities = result.getEntities();
751 LongSparseArray<DataStatus> statuses = result.getStatuses();
752 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
753 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
754 if (rawContactId != currentRawContactId) {
755 currentRawContactId = rawContactId;
756 entity = new android.content.Entity(loadRawContact(cursor));
757 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700758 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800759 if (!cursor.isNull(ContactQuery.DATA_ID)) {
760 ContentValues data = loadData(cursor);
761 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700762
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800763 if (!cursor.isNull(ContactQuery.PRESENCE)
764 || !cursor.isNull(ContactQuery.STATUS)) {
765 final DataStatus status = new DataStatus(cursor);
766 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
767 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700768 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700769 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700770 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800771
772 return result;
773 } finally {
774 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700775 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800776 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700777
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800778 /**
779 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
780 * not found, returns null
781 */
782 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700783
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800784 // If we have a photo URI, try loading that first.
785 String photoUri = contactData.getPhotoUri();
786 if (photoUri != null) {
787 try {
788 AssetFileDescriptor fd = getContext().getContentResolver()
789 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
790 byte[] buffer = new byte[16 * 1024];
791 FileInputStream fis = fd.createInputStream();
792 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700793 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800794 int size;
795 while ((size = fis.read(buffer)) != -1) {
796 baos.write(buffer, 0, size);
797 }
798 contactData.setPhotoBinaryData(baos.toByteArray());
799 } finally {
800 fis.close();
801 fd.close();
802 }
803 return;
804 } catch (IOException ioe) {
805 // Just fall back to the case below.
806 }
807 }
808
809 // If we couldn't load from a file, fall back to the data blob.
810 final long photoId = contactData.getPhotoId();
811 if (photoId <= 0) {
812 // No photo ID
813 return;
814 }
815
816 for (Entity entity : contactData.getEntities()) {
817 for (NamedContentValues subValue : entity.getSubValues()) {
818 final ContentValues entryValues = subValue.values;
819 final long dataId = entryValues.getAsLong(Data._ID);
820 if (dataId == photoId) {
821 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
822 // Correct Data Id but incorrect MimeType? Don't load
823 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
824 return;
825 }
826 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
827 break;
828 }
829 }
830 }
831 }
832
833 /**
834 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
835 */
836 private void loadInvitableAccountTypes(Result contactData) {
837 Map<AccountTypeWithDataSet, AccountType> invitables =
838 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
839 if (invitables.isEmpty()) {
840 return;
841 }
842
843 Map<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(invitables);
844
845 // Remove the ones that already have a raw contact in the current contact
846 for (Entity entity : contactData.getEntities()) {
847 final ContentValues values = entity.getEntityValues();
848 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
849 values.getAsString(RawContacts.ACCOUNT_TYPE),
850 values.getAsString(RawContacts.DATA_SET));
851 result.remove(type);
852 }
853
854 // Set to mInvitableAccountTypes
855 contactData.mInvitableAccountTypes.addAll(result.values());
856 }
857
858 /**
859 * Extracts Contact level columns from the cursor.
860 */
861 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
862 final String directoryParameter =
863 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
864 final long directoryId = directoryParameter == null
865 ? Directory.DEFAULT
866 : Long.parseLong(directoryParameter);
867 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
868 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
869 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
870 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
871 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
872 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
873 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
874 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
875 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
876 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
877 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
878 ? null
879 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
880 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
881 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
882 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
883
884 Uri lookupUri;
885 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
886 lookupUri = ContentUris.withAppendedId(
887 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
888 } else {
889 lookupUri = contactUri;
890 }
891
892 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
893 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
894 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
895 customRingtone, isUserProfile);
896 }
897
898 /**
899 * Extracts RawContact level columns from the cursor.
900 */
901 private ContentValues loadRawContact(Cursor cursor) {
902 ContentValues cv = new ContentValues();
903
904 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
905
906 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
907 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
908 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
909 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
910 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
911 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
912 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
913 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
914 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
915 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
916 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
917 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
918 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
919 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
920 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
921
922 return cv;
923 }
924
925 /**
926 * Extracts Data level columns from the cursor.
927 */
928 private ContentValues loadData(Cursor cursor) {
929 ContentValues cv = new ContentValues();
930
931 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
932
933 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
934 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
935 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
936 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
937 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
938 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
939 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
940 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
941 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
942 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
943 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
944 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
945 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
946 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
947 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
948 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
949 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
950 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
951 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
952 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
953 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
954 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
955 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
956 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
957 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
958 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
959
960 return cv;
961 }
962
963 private void cursorColumnToContentValues(
964 Cursor cursor, ContentValues values, int index) {
965 switch (cursor.getType(index)) {
966 case Cursor.FIELD_TYPE_NULL:
967 // don't put anything in the content values
968 break;
969 case Cursor.FIELD_TYPE_INTEGER:
970 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
971 break;
972 case Cursor.FIELD_TYPE_STRING:
973 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
974 break;
975 case Cursor.FIELD_TYPE_BLOB:
976 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
977 break;
978 default:
979 throw new IllegalStateException("Invalid or unhandled data type");
980 }
981 }
982
983 private void loadDirectoryMetaData(Result result) {
984 long directoryId = result.getDirectoryId();
985
986 Cursor cursor = getContext().getContentResolver().query(
987 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
988 DirectoryQuery.COLUMNS, null, null, null);
989 if (cursor == null) {
990 return;
991 }
992 try {
993 if (cursor.moveToFirst()) {
994 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
995 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
996 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
997 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
998 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
999 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
1000 String directoryType = null;
1001 if (!TextUtils.isEmpty(packageName)) {
1002 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -07001003 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001004 Resources resources = pm.getResourcesForApplication(packageName);
1005 directoryType = resources.getString(typeResourceId);
1006 } catch (NameNotFoundException e) {
1007 Log.w(TAG, "Contact directory resource not found: "
1008 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001009 }
1010 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001011
1012 result.setDirectoryMetaData(
1013 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001014 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001015 } finally {
1016 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001017 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001018 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001019
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001020 /**
1021 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1022 * accounts.
1023 */
1024 private void loadGroupMetaData(Result result) {
1025 StringBuilder selection = new StringBuilder();
1026 ArrayList<String> selectionArgs = new ArrayList<String>();
1027 for (Entity entity : result.mEntities) {
1028 ContentValues values = entity.getEntityValues();
1029 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1030 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1031 String dataSet = values.getAsString(RawContacts.DATA_SET);
1032 if (accountName != null && accountType != null) {
1033 if (selection.length() != 0) {
1034 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001035 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001036 selection.append(
1037 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1038 selectionArgs.add(accountName);
1039 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001040
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001041 if (dataSet != null) {
1042 selection.append(" AND " + Groups.DATA_SET + "=?");
1043 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001044 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001045 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1046 }
1047 selection.append(")");
1048 }
1049 }
1050 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
1051 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1052 null);
1053 try {
1054 while (cursor.moveToNext()) {
1055 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1056 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1057 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1058 final long groupId = cursor.getLong(GroupQuery.ID);
1059 final String title = cursor.getString(GroupQuery.TITLE);
1060 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1061 ? false
1062 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1063 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1064 ? false
1065 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1066
1067 result.addGroupMetaData(new GroupMetaData(
1068 accountName, accountType, dataSet, groupId, title, defaultGroup,
1069 favorites));
1070 }
1071 } finally {
1072 cursor.close();
1073 }
1074 }
1075
1076 /**
1077 * Loads all stream items and stream item photos belonging to this contact.
1078 */
1079 private void loadStreamItems(Result result) {
1080 Cursor cursor = getContext().getContentResolver().query(
1081 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1082 .appendPath(result.getLookupKey())
1083 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1084 null, null, null, null);
1085 LongSparseArray<StreamItemEntry> streamItemsById =
1086 new LongSparseArray<StreamItemEntry>();
1087 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1088 try {
1089 while (cursor.moveToNext()) {
1090 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1091 streamItemsById.put(streamItem.getId(), streamItem);
1092 streamItems.add(streamItem);
1093 }
1094 } finally {
1095 cursor.close();
1096 }
1097
1098 // Pre-decode all HTMLs
1099 final long start = System.currentTimeMillis();
1100 for (StreamItemEntry streamItem : streamItems) {
1101 streamItem.decodeHtml(getContext());
1102 }
1103 final long end = System.currentTimeMillis();
1104 if (DEBUG) {
1105 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1106 + (end - start) + " ms");
1107 }
1108
1109 // Now retrieve any photo records associated with the stream items.
1110 if (!streamItems.isEmpty()) {
1111 if (result.isUserProfile()) {
1112 // If the stream items we're loading are for the profile, we can't bulk-load the
1113 // stream items with a custom selection.
1114 for (StreamItemEntry entry : streamItems) {
1115 Cursor siCursor = getContext().getContentResolver().query(
1116 Uri.withAppendedPath(
1117 ContentUris.withAppendedId(
1118 StreamItems.CONTENT_URI, entry.getId()),
1119 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1120 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001121 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001122 while (siCursor.moveToNext()) {
1123 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001124 }
1125 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001126 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001127 }
Dave Santoro39156002011-07-19 01:18:14 -07001128 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001129 } else {
1130 String[] streamItemIdArr = new String[streamItems.size()];
1131 StringBuilder streamItemPhotoSelection = new StringBuilder();
1132 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1133 for (int i = 0; i < streamItems.size(); i++) {
1134 if (i > 0) {
1135 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001136 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001137 streamItemPhotoSelection.append("?");
1138 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001139 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001140 streamItemPhotoSelection.append(")");
1141 Cursor sipCursor = getContext().getContentResolver().query(
1142 StreamItems.CONTENT_PHOTO_URI,
1143 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1144 StreamItemPhotos.STREAM_ITEM_ID);
1145 try {
1146 while (sipCursor.moveToNext()) {
1147 long streamItemId = sipCursor.getLong(
1148 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1149 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1150 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1151 }
1152 } finally {
1153 sipCursor.close();
1154 }
1155 }
1156 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001157
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001158 // Set the sorted stream items on the result.
1159 Collections.sort(streamItems);
1160 result.mStreamItems.addAll(streamItems);
1161 }
1162
1163 @Override
1164 public void deliverResult(Result result) {
1165 unregisterObserver();
1166
1167 // The creator isn't interested in any further updates
1168 if (isReset() || result == null) {
1169 return;
1170 }
1171
1172 mContact = result;
1173
1174 if (result.isLoaded()) {
1175 mLookupUri = result.getLookupUri();
1176
1177 if (!result.isDirectoryEntry()) {
1178 Log.i(TAG, "Registering content observer for " + mLookupUri);
1179 if (mObserver == null) {
1180 mObserver = new ForceLoadContentObserver();
1181 }
1182 getContext().getContentResolver().registerContentObserver(
1183 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001184 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001185
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001186 // inform the source of the data that this contact is being looked at
1187 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001188 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001189
1190 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001191 }
1192
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001193 /**
1194 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1195 * that the contact has just been loaded
1196 */
1197 private void postViewNotificationToSyncAdapter() {
1198 Context context = getContext();
1199 for (Entity entity : mContact.getEntities()) {
1200 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001201 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1202 if (mNotifiedRawContactIds.contains(rawContactId)) {
1203 continue; // Already notified for this raw contact.
1204 }
1205 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001206 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1207 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001208 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001209 type, dataSet);
1210 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1211 final String resPackageName = accountType.resPackageName;
1212 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001213 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1214 final Intent intent = new Intent();
1215 intent.setClassName(resPackageName, serviceName);
1216 intent.setAction(Intent.ACTION_VIEW);
1217 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1218 try {
1219 context.startService(intent);
1220 } catch (Exception e) {
1221 Log.e(TAG, "Error sending message to source-app", e);
1222 }
1223 }
1224 }
1225 }
1226
Daniel Lehmann3a120772010-06-21 16:21:35 -07001227 private void unregisterObserver() {
1228 if (mObserver != null) {
1229 getContext().getContentResolver().unregisterContentObserver(mObserver);
1230 mObserver = null;
1231 }
1232 }
1233
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001234 /**
1235 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1236 * At the moment, this is only used for debugging purposes
1237 */
1238 public void setLoadStreamItems(boolean value) {
1239 if (mLoadStreamItems != value) {
1240 mLoadStreamItems = value;
1241 onContentChanged();
1242 }
1243 }
1244
1245 public boolean getLoadStreamItems() {
1246 return mLoadStreamItems;
1247 }
1248
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001249 public Uri getLookupUri() {
1250 return mLookupUri;
1251 }
1252
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001253 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001254 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001255 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001256 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001257 }
1258
1259 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001260 forceLoad();
1261 }
1262 }
1263
1264 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001265 protected void onStopLoading() {
1266 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001267 }
1268
1269 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001270 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001271 super.onReset();
1272 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001273 unregisterObserver();
1274 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001275 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001276}