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