blob: 405ba6f6f0a663831055f4d7f546edc6705e2af6 [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;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -070026import com.android.contacts.util.UriUtils;
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 Lehmann9815d7f2012-04-16 18:28:03 -070077 /** A short-lived cache that can be set by {@link #cacheResult()} */
78 private static Result sCachedResult = null;
79
Daniel Lehmann685157e2011-08-29 21:07:01 -070080 private final Uri mRequestedUri;
Makoto Onuki2621c5b2011-10-03 12:56:16 -070081 private Uri mLookupUri;
Dmitri Plotnikove843f912010-09-16 15:21:48 -070082 private boolean mLoadGroupMetaData;
Dave Santoro39156002011-07-19 01:18:14 -070083 private boolean mLoadStreamItems;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -070084 private boolean mLoadInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -070085 private Result mContact;
86 private ForceLoadContentObserver mObserver;
Makoto Onukiaba2b832011-08-12 15:44:53 -070087 private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
Dmitri Plotnikove843f912010-09-16 15:21:48 -070088
Daniel Lehmanncdef2b62010-06-06 18:25:49 -070089 public interface Listener {
Daniel Lehmann4cd94412010-04-08 16:44:36 -070090 public void onContactLoaded(Result contact);
91 }
92
Daniel Lehmann72ff4df2012-02-28 20:03:01 -080093 public ContactLoader(Context context, Uri lookupUri) {
94 this(context, lookupUri, false, false, false);
95 }
96
97 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
98 boolean loadStreamItems, boolean loadInvitableAccountTypes) {
99 super(context);
100 mLookupUri = lookupUri;
101 mRequestedUri = lookupUri;
102 mLoadGroupMetaData = loadGroupMetaData;
103 mLoadStreamItems = loadStreamItems;
104 mLoadInvitableAccountTypes = loadInvitableAccountTypes;
105 }
106
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700107 /**
108 * The result of a load operation. Contains all data necessary to display the contact.
109 */
110 public static final class Result {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700111 private enum Status {
112 /** Contact is successfully loaded */
113 LOADED,
114 /** There was an error loading the contact */
115 ERROR,
116 /** Contact is not found */
117 NOT_FOUND,
118 }
Daniel Lehmann18f104f2010-05-07 15:41:11 -0700119
Daniel Lehmann685157e2011-08-29 21:07:01 -0700120 private final Uri mRequestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700121 private final Uri mLookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700122 private final Uri mUri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700123 private final long mDirectoryId;
124 private final String mLookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700125 private final long mId;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700126 private final long mNameRawContactId;
127 private final int mDisplayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700128 private final long mPhotoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700129 private final String mPhotoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700130 private final String mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700131 private final String mAltDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700132 private final String mPhoneticName;
133 private final boolean mStarred;
134 private final Integer mPresence;
135 private final ArrayList<Entity> mEntities;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700136 private ArrayList<StreamItemEntry> mStreamItems;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800137 private final LongSparseArray<DataStatus> mStatuses;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700138 private ArrayList<AccountType> mInvitableAccountTypes;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700139
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700140 private String mDirectoryDisplayName;
141 private String mDirectoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700142 private String mDirectoryAccountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700143 private String mDirectoryAccountName;
144 private int mDirectoryExportSupport;
145
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700146 private ArrayList<GroupMetaData> mGroups;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700147
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800148 private byte[] mPhotoBinaryData;
Makoto Onuki870a87e2011-08-12 13:40:31 -0700149 private final boolean mSendToVoicemail;
150 private final String mCustomRingtone;
151 private final boolean mIsUserProfile;
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800152
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700153 private final Status mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700154 private final Exception mException;
155
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700156 /**
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700157 * Constructor for special results, namely "no contact found" and "error".
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700158 */
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700159 private Result(Uri requestedUri, Status status, Exception exception) {
160 if (status == Status.ERROR && exception == null) {
161 throw new IllegalArgumentException("ERROR result must have exception");
162 }
163 mStatus = status;
164 mException = exception;
165 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700166 mLookupUri = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700167 mUri = null;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700168 mDirectoryId = -1;
169 mLookupKey = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700170 mId = -1;
171 mEntities = null;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700172 mStreamItems = null;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700173 mStatuses = null;
174 mNameRawContactId = -1;
175 mDisplayNameSource = DisplayNameSources.UNDEFINED;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700176 mPhotoId = -1;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700177 mPhotoUri = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700178 mDisplayName = null;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700179 mAltDisplayName = null;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700180 mPhoneticName = null;
181 mStarred = false;
182 mPresence = null;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700183 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700184 mSendToVoicemail = false;
185 mCustomRingtone = null;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700186 mIsUserProfile = false;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700187 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700188
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700189 private static Result forError(Uri requestedUri, Exception exception) {
190 return new Result(requestedUri, Status.ERROR, exception);
191 }
192
193 private static Result forNotFound(Uri requestedUri) {
194 return new Result(requestedUri, Status.NOT_FOUND, null);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700195 }
196
197 /**
198 * Constructor to call when contact was found
199 */
Daniel Lehmann685157e2011-08-29 21:07:01 -0700200 private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
201 long id, long nameRawContactId, int displayNameSource, long photoId,
202 String photoUri, String displayName, String altDisplayName, String phoneticName,
203 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700204 boolean isUserProfile) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700205 mStatus = Status.LOADED;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700206 mException = null;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700207 mRequestedUri = requestedUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700208 mLookupUri = lookupUri;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700209 mUri = uri;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700210 mDirectoryId = directoryId;
211 mLookupKey = lookupKey;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700212 mId = id;
213 mEntities = new ArrayList<Entity>();
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700214 mStreamItems = null;
Daniel Lehmann18958a22012-02-28 17:45:25 -0800215 mStatuses = new LongSparseArray<DataStatus>();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700216 mNameRawContactId = nameRawContactId;
217 mDisplayNameSource = displayNameSource;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700218 mPhotoId = photoId;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700219 mPhotoUri = photoUri;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700220 mDisplayName = displayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700221 mAltDisplayName = altDisplayName;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700222 mPhoneticName = phoneticName;
223 mStarred = starred;
224 mPresence = presence;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700225 mInvitableAccountTypes = null;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700226 mSendToVoicemail = sendToVoicemail;
227 mCustomRingtone = customRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700228 mIsUserProfile = isUserProfile;
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700229 }
230
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800231 private Result(Result from) {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700232 mStatus = from.mStatus;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700233 mException = from.mException;
Daniel Lehmann685157e2011-08-29 21:07:01 -0700234 mRequestedUri = from.mRequestedUri;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800235 mLookupUri = from.mLookupUri;
236 mUri = from.mUri;
237 mDirectoryId = from.mDirectoryId;
238 mLookupKey = from.mLookupKey;
239 mId = from.mId;
240 mNameRawContactId = from.mNameRawContactId;
241 mDisplayNameSource = from.mDisplayNameSource;
242 mPhotoId = from.mPhotoId;
243 mPhotoUri = from.mPhotoUri;
244 mDisplayName = from.mDisplayName;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700245 mAltDisplayName = from.mAltDisplayName;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800246 mPhoneticName = from.mPhoneticName;
247 mStarred = from.mStarred;
248 mPresence = from.mPresence;
249 mEntities = from.mEntities;
Dave Santoro39156002011-07-19 01:18:14 -0700250 mStreamItems = from.mStreamItems;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800251 mStatuses = from.mStatuses;
Makoto Onuki69b4a882011-07-22 10:05:10 -0700252 mInvitableAccountTypes = from.mInvitableAccountTypes;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800253
254 mDirectoryDisplayName = from.mDirectoryDisplayName;
255 mDirectoryType = from.mDirectoryType;
256 mDirectoryAccountType = from.mDirectoryAccountType;
257 mDirectoryAccountName = from.mDirectoryAccountName;
258 mDirectoryExportSupport = from.mDirectoryExportSupport;
259
260 mGroups = from.mGroups;
261
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800262 mPhotoBinaryData = from.mPhotoBinaryData;
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700263 mSendToVoicemail = from.mSendToVoicemail;
264 mCustomRingtone = from.mCustomRingtone;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700265 mIsUserProfile = from.mIsUserProfile;
Dmitri Plotnikov7cee7742011-01-13 17:11:06 -0800266 }
267
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700268 /**
269 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
270 */
Daniel Lehmann1ad4d1b2010-10-18 19:20:41 -0700271 private void setDirectoryMetaData(String displayName, String directoryType,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700272 String accountType, String accountName, int exportSupport) {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700273 mDirectoryDisplayName = displayName;
274 mDirectoryType = directoryType;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700275 mDirectoryAccountType = accountType;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700276 mDirectoryAccountName = accountName;
277 mDirectoryExportSupport = exportSupport;
278 }
279
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800280 private void setPhotoBinaryData(byte[] photoBinaryData) {
281 mPhotoBinaryData = photoBinaryData;
282 }
283
Daniel Lehmann685157e2011-08-29 21:07:01 -0700284 /**
285 * Returns the URI for the contact that contains both the lookup key and the ID. This is
286 * the best URI to reference a contact.
287 * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
288 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700289 public Uri getLookupUri() {
290 return mLookupUri;
291 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800292
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700293 public String getLookupKey() {
294 return mLookupKey;
295 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800296
Daniel Lehmann685157e2011-08-29 21:07:01 -0700297 /**
298 * Returns the contact Uri that was passed to the provider to make the query. This is
299 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
300 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
301 * always reference the full aggregate contact.
302 */
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700303 public Uri getUri() {
304 return mUri;
305 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800306
Daniel Lehmann685157e2011-08-29 21:07:01 -0700307 /**
308 * Returns the URI for which this {@link ContactLoader) was initially requested.
309 */
310 public Uri getRequestedUri() {
311 return mRequestedUri;
312 }
313
Dave Santoro6fa73842011-09-28 14:37:06 -0700314 /**
315 * Returns the contact ID.
316 */
Makoto Onuki98306102011-11-28 15:16:58 -0800317 @VisibleForTesting
318 /* package */ long getId() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700319 return mId;
320 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800321
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700322 /**
323 * @return true when an exception happened during loading, in which case
324 * {@link #getException} returns the actual exception object.
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700325 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
326 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
327 * and vice versa.
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700328 */
329 public boolean isError() {
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700330 return mStatus == Status.ERROR;
Makoto Onuki9e7b5da2011-08-22 15:51:28 -0700331 }
332
333 public Exception getException() {
334 return mException;
335 }
336
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700337 /**
338 * @return true when the specified contact is not found.
339 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
340 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
341 * and vice versa.
342 */
343 public boolean isNotFound() {
344 return mStatus == Status.NOT_FOUND;
345 }
346
347 /**
348 * @return true if the specified contact is successfully loaded.
349 * i.e. neither {@link #isError()} nor {@link #isNotFound()}.
350 */
351 public boolean isLoaded() {
352 return mStatus == Status.LOADED;
353 }
354
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700355 public long getNameRawContactId() {
356 return mNameRawContactId;
357 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800358
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700359 public int getDisplayNameSource() {
360 return mDisplayNameSource;
361 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800362
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700363 public long getPhotoId() {
364 return mPhotoId;
365 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800366
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700367 public String getPhotoUri() {
368 return mPhotoUri;
369 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800370
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700371 public String getDisplayName() {
372 return mDisplayName;
373 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800374
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700375 public String getAltDisplayName() {
376 return mAltDisplayName;
377 }
378
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700379 public String getPhoneticName() {
380 return mPhoneticName;
381 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800382
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700383 public boolean getStarred() {
384 return mStarred;
385 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800386
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700387 public Integer getPresence() {
388 return mPresence;
389 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800390
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700391 public ArrayList<AccountType> getInvitableAccountTypes() {
Makoto Onuki69b4a882011-07-22 10:05:10 -0700392 return mInvitableAccountTypes;
393 }
394
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700395 public ArrayList<Entity> getEntities() {
396 return mEntities;
397 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800398
Dave Santoro39156002011-07-19 01:18:14 -0700399 public ArrayList<StreamItemEntry> getStreamItems() {
400 return mStreamItems;
401 }
402
Daniel Lehmann18958a22012-02-28 17:45:25 -0800403 public LongSparseArray<DataStatus> getStatuses() {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700404 return mStatuses;
405 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700406
407 public long getDirectoryId() {
408 return mDirectoryId;
409 }
410
411 public boolean isDirectoryEntry() {
Dmitri Plotnikov5f72c1f2010-09-01 21:21:04 -0700412 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
413 && mDirectoryId != Directory.LOCAL_INVISIBLE;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700414 }
415
Josh Gargus187c8162012-03-13 17:06:53 -0700416 /**
417 * @return true if this is a contact (not group, etc.) with at least one
Josh Gargus84edfd92012-03-15 18:25:58 -0700418 * writable raw-contact, and false otherwise.
Josh Gargus187c8162012-03-13 17:06:53 -0700419 */
Josh Gargus84edfd92012-03-15 18:25:58 -0700420 public boolean isWritableContact(final Context context) {
Josh Gargus187c8162012-03-13 17:06:53 -0700421 if (isDirectoryEntry()) return false;
422 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
423 for (Entity rawContact : getEntities()) {
424 final ContentValues rawValues = rawContact.getEntityValues();
425 final String accountType = rawValues.getAsString(RawContacts.ACCOUNT_TYPE);
426 final String dataSet = rawValues.getAsString(RawContacts.DATA_SET);
427 final AccountType type = accountTypes.getAccountType(accountType, dataSet);
428 if (type != null && type.areContactsWritable()) return true;
429 }
430 return false;
431 }
432
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700433 public int getDirectoryExportSupport() {
434 return mDirectoryExportSupport;
435 }
436
437 public String getDirectoryDisplayName() {
438 return mDirectoryDisplayName;
439 }
440
441 public String getDirectoryType() {
442 return mDirectoryType;
443 }
444
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700445 public String getDirectoryAccountType() {
446 return mDirectoryAccountType;
447 }
448
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700449 public String getDirectoryAccountName() {
450 return mDirectoryAccountName;
451 }
452
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -0800453 public byte[] getPhotoBinaryData() {
454 return mPhotoBinaryData;
455 }
456
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700457 public ArrayList<ContentValues> getContentValues() {
458 if (mEntities.size() != 1) {
459 throw new IllegalStateException(
460 "Cannot extract content values from an aggregated contact");
461 }
462
463 Entity entity = mEntities.get(0);
464 ArrayList<ContentValues> result = new ArrayList<ContentValues>();
465 ArrayList<NamedContentValues> subValues = entity.getSubValues();
466 if (subValues != null) {
467 int size = subValues.size();
468 for (int i = 0; i < size; i++) {
469 NamedContentValues pair = subValues.get(i);
470 if (Data.CONTENT_URI.equals(pair.uri)) {
471 result.add(pair.values);
472 }
473 }
474 }
Dmitri Plotnikov40ec3a82010-11-10 11:25:33 -0800475
476 // If the photo was loaded using the URI, create an entry for the photo
477 // binary data.
478 if (mPhotoId == 0 && mPhotoBinaryData != null) {
479 ContentValues photo = new ContentValues();
480 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
481 photo.put(Photo.PHOTO, mPhotoBinaryData);
482 result.add(photo);
483 }
484
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700485 return result;
486 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700487
Dmitri Plotnikove843f912010-09-16 15:21:48 -0700488 public List<GroupMetaData> getGroupMetaData() {
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700489 return mGroups;
490 }
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700491
492 public boolean isSendToVoicemail() {
493 return mSendToVoicemail;
494 }
495
496 public String getCustomRingtone() {
497 return mCustomRingtone;
498 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700499
500 public boolean isUserProfile() {
501 return mIsUserProfile;
502 }
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700503 }
504
Dave Santoro39156002011-07-19 01:18:14 -0700505 /**
506 * Projection used for the query that loads all data for the entire contact (except for
507 * social stream items).
508 */
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700509 private static class ContactQuery {
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700510 final static String[] COLUMNS = new String[] {
511 Contacts.NAME_RAW_CONTACT_ID,
512 Contacts.DISPLAY_NAME_SOURCE,
513 Contacts.LOOKUP_KEY,
514 Contacts.DISPLAY_NAME,
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700515 Contacts.DISPLAY_NAME_ALTERNATIVE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700516 Contacts.PHONETIC_NAME,
517 Contacts.PHOTO_ID,
518 Contacts.STARRED,
519 Contacts.CONTACT_PRESENCE,
520 Contacts.CONTACT_STATUS,
521 Contacts.CONTACT_STATUS_TIMESTAMP,
522 Contacts.CONTACT_STATUS_RES_PACKAGE,
523 Contacts.CONTACT_STATUS_LABEL,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700524 Contacts.Entity.CONTACT_ID,
525 Contacts.Entity.RAW_CONTACT_ID,
526
527 RawContacts.ACCOUNT_NAME,
528 RawContacts.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700529 RawContacts.DATA_SET,
530 RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700531 RawContacts.DIRTY,
532 RawContacts.VERSION,
533 RawContacts.SOURCE_ID,
534 RawContacts.SYNC1,
535 RawContacts.SYNC2,
536 RawContacts.SYNC3,
537 RawContacts.SYNC4,
538 RawContacts.DELETED,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700539 RawContacts.NAME_VERIFIED,
540
541 Contacts.Entity.DATA_ID,
542 Data.DATA1,
543 Data.DATA2,
544 Data.DATA3,
545 Data.DATA4,
546 Data.DATA5,
547 Data.DATA6,
548 Data.DATA7,
549 Data.DATA8,
550 Data.DATA9,
551 Data.DATA10,
552 Data.DATA11,
553 Data.DATA12,
554 Data.DATA13,
555 Data.DATA14,
556 Data.DATA15,
557 Data.SYNC1,
558 Data.SYNC2,
559 Data.SYNC3,
560 Data.SYNC4,
561 Data.DATA_VERSION,
562 Data.IS_PRIMARY,
563 Data.IS_SUPER_PRIMARY,
564 Data.MIMETYPE,
565 Data.RES_PACKAGE,
566
567 GroupMembership.GROUP_SOURCE_ID,
568
569 Data.PRESENCE,
Daniel Lehmann8fd7bb62010-08-13 20:50:31 -0700570 Data.CHAT_CAPABILITY,
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700571 Data.STATUS,
572 Data.STATUS_RES_PACKAGE,
573 Data.STATUS_ICON,
574 Data.STATUS_LABEL,
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700575 Data.STATUS_TIMESTAMP,
576
577 Contacts.PHOTO_URI,
Isaac Katzenelson683b57e2011-07-20 17:06:11 -0700578 Contacts.SEND_TO_VOICEMAIL,
579 Contacts.CUSTOM_RINGTONE,
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700580 Contacts.IS_USER_PROFILE,
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700581 };
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700582
583 public final static int NAME_RAW_CONTACT_ID = 0;
584 public final static int DISPLAY_NAME_SOURCE = 1;
585 public final static int LOOKUP_KEY = 2;
586 public final static int DISPLAY_NAME = 3;
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700587 public final static int ALT_DISPLAY_NAME = 4;
588 public final static int PHONETIC_NAME = 5;
589 public final static int PHOTO_ID = 6;
590 public final static int STARRED = 7;
591 public final static int CONTACT_PRESENCE = 8;
592 public final static int CONTACT_STATUS = 9;
593 public final static int CONTACT_STATUS_TIMESTAMP = 10;
594 public final static int CONTACT_STATUS_RES_PACKAGE = 11;
595 public final static int CONTACT_STATUS_LABEL = 12;
596 public final static int CONTACT_ID = 13;
597 public final static int RAW_CONTACT_ID = 14;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700598
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700599 public final static int ACCOUNT_NAME = 15;
600 public final static int ACCOUNT_TYPE = 16;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700601 public final static int DATA_SET = 17;
602 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18;
603 public final static int DIRTY = 19;
604 public final static int VERSION = 20;
605 public final static int SOURCE_ID = 21;
606 public final static int SYNC1 = 22;
607 public final static int SYNC2 = 23;
608 public final static int SYNC3 = 24;
609 public final static int SYNC4 = 25;
610 public final static int DELETED = 26;
611 public final static int NAME_VERIFIED = 27;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700612
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700613 public final static int DATA_ID = 28;
614 public final static int DATA1 = 29;
615 public final static int DATA2 = 30;
616 public final static int DATA3 = 31;
617 public final static int DATA4 = 32;
618 public final static int DATA5 = 33;
619 public final static int DATA6 = 34;
620 public final static int DATA7 = 35;
621 public final static int DATA8 = 36;
622 public final static int DATA9 = 37;
623 public final static int DATA10 = 38;
624 public final static int DATA11 = 39;
625 public final static int DATA12 = 40;
626 public final static int DATA13 = 41;
627 public final static int DATA14 = 42;
628 public final static int DATA15 = 43;
629 public final static int DATA_SYNC1 = 44;
630 public final static int DATA_SYNC2 = 45;
631 public final static int DATA_SYNC3 = 46;
632 public final static int DATA_SYNC4 = 47;
633 public final static int DATA_VERSION = 48;
634 public final static int IS_PRIMARY = 49;
635 public final static int IS_SUPERPRIMARY = 50;
636 public final static int MIMETYPE = 51;
637 public final static int RES_PACKAGE = 52;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700638
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700639 public final static int GROUP_SOURCE_ID = 53;
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700640
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700641 public final static int PRESENCE = 54;
642 public final static int CHAT_CAPABILITY = 55;
643 public final static int STATUS = 56;
644 public final static int STATUS_RES_PACKAGE = 57;
645 public final static int STATUS_ICON = 58;
646 public final static int STATUS_LABEL = 59;
647 public final static int STATUS_TIMESTAMP = 60;
Dmitri Plotnikovf9eb73f2010-10-21 11:48:56 -0700648
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700649 public final static int PHOTO_URI = 61;
650 public final static int SEND_TO_VOICEMAIL = 62;
651 public final static int CUSTOM_RINGTONE = 63;
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700652 public final static int IS_USER_PROFILE = 64;
Daniel Lehmannd3e0cdb2010-04-19 13:45:53 -0700653 }
Daniel Lehmann1316b132010-04-13 15:08:53 -0700654
Dave Santoro39156002011-07-19 01:18:14 -0700655 /**
656 * Projection used for the query that loads all data for the entire contact.
657 */
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700658 private static class DirectoryQuery {
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700659 final static String[] COLUMNS = new String[] {
660 Directory.DISPLAY_NAME,
661 Directory.PACKAGE_NAME,
662 Directory.TYPE_RESOURCE_ID,
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700663 Directory.ACCOUNT_TYPE,
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700664 Directory.ACCOUNT_NAME,
665 Directory.EXPORT_SUPPORT,
666 };
667
668 public final static int DISPLAY_NAME = 0;
669 public final static int PACKAGE_NAME = 1;
670 public final static int TYPE_RESOURCE_ID = 2;
Dmitri Plotnikovcaf0bc72010-09-03 15:16:21 -0700671 public final static int ACCOUNT_TYPE = 3;
672 public final static int ACCOUNT_NAME = 4;
673 public final static int EXPORT_SUPPORT = 5;
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700674 }
675
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700676 private static class GroupQuery {
677 final static String[] COLUMNS = new String[] {
678 Groups.ACCOUNT_NAME,
679 Groups.ACCOUNT_TYPE,
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700680 Groups.DATA_SET,
681 Groups.ACCOUNT_TYPE_AND_DATA_SET,
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700682 Groups._ID,
683 Groups.TITLE,
684 Groups.AUTO_ADD,
685 Groups.FAVORITES,
686 };
687
688 public final static int ACCOUNT_NAME = 0;
689 public final static int ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700690 public final static int DATA_SET = 2;
691 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3;
692 public final static int ID = 4;
693 public final static int TITLE = 5;
694 public final static int AUTO_ADD = 6;
695 public final static int FAVORITES = 7;
Dmitri Plotnikov2deaee12010-09-15 15:42:08 -0700696 }
697
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800698 @Override
699 public Result loadInBackground() {
700 try {
701 final ContentResolver resolver = getContext().getContentResolver();
702 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
703 resolver, mLookupUri);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700704 final Result cachedResult = sCachedResult;
705 sCachedResult = null;
706 // Is this the same Uri as what we had before already? In that case, reuse that result
707 final Result result;
708 final boolean resultIsCached;
709 if (cachedResult != null &&
710 UriUtils.areEqual(cachedResult.getLookupUri(), mLookupUri)) {
711 // We are using a cached result from earlier. Below, we should make sure
712 // we are not doing any more network or disc accesses
713 result = cachedResult;
714 resultIsCached = true;
715 } else {
716 result = loadContactEntity(resolver, uriCurrentFormat);
717 resultIsCached = false;
718 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800719 if (!result.isNotFound()) {
720 if (result.isDirectoryEntry()) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700721 if (!resultIsCached) {
722 loadDirectoryMetaData(result);
723 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800724 } else if (mLoadGroupMetaData) {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700725 if (result.getGroupMetaData() == null) {
726 loadGroupMetaData(result);
727 }
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -0700728 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700729 if (mLoadStreamItems && result.getStreamItems() == null) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800730 loadStreamItems(result);
731 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700732 if (!resultIsCached) loadPhotoBinaryData(result);
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800733
734 // Note ME profile should never have "Add connection"
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700735 if (mLoadInvitableAccountTypes && result.getInvitableAccountTypes() == null &&
736 !result.isUserProfile()) {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800737 loadInvitableAccountTypes(result);
738 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700739 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800740 return result;
741 } catch (Exception e) {
742 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
743 return Result.forError(mRequestedUri, e);
744 }
745 }
746
747 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
748 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
749 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
750 Contacts.Entity.RAW_CONTACT_ID);
751 if (cursor == null) {
752 Log.e(TAG, "No cursor returned in loadContactEntity");
753 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700754 }
755
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800756 try {
757 if (!cursor.moveToFirst()) {
758 cursor.close();
Makoto Onuki2621c5b2011-10-03 12:56:16 -0700759 return Result.forNotFound(mRequestedUri);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700760 }
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700761
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700762 // Create the loaded result starting with the Contact data.
763 Result result = loadContactHeaderData(cursor, contactUri);
764
765 // Fill in the raw contacts, which is wrapped in an Entity and any
766 // status data. Initially, result has empty entities and statuses.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800767 long currentRawContactId = -1;
768 Entity entity = null;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800769 ArrayList<Entity> entities = result.getEntities();
770 LongSparseArray<DataStatus> statuses = result.getStatuses();
771 for (; !cursor.isAfterLast(); cursor.moveToNext()) {
772 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
773 if (rawContactId != currentRawContactId) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700774 // First time to see this raw contact id, so create a new entity, and
775 // add it to the result's entities.
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800776 currentRawContactId = rawContactId;
777 entity = new android.content.Entity(loadRawContact(cursor));
778 entities.add(entity);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700779 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800780 if (!cursor.isNull(ContactQuery.DATA_ID)) {
781 ContentValues data = loadData(cursor);
782 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700783
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800784 if (!cursor.isNull(ContactQuery.PRESENCE)
785 || !cursor.isNull(ContactQuery.STATUS)) {
786 final DataStatus status = new DataStatus(cursor);
787 final long dataId = cursor.getLong(ContactQuery.DATA_ID);
788 statuses.put(dataId, status);
Dmitri Plotnikov4d444242010-07-30 11:39:39 -0700789 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700790 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700791 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800792
793 return result;
794 } finally {
795 cursor.close();
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700796 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800797 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -0700798
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800799 /**
800 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
801 * not found, returns null
802 */
803 private void loadPhotoBinaryData(Result contactData) {
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700804
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800805 // If we have a photo URI, try loading that first.
806 String photoUri = contactData.getPhotoUri();
807 if (photoUri != null) {
808 try {
809 AssetFileDescriptor fd = getContext().getContentResolver()
810 .openAssetFileDescriptor(Uri.parse(photoUri), "r");
811 byte[] buffer = new byte[16 * 1024];
812 FileInputStream fis = fd.createInputStream();
813 ByteArrayOutputStream baos = new ByteArrayOutputStream();
Dave Santoro0a2a5db2011-06-29 00:37:06 -0700814 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800815 int size;
816 while ((size = fis.read(buffer)) != -1) {
817 baos.write(buffer, 0, size);
818 }
819 contactData.setPhotoBinaryData(baos.toByteArray());
820 } finally {
821 fis.close();
822 fd.close();
823 }
824 return;
825 } catch (IOException ioe) {
826 // Just fall back to the case below.
827 }
828 }
829
830 // If we couldn't load from a file, fall back to the data blob.
831 final long photoId = contactData.getPhotoId();
832 if (photoId <= 0) {
833 // No photo ID
834 return;
835 }
836
837 for (Entity entity : contactData.getEntities()) {
838 for (NamedContentValues subValue : entity.getSubValues()) {
839 final ContentValues entryValues = subValue.values;
840 final long dataId = entryValues.getAsLong(Data._ID);
841 if (dataId == photoId) {
842 final String mimeType = entryValues.getAsString(Data.MIMETYPE);
843 // Correct Data Id but incorrect MimeType? Don't load
844 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
845 return;
846 }
847 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
848 break;
849 }
850 }
851 }
852 }
853
854 /**
855 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
856 */
857 private void loadInvitableAccountTypes(Result contactData) {
858 Map<AccountTypeWithDataSet, AccountType> invitables =
859 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
860 if (invitables.isEmpty()) {
861 return;
862 }
863
864 Map<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(invitables);
865
866 // Remove the ones that already have a raw contact in the current contact
867 for (Entity entity : contactData.getEntities()) {
868 final ContentValues values = entity.getEntityValues();
869 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
870 values.getAsString(RawContacts.ACCOUNT_TYPE),
871 values.getAsString(RawContacts.DATA_SET));
872 result.remove(type);
873 }
874
875 // Set to mInvitableAccountTypes
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700876 contactData.mInvitableAccountTypes = new ArrayList<AccountType>(result.values());
Daniel Lehmann72ff4df2012-02-28 20:03:01 -0800877 }
878
879 /**
880 * Extracts Contact level columns from the cursor.
881 */
882 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
883 final String directoryParameter =
884 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
885 final long directoryId = directoryParameter == null
886 ? Directory.DEFAULT
887 : Long.parseLong(directoryParameter);
888 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
889 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
890 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
891 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
892 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
893 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
894 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
895 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
896 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
897 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
898 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
899 ? null
900 : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
901 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
902 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
903 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
904
905 Uri lookupUri;
906 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
907 lookupUri = ContentUris.withAppendedId(
908 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
909 } else {
910 lookupUri = contactUri;
911 }
912
913 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
914 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
915 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
916 customRingtone, isUserProfile);
917 }
918
919 /**
920 * Extracts RawContact level columns from the cursor.
921 */
922 private ContentValues loadRawContact(Cursor cursor) {
923 ContentValues cv = new ContentValues();
924
925 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
926
927 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
928 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
929 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
930 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
931 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
932 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
933 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
934 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
935 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
936 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
937 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
938 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
939 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
940 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
941 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
942
943 return cv;
944 }
945
946 /**
947 * Extracts Data level columns from the cursor.
948 */
949 private ContentValues loadData(Cursor cursor) {
950 ContentValues cv = new ContentValues();
951
952 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
953
954 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
955 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
956 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
957 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
958 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
959 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
960 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
961 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
962 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
963 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
964 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
965 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
966 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
967 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
968 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
969 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
970 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
971 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
972 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
973 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
974 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
975 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
976 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
977 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
978 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
979 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
980
981 return cv;
982 }
983
984 private void cursorColumnToContentValues(
985 Cursor cursor, ContentValues values, int index) {
986 switch (cursor.getType(index)) {
987 case Cursor.FIELD_TYPE_NULL:
988 // don't put anything in the content values
989 break;
990 case Cursor.FIELD_TYPE_INTEGER:
991 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
992 break;
993 case Cursor.FIELD_TYPE_STRING:
994 values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
995 break;
996 case Cursor.FIELD_TYPE_BLOB:
997 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
998 break;
999 default:
1000 throw new IllegalStateException("Invalid or unhandled data type");
1001 }
1002 }
1003
1004 private void loadDirectoryMetaData(Result result) {
1005 long directoryId = result.getDirectoryId();
1006
1007 Cursor cursor = getContext().getContentResolver().query(
1008 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
1009 DirectoryQuery.COLUMNS, null, null, null);
1010 if (cursor == null) {
1011 return;
1012 }
1013 try {
1014 if (cursor.moveToFirst()) {
1015 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
1016 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
1017 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
1018 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
1019 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
1020 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
1021 String directoryType = null;
1022 if (!TextUtils.isEmpty(packageName)) {
1023 PackageManager pm = getContext().getPackageManager();
Dave Santoro0a2a5db2011-06-29 00:37:06 -07001024 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001025 Resources resources = pm.getResourcesForApplication(packageName);
1026 directoryType = resources.getString(typeResourceId);
1027 } catch (NameNotFoundException e) {
1028 Log.w(TAG, "Contact directory resource not found: "
1029 + packageName + "." + typeResourceId);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001030 }
1031 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001032
1033 result.setDirectoryMetaData(
1034 displayName, directoryType, accountType, accountName, exportSupport);
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001035 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001036 } finally {
1037 cursor.close();
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001038 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001039 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001040
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001041 /**
1042 * Loads groups meta-data for all groups associated with all constituent raw contacts'
1043 * accounts.
1044 */
1045 private void loadGroupMetaData(Result result) {
1046 StringBuilder selection = new StringBuilder();
1047 ArrayList<String> selectionArgs = new ArrayList<String>();
1048 for (Entity entity : result.mEntities) {
1049 ContentValues values = entity.getEntityValues();
1050 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
1051 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
1052 String dataSet = values.getAsString(RawContacts.DATA_SET);
1053 if (accountName != null && accountType != null) {
1054 if (selection.length() != 0) {
1055 selection.append(" OR ");
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001056 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001057 selection.append(
1058 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
1059 selectionArgs.add(accountName);
1060 selectionArgs.add(accountType);
Dmitri Plotnikov02cd4912010-09-01 20:42:17 -07001061
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001062 if (dataSet != null) {
1063 selection.append(" AND " + Groups.DATA_SET + "=?");
1064 selectionArgs.add(dataSet);
Dave Santoroa4400d52011-09-02 16:14:53 -07001065 } else {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001066 selection.append(" AND " + Groups.DATA_SET + " IS NULL");
1067 }
1068 selection.append(")");
1069 }
1070 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001071 final ArrayList<GroupMetaData> groupList = new ArrayList<GroupMetaData>();
1072 final Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001073 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
1074 null);
1075 try {
1076 while (cursor.moveToNext()) {
1077 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
1078 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
1079 final String dataSet = cursor.getString(GroupQuery.DATA_SET);
1080 final long groupId = cursor.getLong(GroupQuery.ID);
1081 final String title = cursor.getString(GroupQuery.TITLE);
1082 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
1083 ? false
1084 : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
1085 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
1086 ? false
1087 : cursor.getInt(GroupQuery.FAVORITES) != 0;
1088
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001089 groupList.add(new GroupMetaData(
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001090 accountName, accountType, dataSet, groupId, title, defaultGroup,
1091 favorites));
1092 }
1093 } finally {
1094 cursor.close();
1095 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001096 result.mGroups = groupList;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001097 }
1098
1099 /**
1100 * Loads all stream items and stream item photos belonging to this contact.
1101 */
1102 private void loadStreamItems(Result result) {
1103 Cursor cursor = getContext().getContentResolver().query(
1104 Contacts.CONTENT_LOOKUP_URI.buildUpon()
1105 .appendPath(result.getLookupKey())
1106 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
1107 null, null, null, null);
1108 LongSparseArray<StreamItemEntry> streamItemsById =
1109 new LongSparseArray<StreamItemEntry>();
1110 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
1111 try {
1112 while (cursor.moveToNext()) {
1113 StreamItemEntry streamItem = new StreamItemEntry(cursor);
1114 streamItemsById.put(streamItem.getId(), streamItem);
1115 streamItems.add(streamItem);
1116 }
1117 } finally {
1118 cursor.close();
1119 }
1120
1121 // Pre-decode all HTMLs
1122 final long start = System.currentTimeMillis();
1123 for (StreamItemEntry streamItem : streamItems) {
1124 streamItem.decodeHtml(getContext());
1125 }
1126 final long end = System.currentTimeMillis();
1127 if (DEBUG) {
1128 Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
1129 + (end - start) + " ms");
1130 }
1131
1132 // Now retrieve any photo records associated with the stream items.
1133 if (!streamItems.isEmpty()) {
1134 if (result.isUserProfile()) {
1135 // If the stream items we're loading are for the profile, we can't bulk-load the
1136 // stream items with a custom selection.
1137 for (StreamItemEntry entry : streamItems) {
1138 Cursor siCursor = getContext().getContentResolver().query(
1139 Uri.withAppendedPath(
1140 ContentUris.withAppendedId(
1141 StreamItems.CONTENT_URI, entry.getId()),
1142 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
1143 null, null, null, null);
Dave Santoroa4400d52011-09-02 16:14:53 -07001144 try {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001145 while (siCursor.moveToNext()) {
1146 entry.addPhoto(new StreamItemPhotoEntry(siCursor));
Dave Santoroa4400d52011-09-02 16:14:53 -07001147 }
1148 } finally {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001149 siCursor.close();
Dave Santoroa4400d52011-09-02 16:14:53 -07001150 }
Dave Santoro39156002011-07-19 01:18:14 -07001151 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001152 } else {
1153 String[] streamItemIdArr = new String[streamItems.size()];
1154 StringBuilder streamItemPhotoSelection = new StringBuilder();
1155 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
1156 for (int i = 0; i < streamItems.size(); i++) {
1157 if (i > 0) {
1158 streamItemPhotoSelection.append(",");
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001159 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001160 streamItemPhotoSelection.append("?");
1161 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
Daniel Lehmann18f104f2010-05-07 15:41:11 -07001162 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001163 streamItemPhotoSelection.append(")");
1164 Cursor sipCursor = getContext().getContentResolver().query(
1165 StreamItems.CONTENT_PHOTO_URI,
1166 null, streamItemPhotoSelection.toString(), streamItemIdArr,
1167 StreamItemPhotos.STREAM_ITEM_ID);
1168 try {
1169 while (sipCursor.moveToNext()) {
1170 long streamItemId = sipCursor.getLong(
1171 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
1172 StreamItemEntry streamItem = streamItemsById.get(streamItemId);
1173 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
1174 }
1175 } finally {
1176 sipCursor.close();
1177 }
1178 }
1179 }
Dmitri Plotnikov7f4f8d12010-11-10 10:22:19 -08001180
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001181 // Set the sorted stream items on the result.
1182 Collections.sort(streamItems);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001183 result.mStreamItems = streamItems;
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001184 }
1185
1186 @Override
1187 public void deliverResult(Result result) {
1188 unregisterObserver();
1189
1190 // The creator isn't interested in any further updates
1191 if (isReset() || result == null) {
1192 return;
1193 }
1194
1195 mContact = result;
1196
1197 if (result.isLoaded()) {
1198 mLookupUri = result.getLookupUri();
1199
1200 if (!result.isDirectoryEntry()) {
1201 Log.i(TAG, "Registering content observer for " + mLookupUri);
1202 if (mObserver == null) {
1203 mObserver = new ForceLoadContentObserver();
1204 }
1205 getContext().getContentResolver().registerContentObserver(
1206 mLookupUri, true, mObserver);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001207 }
Dmitri Plotnikovc3f2a522010-11-17 18:36:17 -08001208
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001209 // inform the source of the data that this contact is being looked at
1210 postViewNotificationToSyncAdapter();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001211 }
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001212
1213 super.deliverResult(mContact);
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001214 }
1215
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001216 /**
1217 * Posts a message to the contributing sync adapters that have opted-in, notifying them
1218 * that the contact has just been loaded
1219 */
1220 private void postViewNotificationToSyncAdapter() {
1221 Context context = getContext();
1222 for (Entity entity : mContact.getEntities()) {
1223 final ContentValues entityValues = entity.getEntityValues();
Makoto Onukiaba2b832011-08-12 15:44:53 -07001224 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
1225 if (mNotifiedRawContactIds.contains(rawContactId)) {
1226 continue; // Already notified for this raw contact.
1227 }
1228 mNotifiedRawContactIds.add(rawContactId);
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001229 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
1230 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
Flavio Lerda59a887e2011-08-14 18:13:17 +01001231 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001232 type, dataSet);
1233 final String serviceName = accountType.getViewContactNotifyServiceClassName();
1234 final String resPackageName = accountType.resPackageName;
1235 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
Daniel Lehmann3ef27fb2011-08-09 14:31:29 -07001236 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
1237 final Intent intent = new Intent();
1238 intent.setClassName(resPackageName, serviceName);
1239 intent.setAction(Intent.ACTION_VIEW);
1240 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE);
1241 try {
1242 context.startService(intent);
1243 } catch (Exception e) {
1244 Log.e(TAG, "Error sending message to source-app", e);
1245 }
1246 }
1247 }
1248 }
1249
Daniel Lehmann3a120772010-06-21 16:21:35 -07001250 private void unregisterObserver() {
1251 if (mObserver != null) {
1252 getContext().getContentResolver().unregisterContentObserver(mObserver);
1253 mObserver = null;
1254 }
1255 }
1256
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001257 /**
1258 * Sets whether to load stream items. Will trigger a reload if the value has changed.
1259 * At the moment, this is only used for debugging purposes
1260 */
1261 public void setLoadStreamItems(boolean value) {
1262 if (mLoadStreamItems != value) {
1263 mLoadStreamItems = value;
1264 onContentChanged();
1265 }
1266 }
1267
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001268 /**
1269 * Fully upgrades this ContactLoader to one with all lists fully loaded. When done, the
1270 * new result will be delivered
1271 */
1272 public void upgradeToFullContact() {
1273 mLoadGroupMetaData = true;
1274 mLoadInvitableAccountTypes = true;
1275 mLoadStreamItems = true;
1276
1277 // Cache the current result, so that we only load the "missing" parts of the contact.
1278 cacheResult();
1279
1280 // Our load parameters have changed, so let's pretend the data has changed. Its the same
1281 // thing, essentially.
1282 onContentChanged();
1283 }
1284
Daniel Lehmann2a45e352012-02-13 15:03:58 -08001285 public boolean getLoadStreamItems() {
1286 return mLoadStreamItems;
1287 }
1288
Dmitri Plotnikov5a30d9a2010-11-23 14:59:50 -08001289 public Uri getLookupUri() {
1290 return mLookupUri;
1291 }
1292
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001293 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001294 protected void onStartLoading() {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001295 if (mContact != null) {
Daniel Lehmanncbcc4492010-04-12 18:03:54 -07001296 deliverResult(mContact);
Dmitri Plotnikov97e90c62011-01-03 11:58:13 -08001297 }
1298
1299 if (takeContentChanged() || mContact == null) {
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001300 forceLoad();
1301 }
1302 }
1303
1304 @Override
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001305 protected void onStopLoading() {
1306 cancelLoad();
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001307 }
1308
1309 @Override
Dianne Hackbornc04fc272010-12-20 23:13:10 -08001310 protected void onReset() {
Daniel Lehmann72ff4df2012-02-28 20:03:01 -08001311 super.onReset();
1312 cancelLoad();
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001313 unregisterObserver();
1314 mContact = null;
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -08001315 }
Daniel Lehmann9815d7f2012-04-16 18:28:03 -07001316
1317 /**
1318 * Caches the result, which is useful when we switch from activity to activity, using the same
1319 * contact. If the next load is for a different contact, the cached result will be dropped
1320 */
1321 public void cacheResult() {
1322 sCachedResult = mContact;
1323 }
Daniel Lehmann4cd94412010-04-08 16:44:36 -07001324}