blob: ddebf4e20149619fc95ef79b93823b94960972d3 [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
Josh Gargus84edfd92012-03-15 18:25:58 -0700415 * writable raw-contact, and false otherwise.
Josh Gargus187c8162012-03-13 17:06:53 -0700416 */
Josh Gargus84edfd92012-03-15 18:25:58 -0700417 public boolean isWritableContact(final Context context) {
Josh Gargus187c8162012-03-13 17:06:53 -0700418 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
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700747 // Create the loaded result starting with the Contact data.
748 Result result = loadContactHeaderData(cursor, contactUri);
749
750 // Fill in the raw contacts, which is wrapped in an Entity and any
751 // status data. Initially, result has empty entities and statuses.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800752 long currentRawContactId = -1;
753 Entity entity = null;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800754 ArrayList<Entity> entities = result.getEntities();
755 LongSparseArray<DataStatus> statuses = result.getStatuses();
756 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
757 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
758 if (rawContactId != currentRawContactId) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700759 // First time to see this raw contact id, so create a new entity, and
760 // add it to the result's entities.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800761 currentRawContactId = rawContactId;
762 entity = new android.content.Entity(loadRawContact(cursor));
763 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700764 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800765 if (!cursor.isNull(ContactQuery.DATA_ID)) {
766 ContentValues data = loadData(cursor);
767 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700768
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800769 if (!cursor.isNull(ContactQuery.PRESENCE)
770 || !cursor.isNull(ContactQuery.STATUS)) {
771 final DataStatus status = new DataStatus(cursor);
772 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
773 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700774 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700775 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700776 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800777
778 return result;
779 } finally {
780 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700781 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800782 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700783
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800784 /**
785 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
786 * not found, returns null
787 */
788 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700789
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800790 // If we have a photo URI, try loading that first.
791 String photoUri = contactData.getPhotoUri();
792 if (photoUri != null) {
793 try {
794 AssetFileDescriptor fd = getContext().getContentResolver()
795 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
796 byte[] buffer = new byte[16 * 1024];
797 FileInputStream fis = fd.createInputStream();
798 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700799 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800800 int size;
801 while ((size = fis.read(buffer)) != -1) {
802 baos.write(buffer, 0, size);
803 }
804 contactData.setPhotoBinaryData(baos.toByteArray());
805 } finally {
806 fis.close();
807 fd.close();
808 }
809 return;
810 } catch (IOException ioe) {
811 // Just fall back to the case below.
812 }
813 }
814
815 // If we couldn't load from a file, fall back to the data blob.
816 final long photoId = contactData.getPhotoId();
817 if (photoId <= 0) {
818 // No photo ID
819 return;
820 }
821
822 for (Entity entity : contactData.getEntities()) {
823 for (NamedContentValues subValue : entity.getSubValues()) {
824 final ContentValues entryValues = subValue.values;
825 final long dataId = entryValues.getAsLong(Data._ID);
826 if (dataId == photoId) {
827 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
828 // Correct Data Id but incorrect MimeType? Don't load
829 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
830 return;
831 }
832 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
833 break;
834 }
835 }
836 }
837 }
838
839 /**
840 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
841 */
842 private void loadInvitableAccountTypes(Result contactData) {
843 Map<AccountTypeWithDataSet, AccountType> invitables =
844 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
845 if (invitables.isEmpty()) {
846 return;
847 }
848
849 Map<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(invitables);
850
851 // Remove the ones that already have a raw contact in the current contact
852 for (Entity entity : contactData.getEntities()) {
853 final ContentValues values = entity.getEntityValues();
854 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
855 values.getAsString(RawContacts.ACCOUNT_TYPE),
856 values.getAsString(RawContacts.DATA_SET));
857 result.remove(type);
858 }
859
860 // Set to mInvitableAccountTypes
861 contactData.mInvitableAccountTypes.addAll(result.values());
862 }
863
864 /**
865 * Extracts Contact level columns from the cursor.
866 */
867 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
868 final String directoryParameter =
869 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
870 final long directoryId = directoryParameter == null
871 ? Directory.DEFAULT
872 : Long.parseLong(directoryParameter);
873 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
874 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
875 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
876 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
877 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
878 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
879 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
880 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
881 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
882 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
883 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
884 ? null
885 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
886 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
887 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
888 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
889
890 Uri lookupUri;
891 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
892 lookupUri = ContentUris.withAppendedId(
893 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
894 } else {
895 lookupUri = contactUri;
896 }
897
898 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
899 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
900 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
901 customRingtone, isUserProfile);
902 }
903
904 /**
905 * Extracts RawContact level columns from the cursor.
906 */
907 private ContentValues loadRawContact(Cursor cursor) {
908 ContentValues cv = new ContentValues();
909
910 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
911
912 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
913 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
914 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
915 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
916 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
917 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
918 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
919 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
920 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
921 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
922 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
923 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
924 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
925 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
926 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
927
928 return cv;
929 }
930
931 /**
932 * Extracts Data level columns from the cursor.
933 */
934 private ContentValues loadData(Cursor cursor) {
935 ContentValues cv = new ContentValues();
936
937 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
938
939 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
940 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
941 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
942 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
943 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
944 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
945 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
946 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
947 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
948 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
949 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
950 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
951 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
952 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
953 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
954 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
955 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
956 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
957 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
958 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
959 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
960 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
961 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
962 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
963 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
964 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
965
966 return cv;
967 }
968
969 private void cursorColumnToContentValues(
970 Cursor cursor, ContentValues values, int index) {
971 switch (cursor.getType(index)) {
972 case Cursor.FIELD_TYPE_NULL:
973 // don't put anything in the content values
974 break;
975 case Cursor.FIELD_TYPE_INTEGER:
976 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
977 break;
978 case Cursor.FIELD_TYPE_STRING:
979 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
980 break;
981 case Cursor.FIELD_TYPE_BLOB:
982 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
983 break;
984 default:
985 throw new IllegalStateException("Invalid or unhandled data type");
986 }
987 }
988
989 private void loadDirectoryMetaData(Result result) {
990 long directoryId = result.getDirectoryId();
991
992 Cursor cursor = getContext().getContentResolver().query(
993 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
994 DirectoryQuery.COLUMNS, null, null, null);
995 if (cursor == null) {
996 return;
997 }
998 try {
999 if (cursor.moveToFirst()) {
1000 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
1001 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
1002 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
1003 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
1004 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
1005 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
1006 String directoryType = null;
1007 if (!TextUtils.isEmpty(packageName)) {
1008 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -07001009 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001010 Resources resources = pm.getResourcesForApplication(packageName);
1011 directoryType = resources.getString(typeResourceId);
1012 } catch (NameNotFoundException e) {
1013 Log.w(TAG, "Contact directory resource not found: "
1014 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001015 }
1016 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001017
1018 result.setDirectoryMetaData(
1019 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001020 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001021 } finally {
1022 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001023 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001024 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001025
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001026 /**
1027 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1028 * accounts.
1029 */
1030 private void loadGroupMetaData(Result result) {
1031 StringBuilder selection = new StringBuilder();
1032 ArrayList<String> selectionArgs = new ArrayList<String>();
1033 for (Entity entity : result.mEntities) {
1034 ContentValues values = entity.getEntityValues();
1035 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1036 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1037 String dataSet = values.getAsString(RawContacts.DATA_SET);
1038 if (accountName != null && accountType != null) {
1039 if (selection.length() != 0) {
1040 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001041 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001042 selection.append(
1043 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1044 selectionArgs.add(accountName);
1045 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001046
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001047 if (dataSet != null) {
1048 selection.append(" AND " + Groups.DATA_SET + "=?");
1049 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001050 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001051 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1052 }
1053 selection.append(")");
1054 }
1055 }
1056 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
1057 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1058 null);
1059 try {
1060 while (cursor.moveToNext()) {
1061 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1062 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1063 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1064 final long groupId = cursor.getLong(GroupQuery.ID);
1065 final String title = cursor.getString(GroupQuery.TITLE);
1066 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1067 ? false
1068 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1069 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1070 ? false
1071 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1072
1073 result.addGroupMetaData(new GroupMetaData(
1074 accountName, accountType, dataSet, groupId, title, defaultGroup,
1075 favorites));
1076 }
1077 } finally {
1078 cursor.close();
1079 }
1080 }
1081
1082 /**
1083 * Loads all stream items and stream item photos belonging to this contact.
1084 */
1085 private void loadStreamItems(Result result) {
1086 Cursor cursor = getContext().getContentResolver().query(
1087 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1088 .appendPath(result.getLookupKey())
1089 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1090 null, null, null, null);
1091 LongSparseArray<StreamItemEntry> streamItemsById =
1092 new LongSparseArray<StreamItemEntry>();
1093 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1094 try {
1095 while (cursor.moveToNext()) {
1096 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1097 streamItemsById.put(streamItem.getId(), streamItem);
1098 streamItems.add(streamItem);
1099 }
1100 } finally {
1101 cursor.close();
1102 }
1103
1104 // Pre-decode all HTMLs
1105 final long start = System.currentTimeMillis();
1106 for (StreamItemEntry streamItem : streamItems) {
1107 streamItem.decodeHtml(getContext());
1108 }
1109 final long end = System.currentTimeMillis();
1110 if (DEBUG) {
1111 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1112 + (end - start) + " ms");
1113 }
1114
1115 // Now retrieve any photo records associated with the stream items.
1116 if (!streamItems.isEmpty()) {
1117 if (result.isUserProfile()) {
1118 // If the stream items we're loading are for the profile, we can't bulk-load the
1119 // stream items with a custom selection.
1120 for (StreamItemEntry entry : streamItems) {
1121 Cursor siCursor = getContext().getContentResolver().query(
1122 Uri.withAppendedPath(
1123 ContentUris.withAppendedId(
1124 StreamItems.CONTENT_URI, entry.getId()),
1125 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1126 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001127 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001128 while (siCursor.moveToNext()) {
1129 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001130 }
1131 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001132 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001133 }
Dave Santoro39156002011-07-19 01:18:14 -07001134 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001135 } else {
1136 String[] streamItemIdArr = new String[streamItems.size()];
1137 StringBuilder streamItemPhotoSelection = new StringBuilder();
1138 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1139 for (int i = 0; i < streamItems.size(); i++) {
1140 if (i > 0) {
1141 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001142 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001143 streamItemPhotoSelection.append("?");
1144 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001145 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001146 streamItemPhotoSelection.append(")");
1147 Cursor sipCursor = getContext().getContentResolver().query(
1148 StreamItems.CONTENT_PHOTO_URI,
1149 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1150 StreamItemPhotos.STREAM_ITEM_ID);
1151 try {
1152 while (sipCursor.moveToNext()) {
1153 long streamItemId = sipCursor.getLong(
1154 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1155 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1156 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1157 }
1158 } finally {
1159 sipCursor.close();
1160 }
1161 }
1162 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001163
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001164 // Set the sorted stream items on the result.
1165 Collections.sort(streamItems);
1166 result.mStreamItems.addAll(streamItems);
1167 }
1168
1169 @Override
1170 public void deliverResult(Result result) {
1171 unregisterObserver();
1172
1173 // The creator isn't interested in any further updates
1174 if (isReset() || result == null) {
1175 return;
1176 }
1177
1178 mContact = result;
1179
1180 if (result.isLoaded()) {
1181 mLookupUri = result.getLookupUri();
1182
1183 if (!result.isDirectoryEntry()) {
1184 Log.i(TAG, "Registering content observer for " + mLookupUri);
1185 if (mObserver == null) {
1186 mObserver = new ForceLoadContentObserver();
1187 }
1188 getContext().getContentResolver().registerContentObserver(
1189 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001190 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001191
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001192 // inform the source of the data that this contact is being looked at
1193 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001194 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001195
1196 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001197 }
1198
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001199 /**
1200 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1201 * that the contact has just been loaded
1202 */
1203 private void postViewNotificationToSyncAdapter() {
1204 Context context = getContext();
1205 for (Entity entity : mContact.getEntities()) {
1206 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001207 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1208 if (mNotifiedRawContactIds.contains(rawContactId)) {
1209 continue; // Already notified for this raw contact.
1210 }
1211 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001212 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1213 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001214 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001215 type, dataSet);
1216 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1217 final String resPackageName = accountType.resPackageName;
1218 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001219 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1220 final Intent intent = new Intent();
1221 intent.setClassName(resPackageName, serviceName);
1222 intent.setAction(Intent.ACTION_VIEW);
1223 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1224 try {
1225 context.startService(intent);
1226 } catch (Exception e) {
1227 Log.e(TAG, "Error sending message to source-app", e);
1228 }
1229 }
1230 }
1231 }
1232
Daniel Lehmann3a120772010-06-21 16:21:35 -07001233 private void unregisterObserver() {
1234 if (mObserver != null) {
1235 getContext().getContentResolver().unregisterContentObserver(mObserver);
1236 mObserver = null;
1237 }
1238 }
1239
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001240 /**
1241 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1242 * At the moment, this is only used for debugging purposes
1243 */
1244 public void setLoadStreamItems(boolean value) {
1245 if (mLoadStreamItems != value) {
1246 mLoadStreamItems = value;
1247 onContentChanged();
1248 }
1249 }
1250
1251 public boolean getLoadStreamItems() {
1252 return mLoadStreamItems;
1253 }
1254
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001255 public Uri getLookupUri() {
1256 return mLookupUri;
1257 }
1258
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001259 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001260 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001261 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001262 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001263 }
1264
1265 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001266 forceLoad();
1267 }
1268 }
1269
1270 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001271 protected void onStopLoading() {
1272 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001273 }
1274
1275 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001276 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001277 super.onReset();
1278 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001279 unregisterObserver();
1280 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001281 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001282}