blob: e85f5f5525225bec10d417314cb75570ddebb862 [file] [log] [blame]
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001/*
Daniel Lehmannef3f8f02010-07-26 18:55:25 -07002 * Copyright (C) 2010 The Android Open Source Project
Daniel Lehmann4cd94412010-04-08 16:44:36 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070018
Makoto Onuki69b4a882011-07-22 10:05:10 -070019import com.android.contacts.model.AccountType;
20import com.android.contacts.model.AccountTypeManager;
Makoto Onuki6ad227f2011-08-15 13:46:59 -070021import com.android.contacts.model.AccountTypeWithDataSet;
Katherine Kuan6cd5b0a2011-09-16 11:46:01 -070022import com.android.contacts.util.ContactLoaderUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070023import com.android.contacts.util.DataStatus;
Dave Santoro39156002011-07-19 01:18:14 -070024import com.android.contacts.util.StreamItemEntry;
25import com.android.contacts.util.StreamItemPhotoEntry;
Makoto Onuki69b4a882011-07-22 10:05:10 -070026import com.google.android.collect.Lists;
Flavio Lerda37a26842011-06-27 11:36:52 +010027import com.google.common.annotations.VisibleForTesting;
Makoto Onuki6ad227f2011-08-15 13:46:59 -070028import com.google.common.collect.Maps;
Makoto Onukiaba2b832011-08-12 15:44:53 -070029import com.google.common.collect.Sets;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070030
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080031import android.content.AsyncTaskLoader;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070032import android.content.ContentResolver;
33import android.content.ContentUris;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070034import android.content.ContentValues;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070035import android.content.Context;
36import android.content.Entity;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070037import android.content.Entity.NamedContentValues;
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -070038import android.content.Intent;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070039import android.content.pm.PackageManager;
40import android.content.pm.PackageManager.NameNotFoundException;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070041import android.content.res.AssetFileDescriptor;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070042import android.content.res.Resources;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070043import android.database.Cursor;
44import android.net.Uri;
Daniel Lehmann1316b132010-04-13 15:08:53 -070045import android.provider.ContactsContract;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -070046import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080047import android.provider.ContactsContract.CommonDataKinds.Photo;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070048import android.provider.ContactsContract.Contacts;
49import android.provider.ContactsContract.Data;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070050import android.provider.ContactsContract.Directory;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070051import android.provider.ContactsContract.DisplayNameSources;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -070052import android.provider.ContactsContract.Groups;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070053import android.provider.ContactsContract.RawContacts;
Dave Santoro39156002011-07-19 01:18:14 -070054import android.provider.ContactsContract.StreamItemPhotos;
Makoto Onuki69b4a882011-07-22 10:05:10 -070055import android.provider.ContactsContract.StreamItems;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -070056import android.text.TextUtils;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070057import android.util.Log;
Daniel Lehmann18958a22012-02-28 17:45:25 -080058import android.util.LongSparseArray;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070059
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080060import java.io.ByteArrayOutputStream;
Dave Santoro0a2a5db2011-06-29 00:37:06 -070061import java.io.FileInputStream;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -080062import java.io.IOException;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070063import java.util.ArrayList;
Dave Santoro39156002011-07-19 01:18:14 -070064import java.util.Collections;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -070065import java.util.List;
Dave Santoro39156002011-07-19 01:18:14 -070066import java.util.Map;
Makoto Onukiaba2b832011-08-12 15:44:53 -070067import java.util.Set;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070068
69/**
70 * Loads a single Contact and all it constituent RawContacts.
71 */
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080072public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> {
Daniel Lehmann18f104f2010-05-07 15:41:11 -070073 private static final String TAG = "ContactLoader";
74
Makoto Onukida9cdc12012-02-27 16:11:50 -080075 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
76
Daniel Lehmann685157e2011-08-29 21:07:01 -070077 private final Uri mRequestedUri;
Makoto Onuki2621c5b2011-10-03 12:56:16 -070078 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070079 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070080 private boolean mLoadStreamItems;
Makoto Onuki69b4a882011-07-22 10:05:10 -070081 private final boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070082 private Result mContact;
83 private ForceLoadContentObserver mObserver;
Makoto Onukiaba2b832011-08-12 15:44:53 -070084 private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
Dmitri Plotnikove843f912010-09-16 15:21:48 -070085
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070086 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070087 public void onContactLoaded(Result contact);
88 }
89
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080090 public ContactLoader(Context context, Uri lookupUri) {
91 this(context, lookupUri, false, false, false);
92 }
93
94 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
95 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
96 super(context);
97 mLookupUri = lookupUri;
98 mRequestedUri = lookupUri;
99 mLoadGroupMetaData = loadGroupMetaData;
100 mLoadStreamItems = loadStreamItems;
101 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
102 }
103
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700104 /**
105 * The result of a load operation. Contains all data necessary to display the contact.
106 */
107 public static final class Result {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700108 private enum Status {
109 /** Contact is successfully loaded */
110 LOADED,
111 /** There was an error loading the contact */
112 ERROR,
113 /** Contact is not found */
114 NOT_FOUND,
115 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700116
Daniel Lehmann685157e2011-08-29 21:07:01 -0700117 private final Uri mRequestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700118 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700119 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700120 private final long mDirectoryId;
121 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700122 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700123 private final long mNameRawContactId;
124 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700125 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700126 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700127 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700128 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700129 private final String mPhoneticName;
130 private final boolean mStarred;
131 private final Integer mPresence;
132 private final ArrayList<Entity> mEntities;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700133 private final ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800134 private final LongSparseArray<DataStatus> mStatuses;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700135 private final ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700136
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700137 private String mDirectoryDisplayName;
138 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700139 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700140 private String mDirectoryAccountName;
141 private int mDirectoryExportSupport;
142
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700143 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700144
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800145 private byte[] mPhotoBinaryData;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700146 private final boolean mSendToVoicemail;
147 private final String mCustomRingtone;
148 private final boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800149
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700150 private final Status mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700151 private final Exception mException;
152
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700153 /**
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700154 * Constructor for special results, namely "no contact found" and "error".
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700155 */
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700156 private Result(Uri requestedUri, Status status, Exception exception) {
157 if (status == Status.ERROR && exception == null) {
158 throw new IllegalArgumentException("ERROR result must have exception");
159 }
160 mStatus = status;
161 mException = exception;
162 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700163 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700164 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700165 mDirectoryId = -1;
166 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700167 mId = -1;
168 mEntities = null;
Dave Santoro39156002011-07-19 01:18:14 -0700169 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700170 mStatuses = null;
171 mNameRawContactId = -1;
172 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700173 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700174 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700175 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700176 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700177 mPhoneticName = null;
178 mStarred = false;
179 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700180 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700181 mSendToVoicemail = false;
182 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700183 mIsUserProfile = false;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700184 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700185
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700186 private static Result forError(Uri requestedUri, Exception exception) {
187 return new Result(requestedUri, Status.ERROR, exception);
188 }
189
190 private static Result forNotFound(Uri requestedUri) {
191 return new Result(requestedUri, Status.NOT_FOUND, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700192 }
193
194 /**
195 * Constructor to call when contact was found
196 */
Daniel Lehmann685157e2011-08-29 21:07:01 -0700197 private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
198 long id, long nameRawContactId, int displayNameSource, long photoId,
199 String photoUri, String displayName, String altDisplayName, String phoneticName,
200 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700201 boolean isUserProfile) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700202 mStatus = Status.LOADED;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700203 mException = null;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700204 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700205 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700206 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700207 mDirectoryId = directoryId;
208 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700209 mId = id;
210 mEntities = new ArrayList<Entity>();
Dave Santoro39156002011-07-19 01:18:14 -0700211 mStreamItems = new ArrayList<StreamItemEntry>();
Daniel Lehmann18958a22012-02-28 17:45:25 -0800212 mStatuses = new LongSparseArray<DataStatus>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700213 mNameRawContactId = nameRawContactId;
214 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700215 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700216 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700217 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700218 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700219 mPhoneticName = phoneticName;
220 mStarred = starred;
221 mPresence = presence;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700222 mInvitableAccountTypes = Lists.newArrayList();
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700223 mSendToVoicemail = sendToVoicemail;
224 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700225 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700226 }
227
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800228 private Result(Result from) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700229 mStatus = from.mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700230 mException = from.mException;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700231 mRequestedUri = from.mRequestedUri;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800232 mLookupUri = from.mLookupUri;
233 mUri = from.mUri;
234 mDirectoryId = from.mDirectoryId;
235 mLookupKey = from.mLookupKey;
236 mId = from.mId;
237 mNameRawContactId = from.mNameRawContactId;
238 mDisplayNameSource = from.mDisplayNameSource;
239 mPhotoId = from.mPhotoId;
240 mPhotoUri = from.mPhotoUri;
241 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700242 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800243 mPhoneticName = from.mPhoneticName;
244 mStarred = from.mStarred;
245 mPresence = from.mPresence;
246 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700247 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800248 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700249 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800250
251 mDirectoryDisplayName = from.mDirectoryDisplayName;
252 mDirectoryType = from.mDirectoryType;
253 mDirectoryAccountType = from.mDirectoryAccountType;
254 mDirectoryAccountName = from.mDirectoryAccountName;
255 mDirectoryExportSupport = from.mDirectoryExportSupport;
256
257 mGroups = from.mGroups;
258
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800259 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700260 mSendToVoicemail = from.mSendToVoicemail;
261 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700262 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800263 }
264
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700265 /**
266 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
267 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700268 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700269 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700270 mDirectoryDisplayName = displayName;
271 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700272 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700273 mDirectoryAccountName = accountName;
274 mDirectoryExportSupport = exportSupport;
275 }
276
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800277 private void setPhotoBinaryData(byte[] photoBinaryData) {
278 mPhotoBinaryData = photoBinaryData;
279 }
280
Daniel Lehmann685157e2011-08-29 21:07:01 -0700281 /**
282 * Returns the URI for the contact that contains both the lookup key and the ID. This is
283 * the best URI to reference a contact.
284 * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
285 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700286 public Uri getLookupUri() {
287 return mLookupUri;
288 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800289
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700290 public String getLookupKey() {
291 return mLookupKey;
292 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800293
Daniel Lehmann685157e2011-08-29 21:07:01 -0700294 /**
295 * Returns the contact Uri that was passed to the provider to make the query. This is
296 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
297 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
298 * always reference the full aggregate contact.
299 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700300 public Uri getUri() {
301 return mUri;
302 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800303
Daniel Lehmann685157e2011-08-29 21:07:01 -0700304 /**
305 * Returns the URI for which this {@link ContactLoader) was initially requested.
306 */
307 public Uri getRequestedUri() {
308 return mRequestedUri;
309 }
310
Dave Santoro6fa73842011-09-28 14:37:06 -0700311 /**
312 * Returns the contact ID.
313 */
Makoto Onuki98306102011-11-28 15:16:58 -0800314 @VisibleForTesting
315 /* package */ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700316 return mId;
317 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800318
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700319 /**
320 * @return true when an exception happened during loading, in which case
321 * {@link #getException} returns the actual exception object.
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700322 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
323 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
324 * and vice versa.
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700325 */
326 public boolean isError() {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700327 return mStatus == Status.ERROR;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700328 }
329
330 public Exception getException() {
331 return mException;
332 }
333
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700334 /**
335 * @return true when the specified contact is not found.
336 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
337 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
338 * and vice versa.
339 */
340 public boolean isNotFound() {
341 return mStatus == Status.NOT_FOUND;
342 }
343
344 /**
345 * @return true if the specified contact is successfully loaded.
346 * i.e. neither {@link #isError()} nor {@link #isNotFound()}.
347 */
348 public boolean isLoaded() {
349 return mStatus == Status.LOADED;
350 }
351
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700352 public long getNameRawContactId() {
353 return mNameRawContactId;
354 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800355
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700356 public int getDisplayNameSource() {
357 return mDisplayNameSource;
358 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800359
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700360 public long getPhotoId() {
361 return mPhotoId;
362 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800363
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700364 public String getPhotoUri() {
365 return mPhotoUri;
366 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800367
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700368 public String getDisplayName() {
369 return mDisplayName;
370 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800371
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700372 public String getAltDisplayName() {
373 return mAltDisplayName;
374 }
375
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700376 public String getPhoneticName() {
377 return mPhoneticName;
378 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800379
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700380 public boolean getStarred() {
381 return mStarred;
382 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800383
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700384 public Integer getPresence() {
385 return mPresence;
386 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800387
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700388 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700389 return mInvitableAccountTypes;
390 }
391
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700392 public ArrayList<Entity> getEntities() {
393 return mEntities;
394 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800395
Dave Santoro39156002011-07-19 01:18:14 -0700396 public ArrayList<StreamItemEntry> getStreamItems() {
397 return mStreamItems;
398 }
399
Daniel Lehmann18958a22012-02-28 17:45:25 -0800400 public LongSparseArray<DataStatus> getStatuses() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700401 return mStatuses;
402 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700403
404 public long getDirectoryId() {
405 return mDirectoryId;
406 }
407
408 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700409 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
410 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700411 }
412
413 public int getDirectoryExportSupport() {
414 return mDirectoryExportSupport;
415 }
416
417 public String getDirectoryDisplayName() {
418 return mDirectoryDisplayName;
419 }
420
421 public String getDirectoryType() {
422 return mDirectoryType;
423 }
424
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700425 public String getDirectoryAccountType() {
426 return mDirectoryAccountType;
427 }
428
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700429 public String getDirectoryAccountName() {
430 return mDirectoryAccountName;
431 }
432
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800433 public byte[] getPhotoBinaryData() {
434 return mPhotoBinaryData;
435 }
436
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700437 public ArrayList<ContentValues> getContentValues() {
438 if (mEntities.size() != 1) {
439 throw new IllegalStateException(
440 "Cannot extract content values from an aggregated contact");
441 }
442
443 Entity entity = mEntities.get(0);
444 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
445 ArrayList<NamedContentValues> subValues = entity.getSubValues();
446 if (subValues != null) {
447 int size = subValues.size();
448 for (int i = 0; i < size; i++) {
449 NamedContentValues pair = subValues.get(i);
450 if (Data.CONTENT_URI.equals(pair.uri)) {
451 result.add(pair.values);
452 }
453 }
454 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800455
456 // If the photo was loaded using the URI, create an entry for the photo
457 // binary data.
458 if (mPhotoId == 0 && mPhotoBinaryData != null) {
459 ContentValues photo = new ContentValues();
460 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
461 photo.put(Photo.PHOTO, mPhotoBinaryData);
462 result.add(photo);
463 }
464
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700465 return result;
466 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700467
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700468 private void addGroupMetaData(GroupMetaData group) {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700469 if (mGroups == null) {
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700470 mGroups = new ArrayList<GroupMetaData>();
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700471 }
472 mGroups.add(group);
473 }
474
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700475 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700476 return mGroups;
477 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700478
479 public boolean isSendToVoicemail() {
480 return mSendToVoicemail;
481 }
482
483 public String getCustomRingtone() {
484 return mCustomRingtone;
485 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700486
487 public boolean isUserProfile() {
488 return mIsUserProfile;
489 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700490 }
491
Dave Santoro39156002011-07-19 01:18:14 -0700492 /**
493 * Projection used for the query that loads all data for the entire contact (except for
494 * social stream items).
495 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700496 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700497 final static String[] COLUMNS = new String[] {
498 Contacts.NAME_RAW_CONTACT_ID,
499 Contacts.DISPLAY_NAME_SOURCE,
500 Contacts.LOOKUP_KEY,
501 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700502 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700503 Contacts.PHONETIC_NAME,
504 Contacts.PHOTO_ID,
505 Contacts.STARRED,
506 Contacts.CONTACT_PRESENCE,
507 Contacts.CONTACT_STATUS,
508 Contacts.CONTACT_STATUS_TIMESTAMP,
509 Contacts.CONTACT_STATUS_RES_PACKAGE,
510 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700511 Contacts.Entity.CONTACT_ID,
512 Contacts.Entity.RAW_CONTACT_ID,
513
514 RawContacts.ACCOUNT_NAME,
515 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700516 RawContacts.DATA_SET,
517 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700518 RawContacts.DIRTY,
519 RawContacts.VERSION,
520 RawContacts.SOURCE_ID,
521 RawContacts.SYNC1,
522 RawContacts.SYNC2,
523 RawContacts.SYNC3,
524 RawContacts.SYNC4,
525 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700526 RawContacts.NAME_VERIFIED,
527
528 Contacts.Entity.DATA_ID,
529 Data.DATA1,
530 Data.DATA2,
531 Data.DATA3,
532 Data.DATA4,
533 Data.DATA5,
534 Data.DATA6,
535 Data.DATA7,
536 Data.DATA8,
537 Data.DATA9,
538 Data.DATA10,
539 Data.DATA11,
540 Data.DATA12,
541 Data.DATA13,
542 Data.DATA14,
543 Data.DATA15,
544 Data.SYNC1,
545 Data.SYNC2,
546 Data.SYNC3,
547 Data.SYNC4,
548 Data.DATA_VERSION,
549 Data.IS_PRIMARY,
550 Data.IS_SUPER_PRIMARY,
551 Data.MIMETYPE,
552 Data.RES_PACKAGE,
553
554 GroupMembership.GROUP_SOURCE_ID,
555
556 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700557 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700558 Data.STATUS,
559 Data.STATUS_RES_PACKAGE,
560 Data.STATUS_ICON,
561 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700562 Data.STATUS_TIMESTAMP,
563
564 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700565 Contacts.SEND_TO_VOICEMAIL,
566 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700567 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700568 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700569
570 public final static int NAME_RAW_CONTACT_ID = 0;
571 public final static int DISPLAY_NAME_SOURCE = 1;
572 public final static int LOOKUP_KEY = 2;
573 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700574 public final static int ALT_DISPLAY_NAME = 4;
575 public final static int PHONETIC_NAME = 5;
576 public final static int PHOTO_ID = 6;
577 public final static int STARRED = 7;
578 public final static int CONTACT_PRESENCE = 8;
579 public final static int CONTACT_STATUS = 9;
580 public final static int CONTACT_STATUS_TIMESTAMP = 10;
581 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
582 public final static int CONTACT_STATUS_LABEL = 12;
583 public final static int CONTACT_ID = 13;
584 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700585
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700586 public final static int ACCOUNT_NAME = 15;
587 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700588 public final static int DATA_SET = 17;
589 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
590 public final static int DIRTY = 19;
591 public final static int VERSION = 20;
592 public final static int SOURCE_ID = 21;
593 public final static int SYNC1 = 22;
594 public final static int SYNC2 = 23;
595 public final static int SYNC3 = 24;
596 public final static int SYNC4 = 25;
597 public final static int DELETED = 26;
598 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700599
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700600 public final static int DATA_ID = 28;
601 public final static int DATA1 = 29;
602 public final static int DATA2 = 30;
603 public final static int DATA3 = 31;
604 public final static int DATA4 = 32;
605 public final static int DATA5 = 33;
606 public final static int DATA6 = 34;
607 public final static int DATA7 = 35;
608 public final static int DATA8 = 36;
609 public final static int DATA9 = 37;
610 public final static int DATA10 = 38;
611 public final static int DATA11 = 39;
612 public final static int DATA12 = 40;
613 public final static int DATA13 = 41;
614 public final static int DATA14 = 42;
615 public final static int DATA15 = 43;
616 public final static int DATA_SYNC1 = 44;
617 public final static int DATA_SYNC2 = 45;
618 public final static int DATA_SYNC3 = 46;
619 public final static int DATA_SYNC4 = 47;
620 public final static int DATA_VERSION = 48;
621 public final static int IS_PRIMARY = 49;
622 public final static int IS_SUPERPRIMARY = 50;
623 public final static int MIMETYPE = 51;
624 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700625
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700626 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700627
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700628 public final static int PRESENCE = 54;
629 public final static int CHAT_CAPABILITY = 55;
630 public final static int STATUS = 56;
631 public final static int STATUS_RES_PACKAGE = 57;
632 public final static int STATUS_ICON = 58;
633 public final static int STATUS_LABEL = 59;
634 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700635
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700636 public final static int PHOTO_URI = 61;
637 public final static int SEND_TO_VOICEMAIL = 62;
638 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700639 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700640 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700641
Dave Santoro39156002011-07-19 01:18:14 -0700642 /**
643 * Projection used for the query that loads all data for the entire contact.
644 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700645 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700646 final static String[] COLUMNS = new String[] {
647 Directory.DISPLAY_NAME,
648 Directory.PACKAGE_NAME,
649 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700650 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700651 Directory.ACCOUNT_NAME,
652 Directory.EXPORT_SUPPORT,
653 };
654
655 public final static int DISPLAY_NAME = 0;
656 public final static int PACKAGE_NAME = 1;
657 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700658 public final static int ACCOUNT_TYPE = 3;
659 public final static int ACCOUNT_NAME = 4;
660 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700661 }
662
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700663 private static class GroupQuery {
664 final static String[] COLUMNS = new String[] {
665 Groups.ACCOUNT_NAME,
666 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700667 Groups.DATA_SET,
668 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700669 Groups._ID,
670 Groups.TITLE,
671 Groups.AUTO_ADD,
672 Groups.FAVORITES,
673 };
674
675 public final static int ACCOUNT_NAME = 0;
676 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700677 public final static int DATA_SET = 2;
678 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
679 public final static int ID = 4;
680 public final static int TITLE = 5;
681 public final static int AUTO_ADD = 6;
682 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700683 }
684
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800685 @Override
686 public Result loadInBackground() {
687 try {
688 final ContentResolver resolver = getContext().getContentResolver();
689 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
690 resolver, mLookupUri);
691 Result result = loadContactEntity(resolver, uriCurrentFormat);
692 if (!result.isNotFound()) {
693 if (result.isDirectoryEntry()) {
694 loadDirectoryMetaData(result);
695 } else if (mLoadGroupMetaData) {
696 loadGroupMetaData(result);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700697 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800698 if (mLoadStreamItems) {
699 loadStreamItems(result);
700 }
701 loadPhotoBinaryData(result);
702
703 // Note ME profile should never have "Add connection"
704 if (mLoadInvitableAccountTypes && !result.isUserProfile()) {
705 loadInvitableAccountTypes(result);
706 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700707 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800708 return result;
709 } catch (Exception e) {
710 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
711 return Result.forError(mRequestedUri, e);
712 }
713 }
714
715 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
716 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
717 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
718 Contacts.Entity.RAW_CONTACT_ID);
719 if (cursor == null) {
720 Log.e(TAG, "No cursor returned in loadContactEntity");
721 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700722 }
723
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800724 try {
725 if (!cursor.moveToFirst()) {
726 cursor.close();
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700727 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700728 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700729
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800730 long currentRawContactId = -1;
731 Entity entity = null;
732 Result result = loadContactHeaderData(cursor, contactUri);
733 ArrayList<Entity> entities = result.getEntities();
734 LongSparseArray<DataStatus> statuses = result.getStatuses();
735 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
736 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
737 if (rawContactId != currentRawContactId) {
738 currentRawContactId = rawContactId;
739 entity = new android.content.Entity(loadRawContact(cursor));
740 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700741 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800742 if (!cursor.isNull(ContactQuery.DATA_ID)) {
743 ContentValues data = loadData(cursor);
744 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700745
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800746 if (!cursor.isNull(ContactQuery.PRESENCE)
747 || !cursor.isNull(ContactQuery.STATUS)) {
748 final DataStatus status = new DataStatus(cursor);
749 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
750 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700751 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700752 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700753 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800754
755 return result;
756 } finally {
757 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700758 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800759 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700760
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800761 /**
762 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
763 * not found, returns null
764 */
765 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700766
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800767 // If we have a photo URI, try loading that first.
768 String photoUri = contactData.getPhotoUri();
769 if (photoUri != null) {
770 try {
771 AssetFileDescriptor fd = getContext().getContentResolver()
772 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
773 byte[] buffer = new byte[16 * 1024];
774 FileInputStream fis = fd.createInputStream();
775 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700776 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800777 int size;
778 while ((size = fis.read(buffer)) != -1) {
779 baos.write(buffer, 0, size);
780 }
781 contactData.setPhotoBinaryData(baos.toByteArray());
782 } finally {
783 fis.close();
784 fd.close();
785 }
786 return;
787 } catch (IOException ioe) {
788 // Just fall back to the case below.
789 }
790 }
791
792 // If we couldn't load from a file, fall back to the data blob.
793 final long photoId = contactData.getPhotoId();
794 if (photoId <= 0) {
795 // No photo ID
796 return;
797 }
798
799 for (Entity entity : contactData.getEntities()) {
800 for (NamedContentValues subValue : entity.getSubValues()) {
801 final ContentValues entryValues = subValue.values;
802 final long dataId = entryValues.getAsLong(Data._ID);
803 if (dataId == photoId) {
804 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
805 // Correct Data Id but incorrect MimeType? Don't load
806 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
807 return;
808 }
809 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
810 break;
811 }
812 }
813 }
814 }
815
816 /**
817 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
818 */
819 private void loadInvitableAccountTypes(Result contactData) {
820 Map<AccountTypeWithDataSet, AccountType> invitables =
821 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
822 if (invitables.isEmpty()) {
823 return;
824 }
825
826 Map<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(invitables);
827
828 // Remove the ones that already have a raw contact in the current contact
829 for (Entity entity : contactData.getEntities()) {
830 final ContentValues values = entity.getEntityValues();
831 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
832 values.getAsString(RawContacts.ACCOUNT_TYPE),
833 values.getAsString(RawContacts.DATA_SET));
834 result.remove(type);
835 }
836
837 // Set to mInvitableAccountTypes
838 contactData.mInvitableAccountTypes.addAll(result.values());
839 }
840
841 /**
842 * Extracts Contact level columns from the cursor.
843 */
844 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
845 final String directoryParameter =
846 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
847 final long directoryId = directoryParameter == null
848 ? Directory.DEFAULT
849 : Long.parseLong(directoryParameter);
850 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
851 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
852 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
853 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
854 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
855 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
856 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
857 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
858 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
859 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
860 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
861 ? null
862 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
863 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
864 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
865 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
866
867 Uri lookupUri;
868 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
869 lookupUri = ContentUris.withAppendedId(
870 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
871 } else {
872 lookupUri = contactUri;
873 }
874
875 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
876 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
877 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
878 customRingtone, isUserProfile);
879 }
880
881 /**
882 * Extracts RawContact level columns from the cursor.
883 */
884 private ContentValues loadRawContact(Cursor cursor) {
885 ContentValues cv = new ContentValues();
886
887 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
888
889 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
890 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
891 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
892 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
893 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
894 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
895 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
896 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
897 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
898 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
899 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
900 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
901 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
902 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
903 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
904
905 return cv;
906 }
907
908 /**
909 * Extracts Data level columns from the cursor.
910 */
911 private ContentValues loadData(Cursor cursor) {
912 ContentValues cv = new ContentValues();
913
914 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
915
916 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
917 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
918 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
919 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
920 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
921 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
922 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
923 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
924 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
925 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
926 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
927 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
928 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
929 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
930 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
931 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
932 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
933 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
934 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
935 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
936 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
937 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
938 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
939 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
940 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
941 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
942
943 return cv;
944 }
945
946 private void cursorColumnToContentValues(
947 Cursor cursor, ContentValues values, int index) {
948 switch (cursor.getType(index)) {
949 case Cursor.FIELD_TYPE_NULL:
950 // don't put anything in the content values
951 break;
952 case Cursor.FIELD_TYPE_INTEGER:
953 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
954 break;
955 case Cursor.FIELD_TYPE_STRING:
956 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
957 break;
958 case Cursor.FIELD_TYPE_BLOB:
959 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
960 break;
961 default:
962 throw new IllegalStateException("Invalid or unhandled data type");
963 }
964 }
965
966 private void loadDirectoryMetaData(Result result) {
967 long directoryId = result.getDirectoryId();
968
969 Cursor cursor = getContext().getContentResolver().query(
970 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
971 DirectoryQuery.COLUMNS, null, null, null);
972 if (cursor == null) {
973 return;
974 }
975 try {
976 if (cursor.moveToFirst()) {
977 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
978 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
979 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
980 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
981 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
982 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
983 String directoryType = null;
984 if (!TextUtils.isEmpty(packageName)) {
985 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700986 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800987 Resources resources = pm.getResourcesForApplication(packageName);
988 directoryType = resources.getString(typeResourceId);
989 } catch (NameNotFoundException e) {
990 Log.w(TAG, "Contact directory resource not found: "
991 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800992 }
993 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800994
995 result.setDirectoryMetaData(
996 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800997 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800998 } finally {
999 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001000 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001001 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001002
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001003 /**
1004 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1005 * accounts.
1006 */
1007 private void loadGroupMetaData(Result result) {
1008 StringBuilder selection = new StringBuilder();
1009 ArrayList<String> selectionArgs = new ArrayList<String>();
1010 for (Entity entity : result.mEntities) {
1011 ContentValues values = entity.getEntityValues();
1012 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1013 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1014 String dataSet = values.getAsString(RawContacts.DATA_SET);
1015 if (accountName != null && accountType != null) {
1016 if (selection.length() != 0) {
1017 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001018 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001019 selection.append(
1020 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1021 selectionArgs.add(accountName);
1022 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001023
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001024 if (dataSet != null) {
1025 selection.append(" AND " + Groups.DATA_SET + "=?");
1026 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001027 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001028 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1029 }
1030 selection.append(")");
1031 }
1032 }
1033 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
1034 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1035 null);
1036 try {
1037 while (cursor.moveToNext()) {
1038 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1039 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1040 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1041 final long groupId = cursor.getLong(GroupQuery.ID);
1042 final String title = cursor.getString(GroupQuery.TITLE);
1043 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1044 ? false
1045 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1046 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1047 ? false
1048 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1049
1050 result.addGroupMetaData(new GroupMetaData(
1051 accountName, accountType, dataSet, groupId, title, defaultGroup,
1052 favorites));
1053 }
1054 } finally {
1055 cursor.close();
1056 }
1057 }
1058
1059 /**
1060 * Loads all stream items and stream item photos belonging to this contact.
1061 */
1062 private void loadStreamItems(Result result) {
1063 Cursor cursor = getContext().getContentResolver().query(
1064 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1065 .appendPath(result.getLookupKey())
1066 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1067 null, null, null, null);
1068 LongSparseArray<StreamItemEntry> streamItemsById =
1069 new LongSparseArray<StreamItemEntry>();
1070 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1071 try {
1072 while (cursor.moveToNext()) {
1073 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1074 streamItemsById.put(streamItem.getId(), streamItem);
1075 streamItems.add(streamItem);
1076 }
1077 } finally {
1078 cursor.close();
1079 }
1080
1081 // Pre-decode all HTMLs
1082 final long start = System.currentTimeMillis();
1083 for (StreamItemEntry streamItem : streamItems) {
1084 streamItem.decodeHtml(getContext());
1085 }
1086 final long end = System.currentTimeMillis();
1087 if (DEBUG) {
1088 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1089 + (end - start) + " ms");
1090 }
1091
1092 // Now retrieve any photo records associated with the stream items.
1093 if (!streamItems.isEmpty()) {
1094 if (result.isUserProfile()) {
1095 // If the stream items we're loading are for the profile, we can't bulk-load the
1096 // stream items with a custom selection.
1097 for (StreamItemEntry entry : streamItems) {
1098 Cursor siCursor = getContext().getContentResolver().query(
1099 Uri.withAppendedPath(
1100 ContentUris.withAppendedId(
1101 StreamItems.CONTENT_URI, entry.getId()),
1102 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1103 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001104 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001105 while (siCursor.moveToNext()) {
1106 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001107 }
1108 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001109 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001110 }
Dave Santoro39156002011-07-19 01:18:14 -07001111 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001112 } else {
1113 String[] streamItemIdArr = new String[streamItems.size()];
1114 StringBuilder streamItemPhotoSelection = new StringBuilder();
1115 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1116 for (int i = 0; i < streamItems.size(); i++) {
1117 if (i > 0) {
1118 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001119 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001120 streamItemPhotoSelection.append("?");
1121 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001122 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001123 streamItemPhotoSelection.append(")");
1124 Cursor sipCursor = getContext().getContentResolver().query(
1125 StreamItems.CONTENT_PHOTO_URI,
1126 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1127 StreamItemPhotos.STREAM_ITEM_ID);
1128 try {
1129 while (sipCursor.moveToNext()) {
1130 long streamItemId = sipCursor.getLong(
1131 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1132 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1133 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1134 }
1135 } finally {
1136 sipCursor.close();
1137 }
1138 }
1139 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001140
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001141 // Set the sorted stream items on the result.
1142 Collections.sort(streamItems);
1143 result.mStreamItems.addAll(streamItems);
1144 }
1145
1146 @Override
1147 public void deliverResult(Result result) {
1148 unregisterObserver();
1149
1150 // The creator isn't interested in any further updates
1151 if (isReset() || result == null) {
1152 return;
1153 }
1154
1155 mContact = result;
1156
1157 if (result.isLoaded()) {
1158 mLookupUri = result.getLookupUri();
1159
1160 if (!result.isDirectoryEntry()) {
1161 Log.i(TAG, "Registering content observer for " + mLookupUri);
1162 if (mObserver == null) {
1163 mObserver = new ForceLoadContentObserver();
1164 }
1165 getContext().getContentResolver().registerContentObserver(
1166 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001167 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001168
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001169 // inform the source of the data that this contact is being looked at
1170 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001171 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001172
1173 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001174 }
1175
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001176 /**
1177 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1178 * that the contact has just been loaded
1179 */
1180 private void postViewNotificationToSyncAdapter() {
1181 Context context = getContext();
1182 for (Entity entity : mContact.getEntities()) {
1183 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001184 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1185 if (mNotifiedRawContactIds.contains(rawContactId)) {
1186 continue; // Already notified for this raw contact.
1187 }
1188 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001189 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1190 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001191 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001192 type, dataSet);
1193 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1194 final String resPackageName = accountType.resPackageName;
1195 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001196 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1197 final Intent intent = new Intent();
1198 intent.setClassName(resPackageName, serviceName);
1199 intent.setAction(Intent.ACTION_VIEW);
1200 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1201 try {
1202 context.startService(intent);
1203 } catch (Exception e) {
1204 Log.e(TAG, "Error sending message to source-app", e);
1205 }
1206 }
1207 }
1208 }
1209
Daniel Lehmann3a120772010-06-21 16:21:35 -07001210 private void unregisterObserver() {
1211 if (mObserver != null) {
1212 getContext().getContentResolver().unregisterContentObserver(mObserver);
1213 mObserver = null;
1214 }
1215 }
1216
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001217 /**
1218 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1219 * At the moment, this is only used for debugging purposes
1220 */
1221 public void setLoadStreamItems(boolean value) {
1222 if (mLoadStreamItems != value) {
1223 mLoadStreamItems = value;
1224 onContentChanged();
1225 }
1226 }
1227
1228 public boolean getLoadStreamItems() {
1229 return mLoadStreamItems;
1230 }
1231
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001232 public Uri getLookupUri() {
1233 return mLookupUri;
1234 }
1235
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001236 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001237 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001238 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001239 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001240 }
1241
1242 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001243 forceLoad();
1244 }
1245 }
1246
1247 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001248 protected void onStopLoading() {
1249 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001250 }
1251
1252 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001253 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001254 super.onReset();
1255 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001256 unregisterObserver();
1257 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001258 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001259}