blob: 10579f3381a427072f0db2354824f07636ad61bf [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;
Josh Garguse5d3f892012-04-11 11:56:15 -070022import com.android.contacts.model.EntityDeltaList;
Katherine Kuan6cd5b0a2011-09-16 11:46:01 -070023import com.android.contacts.util.ContactLoaderUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070024import com.android.contacts.util.DataStatus;
Dave Santoro39156002011-07-19 01:18:14 -070025import com.android.contacts.util.StreamItemEntry;
26import com.android.contacts.util.StreamItemPhotoEntry;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -070027import com.android.contacts.util.UriUtils;
Flavio Lerda37a26842011-06-27 11:36:52 +010028import com.google.common.annotations.VisibleForTesting;
Daniel Lehmann49ea2d22012-04-17 11:17:12 -070029import com.google.common.collect.Lists;
Makoto Onuki6ad227f2011-08-15 13:46:59 -070030import com.google.common.collect.Maps;
Makoto Onukiaba2b832011-08-12 15:44:53 -070031import com.google.common.collect.Sets;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070032
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080033import android.content.AsyncTaskLoader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070034import android.content.ContentResolver;
35import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070036import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070037import android.content.Context;
38import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070039import android.content.Entity.NamedContentValues;
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -070040import android.content.Intent;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070041import android.content.pm.PackageManager;
42import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070043import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070044import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070045import android.database.Cursor;
46import android.net.Uri;
Daniel Lehmann1316b132010-04-13 15:08:53 -070047import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070048import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080049import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070050import android.provider.ContactsContract.Contacts;
51import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070052import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070053import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070054import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070055import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070056import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070057import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070058import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070059import android.util.Log;
Daniel Lehmann18958a22012-02-28 17:45:25 -080060import android.util.LongSparseArray;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070061
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080062import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070063import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080064import java.io.IOException;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070065import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070066import java.util.Collections;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070067import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070068import java.util.Map;
Makoto Onukiaba2b832011-08-12 15:44:53 -070069import java.util.Set;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070070
71/**
72 * Loads a single Contact and all it constituent RawContacts.
73 */
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080074public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> {
Josh Garguse5d3f892012-04-11 11:56:15 -070075 private static final String TAG = ContactLoader.class.getSimpleName();
Daniel Lehmann18f104f2010-05-07 15:41:11 -070076
Makoto Onukida9cdc12012-02-27 16:11:50 -080077 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
78
Daniel Lehmann9815d7f2012-04-16 18:28:03 -070079 /** A short-lived cache that can be set by {@link #cacheResult()} */
80 private static Result sCachedResult = null;
81
Daniel Lehmann685157e2011-08-29 21:07:01 -070082 private final Uri mRequestedUri;
Makoto Onuki2621c5b2011-10-03 12:56:16 -070083 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070084 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070085 private boolean mLoadStreamItems;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -070086 private boolean mLoadInvitableAccountTypes;
Makoto Onuki45ee8722012-05-21 16:33:25 -070087 private boolean mPostViewNotification;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070088 private Result mContact;
89 private ForceLoadContentObserver mObserver;
Makoto Onukiaba2b832011-08-12 15:44:53 -070090 private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
Dmitri Plotnikove843f912010-09-16 15:21:48 -070091
Makoto Onuki45ee8722012-05-21 16:33:25 -070092 public ContactLoader(Context context, Uri lookupUri, boolean postViewNotification) {
93 this(context, lookupUri, false, false, false, postViewNotification);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080094 }
95
96 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
Makoto Onuki45ee8722012-05-21 16:33:25 -070097 boolean loadStreamItems, boolean loadInvitableAccountTypes,
98 boolean postViewNotification) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080099 super(context);
100 mLookupUri = lookupUri;
101 mRequestedUri = lookupUri;
102 mLoadGroupMetaData = loadGroupMetaData;
103 mLoadStreamItems = loadStreamItems;
104 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
Makoto Onuki45ee8722012-05-21 16:33:25 -0700105 mPostViewNotification = postViewNotification;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800106 }
107
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700108 /**
109 * The result of a load operation. Contains all data necessary to display the contact.
110 */
111 public static final class Result {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700112 private enum Status {
113 /** Contact is successfully loaded */
114 LOADED,
115 /** There was an error loading the contact */
116 ERROR,
117 /** Contact is not found */
118 NOT_FOUND,
119 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700120
Daniel Lehmann685157e2011-08-29 21:07:01 -0700121 private final Uri mRequestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700122 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700123 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700124 private final long mDirectoryId;
125 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700126 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700127 private final long mNameRawContactId;
128 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700129 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700130 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700131 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700132 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700133 private final String mPhoneticName;
134 private final boolean mStarred;
135 private final Integer mPresence;
136 private final ArrayList<Entity> mEntities;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700137 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800138 private final LongSparseArray<DataStatus> mStatuses;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700139 private ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700140
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700141 private String mDirectoryDisplayName;
142 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700143 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700144 private String mDirectoryAccountName;
145 private int mDirectoryExportSupport;
146
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700147 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700148
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800149 private byte[] mPhotoBinaryData;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700150 private final boolean mSendToVoicemail;
151 private final String mCustomRingtone;
152 private final boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800153
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700154 private final Status mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700155 private final Exception mException;
156
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700157 /**
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700158 * Constructor for special results, namely "no contact found" and "error".
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700159 */
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700160 private Result(Uri requestedUri, Status status, Exception exception) {
161 if (status == Status.ERROR && exception == null) {
162 throw new IllegalArgumentException("ERROR result must have exception");
163 }
164 mStatus = status;
165 mException = exception;
166 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700167 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700168 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700169 mDirectoryId = -1;
170 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700171 mId = -1;
172 mEntities = null;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700173 mStreamItems = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700174 mStatuses = null;
175 mNameRawContactId = -1;
176 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700177 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700178 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700179 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700180 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700181 mPhoneticName = null;
182 mStarred = false;
183 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700184 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700185 mSendToVoicemail = false;
186 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700187 mIsUserProfile = false;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700188 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700189
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700190 private static Result forError(Uri requestedUri, Exception exception) {
191 return new Result(requestedUri, Status.ERROR, exception);
192 }
193
194 private static Result forNotFound(Uri requestedUri) {
195 return new Result(requestedUri, Status.NOT_FOUND, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700196 }
197
198 /**
199 * Constructor to call when contact was found
200 */
Daniel Lehmann685157e2011-08-29 21:07:01 -0700201 private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
202 long id, long nameRawContactId, int displayNameSource, long photoId,
203 String photoUri, String displayName, String altDisplayName, String phoneticName,
204 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700205 boolean isUserProfile) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700206 mStatus = Status.LOADED;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700207 mException = null;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700208 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700209 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700210 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700211 mDirectoryId = directoryId;
212 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700213 mId = id;
214 mEntities = new ArrayList<Entity>();
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700215 mStreamItems = null;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800216 mStatuses = new LongSparseArray<DataStatus>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700217 mNameRawContactId = nameRawContactId;
218 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700219 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700220 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700221 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700222 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700223 mPhoneticName = phoneticName;
224 mStarred = starred;
225 mPresence = presence;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700226 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700227 mSendToVoicemail = sendToVoicemail;
228 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700229 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700230 }
231
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700232 private Result(Uri requestedUri, Result from) {
233 mRequestedUri = requestedUri;
234
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700235 mStatus = from.mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700236 mException = from.mException;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800237 mLookupUri = from.mLookupUri;
238 mUri = from.mUri;
239 mDirectoryId = from.mDirectoryId;
240 mLookupKey = from.mLookupKey;
241 mId = from.mId;
242 mNameRawContactId = from.mNameRawContactId;
243 mDisplayNameSource = from.mDisplayNameSource;
244 mPhotoId = from.mPhotoId;
245 mPhotoUri = from.mPhotoUri;
246 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700247 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800248 mPhoneticName = from.mPhoneticName;
249 mStarred = from.mStarred;
250 mPresence = from.mPresence;
251 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700252 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800253 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700254 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800255
256 mDirectoryDisplayName = from.mDirectoryDisplayName;
257 mDirectoryType = from.mDirectoryType;
258 mDirectoryAccountType = from.mDirectoryAccountType;
259 mDirectoryAccountName = from.mDirectoryAccountName;
260 mDirectoryExportSupport = from.mDirectoryExportSupport;
261
262 mGroups = from.mGroups;
263
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800264 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700265 mSendToVoicemail = from.mSendToVoicemail;
266 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700267 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800268 }
269
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700270 /**
271 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
272 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700273 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700274 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700275 mDirectoryDisplayName = displayName;
276 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700277 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700278 mDirectoryAccountName = accountName;
279 mDirectoryExportSupport = exportSupport;
280 }
281
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800282 private void setPhotoBinaryData(byte[] photoBinaryData) {
283 mPhotoBinaryData = photoBinaryData;
284 }
285
Daniel Lehmann685157e2011-08-29 21:07:01 -0700286 /**
287 * Returns the URI for the contact that contains both the lookup key and the ID. This is
288 * the best URI to reference a contact.
289 * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
290 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700291 public Uri getLookupUri() {
292 return mLookupUri;
293 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800294
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700295 public String getLookupKey() {
296 return mLookupKey;
297 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800298
Daniel Lehmann685157e2011-08-29 21:07:01 -0700299 /**
300 * Returns the contact Uri that was passed to the provider to make the query. This is
301 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
302 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
303 * always reference the full aggregate contact.
304 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700305 public Uri getUri() {
306 return mUri;
307 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800308
Daniel Lehmann685157e2011-08-29 21:07:01 -0700309 /**
310 * Returns the URI for which this {@link ContactLoader) was initially requested.
311 */
312 public Uri getRequestedUri() {
313 return mRequestedUri;
314 }
315
Dave Santoro6fa73842011-09-28 14:37:06 -0700316 /**
Josh Garguse5d3f892012-04-11 11:56:15 -0700317 * Instantiate a new EntityDeltaList for this contact.
318 */
319 public EntityDeltaList createEntityDeltaList() {
320 return EntityDeltaList.fromIterator(getEntities().iterator());
321 }
322
323 /**
Dave Santoro6fa73842011-09-28 14:37:06 -0700324 * Returns the contact ID.
325 */
Makoto Onuki98306102011-11-28 15:16:58 -0800326 @VisibleForTesting
327 /* package */ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700328 return mId;
329 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800330
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700331 /**
332 * @return true when an exception happened during loading, in which case
333 * {@link #getException} returns the actual exception object.
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700334 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
335 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
336 * and vice versa.
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700337 */
338 public boolean isError() {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700339 return mStatus == Status.ERROR;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700340 }
341
342 public Exception getException() {
343 return mException;
344 }
345
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700346 /**
347 * @return true when the specified contact is not found.
348 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
349 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
350 * and vice versa.
351 */
352 public boolean isNotFound() {
353 return mStatus == Status.NOT_FOUND;
354 }
355
356 /**
357 * @return true if the specified contact is successfully loaded.
358 * i.e. neither {@link #isError()} nor {@link #isNotFound()}.
359 */
360 public boolean isLoaded() {
361 return mStatus == Status.LOADED;
362 }
363
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700364 public long getNameRawContactId() {
365 return mNameRawContactId;
366 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800367
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700368 public int getDisplayNameSource() {
369 return mDisplayNameSource;
370 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800371
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700372 public long getPhotoId() {
373 return mPhotoId;
374 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800375
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700376 public String getPhotoUri() {
377 return mPhotoUri;
378 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800379
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700380 public String getDisplayName() {
381 return mDisplayName;
382 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800383
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700384 public String getAltDisplayName() {
385 return mAltDisplayName;
386 }
387
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700388 public String getPhoneticName() {
389 return mPhoneticName;
390 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800391
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700392 public boolean getStarred() {
393 return mStarred;
394 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800395
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700396 public Integer getPresence() {
397 return mPresence;
398 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800399
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700400 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700401 return mInvitableAccountTypes;
402 }
403
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700404 public ArrayList<Entity> getEntities() {
405 return mEntities;
406 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800407
Dave Santoro39156002011-07-19 01:18:14 -0700408 public ArrayList<StreamItemEntry> getStreamItems() {
409 return mStreamItems;
410 }
411
Daniel Lehmann18958a22012-02-28 17:45:25 -0800412 public LongSparseArray<DataStatus> getStatuses() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700413 return mStatuses;
414 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700415
416 public long getDirectoryId() {
417 return mDirectoryId;
418 }
419
420 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700421 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
422 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700423 }
424
Josh Gargus187c8162012-03-13 17:06:53 -0700425 /**
426 * @return true if this is a contact (not group, etc.) with at least one
Josh Gargus84edfd92012-03-15 18:25:58 -0700427 * writable raw-contact, and false otherwise.
Josh Gargus187c8162012-03-13 17:06:53 -0700428 */
Josh Gargus84edfd92012-03-15 18:25:58 -0700429 public boolean isWritableContact(final Context context) {
Josh Garguse5d3f892012-04-11 11:56:15 -0700430 return getFirstWritableRawContactId(context) != -1;
431 }
432
433 /**
434 * Return the ID of the first raw-contact in the contact data that belongs to a
435 * contact-writable account, or -1 if no such entity exists.
436 */
437 public long getFirstWritableRawContactId(final Context context) {
438 // Directory entries are non-writable
439 if (isDirectoryEntry()) return -1;
440
441 // Iterate through raw-contacts; if we find a writable on, return its ID.
Josh Gargus187c8162012-03-13 17:06:53 -0700442 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
Josh Garguse5d3f892012-04-11 11:56:15 -0700443 for (Entity entity : getEntities()) {
444 ContentValues values = entity.getEntityValues();
445 String type = values.getAsString(RawContacts.ACCOUNT_TYPE);
446 String dataSet = values.getAsString(RawContacts.DATA_SET);
447
448 AccountType accountType = accountTypes.getAccountType(type, dataSet);
449 if (accountType != null && accountType.areContactsWritable()) {
450 return values.getAsLong(RawContacts._ID);
451 }
Josh Gargus187c8162012-03-13 17:06:53 -0700452 }
Josh Garguse5d3f892012-04-11 11:56:15 -0700453 // No writable raw-contact was found.
454 return -1;
Josh Gargus187c8162012-03-13 17:06:53 -0700455 }
456
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700457 public int getDirectoryExportSupport() {
458 return mDirectoryExportSupport;
459 }
460
461 public String getDirectoryDisplayName() {
462 return mDirectoryDisplayName;
463 }
464
465 public String getDirectoryType() {
466 return mDirectoryType;
467 }
468
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700469 public String getDirectoryAccountType() {
470 return mDirectoryAccountType;
471 }
472
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700473 public String getDirectoryAccountName() {
474 return mDirectoryAccountName;
475 }
476
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800477 public byte[] getPhotoBinaryData() {
478 return mPhotoBinaryData;
479 }
480
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700481 public ArrayList<ContentValues> getContentValues() {
482 if (mEntities.size() != 1) {
483 throw new IllegalStateException(
484 "Cannot extract content values from an aggregated contact");
485 }
486
487 Entity entity = mEntities.get(0);
488 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
489 ArrayList<NamedContentValues> subValues = entity.getSubValues();
490 if (subValues != null) {
491 int size = subValues.size();
492 for (int i = 0; i < size; i++) {
493 NamedContentValues pair = subValues.get(i);
494 if (Data.CONTENT_URI.equals(pair.uri)) {
495 result.add(pair.values);
496 }
497 }
498 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800499
500 // If the photo was loaded using the URI, create an entry for the photo
501 // binary data.
502 if (mPhotoId == 0 && mPhotoBinaryData != null) {
503 ContentValues photo = new ContentValues();
504 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
505 photo.put(Photo.PHOTO, mPhotoBinaryData);
506 result.add(photo);
507 }
508
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700509 return result;
510 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700511
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700512 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700513 return mGroups;
514 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700515
516 public boolean isSendToVoicemail() {
517 return mSendToVoicemail;
518 }
519
520 public String getCustomRingtone() {
521 return mCustomRingtone;
522 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700523
524 public boolean isUserProfile() {
525 return mIsUserProfile;
526 }
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700527
528 @Override
529 public String toString() {
530 return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
531 ",uri=" + mUri + ",status=" + mStatus + "}";
532 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700533 }
534
Dave Santoro39156002011-07-19 01:18:14 -0700535 /**
536 * Projection used for the query that loads all data for the entire contact (except for
537 * social stream items).
538 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700539 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700540 final static String[] COLUMNS = new String[] {
541 Contacts.NAME_RAW_CONTACT_ID,
542 Contacts.DISPLAY_NAME_SOURCE,
543 Contacts.LOOKUP_KEY,
544 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700545 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700546 Contacts.PHONETIC_NAME,
547 Contacts.PHOTO_ID,
548 Contacts.STARRED,
549 Contacts.CONTACT_PRESENCE,
550 Contacts.CONTACT_STATUS,
551 Contacts.CONTACT_STATUS_TIMESTAMP,
552 Contacts.CONTACT_STATUS_RES_PACKAGE,
553 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700554 Contacts.Entity.CONTACT_ID,
555 Contacts.Entity.RAW_CONTACT_ID,
556
557 RawContacts.ACCOUNT_NAME,
558 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700559 RawContacts.DATA_SET,
560 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700561 RawContacts.DIRTY,
562 RawContacts.VERSION,
563 RawContacts.SOURCE_ID,
564 RawContacts.SYNC1,
565 RawContacts.SYNC2,
566 RawContacts.SYNC3,
567 RawContacts.SYNC4,
568 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700569 RawContacts.NAME_VERIFIED,
570
571 Contacts.Entity.DATA_ID,
572 Data.DATA1,
573 Data.DATA2,
574 Data.DATA3,
575 Data.DATA4,
576 Data.DATA5,
577 Data.DATA6,
578 Data.DATA7,
579 Data.DATA8,
580 Data.DATA9,
581 Data.DATA10,
582 Data.DATA11,
583 Data.DATA12,
584 Data.DATA13,
585 Data.DATA14,
586 Data.DATA15,
587 Data.SYNC1,
588 Data.SYNC2,
589 Data.SYNC3,
590 Data.SYNC4,
591 Data.DATA_VERSION,
592 Data.IS_PRIMARY,
593 Data.IS_SUPER_PRIMARY,
594 Data.MIMETYPE,
595 Data.RES_PACKAGE,
596
597 GroupMembership.GROUP_SOURCE_ID,
598
599 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700600 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700601 Data.STATUS,
602 Data.STATUS_RES_PACKAGE,
603 Data.STATUS_ICON,
604 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700605 Data.STATUS_TIMESTAMP,
606
607 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700608 Contacts.SEND_TO_VOICEMAIL,
609 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700610 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700611 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700612
613 public final static int NAME_RAW_CONTACT_ID = 0;
614 public final static int DISPLAY_NAME_SOURCE = 1;
615 public final static int LOOKUP_KEY = 2;
616 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700617 public final static int ALT_DISPLAY_NAME = 4;
618 public final static int PHONETIC_NAME = 5;
619 public final static int PHOTO_ID = 6;
620 public final static int STARRED = 7;
621 public final static int CONTACT_PRESENCE = 8;
622 public final static int CONTACT_STATUS = 9;
623 public final static int CONTACT_STATUS_TIMESTAMP = 10;
624 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
625 public final static int CONTACT_STATUS_LABEL = 12;
626 public final static int CONTACT_ID = 13;
627 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700628
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700629 public final static int ACCOUNT_NAME = 15;
630 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700631 public final static int DATA_SET = 17;
632 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
633 public final static int DIRTY = 19;
634 public final static int VERSION = 20;
635 public final static int SOURCE_ID = 21;
636 public final static int SYNC1 = 22;
637 public final static int SYNC2 = 23;
638 public final static int SYNC3 = 24;
639 public final static int SYNC4 = 25;
640 public final static int DELETED = 26;
641 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700642
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700643 public final static int DATA_ID = 28;
644 public final static int DATA1 = 29;
645 public final static int DATA2 = 30;
646 public final static int DATA3 = 31;
647 public final static int DATA4 = 32;
648 public final static int DATA5 = 33;
649 public final static int DATA6 = 34;
650 public final static int DATA7 = 35;
651 public final static int DATA8 = 36;
652 public final static int DATA9 = 37;
653 public final static int DATA10 = 38;
654 public final static int DATA11 = 39;
655 public final static int DATA12 = 40;
656 public final static int DATA13 = 41;
657 public final static int DATA14 = 42;
658 public final static int DATA15 = 43;
659 public final static int DATA_SYNC1 = 44;
660 public final static int DATA_SYNC2 = 45;
661 public final static int DATA_SYNC3 = 46;
662 public final static int DATA_SYNC4 = 47;
663 public final static int DATA_VERSION = 48;
664 public final static int IS_PRIMARY = 49;
665 public final static int IS_SUPERPRIMARY = 50;
666 public final static int MIMETYPE = 51;
667 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700668
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700669 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700670
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700671 public final static int PRESENCE = 54;
672 public final static int CHAT_CAPABILITY = 55;
673 public final static int STATUS = 56;
674 public final static int STATUS_RES_PACKAGE = 57;
675 public final static int STATUS_ICON = 58;
676 public final static int STATUS_LABEL = 59;
677 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700678
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700679 public final static int PHOTO_URI = 61;
680 public final static int SEND_TO_VOICEMAIL = 62;
681 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700682 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700683 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700684
Dave Santoro39156002011-07-19 01:18:14 -0700685 /**
686 * Projection used for the query that loads all data for the entire contact.
687 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700688 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700689 final static String[] COLUMNS = new String[] {
690 Directory.DISPLAY_NAME,
691 Directory.PACKAGE_NAME,
692 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700693 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700694 Directory.ACCOUNT_NAME,
695 Directory.EXPORT_SUPPORT,
696 };
697
698 public final static int DISPLAY_NAME = 0;
699 public final static int PACKAGE_NAME = 1;
700 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700701 public final static int ACCOUNT_TYPE = 3;
702 public final static int ACCOUNT_NAME = 4;
703 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700704 }
705
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700706 private static class GroupQuery {
707 final static String[] COLUMNS = new String[] {
708 Groups.ACCOUNT_NAME,
709 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700710 Groups.DATA_SET,
711 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700712 Groups._ID,
713 Groups.TITLE,
714 Groups.AUTO_ADD,
715 Groups.FAVORITES,
716 };
717
718 public final static int ACCOUNT_NAME = 0;
719 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700720 public final static int DATA_SET = 2;
721 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
722 public final static int ID = 4;
723 public final static int TITLE = 5;
724 public final static int AUTO_ADD = 6;
725 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700726 }
727
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800728 @Override
729 public Result loadInBackground() {
730 try {
731 final ContentResolver resolver = getContext().getContentResolver();
732 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
733 resolver, mLookupUri);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700734 final Result cachedResult = sCachedResult;
735 sCachedResult = null;
736 // Is this the same Uri as what we had before already? In that case, reuse that result
737 final Result result;
738 final boolean resultIsCached;
739 if (cachedResult != null &&
740 UriUtils.areEqual(cachedResult.getLookupUri(), mLookupUri)) {
741 // We are using a cached result from earlier. Below, we should make sure
742 // we are not doing any more network or disc accesses
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700743 result = new Result(mRequestedUri, cachedResult);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700744 resultIsCached = true;
745 } else {
746 result = loadContactEntity(resolver, uriCurrentFormat);
747 resultIsCached = false;
748 }
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700749 if (result.isLoaded()) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800750 if (result.isDirectoryEntry()) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700751 if (!resultIsCached) {
752 loadDirectoryMetaData(result);
753 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800754 } else if (mLoadGroupMetaData) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700755 if (result.getGroupMetaData() == null) {
756 loadGroupMetaData(result);
757 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700758 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700759 if (mLoadStreamItems && result.getStreamItems() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800760 loadStreamItems(result);
761 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700762 if (!resultIsCached) loadPhotoBinaryData(result);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800763
764 // Note ME profile should never have "Add connection"
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700765 if (mLoadInvitableAccountTypes && result.getInvitableAccountTypes() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800766 loadInvitableAccountTypes(result);
767 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700768 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800769 return result;
770 } catch (Exception e) {
771 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
772 return Result.forError(mRequestedUri, e);
773 }
774 }
775
776 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
777 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
778 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
779 Contacts.Entity.RAW_CONTACT_ID);
780 if (cursor == null) {
781 Log.e(TAG, "No cursor returned in loadContactEntity");
782 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700783 }
784
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800785 try {
786 if (!cursor.moveToFirst()) {
787 cursor.close();
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700788 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700789 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700790
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700791 // Create the loaded result starting with the Contact data.
792 Result result = loadContactHeaderData(cursor, contactUri);
793
794 // Fill in the raw contacts, which is wrapped in an Entity and any
795 // status data. Initially, result has empty entities and statuses.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800796 long currentRawContactId = -1;
797 Entity entity = null;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800798 ArrayList<Entity> entities = result.getEntities();
799 LongSparseArray<DataStatus> statuses = result.getStatuses();
800 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
801 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
802 if (rawContactId != currentRawContactId) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700803 // First time to see this raw contact id, so create a new entity, and
804 // add it to the result's entities.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800805 currentRawContactId = rawContactId;
806 entity = new android.content.Entity(loadRawContact(cursor));
807 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700808 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800809 if (!cursor.isNull(ContactQuery.DATA_ID)) {
810 ContentValues data = loadData(cursor);
811 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700812
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800813 if (!cursor.isNull(ContactQuery.PRESENCE)
814 || !cursor.isNull(ContactQuery.STATUS)) {
815 final DataStatus status = new DataStatus(cursor);
816 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
817 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700818 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700819 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700820 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800821
822 return result;
823 } finally {
824 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700825 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800826 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700827
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800828 /**
829 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
830 * not found, returns null
831 */
832 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700833
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800834 // If we have a photo URI, try loading that first.
835 String photoUri = contactData.getPhotoUri();
836 if (photoUri != null) {
837 try {
838 AssetFileDescriptor fd = getContext().getContentResolver()
839 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
840 byte[] buffer = new byte[16 * 1024];
841 FileInputStream fis = fd.createInputStream();
842 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700843 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800844 int size;
845 while ((size = fis.read(buffer)) != -1) {
846 baos.write(buffer, 0, size);
847 }
848 contactData.setPhotoBinaryData(baos.toByteArray());
849 } finally {
850 fis.close();
851 fd.close();
852 }
853 return;
854 } catch (IOException ioe) {
855 // Just fall back to the case below.
856 }
857 }
858
859 // If we couldn't load from a file, fall back to the data blob.
860 final long photoId = contactData.getPhotoId();
861 if (photoId <= 0) {
862 // No photo ID
863 return;
864 }
865
866 for (Entity entity : contactData.getEntities()) {
867 for (NamedContentValues subValue : entity.getSubValues()) {
868 final ContentValues entryValues = subValue.values;
869 final long dataId = entryValues.getAsLong(Data._ID);
870 if (dataId == photoId) {
871 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
872 // Correct Data Id but incorrect MimeType? Don't load
873 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
874 return;
875 }
876 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
877 break;
878 }
879 }
880 }
881 }
882
883 /**
884 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
885 */
886 private void loadInvitableAccountTypes(Result contactData) {
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700887 final ArrayList<AccountType> resultList = Lists.newArrayList();
888 if (!contactData.isUserProfile()) {
889 Map<AccountTypeWithDataSet, AccountType> invitables =
890 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
891 if (!invitables.isEmpty()) {
892 final Map<AccountTypeWithDataSet, AccountType> resultMap =
893 Maps.newHashMap(invitables);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800894
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700895 // Remove the ones that already have a raw contact in the current contact
896 for (Entity entity : contactData.getEntities()) {
897 final ContentValues values = entity.getEntityValues();
898 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
899 values.getAsString(RawContacts.ACCOUNT_TYPE),
900 values.getAsString(RawContacts.DATA_SET));
901 resultMap.remove(type);
902 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800903
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700904 resultList.addAll(resultMap.values());
905 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800906 }
907
908 // Set to mInvitableAccountTypes
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700909 contactData.mInvitableAccountTypes = resultList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800910 }
911
912 /**
913 * Extracts Contact level columns from the cursor.
914 */
915 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
916 final String directoryParameter =
917 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
918 final long directoryId = directoryParameter == null
919 ? Directory.DEFAULT
920 : Long.parseLong(directoryParameter);
921 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
922 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
923 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
924 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
925 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
926 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
927 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
928 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
929 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
930 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
931 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
932 ? null
933 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
934 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
935 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
936 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
937
938 Uri lookupUri;
939 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
940 lookupUri = ContentUris.withAppendedId(
941 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
942 } else {
943 lookupUri = contactUri;
944 }
945
946 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
947 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
948 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
949 customRingtone, isUserProfile);
950 }
951
952 /**
953 * Extracts RawContact level columns from the cursor.
954 */
955 private ContentValues loadRawContact(Cursor cursor) {
956 ContentValues cv = new ContentValues();
957
958 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
959
960 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
961 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
962 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
963 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
964 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
965 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
966 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
967 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
968 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
969 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
970 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
971 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
972 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
973 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
974 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
975
976 return cv;
977 }
978
979 /**
980 * Extracts Data level columns from the cursor.
981 */
982 private ContentValues loadData(Cursor cursor) {
983 ContentValues cv = new ContentValues();
984
985 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
986
987 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
988 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
989 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
990 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
991 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
992 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
993 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
994 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
995 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
996 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
997 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
998 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
999 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
1000 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
1001 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
1002 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
1003 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
1004 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
1005 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
1006 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
1007 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
1008 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
1009 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
1010 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
1011 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
1012 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
1013
1014 return cv;
1015 }
1016
1017 private void cursorColumnToContentValues(
1018 Cursor cursor, ContentValues values, int index) {
1019 switch (cursor.getType(index)) {
1020 case Cursor.FIELD_TYPE_NULL:
1021 // don't put anything in the content values
1022 break;
1023 case Cursor.FIELD_TYPE_INTEGER:
1024 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
1025 break;
1026 case Cursor.FIELD_TYPE_STRING:
1027 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
1028 break;
1029 case Cursor.FIELD_TYPE_BLOB:
1030 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
1031 break;
1032 default:
1033 throw new IllegalStateException("Invalid or unhandled data type");
1034 }
1035 }
1036
1037 private void loadDirectoryMetaData(Result result) {
1038 long directoryId = result.getDirectoryId();
1039
1040 Cursor cursor = getContext().getContentResolver().query(
1041 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
1042 DirectoryQuery.COLUMNS, null, null, null);
1043 if (cursor == null) {
1044 return;
1045 }
1046 try {
1047 if (cursor.moveToFirst()) {
1048 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
1049 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
1050 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
1051 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
1052 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
1053 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
1054 String directoryType = null;
1055 if (!TextUtils.isEmpty(packageName)) {
1056 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -07001057 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001058 Resources resources = pm.getResourcesForApplication(packageName);
1059 directoryType = resources.getString(typeResourceId);
1060 } catch (NameNotFoundException e) {
1061 Log.w(TAG, "Contact directory resource not found: "
1062 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001063 }
1064 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001065
1066 result.setDirectoryMetaData(
1067 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001068 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001069 } finally {
1070 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001071 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001072 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001073
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001074 /**
1075 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1076 * accounts.
1077 */
1078 private void loadGroupMetaData(Result result) {
1079 StringBuilder selection = new StringBuilder();
1080 ArrayList<String> selectionArgs = new ArrayList<String>();
1081 for (Entity entity : result.mEntities) {
1082 ContentValues values = entity.getEntityValues();
1083 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1084 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1085 String dataSet = values.getAsString(RawContacts.DATA_SET);
1086 if (accountName != null && accountType != null) {
1087 if (selection.length() != 0) {
1088 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001089 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001090 selection.append(
1091 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1092 selectionArgs.add(accountName);
1093 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001094
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001095 if (dataSet != null) {
1096 selection.append(" AND " + Groups.DATA_SET + "=?");
1097 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001098 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001099 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1100 }
1101 selection.append(")");
1102 }
1103 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001104 final ArrayList<GroupMetaData> groupList = new ArrayList<GroupMetaData>();
1105 final Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001106 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1107 null);
1108 try {
1109 while (cursor.moveToNext()) {
1110 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1111 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1112 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1113 final long groupId = cursor.getLong(GroupQuery.ID);
1114 final String title = cursor.getString(GroupQuery.TITLE);
1115 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1116 ? false
1117 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1118 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1119 ? false
1120 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1121
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001122 groupList.add(new GroupMetaData(
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001123 accountName, accountType, dataSet, groupId, title, defaultGroup,
1124 favorites));
1125 }
1126 } finally {
1127 cursor.close();
1128 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001129 result.mGroups = groupList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001130 }
1131
1132 /**
1133 * Loads all stream items and stream item photos belonging to this contact.
1134 */
1135 private void loadStreamItems(Result result) {
1136 Cursor cursor = getContext().getContentResolver().query(
1137 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1138 .appendPath(result.getLookupKey())
1139 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1140 null, null, null, null);
1141 LongSparseArray<StreamItemEntry> streamItemsById =
1142 new LongSparseArray<StreamItemEntry>();
1143 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1144 try {
1145 while (cursor.moveToNext()) {
1146 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1147 streamItemsById.put(streamItem.getId(), streamItem);
1148 streamItems.add(streamItem);
1149 }
1150 } finally {
1151 cursor.close();
1152 }
1153
1154 // Pre-decode all HTMLs
1155 final long start = System.currentTimeMillis();
1156 for (StreamItemEntry streamItem : streamItems) {
1157 streamItem.decodeHtml(getContext());
1158 }
1159 final long end = System.currentTimeMillis();
1160 if (DEBUG) {
1161 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1162 + (end - start) + " ms");
1163 }
1164
1165 // Now retrieve any photo records associated with the stream items.
1166 if (!streamItems.isEmpty()) {
1167 if (result.isUserProfile()) {
1168 // If the stream items we're loading are for the profile, we can't bulk-load the
1169 // stream items with a custom selection.
1170 for (StreamItemEntry entry : streamItems) {
1171 Cursor siCursor = getContext().getContentResolver().query(
1172 Uri.withAppendedPath(
1173 ContentUris.withAppendedId(
1174 StreamItems.CONTENT_URI, entry.getId()),
1175 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1176 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001177 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001178 while (siCursor.moveToNext()) {
1179 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001180 }
1181 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001182 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001183 }
Dave Santoro39156002011-07-19 01:18:14 -07001184 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001185 } else {
1186 String[] streamItemIdArr = new String[streamItems.size()];
1187 StringBuilder streamItemPhotoSelection = new StringBuilder();
1188 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1189 for (int i = 0; i < streamItems.size(); i++) {
1190 if (i > 0) {
1191 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001192 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001193 streamItemPhotoSelection.append("?");
1194 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001195 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001196 streamItemPhotoSelection.append(")");
1197 Cursor sipCursor = getContext().getContentResolver().query(
1198 StreamItems.CONTENT_PHOTO_URI,
1199 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1200 StreamItemPhotos.STREAM_ITEM_ID);
1201 try {
1202 while (sipCursor.moveToNext()) {
1203 long streamItemId = sipCursor.getLong(
1204 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1205 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1206 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1207 }
1208 } finally {
1209 sipCursor.close();
1210 }
1211 }
1212 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001213
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001214 // Set the sorted stream items on the result.
1215 Collections.sort(streamItems);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001216 result.mStreamItems = streamItems;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001217 }
1218
1219 @Override
1220 public void deliverResult(Result result) {
1221 unregisterObserver();
1222
1223 // The creator isn't interested in any further updates
1224 if (isReset() || result == null) {
1225 return;
1226 }
1227
1228 mContact = result;
1229
1230 if (result.isLoaded()) {
1231 mLookupUri = result.getLookupUri();
1232
1233 if (!result.isDirectoryEntry()) {
1234 Log.i(TAG, "Registering content observer for " + mLookupUri);
1235 if (mObserver == null) {
1236 mObserver = new ForceLoadContentObserver();
1237 }
1238 getContext().getContentResolver().registerContentObserver(
1239 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001240 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001241
Makoto Onuki45ee8722012-05-21 16:33:25 -07001242 if (mPostViewNotification) {
1243 // inform the source of the data that this contact is being looked at
1244 postViewNotificationToSyncAdapter();
1245 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001246 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001247
1248 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001249 }
1250
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001251 /**
1252 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1253 * that the contact has just been loaded
1254 */
1255 private void postViewNotificationToSyncAdapter() {
1256 Context context = getContext();
1257 for (Entity entity : mContact.getEntities()) {
1258 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001259 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1260 if (mNotifiedRawContactIds.contains(rawContactId)) {
1261 continue; // Already notified for this raw contact.
1262 }
1263 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001264 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1265 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001266 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001267 type, dataSet);
1268 final String serviceName = accountType.getViewContactNotifyServiceClassName();
Makoto Onuki82a4f442012-05-07 17:18:33 -07001269 final String servicePackageName = accountType.getViewContactNotifyServicePackageName();
1270 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(servicePackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001271 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1272 final Intent intent = new Intent();
Makoto Onuki82a4f442012-05-07 17:18:33 -07001273 intent.setClassName(servicePackageName, serviceName);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001274 intent.setAction(Intent.ACTION_VIEW);
1275 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1276 try {
1277 context.startService(intent);
1278 } catch (Exception e) {
1279 Log.e(TAG, "Error sending message to source-app", e);
1280 }
1281 }
1282 }
1283 }
1284
Daniel Lehmann3a120772010-06-21 16:21:35 -07001285 private void unregisterObserver() {
1286 if (mObserver != null) {
1287 getContext().getContentResolver().unregisterContentObserver(mObserver);
1288 mObserver = null;
1289 }
1290 }
1291
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001292 /**
1293 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1294 * At the moment, this is only used for debugging purposes
1295 */
1296 public void setLoadStreamItems(boolean value) {
1297 if (mLoadStreamItems != value) {
1298 mLoadStreamItems = value;
1299 onContentChanged();
1300 }
1301 }
1302
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001303 /**
1304 * Fully upgrades this ContactLoader to one with all lists fully loaded. When done, the
1305 * new result will be delivered
1306 */
1307 public void upgradeToFullContact() {
Daniel Lehmannd9662a82012-04-24 15:43:40 -07001308 // Everything requested already? Nothing to do, so let's bail out
Makoto Onuki45ee8722012-05-21 16:33:25 -07001309 if (mLoadGroupMetaData && mLoadInvitableAccountTypes && mLoadStreamItems
1310 && mPostViewNotification) return;
Daniel Lehmannd9662a82012-04-24 15:43:40 -07001311
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001312 mLoadGroupMetaData = true;
1313 mLoadInvitableAccountTypes = true;
1314 mLoadStreamItems = true;
Makoto Onuki45ee8722012-05-21 16:33:25 -07001315 mPostViewNotification = true;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001316
1317 // Cache the current result, so that we only load the "missing" parts of the contact.
1318 cacheResult();
1319
1320 // Our load parameters have changed, so let's pretend the data has changed. Its the same
1321 // thing, essentially.
1322 onContentChanged();
1323 }
1324
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001325 public boolean getLoadStreamItems() {
1326 return mLoadStreamItems;
1327 }
1328
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001329 public Uri getLookupUri() {
1330 return mLookupUri;
1331 }
1332
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001333 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001334 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001335 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001336 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001337 }
1338
1339 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001340 forceLoad();
1341 }
1342 }
1343
1344 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001345 protected void onStopLoading() {
1346 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001347 }
1348
1349 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001350 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001351 super.onReset();
1352 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001353 unregisterObserver();
1354 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001355 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001356
1357 /**
1358 * Caches the result, which is useful when we switch from activity to activity, using the same
1359 * contact. If the next load is for a different contact, the cached result will be dropped
1360 */
1361 public void cacheResult() {
Makoto Onukie1cc78a2012-05-14 11:11:31 -07001362 if (mContact == null || !mContact.isLoaded()) {
Makoto Onukieb7b0502012-04-26 11:10:27 -07001363 sCachedResult = null;
1364 } else {
Makoto Onukie1cc78a2012-05-14 11:11:31 -07001365 sCachedResult = mContact;
Makoto Onukieb7b0502012-04-26 11:10:27 -07001366 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001367 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001368}