1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
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 
17 package com.android.email.activity;
18 
19 import android.content.AsyncTaskLoader;
20 import android.content.ContentUris;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.net.Uri;
26 import android.provider.ContactsContract.CommonDataKinds.Email;
27 import android.provider.ContactsContract.CommonDataKinds.Photo;
28 import android.provider.ContactsContract.Contacts;
29 import android.provider.ContactsContract.Data;
30 import android.provider.ContactsContract.StatusUpdates;
31 
32 import com.android.emailcommon.Logging;
33 import com.android.emailcommon.utility.Utility;
34 import com.android.mail.utils.LogUtils;
35 import com.google.common.annotations.VisibleForTesting;
36 
37 /**
38  * Loader to load presence statuses and the contact photo.
39  */
40 public class ContactStatusLoader extends AsyncTaskLoader<ContactStatusLoader.Result> {
41     @VisibleForTesting
42     static final int PRESENCE_UNKNOWN_RESOURCE_ID = android.R.drawable.presence_offline;
43 
44     /** email address -> photo id, presence */
45     /* package */ static final String[] PROJECTION_PHOTO_ID_PRESENCE = new String[] {
46             Contacts.PHOTO_ID,
47             Contacts.CONTACT_PRESENCE
48             };
49     private static final int COLUMN_PHOTO_ID = 0;
50     private static final int COLUMN_PRESENCE = 1;
51 
52     /** photo id -> photo data */
53     /* package */ static final String[] PHOTO_PROJECTION = new String[] {
54             Photo.PHOTO
55             };
56     private static final int PHOTO_COLUMN = 0;
57 
58     private final Context mContext;
59     private final String mEmailAddress;
60 
61     /**
62      * Class that encapsulates the result.
63      */
64     public static class Result {
65         public static final Result UNKNOWN = new Result(null, PRESENCE_UNKNOWN_RESOURCE_ID, null);
66 
67         /** Contact photo.  Null if unknown */
68         public final Bitmap mPhoto;
69 
70         /** Presence image resource ID.  Always has a valid value, even if unknown. */
71         public final int mPresenceResId;
72 
73         /** URI for opening quick contact.  Null if unknown. */
74         public final Uri mLookupUri;
75 
Result(Bitmap photo, int presenceResId, Uri lookupUri)76         public Result(Bitmap photo, int presenceResId, Uri lookupUri) {
77             mPhoto = photo;
78             mPresenceResId = presenceResId;
79             mLookupUri = lookupUri;
80         }
81 
isUnknown()82         public boolean isUnknown() {
83             return PRESENCE_UNKNOWN_RESOURCE_ID == mPresenceResId;
84         }
85     }
86 
ContactStatusLoader(Context context, String emailAddress)87     public ContactStatusLoader(Context context, String emailAddress) {
88         super(context);
89         mContext = context;
90         mEmailAddress = emailAddress;
91     }
92 
93     @Override
loadInBackground()94     public Result loadInBackground() {
95         return getContactInfo(mContext, mEmailAddress);
96     }
97 
98     /**
99      * Synchronously loads contact data.
100      *
101      * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
102      */
getContactInfo(Context context, String emailAddress)103     public static Result getContactInfo(Context context, String emailAddress) {
104         // Load photo-id and presence status.
105         Uri uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress));
106         Cursor c = context.getContentResolver().query(
107                 uri,
108                 PROJECTION_PHOTO_ID_PRESENCE, null, null, null);
109         if (c == null) {
110             return Result.UNKNOWN;
111         }
112         final long photoId;
113         final int presenceStatus;
114         try {
115             if (!c.moveToFirst()) {
116                 return Result.UNKNOWN;
117             }
118             photoId = c.getLong(COLUMN_PHOTO_ID);
119             presenceStatus = c.getInt(COLUMN_PRESENCE);
120         } finally {
121             c.close();
122         }
123 
124         // Convert presence status into the res id.
125         final int presenceStatusResId = StatusUpdates.getPresenceIconResourceId(presenceStatus);
126 
127         // load photo from photo-id.
128         Bitmap photo = null;
129         if (photoId != -1) {
130             final byte[] photoData = Utility.getFirstRowBlob(context,
131                     ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PHOTO_PROJECTION,
132                     null, null, null, PHOTO_COLUMN, null);
133             if (photoData != null) {
134                 try {
135                     photo = BitmapFactory.decodeByteArray(photoData, 0, photoData.length, null);
136                 } catch (OutOfMemoryError e) {
137                     LogUtils.d(Logging.LOG_TAG, "Decoding bitmap failed with " + e.getMessage());
138                 }
139             }
140         }
141 
142         // Get lookup URI
143         final Uri lookupUri = Data.getContactLookupUri(context.getContentResolver(), uri);
144         return new Result(photo, presenceStatusResId, lookupUri);
145     }
146 
147     @Override
onStartLoading()148     protected void onStartLoading() {
149         cancelLoad();
150         forceLoad();
151     }
152 
153     @Override
onStopLoading()154     protected void onStopLoading() {
155         cancelLoad();
156     }
157 
158     @Override
onReset()159     protected void onReset() {
160         stopLoading();
161     }
162 }
163