blob: ab2ac41ad39c28d6d1e4a9c28638863d0b82752d [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 Lehmann72ff4df2012-02-28 20:03:01 -080091 public ContactLoader(Context context, Uri lookupUri) {
92 this(context, lookupUri, false, false, false);
93 }
94
95 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
96 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
97 super(context);
98 mLookupUri = lookupUri;
99 mRequestedUri = lookupUri;
100 mLoadGroupMetaData = loadGroupMetaData;
101 mLoadStreamItems = loadStreamItems;
102 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
103 }
104
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700105 /**
106 * The result of a load operation. Contains all data necessary to display the contact.
107 */
108 public static final class Result {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700109 private enum Status {
110 /** Contact is successfully loaded */
111 LOADED,
112 /** There was an error loading the contact */
113 ERROR,
114 /** Contact is not found */
115 NOT_FOUND,
116 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700117
Daniel Lehmann685157e2011-08-29 21:07:01 -0700118 private final Uri mRequestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700119 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700120 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700121 private final long mDirectoryId;
122 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700123 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700124 private final long mNameRawContactId;
125 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700126 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700127 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700128 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700129 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700130 private final String mPhoneticName;
131 private final boolean mStarred;
132 private final Integer mPresence;
133 private final ArrayList<Entity> mEntities;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700134 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800135 private final LongSparseArray<DataStatus> mStatuses;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700136 private ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700137
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700138 private String mDirectoryDisplayName;
139 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700140 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700141 private String mDirectoryAccountName;
142 private int mDirectoryExportSupport;
143
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700144 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700145
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800146 private byte[] mPhotoBinaryData;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700147 private final boolean mSendToVoicemail;
148 private final String mCustomRingtone;
149 private final boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800150
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700151 private final Status mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700152 private final Exception mException;
153
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700154 /**
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700155 * Constructor for special results, namely "no contact found" and "error".
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700156 */
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700157 private Result(Uri requestedUri, Status status, Exception exception) {
158 if (status == Status.ERROR && exception == null) {
159 throw new IllegalArgumentException("ERROR result must have exception");
160 }
161 mStatus = status;
162 mException = exception;
163 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700164 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700165 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700166 mDirectoryId = -1;
167 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700168 mId = -1;
169 mEntities = null;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700170 mStreamItems = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700171 mStatuses = null;
172 mNameRawContactId = -1;
173 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700174 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700175 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700176 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700177 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700178 mPhoneticName = null;
179 mStarred = false;
180 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700181 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700182 mSendToVoicemail = false;
183 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700184 mIsUserProfile = false;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700185 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700186
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700187 private static Result forError(Uri requestedUri, Exception exception) {
188 return new Result(requestedUri, Status.ERROR, exception);
189 }
190
191 private static Result forNotFound(Uri requestedUri) {
192 return new Result(requestedUri, Status.NOT_FOUND, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700193 }
194
195 /**
196 * Constructor to call when contact was found
197 */
Daniel Lehmann685157e2011-08-29 21:07:01 -0700198 private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
199 long id, long nameRawContactId, int displayNameSource, long photoId,
200 String photoUri, String displayName, String altDisplayName, String phoneticName,
201 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700202 boolean isUserProfile) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700203 mStatus = Status.LOADED;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700204 mException = null;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700205 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700206 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700207 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700208 mDirectoryId = directoryId;
209 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700210 mId = id;
211 mEntities = new ArrayList<Entity>();
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700212 mStreamItems = null;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800213 mStatuses = new LongSparseArray<DataStatus>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700214 mNameRawContactId = nameRawContactId;
215 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700216 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700217 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700218 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700219 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700220 mPhoneticName = phoneticName;
221 mStarred = starred;
222 mPresence = presence;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700223 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700224 mSendToVoicemail = sendToVoicemail;
225 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700226 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700227 }
228
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700229 private Result(Uri requestedUri, Result from) {
230 mRequestedUri = requestedUri;
231
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700232 mStatus = from.mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700233 mException = from.mException;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800234 mLookupUri = from.mLookupUri;
235 mUri = from.mUri;
236 mDirectoryId = from.mDirectoryId;
237 mLookupKey = from.mLookupKey;
238 mId = from.mId;
239 mNameRawContactId = from.mNameRawContactId;
240 mDisplayNameSource = from.mDisplayNameSource;
241 mPhotoId = from.mPhotoId;
242 mPhotoUri = from.mPhotoUri;
243 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700244 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800245 mPhoneticName = from.mPhoneticName;
246 mStarred = from.mStarred;
247 mPresence = from.mPresence;
248 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700249 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800250 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700251 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800252
253 mDirectoryDisplayName = from.mDirectoryDisplayName;
254 mDirectoryType = from.mDirectoryType;
255 mDirectoryAccountType = from.mDirectoryAccountType;
256 mDirectoryAccountName = from.mDirectoryAccountName;
257 mDirectoryExportSupport = from.mDirectoryExportSupport;
258
259 mGroups = from.mGroups;
260
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800261 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700262 mSendToVoicemail = from.mSendToVoicemail;
263 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700264 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800265 }
266
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700267 /**
268 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
269 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700270 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700271 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700272 mDirectoryDisplayName = displayName;
273 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700274 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700275 mDirectoryAccountName = accountName;
276 mDirectoryExportSupport = exportSupport;
277 }
278
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800279 private void setPhotoBinaryData(byte[] photoBinaryData) {
280 mPhotoBinaryData = photoBinaryData;
281 }
282
Daniel Lehmann685157e2011-08-29 21:07:01 -0700283 /**
284 * Returns the URI for the contact that contains both the lookup key and the ID. This is
285 * the best URI to reference a contact.
286 * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
287 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700288 public Uri getLookupUri() {
289 return mLookupUri;
290 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800291
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700292 public String getLookupKey() {
293 return mLookupKey;
294 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800295
Daniel Lehmann685157e2011-08-29 21:07:01 -0700296 /**
297 * Returns the contact Uri that was passed to the provider to make the query. This is
298 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
299 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
300 * always reference the full aggregate contact.
301 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700302 public Uri getUri() {
303 return mUri;
304 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800305
Daniel Lehmann685157e2011-08-29 21:07:01 -0700306 /**
307 * Returns the URI for which this {@link ContactLoader) was initially requested.
308 */
309 public Uri getRequestedUri() {
310 return mRequestedUri;
311 }
312
Dave Santoro6fa73842011-09-28 14:37:06 -0700313 /**
Josh Garguse5d3f892012-04-11 11:56:15 -0700314 * Instantiate a new EntityDeltaList for this contact.
315 */
316 public EntityDeltaList createEntityDeltaList() {
317 return EntityDeltaList.fromIterator(getEntities().iterator());
318 }
319
320 /**
Dave Santoro6fa73842011-09-28 14:37:06 -0700321 * Returns the contact ID.
322 */
Makoto Onuki98306102011-11-28 15:16:58 -0800323 @VisibleForTesting
324 /* package */ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700325 return mId;
326 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800327
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700328 /**
329 * @return true when an exception happened during loading, in which case
330 * {@link #getException} returns the actual exception object.
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700331 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
332 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
333 * and vice versa.
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700334 */
335 public boolean isError() {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700336 return mStatus == Status.ERROR;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700337 }
338
339 public Exception getException() {
340 return mException;
341 }
342
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700343 /**
344 * @return true when the specified contact is not found.
345 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
346 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
347 * and vice versa.
348 */
349 public boolean isNotFound() {
350 return mStatus == Status.NOT_FOUND;
351 }
352
353 /**
354 * @return true if the specified contact is successfully loaded.
355 * i.e. neither {@link #isError()} nor {@link #isNotFound()}.
356 */
357 public boolean isLoaded() {
358 return mStatus == Status.LOADED;
359 }
360
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700361 public long getNameRawContactId() {
362 return mNameRawContactId;
363 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800364
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700365 public int getDisplayNameSource() {
366 return mDisplayNameSource;
367 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800368
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700369 public long getPhotoId() {
370 return mPhotoId;
371 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800372
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700373 public String getPhotoUri() {
374 return mPhotoUri;
375 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800376
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700377 public String getDisplayName() {
378 return mDisplayName;
379 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800380
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700381 public String getAltDisplayName() {
382 return mAltDisplayName;
383 }
384
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700385 public String getPhoneticName() {
386 return mPhoneticName;
387 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800388
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700389 public boolean getStarred() {
390 return mStarred;
391 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800392
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700393 public Integer getPresence() {
394 return mPresence;
395 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800396
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700397 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700398 return mInvitableAccountTypes;
399 }
400
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700401 public ArrayList<Entity> getEntities() {
402 return mEntities;
403 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800404
Dave Santoro39156002011-07-19 01:18:14 -0700405 public ArrayList<StreamItemEntry> getStreamItems() {
406 return mStreamItems;
407 }
408
Daniel Lehmann18958a22012-02-28 17:45:25 -0800409 public LongSparseArray<DataStatus> getStatuses() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700410 return mStatuses;
411 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700412
413 public long getDirectoryId() {
414 return mDirectoryId;
415 }
416
417 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700418 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
419 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700420 }
421
Josh Gargus187c8162012-03-13 17:06:53 -0700422 /**
423 * @return true if this is a contact (not group, etc.) with at least one
Josh Gargus84edfd92012-03-15 18:25:58 -0700424 * writable raw-contact, and false otherwise.
Josh Gargus187c8162012-03-13 17:06:53 -0700425 */
Josh Gargus84edfd92012-03-15 18:25:58 -0700426 public boolean isWritableContact(final Context context) {
Josh Garguse5d3f892012-04-11 11:56:15 -0700427 return getFirstWritableRawContactId(context) != -1;
428 }
429
430 /**
431 * Return the ID of the first raw-contact in the contact data that belongs to a
432 * contact-writable account, or -1 if no such entity exists.
433 */
434 public long getFirstWritableRawContactId(final Context context) {
435 // Directory entries are non-writable
436 if (isDirectoryEntry()) return -1;
437
438 // Iterate through raw-contacts; if we find a writable on, return its ID.
Josh Gargus187c8162012-03-13 17:06:53 -0700439 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
Josh Garguse5d3f892012-04-11 11:56:15 -0700440 for (Entity entity : getEntities()) {
441 ContentValues values = entity.getEntityValues();
442 String type = values.getAsString(RawContacts.ACCOUNT_TYPE);
443 String dataSet = values.getAsString(RawContacts.DATA_SET);
444
445 AccountType accountType = accountTypes.getAccountType(type, dataSet);
446 if (accountType != null && accountType.areContactsWritable()) {
447 return values.getAsLong(RawContacts._ID);
448 }
Josh Gargus187c8162012-03-13 17:06:53 -0700449 }
Josh Garguse5d3f892012-04-11 11:56:15 -0700450 // No writable raw-contact was found.
451 return -1;
Josh Gargus187c8162012-03-13 17:06:53 -0700452 }
453
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700454 public int getDirectoryExportSupport() {
455 return mDirectoryExportSupport;
456 }
457
458 public String getDirectoryDisplayName() {
459 return mDirectoryDisplayName;
460 }
461
462 public String getDirectoryType() {
463 return mDirectoryType;
464 }
465
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700466 public String getDirectoryAccountType() {
467 return mDirectoryAccountType;
468 }
469
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700470 public String getDirectoryAccountName() {
471 return mDirectoryAccountName;
472 }
473
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800474 public byte[] getPhotoBinaryData() {
475 return mPhotoBinaryData;
476 }
477
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700478 public ArrayList<ContentValues> getContentValues() {
479 if (mEntities.size() != 1) {
480 throw new IllegalStateException(
481 "Cannot extract content values from an aggregated contact");
482 }
483
484 Entity entity = mEntities.get(0);
485 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
486 ArrayList<NamedContentValues> subValues = entity.getSubValues();
487 if (subValues != null) {
488 int size = subValues.size();
489 for (int i = 0; i < size; i++) {
490 NamedContentValues pair = subValues.get(i);
491 if (Data.CONTENT_URI.equals(pair.uri)) {
492 result.add(pair.values);
493 }
494 }
495 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800496
497 // If the photo was loaded using the URI, create an entry for the photo
498 // binary data.
499 if (mPhotoId == 0 && mPhotoBinaryData != null) {
500 ContentValues photo = new ContentValues();
501 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
502 photo.put(Photo.PHOTO, mPhotoBinaryData);
503 result.add(photo);
504 }
505
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700506 return result;
507 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700508
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700509 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700510 return mGroups;
511 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700512
513 public boolean isSendToVoicemail() {
514 return mSendToVoicemail;
515 }
516
517 public String getCustomRingtone() {
518 return mCustomRingtone;
519 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700520
521 public boolean isUserProfile() {
522 return mIsUserProfile;
523 }
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700524
525 @Override
526 public String toString() {
527 return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
528 ",uri=" + mUri + ",status=" + mStatus + "}";
529 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700530 }
531
Dave Santoro39156002011-07-19 01:18:14 -0700532 /**
533 * Projection used for the query that loads all data for the entire contact (except for
534 * social stream items).
535 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700536 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700537 final static String[] COLUMNS = new String[] {
538 Contacts.NAME_RAW_CONTACT_ID,
539 Contacts.DISPLAY_NAME_SOURCE,
540 Contacts.LOOKUP_KEY,
541 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700542 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700543 Contacts.PHONETIC_NAME,
544 Contacts.PHOTO_ID,
545 Contacts.STARRED,
546 Contacts.CONTACT_PRESENCE,
547 Contacts.CONTACT_STATUS,
548 Contacts.CONTACT_STATUS_TIMESTAMP,
549 Contacts.CONTACT_STATUS_RES_PACKAGE,
550 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700551 Contacts.Entity.CONTACT_ID,
552 Contacts.Entity.RAW_CONTACT_ID,
553
554 RawContacts.ACCOUNT_NAME,
555 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700556 RawContacts.DATA_SET,
557 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700558 RawContacts.DIRTY,
559 RawContacts.VERSION,
560 RawContacts.SOURCE_ID,
561 RawContacts.SYNC1,
562 RawContacts.SYNC2,
563 RawContacts.SYNC3,
564 RawContacts.SYNC4,
565 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700566 RawContacts.NAME_VERIFIED,
567
568 Contacts.Entity.DATA_ID,
569 Data.DATA1,
570 Data.DATA2,
571 Data.DATA3,
572 Data.DATA4,
573 Data.DATA5,
574 Data.DATA6,
575 Data.DATA7,
576 Data.DATA8,
577 Data.DATA9,
578 Data.DATA10,
579 Data.DATA11,
580 Data.DATA12,
581 Data.DATA13,
582 Data.DATA14,
583 Data.DATA15,
584 Data.SYNC1,
585 Data.SYNC2,
586 Data.SYNC3,
587 Data.SYNC4,
588 Data.DATA_VERSION,
589 Data.IS_PRIMARY,
590 Data.IS_SUPER_PRIMARY,
591 Data.MIMETYPE,
592 Data.RES_PACKAGE,
593
594 GroupMembership.GROUP_SOURCE_ID,
595
596 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700597 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700598 Data.STATUS,
599 Data.STATUS_RES_PACKAGE,
600 Data.STATUS_ICON,
601 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700602 Data.STATUS_TIMESTAMP,
603
604 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700605 Contacts.SEND_TO_VOICEMAIL,
606 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700607 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700608 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700609
610 public final static int NAME_RAW_CONTACT_ID = 0;
611 public final static int DISPLAY_NAME_SOURCE = 1;
612 public final static int LOOKUP_KEY = 2;
613 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700614 public final static int ALT_DISPLAY_NAME = 4;
615 public final static int PHONETIC_NAME = 5;
616 public final static int PHOTO_ID = 6;
617 public final static int STARRED = 7;
618 public final static int CONTACT_PRESENCE = 8;
619 public final static int CONTACT_STATUS = 9;
620 public final static int CONTACT_STATUS_TIMESTAMP = 10;
621 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
622 public final static int CONTACT_STATUS_LABEL = 12;
623 public final static int CONTACT_ID = 13;
624 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700625
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700626 public final static int ACCOUNT_NAME = 15;
627 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700628 public final static int DATA_SET = 17;
629 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
630 public final static int DIRTY = 19;
631 public final static int VERSION = 20;
632 public final static int SOURCE_ID = 21;
633 public final static int SYNC1 = 22;
634 public final static int SYNC2 = 23;
635 public final static int SYNC3 = 24;
636 public final static int SYNC4 = 25;
637 public final static int DELETED = 26;
638 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700639
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700640 public final static int DATA_ID = 28;
641 public final static int DATA1 = 29;
642 public final static int DATA2 = 30;
643 public final static int DATA3 = 31;
644 public final static int DATA4 = 32;
645 public final static int DATA5 = 33;
646 public final static int DATA6 = 34;
647 public final static int DATA7 = 35;
648 public final static int DATA8 = 36;
649 public final static int DATA9 = 37;
650 public final static int DATA10 = 38;
651 public final static int DATA11 = 39;
652 public final static int DATA12 = 40;
653 public final static int DATA13 = 41;
654 public final static int DATA14 = 42;
655 public final static int DATA15 = 43;
656 public final static int DATA_SYNC1 = 44;
657 public final static int DATA_SYNC2 = 45;
658 public final static int DATA_SYNC3 = 46;
659 public final static int DATA_SYNC4 = 47;
660 public final static int DATA_VERSION = 48;
661 public final static int IS_PRIMARY = 49;
662 public final static int IS_SUPERPRIMARY = 50;
663 public final static int MIMETYPE = 51;
664 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700665
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700666 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700667
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700668 public final static int PRESENCE = 54;
669 public final static int CHAT_CAPABILITY = 55;
670 public final static int STATUS = 56;
671 public final static int STATUS_RES_PACKAGE = 57;
672 public final static int STATUS_ICON = 58;
673 public final static int STATUS_LABEL = 59;
674 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700675
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700676 public final static int PHOTO_URI = 61;
677 public final static int SEND_TO_VOICEMAIL = 62;
678 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700679 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700680 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700681
Dave Santoro39156002011-07-19 01:18:14 -0700682 /**
683 * Projection used for the query that loads all data for the entire contact.
684 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700685 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700686 final static String[] COLUMNS = new String[] {
687 Directory.DISPLAY_NAME,
688 Directory.PACKAGE_NAME,
689 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700690 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700691 Directory.ACCOUNT_NAME,
692 Directory.EXPORT_SUPPORT,
693 };
694
695 public final static int DISPLAY_NAME = 0;
696 public final static int PACKAGE_NAME = 1;
697 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700698 public final static int ACCOUNT_TYPE = 3;
699 public final static int ACCOUNT_NAME = 4;
700 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700701 }
702
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700703 private static class GroupQuery {
704 final static String[] COLUMNS = new String[] {
705 Groups.ACCOUNT_NAME,
706 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700707 Groups.DATA_SET,
708 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700709 Groups._ID,
710 Groups.TITLE,
711 Groups.AUTO_ADD,
712 Groups.FAVORITES,
713 };
714
715 public final static int ACCOUNT_NAME = 0;
716 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700717 public final static int DATA_SET = 2;
718 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
719 public final static int ID = 4;
720 public final static int TITLE = 5;
721 public final static int AUTO_ADD = 6;
722 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700723 }
724
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800725 @Override
726 public Result loadInBackground() {
727 try {
728 final ContentResolver resolver = getContext().getContentResolver();
729 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
730 resolver, mLookupUri);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700731 final Result cachedResult = sCachedResult;
732 sCachedResult = null;
733 // Is this the same Uri as what we had before already? In that case, reuse that result
734 final Result result;
735 final boolean resultIsCached;
736 if (cachedResult != null &&
737 UriUtils.areEqual(cachedResult.getLookupUri(), mLookupUri)) {
738 // We are using a cached result from earlier. Below, we should make sure
739 // we are not doing any more network or disc accesses
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700740 result = new Result(mRequestedUri, cachedResult);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700741 resultIsCached = true;
742 } else {
743 result = loadContactEntity(resolver, uriCurrentFormat);
744 resultIsCached = false;
745 }
Makoto Onukie1cc78a2012-05-14 11:11:31 -0700746 if (result.isLoaded()) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800747 if (result.isDirectoryEntry()) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700748 if (!resultIsCached) {
749 loadDirectoryMetaData(result);
750 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800751 } else if (mLoadGroupMetaData) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700752 if (result.getGroupMetaData() == null) {
753 loadGroupMetaData(result);
754 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700755 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700756 if (mLoadStreamItems && result.getStreamItems() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800757 loadStreamItems(result);
758 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700759 if (!resultIsCached) loadPhotoBinaryData(result);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800760
761 // Note ME profile should never have "Add connection"
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700762 if (mLoadInvitableAccountTypes && result.getInvitableAccountTypes() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800763 loadInvitableAccountTypes(result);
764 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700765 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800766 return result;
767 } catch (Exception e) {
768 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
769 return Result.forError(mRequestedUri, e);
770 }
771 }
772
773 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
774 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
775 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
776 Contacts.Entity.RAW_CONTACT_ID);
777 if (cursor == null) {
778 Log.e(TAG, "No cursor returned in loadContactEntity");
779 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700780 }
781
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800782 try {
783 if (!cursor.moveToFirst()) {
784 cursor.close();
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700785 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700786 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700787
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700788 // Create the loaded result starting with the Contact data.
789 Result result = loadContactHeaderData(cursor, contactUri);
790
791 // Fill in the raw contacts, which is wrapped in an Entity and any
792 // status data. Initially, result has empty entities and statuses.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800793 long currentRawContactId = -1;
794 Entity entity = null;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800795 ArrayList<Entity> entities = result.getEntities();
796 LongSparseArray<DataStatus> statuses = result.getStatuses();
797 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
798 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
799 if (rawContactId != currentRawContactId) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700800 // First time to see this raw contact id, so create a new entity, and
801 // add it to the result's entities.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800802 currentRawContactId = rawContactId;
803 entity = new android.content.Entity(loadRawContact(cursor));
804 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700805 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800806 if (!cursor.isNull(ContactQuery.DATA_ID)) {
807 ContentValues data = loadData(cursor);
808 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700809
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800810 if (!cursor.isNull(ContactQuery.PRESENCE)
811 || !cursor.isNull(ContactQuery.STATUS)) {
812 final DataStatus status = new DataStatus(cursor);
813 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
814 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700815 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700816 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700817 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800818
819 return result;
820 } finally {
821 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700822 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800823 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700824
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800825 /**
826 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
827 * not found, returns null
828 */
829 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700830
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800831 // If we have a photo URI, try loading that first.
832 String photoUri = contactData.getPhotoUri();
833 if (photoUri != null) {
834 try {
835 AssetFileDescriptor fd = getContext().getContentResolver()
836 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
837 byte[] buffer = new byte[16 * 1024];
838 FileInputStream fis = fd.createInputStream();
839 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700840 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800841 int size;
842 while ((size = fis.read(buffer)) != -1) {
843 baos.write(buffer, 0, size);
844 }
845 contactData.setPhotoBinaryData(baos.toByteArray());
846 } finally {
847 fis.close();
848 fd.close();
849 }
850 return;
851 } catch (IOException ioe) {
852 // Just fall back to the case below.
853 }
854 }
855
856 // If we couldn't load from a file, fall back to the data blob.
857 final long photoId = contactData.getPhotoId();
858 if (photoId <= 0) {
859 // No photo ID
860 return;
861 }
862
863 for (Entity entity : contactData.getEntities()) {
864 for (NamedContentValues subValue : entity.getSubValues()) {
865 final ContentValues entryValues = subValue.values;
866 final long dataId = entryValues.getAsLong(Data._ID);
867 if (dataId == photoId) {
868 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
869 // Correct Data Id but incorrect MimeType? Don't load
870 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
871 return;
872 }
873 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
874 break;
875 }
876 }
877 }
878 }
879
880 /**
881 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
882 */
883 private void loadInvitableAccountTypes(Result contactData) {
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700884 final ArrayList<AccountType> resultList = Lists.newArrayList();
885 if (!contactData.isUserProfile()) {
886 Map<AccountTypeWithDataSet, AccountType> invitables =
887 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
888 if (!invitables.isEmpty()) {
889 final Map<AccountTypeWithDataSet, AccountType> resultMap =
890 Maps.newHashMap(invitables);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800891
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700892 // Remove the ones that already have a raw contact in the current contact
893 for (Entity entity : contactData.getEntities()) {
894 final ContentValues values = entity.getEntityValues();
895 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
896 values.getAsString(RawContacts.ACCOUNT_TYPE),
897 values.getAsString(RawContacts.DATA_SET));
898 resultMap.remove(type);
899 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800900
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700901 resultList.addAll(resultMap.values());
902 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800903 }
904
905 // Set to mInvitableAccountTypes
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700906 contactData.mInvitableAccountTypes = resultList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800907 }
908
909 /**
910 * Extracts Contact level columns from the cursor.
911 */
912 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
913 final String directoryParameter =
914 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
915 final long directoryId = directoryParameter == null
916 ? Directory.DEFAULT
917 : Long.parseLong(directoryParameter);
918 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
919 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
920 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
921 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
922 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
923 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
924 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
925 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
926 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
927 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
928 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
929 ? null
930 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
931 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
932 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
933 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
934
935 Uri lookupUri;
936 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
937 lookupUri = ContentUris.withAppendedId(
938 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
939 } else {
940 lookupUri = contactUri;
941 }
942
943 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
944 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
945 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
946 customRingtone, isUserProfile);
947 }
948
949 /**
950 * Extracts RawContact level columns from the cursor.
951 */
952 private ContentValues loadRawContact(Cursor cursor) {
953 ContentValues cv = new ContentValues();
954
955 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
956
957 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
958 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
959 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
960 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
961 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
962 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
963 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
964 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
965 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
966 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
967 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
968 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
969 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
970 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
971 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
972
973 return cv;
974 }
975
976 /**
977 * Extracts Data level columns from the cursor.
978 */
979 private ContentValues loadData(Cursor cursor) {
980 ContentValues cv = new ContentValues();
981
982 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
983
984 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
985 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
986 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
987 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
988 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
989 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
990 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
991 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
992 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
993 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
994 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
995 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
996 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
997 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
998 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
999 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
1000 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
1001 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
1002 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
1003 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
1004 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
1005 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
1006 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
1007 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
1008 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
1009 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
1010
1011 return cv;
1012 }
1013
1014 private void cursorColumnToContentValues(
1015 Cursor cursor, ContentValues values, int index) {
1016 switch (cursor.getType(index)) {
1017 case Cursor.FIELD_TYPE_NULL:
1018 // don't put anything in the content values
1019 break;
1020 case Cursor.FIELD_TYPE_INTEGER:
1021 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
1022 break;
1023 case Cursor.FIELD_TYPE_STRING:
1024 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
1025 break;
1026 case Cursor.FIELD_TYPE_BLOB:
1027 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
1028 break;
1029 default:
1030 throw new IllegalStateException("Invalid or unhandled data type");
1031 }
1032 }
1033
1034 private void loadDirectoryMetaData(Result result) {
1035 long directoryId = result.getDirectoryId();
1036
1037 Cursor cursor = getContext().getContentResolver().query(
1038 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
1039 DirectoryQuery.COLUMNS, null, null, null);
1040 if (cursor == null) {
1041 return;
1042 }
1043 try {
1044 if (cursor.moveToFirst()) {
1045 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
1046 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
1047 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
1048 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
1049 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
1050 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
1051 String directoryType = null;
1052 if (!TextUtils.isEmpty(packageName)) {
1053 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -07001054 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001055 Resources resources = pm.getResourcesForApplication(packageName);
1056 directoryType = resources.getString(typeResourceId);
1057 } catch (NameNotFoundException e) {
1058 Log.w(TAG, "Contact directory resource not found: "
1059 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001060 }
1061 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001062
1063 result.setDirectoryMetaData(
1064 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001065 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001066 } finally {
1067 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001068 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001069 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001070
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001071 /**
1072 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1073 * accounts.
1074 */
1075 private void loadGroupMetaData(Result result) {
1076 StringBuilder selection = new StringBuilder();
1077 ArrayList<String> selectionArgs = new ArrayList<String>();
1078 for (Entity entity : result.mEntities) {
1079 ContentValues values = entity.getEntityValues();
1080 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1081 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1082 String dataSet = values.getAsString(RawContacts.DATA_SET);
1083 if (accountName != null && accountType != null) {
1084 if (selection.length() != 0) {
1085 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001086 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001087 selection.append(
1088 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1089 selectionArgs.add(accountName);
1090 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001091
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001092 if (dataSet != null) {
1093 selection.append(" AND " + Groups.DATA_SET + "=?");
1094 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001095 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001096 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1097 }
1098 selection.append(")");
1099 }
1100 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001101 final ArrayList<GroupMetaData> groupList = new ArrayList<GroupMetaData>();
1102 final Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001103 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1104 null);
1105 try {
1106 while (cursor.moveToNext()) {
1107 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1108 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1109 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1110 final long groupId = cursor.getLong(GroupQuery.ID);
1111 final String title = cursor.getString(GroupQuery.TITLE);
1112 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1113 ? false
1114 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1115 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1116 ? false
1117 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1118
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001119 groupList.add(new GroupMetaData(
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001120 accountName, accountType, dataSet, groupId, title, defaultGroup,
1121 favorites));
1122 }
1123 } finally {
1124 cursor.close();
1125 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001126 result.mGroups = groupList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001127 }
1128
1129 /**
1130 * Loads all stream items and stream item photos belonging to this contact.
1131 */
1132 private void loadStreamItems(Result result) {
1133 Cursor cursor = getContext().getContentResolver().query(
1134 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1135 .appendPath(result.getLookupKey())
1136 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1137 null, null, null, null);
1138 LongSparseArray<StreamItemEntry> streamItemsById =
1139 new LongSparseArray<StreamItemEntry>();
1140 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1141 try {
1142 while (cursor.moveToNext()) {
1143 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1144 streamItemsById.put(streamItem.getId(), streamItem);
1145 streamItems.add(streamItem);
1146 }
1147 } finally {
1148 cursor.close();
1149 }
1150
1151 // Pre-decode all HTMLs
1152 final long start = System.currentTimeMillis();
1153 for (StreamItemEntry streamItem : streamItems) {
1154 streamItem.decodeHtml(getContext());
1155 }
1156 final long end = System.currentTimeMillis();
1157 if (DEBUG) {
1158 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1159 + (end - start) + " ms");
1160 }
1161
1162 // Now retrieve any photo records associated with the stream items.
1163 if (!streamItems.isEmpty()) {
1164 if (result.isUserProfile()) {
1165 // If the stream items we're loading are for the profile, we can't bulk-load the
1166 // stream items with a custom selection.
1167 for (StreamItemEntry entry : streamItems) {
1168 Cursor siCursor = getContext().getContentResolver().query(
1169 Uri.withAppendedPath(
1170 ContentUris.withAppendedId(
1171 StreamItems.CONTENT_URI, entry.getId()),
1172 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1173 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001174 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001175 while (siCursor.moveToNext()) {
1176 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001177 }
1178 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001179 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001180 }
Dave Santoro39156002011-07-19 01:18:14 -07001181 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001182 } else {
1183 String[] streamItemIdArr = new String[streamItems.size()];
1184 StringBuilder streamItemPhotoSelection = new StringBuilder();
1185 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1186 for (int i = 0; i < streamItems.size(); i++) {
1187 if (i > 0) {
1188 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001189 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001190 streamItemPhotoSelection.append("?");
1191 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001192 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001193 streamItemPhotoSelection.append(")");
1194 Cursor sipCursor = getContext().getContentResolver().query(
1195 StreamItems.CONTENT_PHOTO_URI,
1196 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1197 StreamItemPhotos.STREAM_ITEM_ID);
1198 try {
1199 while (sipCursor.moveToNext()) {
1200 long streamItemId = sipCursor.getLong(
1201 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1202 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1203 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1204 }
1205 } finally {
1206 sipCursor.close();
1207 }
1208 }
1209 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001210
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001211 // Set the sorted stream items on the result.
1212 Collections.sort(streamItems);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001213 result.mStreamItems = streamItems;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001214 }
1215
1216 @Override
1217 public void deliverResult(Result result) {
1218 unregisterObserver();
1219
1220 // The creator isn't interested in any further updates
1221 if (isReset() || result == null) {
1222 return;
1223 }
1224
1225 mContact = result;
1226
1227 if (result.isLoaded()) {
1228 mLookupUri = result.getLookupUri();
1229
1230 if (!result.isDirectoryEntry()) {
1231 Log.i(TAG, "Registering content observer for " + mLookupUri);
1232 if (mObserver == null) {
1233 mObserver = new ForceLoadContentObserver();
1234 }
1235 getContext().getContentResolver().registerContentObserver(
1236 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001237 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001238
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001239 // inform the source of the data that this contact is being looked at
1240 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001241 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001242
1243 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001244 }
1245
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001246 /**
1247 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1248 * that the contact has just been loaded
1249 */
1250 private void postViewNotificationToSyncAdapter() {
1251 Context context = getContext();
1252 for (Entity entity : mContact.getEntities()) {
1253 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001254 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1255 if (mNotifiedRawContactIds.contains(rawContactId)) {
1256 continue; // Already notified for this raw contact.
1257 }
1258 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001259 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1260 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001261 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001262 type, dataSet);
1263 final String serviceName = accountType.getViewContactNotifyServiceClassName();
Makoto Onuki82a4f442012-05-07 17:18:33 -07001264 final String servicePackageName = accountType.getViewContactNotifyServicePackageName();
1265 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(servicePackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001266 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1267 final Intent intent = new Intent();
Makoto Onuki82a4f442012-05-07 17:18:33 -07001268 intent.setClassName(servicePackageName, serviceName);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001269 intent.setAction(Intent.ACTION_VIEW);
1270 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1271 try {
1272 context.startService(intent);
1273 } catch (Exception e) {
1274 Log.e(TAG, "Error sending message to source-app", e);
1275 }
1276 }
1277 }
1278 }
1279
Daniel Lehmann3a120772010-06-21 16:21:35 -07001280 private void unregisterObserver() {
1281 if (mObserver != null) {
1282 getContext().getContentResolver().unregisterContentObserver(mObserver);
1283 mObserver = null;
1284 }
1285 }
1286
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001287 /**
1288 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1289 * At the moment, this is only used for debugging purposes
1290 */
1291 public void setLoadStreamItems(boolean value) {
1292 if (mLoadStreamItems != value) {
1293 mLoadStreamItems = value;
1294 onContentChanged();
1295 }
1296 }
1297
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001298 /**
1299 * Fully upgrades this ContactLoader to one with all lists fully loaded. When done, the
1300 * new result will be delivered
1301 */
1302 public void upgradeToFullContact() {
Daniel Lehmannd9662a82012-04-24 15:43:40 -07001303 // Everything requested already? Nothing to do, so let's bail out
1304 if (mLoadGroupMetaData && mLoadInvitableAccountTypes && mLoadStreamItems) return;
1305
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001306 mLoadGroupMetaData = true;
1307 mLoadInvitableAccountTypes = true;
1308 mLoadStreamItems = true;
1309
1310 // Cache the current result, so that we only load the "missing" parts of the contact.
1311 cacheResult();
1312
1313 // Our load parameters have changed, so let's pretend the data has changed. Its the same
1314 // thing, essentially.
1315 onContentChanged();
1316 }
1317
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001318 public boolean getLoadStreamItems() {
1319 return mLoadStreamItems;
1320 }
1321
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001322 public Uri getLookupUri() {
1323 return mLookupUri;
1324 }
1325
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001326 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001327 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001328 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001329 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001330 }
1331
1332 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001333 forceLoad();
1334 }
1335 }
1336
1337 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001338 protected void onStopLoading() {
1339 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001340 }
1341
1342 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001343 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001344 super.onReset();
1345 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001346 unregisterObserver();
1347 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001348 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001349
1350 /**
1351 * Caches the result, which is useful when we switch from activity to activity, using the same
1352 * contact. If the next load is for a different contact, the cached result will be dropped
1353 */
1354 public void cacheResult() {
Makoto Onukie1cc78a2012-05-14 11:11:31 -07001355 if (mContact == null || !mContact.isLoaded()) {
Makoto Onukieb7b0502012-04-26 11:10:27 -07001356 sCachedResult = null;
1357 } else {
Makoto Onukie1cc78a2012-05-14 11:11:31 -07001358 sCachedResult = mContact;
Makoto Onukieb7b0502012-04-26 11:10:27 -07001359 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001360 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001361}