1b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki/* 2b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * Copyright (C) 2010 The Android Open Source Project 3b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * 4b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * Licensed under the Apache License, Version 2.0 (the "License"); 5b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * you may not use this file except in compliance with the License. 6b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * You may obtain a copy of the License at 7b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * 8b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * http://www.apache.org/licenses/LICENSE-2.0 9b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * 10b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * Unless required by applicable law or agreed to in writing, software 11b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * distributed under the License is distributed on an "AS IS" BASIS, 12b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * See the License for the specific language governing permissions and 14b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * limitations under the License. 15b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki */ 16b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 17b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukipackage com.android.email.activity; 18b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 19b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.content.AsyncTaskLoader; 20b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.content.ContentUris; 21b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.content.Context; 22b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.database.Cursor; 23b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.graphics.Bitmap; 24b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.graphics.BitmapFactory; 25b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.net.Uri; 26b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.CommonDataKinds.Email; 27b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.CommonDataKinds.Photo; 28b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.Contacts; 29b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.Data; 30b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.StatusUpdates; 31b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 326f96c779cf572b1680c2cbd68ae9858ca78f101eBen Komaloimport com.android.emailcommon.Logging; 336f96c779cf572b1680c2cbd68ae9858ca78f101eBen Komaloimport com.android.emailcommon.utility.Utility; 34560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils; 35526fadbaa0f677a95866f516cbec875fdf1a1376Ben Komaloimport com.google.common.annotations.VisibleForTesting; 366f96c779cf572b1680c2cbd68ae9858ca78f101eBen Komalo 37b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki/** 38958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Loader to load presence statuses and the contact photo. 39b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki */ 40b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukipublic class ContactStatusLoader extends AsyncTaskLoader<ContactStatusLoader.Result> { 41526fadbaa0f677a95866f516cbec875fdf1a1376Ben Komalo @VisibleForTesting 42526fadbaa0f677a95866f516cbec875fdf1a1376Ben Komalo static final int PRESENCE_UNKNOWN_RESOURCE_ID = android.R.drawable.presence_offline; 43b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 44b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** email address -> photo id, presence */ 45b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /* package */ static final String[] PROJECTION_PHOTO_ID_PRESENCE = new String[] { 46b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Contacts.PHOTO_ID, 47b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Contacts.CONTACT_PRESENCE 48b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki }; 49b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private static final int COLUMN_PHOTO_ID = 0; 50b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private static final int COLUMN_PRESENCE = 1; 51b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 52b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** photo id -> photo data */ 53b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /* package */ static final String[] PHOTO_PROJECTION = new String[] { 54b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Photo.PHOTO 55b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki }; 56b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private static final int PHOTO_COLUMN = 0; 57b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 58b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private final Context mContext; 59b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private final String mEmailAddress; 60b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 61b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** 62b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * Class that encapsulates the result. 63b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki */ 64b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public static class Result { 65b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public static final Result UNKNOWN = new Result(null, PRESENCE_UNKNOWN_RESOURCE_ID, null); 66b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 67b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** Contact photo. Null if unknown */ 68b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public final Bitmap mPhoto; 69b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 70b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** Presence image resource ID. Always has a valid value, even if unknown. */ 71b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public final int mPresenceResId; 72b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 73b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** URI for opening quick contact. Null if unknown. */ 74b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public final Uri mLookupUri; 75b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 76b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public Result(Bitmap photo, int presenceResId, Uri lookupUri) { 77b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mPhoto = photo; 78b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mPresenceResId = presenceResId; 79b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mLookupUri = lookupUri; 80b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 816f96c779cf572b1680c2cbd68ae9858ca78f101eBen Komalo 826f96c779cf572b1680c2cbd68ae9858ca78f101eBen Komalo public boolean isUnknown() { 836f96c779cf572b1680c2cbd68ae9858ca78f101eBen Komalo return PRESENCE_UNKNOWN_RESOURCE_ID == mPresenceResId; 846f96c779cf572b1680c2cbd68ae9858ca78f101eBen Komalo } 85b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 86b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 87b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public ContactStatusLoader(Context context, String emailAddress) { 88b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki super(context); 89b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mContext = context; 90b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mEmailAddress = emailAddress; 91b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 92b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 93b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki @Override 94b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public Result loadInBackground() { 95958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy return getContactInfo(mContext, mEmailAddress); 9608346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki } 9708346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki 9808346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki /** 99958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * Synchronously loads contact data. 10008346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki * 101958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS) 10208346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki */ 103958b15e8f30fd4e9eae1b05d48cb9a817326be6dTodd Kennedy public static Result getContactInfo(Context context, String emailAddress) { 104b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // Load photo-id and presence status. 10508346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki Uri uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)); 10608346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki Cursor c = context.getContentResolver().query( 107b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki uri, 108b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki PROJECTION_PHOTO_ID_PRESENCE, null, null, null); 109b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (c == null) { 110b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki return Result.UNKNOWN; 111b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 112b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki final long photoId; 113b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki final int presenceStatus; 114b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki try { 115b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (!c.moveToFirst()) { 116b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki return Result.UNKNOWN; 117b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 118b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki photoId = c.getLong(COLUMN_PHOTO_ID); 119b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki presenceStatus = c.getInt(COLUMN_PRESENCE); 120b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } finally { 121b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki c.close(); 122b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 123b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 124b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // Convert presence status into the res id. 125b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki final int presenceStatusResId = StatusUpdates.getPresenceIconResourceId(presenceStatus); 126b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 127b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // load photo from photo-id. 128b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Bitmap photo = null; 129b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (photoId != -1) { 13008346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki final byte[] photoData = Utility.getFirstRowBlob(context, 131b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PHOTO_PROJECTION, 132b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki null, null, null, PHOTO_COLUMN, null); 133b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (photoData != null) { 134d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki try { 135d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki photo = BitmapFactory.decodeByteArray(photoData, 0, photoData.length, null); 136d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki } catch (OutOfMemoryError e) { 137560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Decoding bitmap failed with " + e.getMessage()); 138d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki } 139b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 140b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 141b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 142b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // Get lookup URI 14308346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki final Uri lookupUri = Data.getContactLookupUri(context.getContentResolver(), uri); 144b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki return new Result(photo, presenceStatusResId, lookupUri); 145b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 146b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 147b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki @Override 1488b9f2a7284337bfb23d5e2f8de9f1c70cb9532a1Dianne Hackborn protected void onStartLoading() { 149b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki cancelLoad(); 150b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki forceLoad(); 151b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 152b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 153b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki @Override 1548b9f2a7284337bfb23d5e2f8de9f1c70cb9532a1Dianne Hackborn protected void onStopLoading() { 155b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki cancelLoad(); 156b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 157b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 15828d6e09575cc5b9091c5d29990792db3465cccf0Makoto Onuki @Override 1598b9f2a7284337bfb23d5e2f8de9f1c70cb9532a1Dianne Hackborn protected void onReset() { 16080769cefb3f34dfeccb38fb5b67b3ced172b830eDianne Hackborn stopLoading(); 16180769cefb3f34dfeccb38fb5b67b3ced172b830eDianne Hackborn } 162b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki} 163