blob: 5f5c1cb0f80247fa49cae7447363f120075586ab [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;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070087 private Result mContact;
88 private ForceLoadContentObserver mObserver;
Makoto Onukiaba2b832011-08-12 15:44:53 -070089 private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
Dmitri Plotnikove843f912010-09-16 15:21:48 -070090
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070091 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070092 public void onContactLoaded(Result contact);
93 }
94
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080095 public ContactLoader(Context context, Uri lookupUri) {
96 this(context, lookupUri, false, false, false);
97 }
98
99 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
100 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
101 super(context);
102 mLookupUri = lookupUri;
103 mRequestedUri = lookupUri;
104 mLoadGroupMetaData = loadGroupMetaData;
105 mLoadStreamItems = loadStreamItems;
106 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
107 }
108
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700109 /**
110 * The result of a load operation. Contains all data necessary to display the contact.
111 */
112 public static final class Result {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700113 private enum Status {
114 /** Contact is successfully loaded */
115 LOADED,
116 /** There was an error loading the contact */
117 ERROR,
118 /** Contact is not found */
119 NOT_FOUND,
120 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700121
Daniel Lehmann685157e2011-08-29 21:07:01 -0700122 private final Uri mRequestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700123 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700124 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700125 private final long mDirectoryId;
126 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700127 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700128 private final long mNameRawContactId;
129 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700130 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700131 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700132 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700133 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700134 private final String mPhoneticName;
135 private final boolean mStarred;
136 private final Integer mPresence;
137 private final ArrayList<Entity> mEntities;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700138 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800139 private final LongSparseArray<DataStatus> mStatuses;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700140 private ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700141
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700142 private String mDirectoryDisplayName;
143 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700144 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700145 private String mDirectoryAccountName;
146 private int mDirectoryExportSupport;
147
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700148 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700149
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800150 private byte[] mPhotoBinaryData;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700151 private final boolean mSendToVoicemail;
152 private final String mCustomRingtone;
153 private final boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800154
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700155 private final Status mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700156 private final Exception mException;
157
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700158 /**
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700159 * Constructor for special results, namely "no contact found" and "error".
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700160 */
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700161 private Result(Uri requestedUri, Status status, Exception exception) {
162 if (status == Status.ERROR && exception == null) {
163 throw new IllegalArgumentException("ERROR result must have exception");
164 }
165 mStatus = status;
166 mException = exception;
167 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700168 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700169 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700170 mDirectoryId = -1;
171 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700172 mId = -1;
173 mEntities = null;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700174 mStreamItems = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700175 mStatuses = null;
176 mNameRawContactId = -1;
177 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700178 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700179 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700180 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700181 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700182 mPhoneticName = null;
183 mStarred = false;
184 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700185 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700186 mSendToVoicemail = false;
187 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700188 mIsUserProfile = false;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700189 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700190
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700191 private static Result forError(Uri requestedUri, Exception exception) {
192 return new Result(requestedUri, Status.ERROR, exception);
193 }
194
195 private static Result forNotFound(Uri requestedUri) {
196 return new Result(requestedUri, Status.NOT_FOUND, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700197 }
198
199 /**
200 * Constructor to call when contact was found
201 */
Daniel Lehmann685157e2011-08-29 21:07:01 -0700202 private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
203 long id, long nameRawContactId, int displayNameSource, long photoId,
204 String photoUri, String displayName, String altDisplayName, String phoneticName,
205 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700206 boolean isUserProfile) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700207 mStatus = Status.LOADED;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700208 mException = null;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700209 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700210 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700211 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700212 mDirectoryId = directoryId;
213 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700214 mId = id;
215 mEntities = new ArrayList<Entity>();
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700216 mStreamItems = null;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800217 mStatuses = new LongSparseArray<DataStatus>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700218 mNameRawContactId = nameRawContactId;
219 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700220 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700221 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700222 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700223 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700224 mPhoneticName = phoneticName;
225 mStarred = starred;
226 mPresence = presence;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700227 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700228 mSendToVoicemail = sendToVoicemail;
229 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700230 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700231 }
232
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800233 private Result(Result from) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700234 mStatus = from.mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700235 mException = from.mException;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700236 mRequestedUri = from.mRequestedUri;
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 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700527 }
528
Dave Santoro39156002011-07-19 01:18:14 -0700529 /**
530 * Projection used for the query that loads all data for the entire contact (except for
531 * social stream items).
532 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700533 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700534 final static String[] COLUMNS = new String[] {
535 Contacts.NAME_RAW_CONTACT_ID,
536 Contacts.DISPLAY_NAME_SOURCE,
537 Contacts.LOOKUP_KEY,
538 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700539 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700540 Contacts.PHONETIC_NAME,
541 Contacts.PHOTO_ID,
542 Contacts.STARRED,
543 Contacts.CONTACT_PRESENCE,
544 Contacts.CONTACT_STATUS,
545 Contacts.CONTACT_STATUS_TIMESTAMP,
546 Contacts.CONTACT_STATUS_RES_PACKAGE,
547 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700548 Contacts.Entity.CONTACT_ID,
549 Contacts.Entity.RAW_CONTACT_ID,
550
551 RawContacts.ACCOUNT_NAME,
552 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700553 RawContacts.DATA_SET,
554 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700555 RawContacts.DIRTY,
556 RawContacts.VERSION,
557 RawContacts.SOURCE_ID,
558 RawContacts.SYNC1,
559 RawContacts.SYNC2,
560 RawContacts.SYNC3,
561 RawContacts.SYNC4,
562 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700563 RawContacts.NAME_VERIFIED,
564
565 Contacts.Entity.DATA_ID,
566 Data.DATA1,
567 Data.DATA2,
568 Data.DATA3,
569 Data.DATA4,
570 Data.DATA5,
571 Data.DATA6,
572 Data.DATA7,
573 Data.DATA8,
574 Data.DATA9,
575 Data.DATA10,
576 Data.DATA11,
577 Data.DATA12,
578 Data.DATA13,
579 Data.DATA14,
580 Data.DATA15,
581 Data.SYNC1,
582 Data.SYNC2,
583 Data.SYNC3,
584 Data.SYNC4,
585 Data.DATA_VERSION,
586 Data.IS_PRIMARY,
587 Data.IS_SUPER_PRIMARY,
588 Data.MIMETYPE,
589 Data.RES_PACKAGE,
590
591 GroupMembership.GROUP_SOURCE_ID,
592
593 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700594 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700595 Data.STATUS,
596 Data.STATUS_RES_PACKAGE,
597 Data.STATUS_ICON,
598 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700599 Data.STATUS_TIMESTAMP,
600
601 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700602 Contacts.SEND_TO_VOICEMAIL,
603 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700604 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700605 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700606
607 public final static int NAME_RAW_CONTACT_ID = 0;
608 public final static int DISPLAY_NAME_SOURCE = 1;
609 public final static int LOOKUP_KEY = 2;
610 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700611 public final static int ALT_DISPLAY_NAME = 4;
612 public final static int PHONETIC_NAME = 5;
613 public final static int PHOTO_ID = 6;
614 public final static int STARRED = 7;
615 public final static int CONTACT_PRESENCE = 8;
616 public final static int CONTACT_STATUS = 9;
617 public final static int CONTACT_STATUS_TIMESTAMP = 10;
618 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
619 public final static int CONTACT_STATUS_LABEL = 12;
620 public final static int CONTACT_ID = 13;
621 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700622
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700623 public final static int ACCOUNT_NAME = 15;
624 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700625 public final static int DATA_SET = 17;
626 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
627 public final static int DIRTY = 19;
628 public final static int VERSION = 20;
629 public final static int SOURCE_ID = 21;
630 public final static int SYNC1 = 22;
631 public final static int SYNC2 = 23;
632 public final static int SYNC3 = 24;
633 public final static int SYNC4 = 25;
634 public final static int DELETED = 26;
635 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700636
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700637 public final static int DATA_ID = 28;
638 public final static int DATA1 = 29;
639 public final static int DATA2 = 30;
640 public final static int DATA3 = 31;
641 public final static int DATA4 = 32;
642 public final static int DATA5 = 33;
643 public final static int DATA6 = 34;
644 public final static int DATA7 = 35;
645 public final static int DATA8 = 36;
646 public final static int DATA9 = 37;
647 public final static int DATA10 = 38;
648 public final static int DATA11 = 39;
649 public final static int DATA12 = 40;
650 public final static int DATA13 = 41;
651 public final static int DATA14 = 42;
652 public final static int DATA15 = 43;
653 public final static int DATA_SYNC1 = 44;
654 public final static int DATA_SYNC2 = 45;
655 public final static int DATA_SYNC3 = 46;
656 public final static int DATA_SYNC4 = 47;
657 public final static int DATA_VERSION = 48;
658 public final static int IS_PRIMARY = 49;
659 public final static int IS_SUPERPRIMARY = 50;
660 public final static int MIMETYPE = 51;
661 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700662
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700663 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700664
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700665 public final static int PRESENCE = 54;
666 public final static int CHAT_CAPABILITY = 55;
667 public final static int STATUS = 56;
668 public final static int STATUS_RES_PACKAGE = 57;
669 public final static int STATUS_ICON = 58;
670 public final static int STATUS_LABEL = 59;
671 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700672
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700673 public final static int PHOTO_URI = 61;
674 public final static int SEND_TO_VOICEMAIL = 62;
675 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700676 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700677 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700678
Dave Santoro39156002011-07-19 01:18:14 -0700679 /**
680 * Projection used for the query that loads all data for the entire contact.
681 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700682 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700683 final static String[] COLUMNS = new String[] {
684 Directory.DISPLAY_NAME,
685 Directory.PACKAGE_NAME,
686 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700687 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700688 Directory.ACCOUNT_NAME,
689 Directory.EXPORT_SUPPORT,
690 };
691
692 public final static int DISPLAY_NAME = 0;
693 public final static int PACKAGE_NAME = 1;
694 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700695 public final static int ACCOUNT_TYPE = 3;
696 public final static int ACCOUNT_NAME = 4;
697 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700698 }
699
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700700 private static class GroupQuery {
701 final static String[] COLUMNS = new String[] {
702 Groups.ACCOUNT_NAME,
703 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700704 Groups.DATA_SET,
705 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700706 Groups._ID,
707 Groups.TITLE,
708 Groups.AUTO_ADD,
709 Groups.FAVORITES,
710 };
711
712 public final static int ACCOUNT_NAME = 0;
713 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700714 public final static int DATA_SET = 2;
715 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
716 public final static int ID = 4;
717 public final static int TITLE = 5;
718 public final static int AUTO_ADD = 6;
719 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700720 }
721
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800722 @Override
723 public Result loadInBackground() {
724 try {
725 final ContentResolver resolver = getContext().getContentResolver();
726 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
727 resolver, mLookupUri);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700728 final Result cachedResult = sCachedResult;
729 sCachedResult = null;
730 // Is this the same Uri as what we had before already? In that case, reuse that result
731 final Result result;
732 final boolean resultIsCached;
733 if (cachedResult != null &&
734 UriUtils.areEqual(cachedResult.getLookupUri(), mLookupUri)) {
735 // We are using a cached result from earlier. Below, we should make sure
736 // we are not doing any more network or disc accesses
737 result = cachedResult;
738 resultIsCached = true;
739 } else {
740 result = loadContactEntity(resolver, uriCurrentFormat);
741 resultIsCached = false;
742 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800743 if (!result.isNotFound()) {
744 if (result.isDirectoryEntry()) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700745 if (!resultIsCached) {
746 loadDirectoryMetaData(result);
747 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800748 } else if (mLoadGroupMetaData) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700749 if (result.getGroupMetaData() == null) {
750 loadGroupMetaData(result);
751 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700752 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700753 if (mLoadStreamItems && result.getStreamItems() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800754 loadStreamItems(result);
755 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700756 if (!resultIsCached) loadPhotoBinaryData(result);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800757
758 // Note ME profile should never have "Add connection"
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700759 if (mLoadInvitableAccountTypes && result.getInvitableAccountTypes() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800760 loadInvitableAccountTypes(result);
761 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700762 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800763 return result;
764 } catch (Exception e) {
765 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
766 return Result.forError(mRequestedUri, e);
767 }
768 }
769
770 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
771 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
772 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
773 Contacts.Entity.RAW_CONTACT_ID);
774 if (cursor == null) {
775 Log.e(TAG, "No cursor returned in loadContactEntity");
776 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700777 }
778
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800779 try {
780 if (!cursor.moveToFirst()) {
781 cursor.close();
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700782 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700783 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700784
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700785 // Create the loaded result starting with the Contact data.
786 Result result = loadContactHeaderData(cursor, contactUri);
787
788 // Fill in the raw contacts, which is wrapped in an Entity and any
789 // status data. Initially, result has empty entities and statuses.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800790 long currentRawContactId = -1;
791 Entity entity = null;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800792 ArrayList<Entity> entities = result.getEntities();
793 LongSparseArray<DataStatus> statuses = result.getStatuses();
794 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
795 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
796 if (rawContactId != currentRawContactId) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700797 // First time to see this raw contact id, so create a new entity, and
798 // add it to the result's entities.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800799 currentRawContactId = rawContactId;
800 entity = new android.content.Entity(loadRawContact(cursor));
801 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700802 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800803 if (!cursor.isNull(ContactQuery.DATA_ID)) {
804 ContentValues data = loadData(cursor);
805 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700806
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800807 if (!cursor.isNull(ContactQuery.PRESENCE)
808 || !cursor.isNull(ContactQuery.STATUS)) {
809 final DataStatus status = new DataStatus(cursor);
810 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
811 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700812 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700813 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700814 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800815
816 return result;
817 } finally {
818 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700819 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800820 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700821
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800822 /**
823 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
824 * not found, returns null
825 */
826 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700827
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800828 // If we have a photo URI, try loading that first.
829 String photoUri = contactData.getPhotoUri();
830 if (photoUri != null) {
831 try {
832 AssetFileDescriptor fd = getContext().getContentResolver()
833 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
834 byte[] buffer = new byte[16 * 1024];
835 FileInputStream fis = fd.createInputStream();
836 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700837 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800838 int size;
839 while ((size = fis.read(buffer)) != -1) {
840 baos.write(buffer, 0, size);
841 }
842 contactData.setPhotoBinaryData(baos.toByteArray());
843 } finally {
844 fis.close();
845 fd.close();
846 }
847 return;
848 } catch (IOException ioe) {
849 // Just fall back to the case below.
850 }
851 }
852
853 // If we couldn't load from a file, fall back to the data blob.
854 final long photoId = contactData.getPhotoId();
855 if (photoId <= 0) {
856 // No photo ID
857 return;
858 }
859
860 for (Entity entity : contactData.getEntities()) {
861 for (NamedContentValues subValue : entity.getSubValues()) {
862 final ContentValues entryValues = subValue.values;
863 final long dataId = entryValues.getAsLong(Data._ID);
864 if (dataId == photoId) {
865 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
866 // Correct Data Id but incorrect MimeType? Don't load
867 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
868 return;
869 }
870 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
871 break;
872 }
873 }
874 }
875 }
876
877 /**
878 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
879 */
880 private void loadInvitableAccountTypes(Result contactData) {
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700881 final ArrayList<AccountType> resultList = Lists.newArrayList();
882 if (!contactData.isUserProfile()) {
883 Map<AccountTypeWithDataSet, AccountType> invitables =
884 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
885 if (!invitables.isEmpty()) {
886 final Map<AccountTypeWithDataSet, AccountType> resultMap =
887 Maps.newHashMap(invitables);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800888
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700889 // Remove the ones that already have a raw contact in the current contact
890 for (Entity entity : contactData.getEntities()) {
891 final ContentValues values = entity.getEntityValues();
892 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
893 values.getAsString(RawContacts.ACCOUNT_TYPE),
894 values.getAsString(RawContacts.DATA_SET));
895 resultMap.remove(type);
896 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800897
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700898 resultList.addAll(resultMap.values());
899 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800900 }
901
902 // Set to mInvitableAccountTypes
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700903 contactData.mInvitableAccountTypes = resultList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800904 }
905
906 /**
907 * Extracts Contact level columns from the cursor.
908 */
909 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
910 final String directoryParameter =
911 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
912 final long directoryId = directoryParameter == null
913 ? Directory.DEFAULT
914 : Long.parseLong(directoryParameter);
915 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
916 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
917 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
918 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
919 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
920 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
921 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
922 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
923 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
924 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
925 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
926 ? null
927 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
928 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
929 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
930 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
931
932 Uri lookupUri;
933 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
934 lookupUri = ContentUris.withAppendedId(
935 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
936 } else {
937 lookupUri = contactUri;
938 }
939
940 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
941 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
942 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
943 customRingtone, isUserProfile);
944 }
945
946 /**
947 * Extracts RawContact level columns from the cursor.
948 */
949 private ContentValues loadRawContact(Cursor cursor) {
950 ContentValues cv = new ContentValues();
951
952 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
953
954 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
955 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
956 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
957 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
958 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
959 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
960 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
961 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
962 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
963 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
964 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
965 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
966 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
967 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
968 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
969
970 return cv;
971 }
972
973 /**
974 * Extracts Data level columns from the cursor.
975 */
976 private ContentValues loadData(Cursor cursor) {
977 ContentValues cv = new ContentValues();
978
979 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
980
981 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
982 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
983 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
984 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
985 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
986 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
987 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
988 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
989 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
990 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
991 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
992 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
993 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
994 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
995 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
996 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
997 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
998 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
999 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
1000 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
1001 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
1002 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
1003 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
1004 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
1005 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
1006 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
1007
1008 return cv;
1009 }
1010
1011 private void cursorColumnToContentValues(
1012 Cursor cursor, ContentValues values, int index) {
1013 switch (cursor.getType(index)) {
1014 case Cursor.FIELD_TYPE_NULL:
1015 // don't put anything in the content values
1016 break;
1017 case Cursor.FIELD_TYPE_INTEGER:
1018 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
1019 break;
1020 case Cursor.FIELD_TYPE_STRING:
1021 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
1022 break;
1023 case Cursor.FIELD_TYPE_BLOB:
1024 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
1025 break;
1026 default:
1027 throw new IllegalStateException("Invalid or unhandled data type");
1028 }
1029 }
1030
1031 private void loadDirectoryMetaData(Result result) {
1032 long directoryId = result.getDirectoryId();
1033
1034 Cursor cursor = getContext().getContentResolver().query(
1035 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
1036 DirectoryQuery.COLUMNS, null, null, null);
1037 if (cursor == null) {
1038 return;
1039 }
1040 try {
1041 if (cursor.moveToFirst()) {
1042 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
1043 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
1044 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
1045 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
1046 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
1047 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
1048 String directoryType = null;
1049 if (!TextUtils.isEmpty(packageName)) {
1050 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -07001051 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001052 Resources resources = pm.getResourcesForApplication(packageName);
1053 directoryType = resources.getString(typeResourceId);
1054 } catch (NameNotFoundException e) {
1055 Log.w(TAG, "Contact directory resource not found: "
1056 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001057 }
1058 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001059
1060 result.setDirectoryMetaData(
1061 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001062 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001063 } finally {
1064 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001065 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001066 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001067
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001068 /**
1069 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1070 * accounts.
1071 */
1072 private void loadGroupMetaData(Result result) {
1073 StringBuilder selection = new StringBuilder();
1074 ArrayList<String> selectionArgs = new ArrayList<String>();
1075 for (Entity entity : result.mEntities) {
1076 ContentValues values = entity.getEntityValues();
1077 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1078 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1079 String dataSet = values.getAsString(RawContacts.DATA_SET);
1080 if (accountName != null && accountType != null) {
1081 if (selection.length() != 0) {
1082 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001083 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001084 selection.append(
1085 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1086 selectionArgs.add(accountName);
1087 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001088
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001089 if (dataSet != null) {
1090 selection.append(" AND " + Groups.DATA_SET + "=?");
1091 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001092 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001093 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1094 }
1095 selection.append(")");
1096 }
1097 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001098 final ArrayList<GroupMetaData> groupList = new ArrayList<GroupMetaData>();
1099 final Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001100 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1101 null);
1102 try {
1103 while (cursor.moveToNext()) {
1104 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1105 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1106 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1107 final long groupId = cursor.getLong(GroupQuery.ID);
1108 final String title = cursor.getString(GroupQuery.TITLE);
1109 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1110 ? false
1111 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1112 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1113 ? false
1114 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1115
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001116 groupList.add(new GroupMetaData(
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001117 accountName, accountType, dataSet, groupId, title, defaultGroup,
1118 favorites));
1119 }
1120 } finally {
1121 cursor.close();
1122 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001123 result.mGroups = groupList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001124 }
1125
1126 /**
1127 * Loads all stream items and stream item photos belonging to this contact.
1128 */
1129 private void loadStreamItems(Result result) {
1130 Cursor cursor = getContext().getContentResolver().query(
1131 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1132 .appendPath(result.getLookupKey())
1133 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1134 null, null, null, null);
1135 LongSparseArray<StreamItemEntry> streamItemsById =
1136 new LongSparseArray<StreamItemEntry>();
1137 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1138 try {
1139 while (cursor.moveToNext()) {
1140 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1141 streamItemsById.put(streamItem.getId(), streamItem);
1142 streamItems.add(streamItem);
1143 }
1144 } finally {
1145 cursor.close();
1146 }
1147
1148 // Pre-decode all HTMLs
1149 final long start = System.currentTimeMillis();
1150 for (StreamItemEntry streamItem : streamItems) {
1151 streamItem.decodeHtml(getContext());
1152 }
1153 final long end = System.currentTimeMillis();
1154 if (DEBUG) {
1155 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1156 + (end - start) + " ms");
1157 }
1158
1159 // Now retrieve any photo records associated with the stream items.
1160 if (!streamItems.isEmpty()) {
1161 if (result.isUserProfile()) {
1162 // If the stream items we're loading are for the profile, we can't bulk-load the
1163 // stream items with a custom selection.
1164 for (StreamItemEntry entry : streamItems) {
1165 Cursor siCursor = getContext().getContentResolver().query(
1166 Uri.withAppendedPath(
1167 ContentUris.withAppendedId(
1168 StreamItems.CONTENT_URI, entry.getId()),
1169 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1170 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001171 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001172 while (siCursor.moveToNext()) {
1173 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001174 }
1175 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001176 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001177 }
Dave Santoro39156002011-07-19 01:18:14 -07001178 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001179 } else {
1180 String[] streamItemIdArr = new String[streamItems.size()];
1181 StringBuilder streamItemPhotoSelection = new StringBuilder();
1182 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1183 for (int i = 0; i < streamItems.size(); i++) {
1184 if (i > 0) {
1185 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001186 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001187 streamItemPhotoSelection.append("?");
1188 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001189 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001190 streamItemPhotoSelection.append(")");
1191 Cursor sipCursor = getContext().getContentResolver().query(
1192 StreamItems.CONTENT_PHOTO_URI,
1193 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1194 StreamItemPhotos.STREAM_ITEM_ID);
1195 try {
1196 while (sipCursor.moveToNext()) {
1197 long streamItemId = sipCursor.getLong(
1198 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1199 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1200 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1201 }
1202 } finally {
1203 sipCursor.close();
1204 }
1205 }
1206 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001207
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001208 // Set the sorted stream items on the result.
1209 Collections.sort(streamItems);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001210 result.mStreamItems = streamItems;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001211 }
1212
1213 @Override
1214 public void deliverResult(Result result) {
1215 unregisterObserver();
1216
1217 // The creator isn't interested in any further updates
1218 if (isReset() || result == null) {
1219 return;
1220 }
1221
1222 mContact = result;
1223
1224 if (result.isLoaded()) {
1225 mLookupUri = result.getLookupUri();
1226
1227 if (!result.isDirectoryEntry()) {
1228 Log.i(TAG, "Registering content observer for " + mLookupUri);
1229 if (mObserver == null) {
1230 mObserver = new ForceLoadContentObserver();
1231 }
1232 getContext().getContentResolver().registerContentObserver(
1233 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001234 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001235
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001236 // inform the source of the data that this contact is being looked at
1237 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001238 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001239
1240 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001241 }
1242
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001243 /**
1244 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1245 * that the contact has just been loaded
1246 */
1247 private void postViewNotificationToSyncAdapter() {
1248 Context context = getContext();
1249 for (Entity entity : mContact.getEntities()) {
1250 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001251 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1252 if (mNotifiedRawContactIds.contains(rawContactId)) {
1253 continue; // Already notified for this raw contact.
1254 }
1255 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001256 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1257 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001258 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001259 type, dataSet);
1260 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1261 final String resPackageName = accountType.resPackageName;
1262 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001263 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1264 final Intent intent = new Intent();
1265 intent.setClassName(resPackageName, serviceName);
1266 intent.setAction(Intent.ACTION_VIEW);
1267 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1268 try {
1269 context.startService(intent);
1270 } catch (Exception e) {
1271 Log.e(TAG, "Error sending message to source-app", e);
1272 }
1273 }
1274 }
1275 }
1276
Daniel Lehmann3a120772010-06-21 16:21:35 -07001277 private void unregisterObserver() {
1278 if (mObserver != null) {
1279 getContext().getContentResolver().unregisterContentObserver(mObserver);
1280 mObserver = null;
1281 }
1282 }
1283
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001284 /**
1285 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1286 * At the moment, this is only used for debugging purposes
1287 */
1288 public void setLoadStreamItems(boolean value) {
1289 if (mLoadStreamItems != value) {
1290 mLoadStreamItems = value;
1291 onContentChanged();
1292 }
1293 }
1294
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001295 /**
1296 * Fully upgrades this ContactLoader to one with all lists fully loaded. When done, the
1297 * new result will be delivered
1298 */
1299 public void upgradeToFullContact() {
Daniel Lehmannd9662a82012-04-24 15:43:40 -07001300 // Everything requested already? Nothing to do, so let's bail out
1301 if (mLoadGroupMetaData && mLoadInvitableAccountTypes && mLoadStreamItems) return;
1302
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001303 mLoadGroupMetaData = true;
1304 mLoadInvitableAccountTypes = true;
1305 mLoadStreamItems = true;
1306
1307 // Cache the current result, so that we only load the "missing" parts of the contact.
1308 cacheResult();
1309
1310 // Our load parameters have changed, so let's pretend the data has changed. Its the same
1311 // thing, essentially.
1312 onContentChanged();
1313 }
1314
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001315 public boolean getLoadStreamItems() {
1316 return mLoadStreamItems;
1317 }
1318
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001319 public Uri getLookupUri() {
1320 return mLookupUri;
1321 }
1322
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001323 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001324 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001325 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001326 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001327 }
1328
1329 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001330 forceLoad();
1331 }
1332 }
1333
1334 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001335 protected void onStopLoading() {
1336 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001337 }
1338
1339 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001340 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001341 super.onReset();
1342 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001343 unregisterObserver();
1344 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001345 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001346
1347 /**
1348 * Caches the result, which is useful when we switch from activity to activity, using the same
1349 * contact. If the next load is for a different contact, the cached result will be dropped
1350 */
1351 public void cacheResult() {
Daniel Lehmannd9662a82012-04-24 15:43:40 -07001352 sCachedResult = new Result(mContact);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001353 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001354}