blob: 5d188fb2a5811813ac050ee080ba881043e9d293 [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
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800229 private Result(Result from) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700230 mStatus = from.mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700231 mException = from.mException;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700232 mRequestedUri = from.mRequestedUri;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800233 mLookupUri = from.mLookupUri;
234 mUri = from.mUri;
235 mDirectoryId = from.mDirectoryId;
236 mLookupKey = from.mLookupKey;
237 mId = from.mId;
238 mNameRawContactId = from.mNameRawContactId;
239 mDisplayNameSource = from.mDisplayNameSource;
240 mPhotoId = from.mPhotoId;
241 mPhotoUri = from.mPhotoUri;
242 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700243 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800244 mPhoneticName = from.mPhoneticName;
245 mStarred = from.mStarred;
246 mPresence = from.mPresence;
247 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700248 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800249 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700250 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800251
252 mDirectoryDisplayName = from.mDirectoryDisplayName;
253 mDirectoryType = from.mDirectoryType;
254 mDirectoryAccountType = from.mDirectoryAccountType;
255 mDirectoryAccountName = from.mDirectoryAccountName;
256 mDirectoryExportSupport = from.mDirectoryExportSupport;
257
258 mGroups = from.mGroups;
259
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800260 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700261 mSendToVoicemail = from.mSendToVoicemail;
262 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700263 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800264 }
265
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700266 /**
267 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
268 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700269 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700270 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700271 mDirectoryDisplayName = displayName;
272 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700273 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700274 mDirectoryAccountName = accountName;
275 mDirectoryExportSupport = exportSupport;
276 }
277
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800278 private void setPhotoBinaryData(byte[] photoBinaryData) {
279 mPhotoBinaryData = photoBinaryData;
280 }
281
Daniel Lehmann685157e2011-08-29 21:07:01 -0700282 /**
283 * Returns the URI for the contact that contains both the lookup key and the ID. This is
284 * the best URI to reference a contact.
285 * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
286 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700287 public Uri getLookupUri() {
288 return mLookupUri;
289 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800290
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700291 public String getLookupKey() {
292 return mLookupKey;
293 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800294
Daniel Lehmann685157e2011-08-29 21:07:01 -0700295 /**
296 * Returns the contact Uri that was passed to the provider to make the query. This is
297 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
298 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
299 * always reference the full aggregate contact.
300 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700301 public Uri getUri() {
302 return mUri;
303 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800304
Daniel Lehmann685157e2011-08-29 21:07:01 -0700305 /**
306 * Returns the URI for which this {@link ContactLoader) was initially requested.
307 */
308 public Uri getRequestedUri() {
309 return mRequestedUri;
310 }
311
Dave Santoro6fa73842011-09-28 14:37:06 -0700312 /**
Josh Garguse5d3f892012-04-11 11:56:15 -0700313 * Instantiate a new EntityDeltaList for this contact.
314 */
315 public EntityDeltaList createEntityDeltaList() {
316 return EntityDeltaList.fromIterator(getEntities().iterator());
317 }
318
319 /**
Dave Santoro6fa73842011-09-28 14:37:06 -0700320 * Returns the contact ID.
321 */
Makoto Onuki98306102011-11-28 15:16:58 -0800322 @VisibleForTesting
323 /* package */ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700324 return mId;
325 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800326
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700327 /**
328 * @return true when an exception happened during loading, in which case
329 * {@link #getException} returns the actual exception object.
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700330 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
331 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
332 * and vice versa.
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700333 */
334 public boolean isError() {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700335 return mStatus == Status.ERROR;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700336 }
337
338 public Exception getException() {
339 return mException;
340 }
341
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700342 /**
343 * @return true when the specified contact is not found.
344 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
345 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
346 * and vice versa.
347 */
348 public boolean isNotFound() {
349 return mStatus == Status.NOT_FOUND;
350 }
351
352 /**
353 * @return true if the specified contact is successfully loaded.
354 * i.e. neither {@link #isError()} nor {@link #isNotFound()}.
355 */
356 public boolean isLoaded() {
357 return mStatus == Status.LOADED;
358 }
359
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700360 public long getNameRawContactId() {
361 return mNameRawContactId;
362 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800363
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700364 public int getDisplayNameSource() {
365 return mDisplayNameSource;
366 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800367
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700368 public long getPhotoId() {
369 return mPhotoId;
370 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800371
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700372 public String getPhotoUri() {
373 return mPhotoUri;
374 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800375
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700376 public String getDisplayName() {
377 return mDisplayName;
378 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800379
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700380 public String getAltDisplayName() {
381 return mAltDisplayName;
382 }
383
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700384 public String getPhoneticName() {
385 return mPhoneticName;
386 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800387
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700388 public boolean getStarred() {
389 return mStarred;
390 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800391
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700392 public Integer getPresence() {
393 return mPresence;
394 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800395
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700396 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700397 return mInvitableAccountTypes;
398 }
399
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700400 public ArrayList<Entity> getEntities() {
401 return mEntities;
402 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800403
Dave Santoro39156002011-07-19 01:18:14 -0700404 public ArrayList<StreamItemEntry> getStreamItems() {
405 return mStreamItems;
406 }
407
Daniel Lehmann18958a22012-02-28 17:45:25 -0800408 public LongSparseArray<DataStatus> getStatuses() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700409 return mStatuses;
410 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700411
412 public long getDirectoryId() {
413 return mDirectoryId;
414 }
415
416 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700417 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
418 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700419 }
420
Josh Gargus187c8162012-03-13 17:06:53 -0700421 /**
422 * @return true if this is a contact (not group, etc.) with at least one
Josh Gargus84edfd92012-03-15 18:25:58 -0700423 * writable raw-contact, and false otherwise.
Josh Gargus187c8162012-03-13 17:06:53 -0700424 */
Josh Gargus84edfd92012-03-15 18:25:58 -0700425 public boolean isWritableContact(final Context context) {
Josh Garguse5d3f892012-04-11 11:56:15 -0700426 return getFirstWritableRawContactId(context) != -1;
427 }
428
429 /**
430 * Return the ID of the first raw-contact in the contact data that belongs to a
431 * contact-writable account, or -1 if no such entity exists.
432 */
433 public long getFirstWritableRawContactId(final Context context) {
434 // Directory entries are non-writable
435 if (isDirectoryEntry()) return -1;
436
437 // Iterate through raw-contacts; if we find a writable on, return its ID.
Josh Gargus187c8162012-03-13 17:06:53 -0700438 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
Josh Garguse5d3f892012-04-11 11:56:15 -0700439 for (Entity entity : getEntities()) {
440 ContentValues values = entity.getEntityValues();
441 String type = values.getAsString(RawContacts.ACCOUNT_TYPE);
442 String dataSet = values.getAsString(RawContacts.DATA_SET);
443
444 AccountType accountType = accountTypes.getAccountType(type, dataSet);
445 if (accountType != null && accountType.areContactsWritable()) {
446 return values.getAsLong(RawContacts._ID);
447 }
Josh Gargus187c8162012-03-13 17:06:53 -0700448 }
Josh Garguse5d3f892012-04-11 11:56:15 -0700449 // No writable raw-contact was found.
450 return -1;
Josh Gargus187c8162012-03-13 17:06:53 -0700451 }
452
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700453 public int getDirectoryExportSupport() {
454 return mDirectoryExportSupport;
455 }
456
457 public String getDirectoryDisplayName() {
458 return mDirectoryDisplayName;
459 }
460
461 public String getDirectoryType() {
462 return mDirectoryType;
463 }
464
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700465 public String getDirectoryAccountType() {
466 return mDirectoryAccountType;
467 }
468
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700469 public String getDirectoryAccountName() {
470 return mDirectoryAccountName;
471 }
472
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800473 public byte[] getPhotoBinaryData() {
474 return mPhotoBinaryData;
475 }
476
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700477 public ArrayList<ContentValues> getContentValues() {
478 if (mEntities.size() != 1) {
479 throw new IllegalStateException(
480 "Cannot extract content values from an aggregated contact");
481 }
482
483 Entity entity = mEntities.get(0);
484 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
485 ArrayList<NamedContentValues> subValues = entity.getSubValues();
486 if (subValues != null) {
487 int size = subValues.size();
488 for (int i = 0; i < size; i++) {
489 NamedContentValues pair = subValues.get(i);
490 if (Data.CONTENT_URI.equals(pair.uri)) {
491 result.add(pair.values);
492 }
493 }
494 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800495
496 // If the photo was loaded using the URI, create an entry for the photo
497 // binary data.
498 if (mPhotoId == 0 && mPhotoBinaryData != null) {
499 ContentValues photo = new ContentValues();
500 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
501 photo.put(Photo.PHOTO, mPhotoBinaryData);
502 result.add(photo);
503 }
504
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700505 return result;
506 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700507
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700508 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700509 return mGroups;
510 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700511
512 public boolean isSendToVoicemail() {
513 return mSendToVoicemail;
514 }
515
516 public String getCustomRingtone() {
517 return mCustomRingtone;
518 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700519
520 public boolean isUserProfile() {
521 return mIsUserProfile;
522 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700523 }
524
Dave Santoro39156002011-07-19 01:18:14 -0700525 /**
526 * Projection used for the query that loads all data for the entire contact (except for
527 * social stream items).
528 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700529 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700530 final static String[] COLUMNS = new String[] {
531 Contacts.NAME_RAW_CONTACT_ID,
532 Contacts.DISPLAY_NAME_SOURCE,
533 Contacts.LOOKUP_KEY,
534 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700535 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700536 Contacts.PHONETIC_NAME,
537 Contacts.PHOTO_ID,
538 Contacts.STARRED,
539 Contacts.CONTACT_PRESENCE,
540 Contacts.CONTACT_STATUS,
541 Contacts.CONTACT_STATUS_TIMESTAMP,
542 Contacts.CONTACT_STATUS_RES_PACKAGE,
543 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700544 Contacts.Entity.CONTACT_ID,
545 Contacts.Entity.RAW_CONTACT_ID,
546
547 RawContacts.ACCOUNT_NAME,
548 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700549 RawContacts.DATA_SET,
550 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700551 RawContacts.DIRTY,
552 RawContacts.VERSION,
553 RawContacts.SOURCE_ID,
554 RawContacts.SYNC1,
555 RawContacts.SYNC2,
556 RawContacts.SYNC3,
557 RawContacts.SYNC4,
558 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700559 RawContacts.NAME_VERIFIED,
560
561 Contacts.Entity.DATA_ID,
562 Data.DATA1,
563 Data.DATA2,
564 Data.DATA3,
565 Data.DATA4,
566 Data.DATA5,
567 Data.DATA6,
568 Data.DATA7,
569 Data.DATA8,
570 Data.DATA9,
571 Data.DATA10,
572 Data.DATA11,
573 Data.DATA12,
574 Data.DATA13,
575 Data.DATA14,
576 Data.DATA15,
577 Data.SYNC1,
578 Data.SYNC2,
579 Data.SYNC3,
580 Data.SYNC4,
581 Data.DATA_VERSION,
582 Data.IS_PRIMARY,
583 Data.IS_SUPER_PRIMARY,
584 Data.MIMETYPE,
585 Data.RES_PACKAGE,
586
587 GroupMembership.GROUP_SOURCE_ID,
588
589 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700590 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700591 Data.STATUS,
592 Data.STATUS_RES_PACKAGE,
593 Data.STATUS_ICON,
594 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700595 Data.STATUS_TIMESTAMP,
596
597 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700598 Contacts.SEND_TO_VOICEMAIL,
599 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700600 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700601 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700602
603 public final static int NAME_RAW_CONTACT_ID = 0;
604 public final static int DISPLAY_NAME_SOURCE = 1;
605 public final static int LOOKUP_KEY = 2;
606 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700607 public final static int ALT_DISPLAY_NAME = 4;
608 public final static int PHONETIC_NAME = 5;
609 public final static int PHOTO_ID = 6;
610 public final static int STARRED = 7;
611 public final static int CONTACT_PRESENCE = 8;
612 public final static int CONTACT_STATUS = 9;
613 public final static int CONTACT_STATUS_TIMESTAMP = 10;
614 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
615 public final static int CONTACT_STATUS_LABEL = 12;
616 public final static int CONTACT_ID = 13;
617 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700618
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700619 public final static int ACCOUNT_NAME = 15;
620 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700621 public final static int DATA_SET = 17;
622 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
623 public final static int DIRTY = 19;
624 public final static int VERSION = 20;
625 public final static int SOURCE_ID = 21;
626 public final static int SYNC1 = 22;
627 public final static int SYNC2 = 23;
628 public final static int SYNC3 = 24;
629 public final static int SYNC4 = 25;
630 public final static int DELETED = 26;
631 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700632
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700633 public final static int DATA_ID = 28;
634 public final static int DATA1 = 29;
635 public final static int DATA2 = 30;
636 public final static int DATA3 = 31;
637 public final static int DATA4 = 32;
638 public final static int DATA5 = 33;
639 public final static int DATA6 = 34;
640 public final static int DATA7 = 35;
641 public final static int DATA8 = 36;
642 public final static int DATA9 = 37;
643 public final static int DATA10 = 38;
644 public final static int DATA11 = 39;
645 public final static int DATA12 = 40;
646 public final static int DATA13 = 41;
647 public final static int DATA14 = 42;
648 public final static int DATA15 = 43;
649 public final static int DATA_SYNC1 = 44;
650 public final static int DATA_SYNC2 = 45;
651 public final static int DATA_SYNC3 = 46;
652 public final static int DATA_SYNC4 = 47;
653 public final static int DATA_VERSION = 48;
654 public final static int IS_PRIMARY = 49;
655 public final static int IS_SUPERPRIMARY = 50;
656 public final static int MIMETYPE = 51;
657 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700658
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700659 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700660
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700661 public final static int PRESENCE = 54;
662 public final static int CHAT_CAPABILITY = 55;
663 public final static int STATUS = 56;
664 public final static int STATUS_RES_PACKAGE = 57;
665 public final static int STATUS_ICON = 58;
666 public final static int STATUS_LABEL = 59;
667 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700668
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700669 public final static int PHOTO_URI = 61;
670 public final static int SEND_TO_VOICEMAIL = 62;
671 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700672 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700673 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700674
Dave Santoro39156002011-07-19 01:18:14 -0700675 /**
676 * Projection used for the query that loads all data for the entire contact.
677 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700678 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700679 final static String[] COLUMNS = new String[] {
680 Directory.DISPLAY_NAME,
681 Directory.PACKAGE_NAME,
682 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700683 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700684 Directory.ACCOUNT_NAME,
685 Directory.EXPORT_SUPPORT,
686 };
687
688 public final static int DISPLAY_NAME = 0;
689 public final static int PACKAGE_NAME = 1;
690 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700691 public final static int ACCOUNT_TYPE = 3;
692 public final static int ACCOUNT_NAME = 4;
693 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700694 }
695
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700696 private static class GroupQuery {
697 final static String[] COLUMNS = new String[] {
698 Groups.ACCOUNT_NAME,
699 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700700 Groups.DATA_SET,
701 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700702 Groups._ID,
703 Groups.TITLE,
704 Groups.AUTO_ADD,
705 Groups.FAVORITES,
706 };
707
708 public final static int ACCOUNT_NAME = 0;
709 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700710 public final static int DATA_SET = 2;
711 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
712 public final static int ID = 4;
713 public final static int TITLE = 5;
714 public final static int AUTO_ADD = 6;
715 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700716 }
717
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800718 @Override
719 public Result loadInBackground() {
720 try {
721 final ContentResolver resolver = getContext().getContentResolver();
722 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
723 resolver, mLookupUri);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700724 final Result cachedResult = sCachedResult;
725 sCachedResult = null;
726 // Is this the same Uri as what we had before already? In that case, reuse that result
727 final Result result;
728 final boolean resultIsCached;
729 if (cachedResult != null &&
730 UriUtils.areEqual(cachedResult.getLookupUri(), mLookupUri)) {
731 // We are using a cached result from earlier. Below, we should make sure
732 // we are not doing any more network or disc accesses
733 result = cachedResult;
734 resultIsCached = true;
735 } else {
736 result = loadContactEntity(resolver, uriCurrentFormat);
737 resultIsCached = false;
738 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800739 if (!result.isNotFound()) {
740 if (result.isDirectoryEntry()) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700741 if (!resultIsCached) {
742 loadDirectoryMetaData(result);
743 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800744 } else if (mLoadGroupMetaData) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700745 if (result.getGroupMetaData() == null) {
746 loadGroupMetaData(result);
747 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700748 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700749 if (mLoadStreamItems && result.getStreamItems() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800750 loadStreamItems(result);
751 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700752 if (!resultIsCached) loadPhotoBinaryData(result);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800753
754 // Note ME profile should never have "Add connection"
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700755 if (mLoadInvitableAccountTypes && result.getInvitableAccountTypes() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800756 loadInvitableAccountTypes(result);
757 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700758 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800759 return result;
760 } catch (Exception e) {
761 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
762 return Result.forError(mRequestedUri, e);
763 }
764 }
765
766 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
767 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
768 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
769 Contacts.Entity.RAW_CONTACT_ID);
770 if (cursor == null) {
771 Log.e(TAG, "No cursor returned in loadContactEntity");
772 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700773 }
774
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800775 try {
776 if (!cursor.moveToFirst()) {
777 cursor.close();
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700778 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700779 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700780
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700781 // Create the loaded result starting with the Contact data.
782 Result result = loadContactHeaderData(cursor, contactUri);
783
784 // Fill in the raw contacts, which is wrapped in an Entity and any
785 // status data. Initially, result has empty entities and statuses.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800786 long currentRawContactId = -1;
787 Entity entity = null;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800788 ArrayList<Entity> entities = result.getEntities();
789 LongSparseArray<DataStatus> statuses = result.getStatuses();
790 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
791 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
792 if (rawContactId != currentRawContactId) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700793 // First time to see this raw contact id, so create a new entity, and
794 // add it to the result's entities.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800795 currentRawContactId = rawContactId;
796 entity = new android.content.Entity(loadRawContact(cursor));
797 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700798 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800799 if (!cursor.isNull(ContactQuery.DATA_ID)) {
800 ContentValues data = loadData(cursor);
801 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700802
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800803 if (!cursor.isNull(ContactQuery.PRESENCE)
804 || !cursor.isNull(ContactQuery.STATUS)) {
805 final DataStatus status = new DataStatus(cursor);
806 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
807 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700808 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700809 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700810 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800811
812 return result;
813 } finally {
814 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700815 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800816 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700817
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800818 /**
819 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
820 * not found, returns null
821 */
822 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700823
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800824 // If we have a photo URI, try loading that first.
825 String photoUri = contactData.getPhotoUri();
826 if (photoUri != null) {
827 try {
828 AssetFileDescriptor fd = getContext().getContentResolver()
829 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
830 byte[] buffer = new byte[16 * 1024];
831 FileInputStream fis = fd.createInputStream();
832 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700833 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800834 int size;
835 while ((size = fis.read(buffer)) != -1) {
836 baos.write(buffer, 0, size);
837 }
838 contactData.setPhotoBinaryData(baos.toByteArray());
839 } finally {
840 fis.close();
841 fd.close();
842 }
843 return;
844 } catch (IOException ioe) {
845 // Just fall back to the case below.
846 }
847 }
848
849 // If we couldn't load from a file, fall back to the data blob.
850 final long photoId = contactData.getPhotoId();
851 if (photoId <= 0) {
852 // No photo ID
853 return;
854 }
855
856 for (Entity entity : contactData.getEntities()) {
857 for (NamedContentValues subValue : entity.getSubValues()) {
858 final ContentValues entryValues = subValue.values;
859 final long dataId = entryValues.getAsLong(Data._ID);
860 if (dataId == photoId) {
861 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
862 // Correct Data Id but incorrect MimeType? Don't load
863 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
864 return;
865 }
866 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
867 break;
868 }
869 }
870 }
871 }
872
873 /**
874 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
875 */
876 private void loadInvitableAccountTypes(Result contactData) {
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700877 final ArrayList<AccountType> resultList = Lists.newArrayList();
878 if (!contactData.isUserProfile()) {
879 Map<AccountTypeWithDataSet, AccountType> invitables =
880 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
881 if (!invitables.isEmpty()) {
882 final Map<AccountTypeWithDataSet, AccountType> resultMap =
883 Maps.newHashMap(invitables);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800884
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700885 // Remove the ones that already have a raw contact in the current contact
886 for (Entity entity : contactData.getEntities()) {
887 final ContentValues values = entity.getEntityValues();
888 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
889 values.getAsString(RawContacts.ACCOUNT_TYPE),
890 values.getAsString(RawContacts.DATA_SET));
891 resultMap.remove(type);
892 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800893
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700894 resultList.addAll(resultMap.values());
895 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800896 }
897
898 // Set to mInvitableAccountTypes
Daniel Lehmann49ea2d22012-04-17 11:17:12 -0700899 contactData.mInvitableAccountTypes = resultList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800900 }
901
902 /**
903 * Extracts Contact level columns from the cursor.
904 */
905 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
906 final String directoryParameter =
907 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
908 final long directoryId = directoryParameter == null
909 ? Directory.DEFAULT
910 : Long.parseLong(directoryParameter);
911 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
912 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
913 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
914 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
915 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
916 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
917 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
918 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
919 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
920 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
921 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
922 ? null
923 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
924 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
925 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
926 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
927
928 Uri lookupUri;
929 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
930 lookupUri = ContentUris.withAppendedId(
931 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
932 } else {
933 lookupUri = contactUri;
934 }
935
936 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
937 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
938 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
939 customRingtone, isUserProfile);
940 }
941
942 /**
943 * Extracts RawContact level columns from the cursor.
944 */
945 private ContentValues loadRawContact(Cursor cursor) {
946 ContentValues cv = new ContentValues();
947
948 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
949
950 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
951 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
952 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
953 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
954 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
955 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
956 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
957 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
958 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
959 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
960 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
961 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
962 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
963 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
964 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
965
966 return cv;
967 }
968
969 /**
970 * Extracts Data level columns from the cursor.
971 */
972 private ContentValues loadData(Cursor cursor) {
973 ContentValues cv = new ContentValues();
974
975 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
976
977 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
978 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
979 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
980 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
981 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
982 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
983 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
984 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
985 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
986 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
987 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
988 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
989 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
990 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
991 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
992 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
993 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
994 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
995 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
996 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
997 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
998 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
999 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
1000 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
1001 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
1002 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
1003
1004 return cv;
1005 }
1006
1007 private void cursorColumnToContentValues(
1008 Cursor cursor, ContentValues values, int index) {
1009 switch (cursor.getType(index)) {
1010 case Cursor.FIELD_TYPE_NULL:
1011 // don't put anything in the content values
1012 break;
1013 case Cursor.FIELD_TYPE_INTEGER:
1014 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
1015 break;
1016 case Cursor.FIELD_TYPE_STRING:
1017 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
1018 break;
1019 case Cursor.FIELD_TYPE_BLOB:
1020 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
1021 break;
1022 default:
1023 throw new IllegalStateException("Invalid or unhandled data type");
1024 }
1025 }
1026
1027 private void loadDirectoryMetaData(Result result) {
1028 long directoryId = result.getDirectoryId();
1029
1030 Cursor cursor = getContext().getContentResolver().query(
1031 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
1032 DirectoryQuery.COLUMNS, null, null, null);
1033 if (cursor == null) {
1034 return;
1035 }
1036 try {
1037 if (cursor.moveToFirst()) {
1038 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
1039 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
1040 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
1041 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
1042 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
1043 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
1044 String directoryType = null;
1045 if (!TextUtils.isEmpty(packageName)) {
1046 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -07001047 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001048 Resources resources = pm.getResourcesForApplication(packageName);
1049 directoryType = resources.getString(typeResourceId);
1050 } catch (NameNotFoundException e) {
1051 Log.w(TAG, "Contact directory resource not found: "
1052 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001053 }
1054 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001055
1056 result.setDirectoryMetaData(
1057 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001058 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001059 } finally {
1060 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001061 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001062 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001063
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001064 /**
1065 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1066 * accounts.
1067 */
1068 private void loadGroupMetaData(Result result) {
1069 StringBuilder selection = new StringBuilder();
1070 ArrayList<String> selectionArgs = new ArrayList<String>();
1071 for (Entity entity : result.mEntities) {
1072 ContentValues values = entity.getEntityValues();
1073 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1074 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1075 String dataSet = values.getAsString(RawContacts.DATA_SET);
1076 if (accountName != null && accountType != null) {
1077 if (selection.length() != 0) {
1078 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001079 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001080 selection.append(
1081 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1082 selectionArgs.add(accountName);
1083 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001084
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001085 if (dataSet != null) {
1086 selection.append(" AND " + Groups.DATA_SET + "=?");
1087 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001088 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001089 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1090 }
1091 selection.append(")");
1092 }
1093 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001094 final ArrayList<GroupMetaData> groupList = new ArrayList<GroupMetaData>();
1095 final Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001096 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1097 null);
1098 try {
1099 while (cursor.moveToNext()) {
1100 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1101 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1102 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1103 final long groupId = cursor.getLong(GroupQuery.ID);
1104 final String title = cursor.getString(GroupQuery.TITLE);
1105 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1106 ? false
1107 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1108 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1109 ? false
1110 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1111
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001112 groupList.add(new GroupMetaData(
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001113 accountName, accountType, dataSet, groupId, title, defaultGroup,
1114 favorites));
1115 }
1116 } finally {
1117 cursor.close();
1118 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001119 result.mGroups = groupList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001120 }
1121
1122 /**
1123 * Loads all stream items and stream item photos belonging to this contact.
1124 */
1125 private void loadStreamItems(Result result) {
1126 Cursor cursor = getContext().getContentResolver().query(
1127 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1128 .appendPath(result.getLookupKey())
1129 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1130 null, null, null, null);
1131 LongSparseArray<StreamItemEntry> streamItemsById =
1132 new LongSparseArray<StreamItemEntry>();
1133 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1134 try {
1135 while (cursor.moveToNext()) {
1136 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1137 streamItemsById.put(streamItem.getId(), streamItem);
1138 streamItems.add(streamItem);
1139 }
1140 } finally {
1141 cursor.close();
1142 }
1143
1144 // Pre-decode all HTMLs
1145 final long start = System.currentTimeMillis();
1146 for (StreamItemEntry streamItem : streamItems) {
1147 streamItem.decodeHtml(getContext());
1148 }
1149 final long end = System.currentTimeMillis();
1150 if (DEBUG) {
1151 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1152 + (end - start) + " ms");
1153 }
1154
1155 // Now retrieve any photo records associated with the stream items.
1156 if (!streamItems.isEmpty()) {
1157 if (result.isUserProfile()) {
1158 // If the stream items we're loading are for the profile, we can't bulk-load the
1159 // stream items with a custom selection.
1160 for (StreamItemEntry entry : streamItems) {
1161 Cursor siCursor = getContext().getContentResolver().query(
1162 Uri.withAppendedPath(
1163 ContentUris.withAppendedId(
1164 StreamItems.CONTENT_URI, entry.getId()),
1165 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1166 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001167 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001168 while (siCursor.moveToNext()) {
1169 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001170 }
1171 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001172 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001173 }
Dave Santoro39156002011-07-19 01:18:14 -07001174 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001175 } else {
1176 String[] streamItemIdArr = new String[streamItems.size()];
1177 StringBuilder streamItemPhotoSelection = new StringBuilder();
1178 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1179 for (int i = 0; i < streamItems.size(); i++) {
1180 if (i > 0) {
1181 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001182 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001183 streamItemPhotoSelection.append("?");
1184 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001185 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001186 streamItemPhotoSelection.append(")");
1187 Cursor sipCursor = getContext().getContentResolver().query(
1188 StreamItems.CONTENT_PHOTO_URI,
1189 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1190 StreamItemPhotos.STREAM_ITEM_ID);
1191 try {
1192 while (sipCursor.moveToNext()) {
1193 long streamItemId = sipCursor.getLong(
1194 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1195 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1196 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1197 }
1198 } finally {
1199 sipCursor.close();
1200 }
1201 }
1202 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001203
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001204 // Set the sorted stream items on the result.
1205 Collections.sort(streamItems);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001206 result.mStreamItems = streamItems;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001207 }
1208
1209 @Override
1210 public void deliverResult(Result result) {
1211 unregisterObserver();
1212
1213 // The creator isn't interested in any further updates
1214 if (isReset() || result == null) {
1215 return;
1216 }
1217
1218 mContact = result;
1219
1220 if (result.isLoaded()) {
1221 mLookupUri = result.getLookupUri();
1222
1223 if (!result.isDirectoryEntry()) {
1224 Log.i(TAG, "Registering content observer for " + mLookupUri);
1225 if (mObserver == null) {
1226 mObserver = new ForceLoadContentObserver();
1227 }
1228 getContext().getContentResolver().registerContentObserver(
1229 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001230 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001231
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001232 // inform the source of the data that this contact is being looked at
1233 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001234 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001235
1236 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001237 }
1238
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001239 /**
1240 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1241 * that the contact has just been loaded
1242 */
1243 private void postViewNotificationToSyncAdapter() {
1244 Context context = getContext();
1245 for (Entity entity : mContact.getEntities()) {
1246 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001247 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1248 if (mNotifiedRawContactIds.contains(rawContactId)) {
1249 continue; // Already notified for this raw contact.
1250 }
1251 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001252 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1253 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001254 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001255 type, dataSet);
1256 final String serviceName = accountType.getViewContactNotifyServiceClassName();
Makoto Onuki82a4f442012-05-07 17:18:33 -07001257 final String servicePackageName = accountType.getViewContactNotifyServicePackageName();
1258 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(servicePackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001259 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1260 final Intent intent = new Intent();
Makoto Onuki82a4f442012-05-07 17:18:33 -07001261 intent.setClassName(servicePackageName, serviceName);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001262 intent.setAction(Intent.ACTION_VIEW);
1263 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1264 try {
1265 context.startService(intent);
1266 } catch (Exception e) {
1267 Log.e(TAG, "Error sending message to source-app", e);
1268 }
1269 }
1270 }
1271 }
1272
Daniel Lehmann3a120772010-06-21 16:21:35 -07001273 private void unregisterObserver() {
1274 if (mObserver != null) {
1275 getContext().getContentResolver().unregisterContentObserver(mObserver);
1276 mObserver = null;
1277 }
1278 }
1279
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001280 /**
1281 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1282 * At the moment, this is only used for debugging purposes
1283 */
1284 public void setLoadStreamItems(boolean value) {
1285 if (mLoadStreamItems != value) {
1286 mLoadStreamItems = value;
1287 onContentChanged();
1288 }
1289 }
1290
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001291 /**
1292 * Fully upgrades this ContactLoader to one with all lists fully loaded. When done, the
1293 * new result will be delivered
1294 */
1295 public void upgradeToFullContact() {
Daniel Lehmannd9662a82012-04-24 15:43:40 -07001296 // Everything requested already? Nothing to do, so let's bail out
1297 if (mLoadGroupMetaData && mLoadInvitableAccountTypes && mLoadStreamItems) return;
1298
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001299 mLoadGroupMetaData = true;
1300 mLoadInvitableAccountTypes = true;
1301 mLoadStreamItems = true;
1302
1303 // Cache the current result, so that we only load the "missing" parts of the contact.
1304 cacheResult();
1305
1306 // Our load parameters have changed, so let's pretend the data has changed. Its the same
1307 // thing, essentially.
1308 onContentChanged();
1309 }
1310
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001311 public boolean getLoadStreamItems() {
1312 return mLoadStreamItems;
1313 }
1314
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001315 public Uri getLookupUri() {
1316 return mLookupUri;
1317 }
1318
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001319 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001320 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001321 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001322 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001323 }
1324
1325 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001326 forceLoad();
1327 }
1328 }
1329
1330 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001331 protected void onStopLoading() {
1332 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001333 }
1334
1335 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001336 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001337 super.onReset();
1338 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001339 unregisterObserver();
1340 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001341 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001342
1343 /**
1344 * Caches the result, which is useful when we switch from activity to activity, using the same
1345 * contact. If the next load is for a different contact, the cached result will be dropped
1346 */
1347 public void cacheResult() {
Makoto Onukieb7b0502012-04-26 11:10:27 -07001348 if (mContact == null) {
1349 sCachedResult = null;
1350 } else {
1351 sCachedResult = new Result(mContact);
1352 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001353 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001354}