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