1/*
2 * Copyright (C) 2009 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
17package com.android.loaderapp;
18
19import android.Manifest;
20import android.content.AsyncQueryHandler;
21import android.content.ContentResolver;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.res.Resources;
28import android.content.res.Resources.NotFoundException;
29import android.database.Cursor;
30import android.graphics.Bitmap;
31import android.graphics.BitmapFactory;
32import android.net.Uri;
33import android.os.SystemClock;
34import android.provider.ContactsContract.Contacts;
35import android.provider.ContactsContract.Data;
36import android.provider.ContactsContract.PhoneLookup;
37import android.provider.ContactsContract.RawContacts;
38import android.provider.ContactsContract.StatusUpdates;
39import android.provider.ContactsContract.CommonDataKinds.Email;
40import android.provider.ContactsContract.CommonDataKinds.Photo;
41import android.text.TextUtils;
42import android.text.format.DateUtils;
43import android.util.AttributeSet;
44import android.util.Log;
45import android.view.LayoutInflater;
46import android.view.View;
47import android.widget.CheckBox;
48import android.widget.QuickContactBadge;
49import android.widget.FrameLayout;
50import android.widget.ImageView;
51import android.widget.TextView;
52
53/**
54 * Header used across system for displaying a title bar with contact info. You
55 * can bind specific values on the header, or use helper methods like
56 * {@link #bindFromContactLookupUri(Uri)} to populate asynchronously.
57 * <p>
58 * The parent must request the {@link Manifest.permission#READ_CONTACTS}
59 * permission to access contact data.
60 */
61public class ContactHeaderWidget extends FrameLayout implements View.OnClickListener {
62
63    private static final String TAG = "ContactHeaderWidget";
64
65    private TextView mDisplayNameView;
66    private View mAggregateBadge;
67    private TextView mPhoneticNameView;
68    private CheckBox mStarredView;
69    private QuickContactBadge mPhotoView;
70    private ImageView mPresenceView;
71    private TextView mStatusView;
72    private TextView mStatusAttributionView;
73    private int mNoPhotoResource;
74    private QueryHandler mQueryHandler;
75
76    protected Uri mContactUri;
77
78    protected String[] mExcludeMimes = null;
79
80    protected ContentResolver mContentResolver;
81
82    /**
83     * Interface for callbacks invoked when the user interacts with a header.
84     */
85    public interface ContactHeaderListener {
86        public void onPhotoClick(View view);
87        public void onDisplayNameClick(View view);
88    }
89
90    private ContactHeaderListener mListener;
91
92
93    private interface ContactQuery {
94        //Projection used for the summary info in the header.
95        String[] COLUMNS = new String[] {
96            Contacts._ID,
97            Contacts.LOOKUP_KEY,
98            Contacts.PHOTO_ID,
99            Contacts.DISPLAY_NAME,
100            Contacts.PHONETIC_NAME,
101            Contacts.STARRED,
102            Contacts.CONTACT_PRESENCE,
103            Contacts.CONTACT_STATUS,
104            Contacts.CONTACT_STATUS_TIMESTAMP,
105            Contacts.CONTACT_STATUS_RES_PACKAGE,
106            Contacts.CONTACT_STATUS_LABEL,
107        };
108        int _ID = 0;
109        int LOOKUP_KEY = 1;
110        int PHOTO_ID = 2;
111        int DISPLAY_NAME = 3;
112        int PHONETIC_NAME = 4;
113        //TODO: We need to figure out how we're going to get the phonetic name.
114        //static final int HEADER_PHONETIC_NAME_COLUMN_INDEX
115        int STARRED = 5;
116        int CONTACT_PRESENCE_STATUS = 6;
117        int CONTACT_STATUS = 7;
118        int CONTACT_STATUS_TIMESTAMP = 8;
119        int CONTACT_STATUS_RES_PACKAGE = 9;
120        int CONTACT_STATUS_LABEL = 10;
121    }
122
123    private interface PhotoQuery {
124        String[] COLUMNS = new String[] {
125            Photo.PHOTO
126        };
127
128        int PHOTO = 0;
129    }
130
131    //Projection used for looking up contact id from phone number
132    protected static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
133        PhoneLookup._ID,
134        PhoneLookup.LOOKUP_KEY,
135    };
136    protected static final int PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
137    protected static final int PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
138
139    //Projection used for looking up contact id from email address
140    protected static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
141        RawContacts.CONTACT_ID,
142        Contacts.LOOKUP_KEY,
143    };
144    protected static final int EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
145    protected static final int EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
146
147    protected static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
148        Contacts._ID,
149    };
150    protected static final int CONTACT_LOOKUP_ID_COLUMN_INDEX = 0;
151
152    private static final int TOKEN_CONTACT_INFO = 0;
153    private static final int TOKEN_PHONE_LOOKUP = 1;
154    private static final int TOKEN_EMAIL_LOOKUP = 2;
155    private static final int TOKEN_PHOTO_QUERY = 3;
156
157    public ContactHeaderWidget(Context context) {
158        this(context, null);
159    }
160
161    public ContactHeaderWidget(Context context, AttributeSet attrs) {
162        this(context, attrs, 0);
163    }
164
165    public ContactHeaderWidget(Context context, AttributeSet attrs, int defStyle) {
166        super(context, attrs, defStyle);
167
168        mContentResolver = mContext.getContentResolver();
169
170        LayoutInflater inflater =
171            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
172        inflater.inflate(R.layout.contact_header, this);
173
174        mDisplayNameView = (TextView) findViewById(R.id.name);
175
176        mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
177
178        mPhotoView = (QuickContactBadge) findViewById(R.id.photo);
179
180        mPresenceView = (ImageView) findViewById(R.id.presence);
181
182        mStatusView = (TextView)findViewById(R.id.status);
183        mStatusAttributionView = (TextView)findViewById(R.id.status_date);
184
185        // Set the photo with a random "no contact" image
186        long now = SystemClock.elapsedRealtime();
187        int num = (int) now & 0xf;
188        if (num < 9) {
189            // Leaning in from right, common
190            mNoPhotoResource = R.drawable.ic_contact_picture;
191        } else if (num < 14) {
192            // Leaning in from left uncommon
193            mNoPhotoResource = R.drawable.ic_contact_picture_2;
194        } else {
195            // Coming in from the top, rare
196            mNoPhotoResource = R.drawable.ic_contact_picture_3;
197        }
198
199        resetAsyncQueryHandler();
200    }
201
202    public void enableClickListeners() {
203        mDisplayNameView.setOnClickListener(this);
204        mPhotoView.setOnClickListener(this);
205    }
206
207    /**
208     * Set the given {@link ContactHeaderListener} to handle header events.
209     */
210    public void setContactHeaderListener(ContactHeaderListener listener) {
211        mListener = listener;
212    }
213
214    private void performPhotoClick() {
215        if (mListener != null) {
216            mListener.onPhotoClick(mPhotoView);
217        }
218    }
219
220    private void performDisplayNameClick() {
221        if (mListener != null) {
222            mListener.onDisplayNameClick(mDisplayNameView);
223        }
224    }
225
226    private class QueryHandler extends AsyncQueryHandler {
227
228        public QueryHandler(ContentResolver cr) {
229            super(cr);
230        }
231
232        @Override
233        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
234            try{
235                if (this != mQueryHandler) {
236                    Log.d(TAG, "onQueryComplete: discard result, the query handler is reset!");
237                    return;
238                }
239
240                switch (token) {
241                    case TOKEN_PHOTO_QUERY: {
242                        //Set the photo
243                        Bitmap photoBitmap = null;
244                        if (cursor != null && cursor.moveToFirst()
245                                && !cursor.isNull(PhotoQuery.PHOTO)) {
246                            byte[] photoData = cursor.getBlob(PhotoQuery.PHOTO);
247                            photoBitmap = BitmapFactory.decodeByteArray(photoData, 0,
248                                    photoData.length, null);
249                        }
250
251                        if (photoBitmap == null) {
252                            photoBitmap = loadPlaceholderPhoto(null);
253                        }
254                        setPhoto(photoBitmap);
255                        if (cookie != null && cookie instanceof Uri) {
256                            mPhotoView.assignContactUri((Uri) cookie);
257                        }
258                        invalidate();
259                        break;
260                    }
261                    case TOKEN_CONTACT_INFO: {
262                        if (cursor != null && cursor.moveToFirst()) {
263                            bindContactInfo(cursor);
264                            final Uri lookupUri = Contacts.getLookupUri(
265                                    cursor.getLong(ContactQuery._ID),
266                                    cursor.getString(ContactQuery.LOOKUP_KEY));
267
268                            final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
269
270                            setPhotoId(photoId, lookupUri);
271                        } else {
272                            // shouldn't really happen
273                            setDisplayName(null, null);
274                            setSocialSnippet(null);
275                            setPhoto(loadPlaceholderPhoto(null));
276                        }
277                        break;
278                    }
279                    case TOKEN_PHONE_LOOKUP: {
280                        if (cursor != null && cursor.moveToFirst()) {
281                            long contactId = cursor.getLong(PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX);
282                            String lookupKey = cursor.getString(
283                                    PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
284                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
285                                    false /* don't reset query handler */);
286                        } else {
287                            String phoneNumber = (String) cookie;
288                            setDisplayName(phoneNumber, null);
289                            setSocialSnippet(null);
290                            setPhoto(loadPlaceholderPhoto(null));
291                            mPhotoView.assignContactFromPhone(phoneNumber, true);
292                        }
293                        break;
294                    }
295                    case TOKEN_EMAIL_LOOKUP: {
296                        if (cursor != null && cursor.moveToFirst()) {
297                            long contactId = cursor.getLong(EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX);
298                            String lookupKey = cursor.getString(
299                                    EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
300                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
301                                    false /* don't reset query handler */);
302                        } else {
303                            String emailAddress = (String) cookie;
304                            setDisplayName(emailAddress, null);
305                            setSocialSnippet(null);
306                            setPhoto(loadPlaceholderPhoto(null));
307                            mPhotoView.assignContactFromEmail(emailAddress, true);
308                        }
309                        break;
310                    }
311                }
312            } finally {
313                if (cursor != null) {
314                    cursor.close();
315                }
316            }
317        }
318    }
319
320    /**
321     * Manually set the presence.
322     */
323    public void setPresence(int presence) {
324        mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
325    }
326
327    /**
328     * Manually set the presence. If presence is null, it is hidden.
329     * This doesn't change the underlying {@link Contacts} value, only the UI state.
330     * @hide
331     */
332    public void setPresence(Integer presence) {
333        if (presence == null) {
334            showPresence(false);
335        } else {
336            showPresence(true);
337            setPresence(presence.intValue());
338        }
339    }
340
341    /**
342     * Turn on/off showing the presence.
343     * @hide this is here for consistency with setStared/showStar and should be public
344     */
345    public void showPresence(boolean showPresence) {
346        mPresenceView.setVisibility(showPresence ? View.VISIBLE : View.GONE);
347    }
348
349    /**
350     * Manually set the contact uri without loading any data
351     */
352    public void setContactUri(Uri uri) {
353        setContactUri(uri, true);
354    }
355
356    /**
357     * Manually set the contact uri without loading any data
358     */
359    public void setContactUri(Uri uri, boolean sendToQuickContact) {
360        mContactUri = uri;
361        if (sendToQuickContact) {
362            mPhotoView.assignContactUri(uri);
363        }
364    }
365
366    /**
367     * Manually set the photo to display in the header. This doesn't change the
368     * underlying {@link Contacts}, only the UI state.
369     */
370    public void setPhoto(Bitmap bitmap) {
371        mPhotoView.setImageBitmap(bitmap);
372    }
373
374    /**
375     * Manually set the photo given its id. If the id is 0, a placeholder picture will
376     * be loaded. For any other Id, an async query is started
377     * @hide
378     */
379    public void setPhotoId(final long photoId, final Uri lookupUri) {
380        if (photoId == 0) {
381            setPhoto(loadPlaceholderPhoto(null));
382            mPhotoView.assignContactUri(lookupUri);
383            invalidate();
384        } else {
385            startPhotoQuery(photoId, lookupUri,
386                    false /* don't reset query handler */);
387        }
388    }
389
390    /**
391     * Manually set the display name and phonetic name to show in the header.
392     * This doesn't change the underlying {@link Contacts}, only the UI state.
393     */
394    public void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
395        mDisplayNameView.setText(displayName);
396        if (!TextUtils.isEmpty(phoneticName)) {
397            mPhoneticNameView.setText(phoneticName);
398            mPhoneticNameView.setVisibility(View.VISIBLE);
399        } else {
400            mPhoneticNameView.setVisibility(View.GONE);
401        }
402    }
403
404    /**
405     * Manually set the social snippet text to display in the header. This doesn't change the
406     * underlying {@link Contacts}, only the UI state.
407     */
408    public void setSocialSnippet(CharSequence snippet) {
409        if (snippet == null) {
410            mStatusView.setVisibility(View.GONE);
411            mStatusAttributionView.setVisibility(View.GONE);
412        } else {
413            mStatusView.setText(snippet);
414            mStatusView.setVisibility(View.VISIBLE);
415        }
416    }
417
418    /**
419     * Manually set the status attribution text to display in the header.
420     * This doesn't change the underlying {@link Contacts}, only the UI state.
421     * @hide
422     */
423    public void setStatusAttribution(CharSequence attribution) {
424        if (attribution != null) {
425            mStatusAttributionView.setText(attribution);
426            mStatusAttributionView.setVisibility(View.VISIBLE);
427        } else {
428            mStatusAttributionView.setVisibility(View.GONE);
429        }
430    }
431
432    /**
433     * Set a list of specific MIME-types to exclude and not display. For
434     * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
435     * profile icon.
436     */
437    public void setExcludeMimes(String[] excludeMimes) {
438        mExcludeMimes = excludeMimes;
439        mPhotoView.setExcludeMimes(excludeMimes);
440    }
441
442    /**
443     * Manually set all the status values to display in the header.
444     * This doesn't change the underlying {@link Contacts}, only the UI state.
445     * @hide
446     * @param status             The status of the contact. If this is either null or empty,
447     *                           the status is cleared and the other parameters are ignored.
448     * @param statusTimestamp    The timestamp (retrieved via a call to
449     *                           {@link System#currentTimeMillis()}) of the last status update.
450     *                           This value can be null if it is not known.
451     * @param statusLabel        The id of a resource string that specifies the current
452     *                           status. This value can be null if no Label should be used.
453     * @param statusResPackage   The name of the resource package containing the resource string
454     *                           referenced in the parameter statusLabel.
455     */
456    public void setStatus(final String status, final Long statusTimestamp,
457            final Integer statusLabel, final String statusResPackage) {
458        if (TextUtils.isEmpty(status)) {
459            setSocialSnippet(null);
460            return;
461        }
462
463        setSocialSnippet(status);
464
465        final CharSequence timestampDisplayValue;
466
467        if (statusTimestamp != null) {
468            // Set the date/time field by mixing relative and absolute
469            // times.
470            int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
471
472            timestampDisplayValue = DateUtils.getRelativeTimeSpanString(
473                    statusTimestamp.longValue(), System.currentTimeMillis(),
474                    DateUtils.MINUTE_IN_MILLIS, flags);
475        } else {
476            timestampDisplayValue = null;
477        }
478
479
480        String labelDisplayValue = null;
481
482        if (statusLabel != null) {
483            Resources resources;
484            if (TextUtils.isEmpty(statusResPackage)) {
485                resources = getResources();
486            } else {
487                PackageManager pm = getContext().getPackageManager();
488                try {
489                    resources = pm.getResourcesForApplication(statusResPackage);
490                } catch (NameNotFoundException e) {
491                    Log.w(TAG, "Contact status update resource package not found: "
492                            + statusResPackage);
493                    resources = null;
494                }
495            }
496
497            if (resources != null) {
498                try {
499                    labelDisplayValue = resources.getString(statusLabel.intValue());
500                } catch (NotFoundException e) {
501                    Log.w(TAG, "Contact status update resource not found: " + statusResPackage + "@"
502                            + statusLabel.intValue());
503                }
504            }
505        }
506
507        final CharSequence attribution;
508        if (timestampDisplayValue != null && labelDisplayValue != null) {
509            attribution = getContext().getString(
510                    R.string.contact_status_update_attribution_with_date,
511                    timestampDisplayValue, labelDisplayValue);
512        } else if (timestampDisplayValue == null && labelDisplayValue != null) {
513            attribution = getContext().getString(
514                    R.string.contact_status_update_attribution,
515                    labelDisplayValue);
516        } else if (timestampDisplayValue != null) {
517            attribution = timestampDisplayValue;
518        } else {
519            attribution = null;
520        }
521        setStatusAttribution(attribution);
522    }
523
524    /**
525     * Convenience method for binding all available data from an existing
526     * contact.
527     *
528     * @param contactLookupUri a {Contacts.CONTENT_LOOKUP_URI} style URI.
529     */
530    public void bindFromContactLookupUri(Uri contactLookupUri) {
531        bindFromContactUriInternal(contactLookupUri, true /* reset query handler */);
532    }
533
534    /**
535     * Convenience method for binding all available data from an existing
536     * contact.
537     *
538     * @param contactUri a {Contacts.CONTENT_URI} style URI.
539     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
540     */
541    private void bindFromContactUriInternal(Uri contactUri, boolean resetQueryHandler) {
542        mContactUri = contactUri;
543        startContactQuery(contactUri, resetQueryHandler);
544    }
545
546    /**
547     * Convenience method for binding all available data from an existing
548     * contact.
549     *
550     * @param emailAddress The email address used to do a reverse lookup in
551     * the contacts database. If more than one contact contains this email
552     * address, one of them will be chosen to bind to.
553     */
554    public void bindFromEmail(String emailAddress) {
555        resetAsyncQueryHandler();
556
557        mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, emailAddress,
558                Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)),
559                EMAIL_LOOKUP_PROJECTION, null, null, null);
560    }
561
562    /**
563     * Convenience method for binding all available data from an existing
564     * contact.
565     *
566     * @param number The phone number used to do a reverse lookup in
567     * the contacts database. If more than one contact contains this phone
568     * number, one of them will be chosen to bind to.
569     */
570    public void bindFromPhoneNumber(String number) {
571        resetAsyncQueryHandler();
572
573        mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, number,
574                Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
575                PHONE_LOOKUP_PROJECTION, null, null, null);
576    }
577
578    /**
579     * startContactQuery
580     *
581     * internal method to query contact by Uri.
582     *
583     * @param contactUri the contact uri
584     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not
585     */
586    private void startContactQuery(Uri contactUri, boolean resetQueryHandler) {
587        if (resetQueryHandler) {
588            resetAsyncQueryHandler();
589        }
590
591        mQueryHandler.startQuery(TOKEN_CONTACT_INFO, contactUri, contactUri, ContactQuery.COLUMNS,
592                null, null, null);
593    }
594
595    /**
596     * startPhotoQuery
597     *
598     * internal method to query contact photo by photo id and uri.
599     *
600     * @param photoId the photo id.
601     * @param lookupKey the lookup uri.
602     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
603     */
604    protected void startPhotoQuery(long photoId, Uri lookupKey, boolean resetQueryHandler) {
605        if (resetQueryHandler) {
606            resetAsyncQueryHandler();
607        }
608
609        mQueryHandler.startQuery(TOKEN_PHOTO_QUERY, lookupKey,
610                ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PhotoQuery.COLUMNS,
611                null, null, null);
612    }
613
614    /**
615     * Method to force this widget to forget everything it knows about the contact.
616     * We need to stop any existing async queries for phone, email, contact, and photos.
617     */
618    public void wipeClean() {
619        resetAsyncQueryHandler();
620
621        setDisplayName(null, null);
622        setPhoto(loadPlaceholderPhoto(null));
623        setSocialSnippet(null);
624        setPresence(0);
625        mContactUri = null;
626        mExcludeMimes = null;
627    }
628
629
630    private void resetAsyncQueryHandler() {
631        // the api AsyncQueryHandler.cancelOperation() doesn't really work. Since we really
632        // need the old async queries to be cancelled, let's do it the hard way.
633        mQueryHandler = new QueryHandler(mContentResolver);
634    }
635
636    /**
637     * Bind the contact details provided by the given {@link Cursor}.
638     */
639    protected void bindContactInfo(Cursor c) {
640        final String displayName = c.getString(ContactQuery.DISPLAY_NAME);
641        final String phoneticName = c.getString(ContactQuery.PHONETIC_NAME);
642        this.setDisplayName(displayName, phoneticName);
643
644        //Set the presence status
645        if (!c.isNull(ContactQuery.CONTACT_PRESENCE_STATUS)) {
646            int presence = c.getInt(ContactQuery.CONTACT_PRESENCE_STATUS);
647            setPresence(presence);
648            showPresence(true);
649        } else {
650            showPresence(false);
651        }
652
653        //Set the status update
654        final String status = c.getString(ContactQuery.CONTACT_STATUS);
655        final Long statusTimestamp = c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
656                ? null
657                : c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
658        final Integer statusLabel = c.isNull(ContactQuery.CONTACT_STATUS_LABEL)
659                ? null
660                : c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
661        final String statusResPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
662
663        setStatus(status, statusTimestamp, statusLabel, statusResPackage);
664    }
665
666    public void onClick(View view) {
667        switch (view.getId()) {
668            case R.id.photo: {
669                performPhotoClick();
670                break;
671            }
672            case R.id.name: {
673                performDisplayNameClick();
674                break;
675            }
676        }
677    }
678
679    private Bitmap loadPlaceholderPhoto(BitmapFactory.Options options) {
680        if (mNoPhotoResource == 0) {
681            return null;
682        }
683        return BitmapFactory.decodeResource(mContext.getResources(),
684                mNoPhotoResource, options);
685    }
686}
687