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