ContactStatusLoader.java revision 08346b67b8aa3e36057fa4e439275efa8c3a8098
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 com.android.email.R; 20b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport com.android.email.Utility; 21b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 22b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.content.AsyncTaskLoader; 23b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.content.ContentUris; 24b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.content.Context; 25b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.database.Cursor; 26b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.graphics.Bitmap; 27b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.graphics.BitmapFactory; 28b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.net.Uri; 29b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.CommonDataKinds.Email; 30b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.CommonDataKinds.Photo; 31b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.Contacts; 32b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.Data; 33b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukiimport android.provider.ContactsContract.StatusUpdates; 34d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onukiimport android.util.Log; 35b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 36b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki/** 37b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * Loader to load presence statuses and the contact photoes. 38b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki */ 39b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onukipublic class ContactStatusLoader extends AsyncTaskLoader<ContactStatusLoader.Result> { 40b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public static final int PRESENCE_UNKNOWN_RESOURCE_ID = R.drawable.presence_inactive; 41b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 42b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** email address -> photo id, presence */ 43b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /* package */ static final String[] PROJECTION_PHOTO_ID_PRESENCE = new String[] { 44b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Contacts.PHOTO_ID, 45b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Contacts.CONTACT_PRESENCE 46b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki }; 47b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private static final int COLUMN_PHOTO_ID = 0; 48b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private static final int COLUMN_PRESENCE = 1; 49b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 50b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** photo id -> photo data */ 51b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /* package */ static final String[] PHOTO_PROJECTION = new String[] { 52b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Photo.PHOTO 53b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki }; 54b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private static final int PHOTO_COLUMN = 0; 55b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 56b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private final Context mContext; 57b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki private final String mEmailAddress; 58b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 59b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** 60b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki * Class that encapsulates the result. 61b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki */ 62b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public static class Result { 63b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public static final Result UNKNOWN = new Result(null, PRESENCE_UNKNOWN_RESOURCE_ID, null); 64b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 65b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** Contact photo. Null if unknown */ 66b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public final Bitmap mPhoto; 67b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 68b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** Presence image resource ID. Always has a valid value, even if unknown. */ 69b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public final int mPresenceResId; 70b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 71b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki /** URI for opening quick contact. Null if unknown. */ 72b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public final Uri mLookupUri; 73b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 74b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public Result(Bitmap photo, int presenceResId, Uri lookupUri) { 75b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mPhoto = photo; 76b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mPresenceResId = presenceResId; 77b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mLookupUri = lookupUri; 78b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 79b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 80b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 81b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public ContactStatusLoader(Context context, String emailAddress) { 82b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki super(context); 83b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mContext = context; 84b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki mEmailAddress = emailAddress; 85b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 86b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 87b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki @Override 88b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public Result loadInBackground() { 8908346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki return load(mContext, mEmailAddress); 9008346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki } 9108346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki 9208346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki /** 9308346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki * Load synchronously. 9408346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki * 9508346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki * Used to fetch a photo for notification, in which calls the callsite is already on a worker 9608346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki * thread. 9708346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki */ 9808346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki public static Result load(Context context, String emailAddress) { 99b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // Load photo-id and presence status. 10008346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki Uri uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)); 10108346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki Cursor c = context.getContentResolver().query( 102b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki uri, 103b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki PROJECTION_PHOTO_ID_PRESENCE, null, null, null); 104b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (c == null) { 105b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki return Result.UNKNOWN; 106b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 107b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki final long photoId; 108b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki final int presenceStatus; 109b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki try { 110b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (!c.moveToFirst()) { 111b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki return Result.UNKNOWN; 112b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 113b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki photoId = c.getLong(COLUMN_PHOTO_ID); 114b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki presenceStatus = c.getInt(COLUMN_PRESENCE); 115b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } finally { 116b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki c.close(); 117b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 118b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 119b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // Convert presence status into the res id. 120b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki final int presenceStatusResId = StatusUpdates.getPresenceIconResourceId(presenceStatus); 121b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 122b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // load photo from photo-id. 123b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki Bitmap photo = null; 124b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (photoId != -1) { 12508346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki final byte[] photoData = Utility.getFirstRowBlob(context, 126b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PHOTO_PROJECTION, 127b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki null, null, null, PHOTO_COLUMN, null); 128b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (photoData != null) { 129d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki try { 130d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki photo = BitmapFactory.decodeByteArray(photoData, 0, photoData.length, null); 131d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki } catch (OutOfMemoryError e) { 132d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki Log.d(com.android.email.Email.LOG_TAG, 133d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki "Decoding bitmap failed with " + e.getMessage()); 134d7f886c5c80c98179054c2e02014dc088f925bc2Makoto Onuki } 135b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 136b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 137b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 138b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki // Get lookup URI 13908346b67b8aa3e36057fa4e439275efa8c3a8098Makoto Onuki final Uri lookupUri = Data.getContactLookupUri(context.getContentResolver(), uri); 140b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki return new Result(photo, presenceStatusResId, lookupUri); 141b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 142b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 143b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki @Override 144b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public void startLoading() { 145b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki cancelLoad(); 146b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki forceLoad(); 147b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 148b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 149b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki @Override 150b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public void stopLoading() { 151b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki cancelLoad(); 152b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 153b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 154b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki @Override 155b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public void destroy() { 156b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki stopLoading(); 157b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 158b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki} 159