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      * Turn on/off showing of the aggregate badge element.
    336      */
    337     public void showAggregateBadge(boolean showBagde) {
    338         mAggregateBadge.setVisibility(showBagde ? View.VISIBLE : View.GONE);
    339     }
    340 
    341     /**
    342      * Turn on/off showing of the star element.
    343      */
    344     public void showStar(boolean showStar) {
    345         mStarredView.setVisibility(showStar ? View.VISIBLE : View.GONE);
    346     }
    347 
    348     /**
    349      * Manually set the starred state of this header widget. This doesn't change
    350      * the underlying {@link Contacts} value, only the UI state.
    351      */
    352     public void setStared(boolean starred) {
    353         mStarredView.setChecked(starred);
    354     }
    355 
    356     /**
    357      * Manually set the presence.
    358      */
    359     public void setPresence(int presence) {
    360         mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
    361     }
    362 
    363     /**
    364      * Manually set the contact uri
    365      */
    366     public void setContactUri(Uri uri) {
    367         setContactUri(uri, true);
    368     }
    369 
    370     /**
    371      * Manually set the contact uri
    372      */
    373     public void setContactUri(Uri uri, boolean sendToFastrack) {
    374         mContactUri = uri;
    375         if (sendToFastrack) {
    376             mPhotoView.assignContactUri(uri);
    377         }
    378     }
    379 
    380     /**
    381      * Manually set the photo to display in the header. This doesn't change the
    382      * underlying {@link Contacts}, only the UI state.
    383      */
    384     public void setPhoto(Bitmap bitmap) {
    385         mPhotoView.setImageBitmap(bitmap);
    386     }
    387 
    388     /**
    389      * Manually set the display name and phonetic name to show in the header.
    390      * This doesn't change the underlying {@link Contacts}, only the UI state.
    391      */
    392     public void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
    393         mDisplayNameView.setText(displayName);
    394         if (!TextUtils.isEmpty(phoneticName)) {
    395             mPhoneticNameView.setText(phoneticName);
    396             mPhoneticNameView.setVisibility(View.VISIBLE);
    397         } else {
    398             mPhoneticNameView.setVisibility(View.GONE);
    399         }
    400     }
    401 
    402     /**
    403      * Manually set the social snippet text to display in the header.
    404      */
    405     public void setSocialSnippet(CharSequence snippet) {
    406         if (snippet == null) {
    407             mStatusView.setVisibility(View.GONE);
    408             mStatusAttributionView.setVisibility(View.GONE);
    409         } else {
    410             mStatusView.setText(snippet);
    411             mStatusView.setVisibility(View.VISIBLE);
    412         }
    413     }
    414 
    415     /**
    416      * Set a list of specific MIME-types to exclude and not display. For
    417      * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
    418      * profile icon.
    419      */
    420     public void setExcludeMimes(String[] excludeMimes) {
    421         mExcludeMimes = excludeMimes;
    422         mPhotoView.setExcludeMimes(excludeMimes);
    423     }
    424 
    425     /**
    426      * Convenience method for binding all available data from an existing
    427      * contact.
    428      *
    429      * @param contactLookupUri a {Contacts.CONTENT_LOOKUP_URI} style URI.
    430      */
    431     public void bindFromContactLookupUri(Uri contactLookupUri) {
    432         bindFromContactUriInternal(contactLookupUri, true /* reset query handler */);
    433     }
    434 
    435     /**
    436      * Convenience method for binding all available data from an existing
    437      * contact.
    438      *
    439      * @param contactUri a {Contacts.CONTENT_URI} style URI.
    440      * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
    441      */
    442     private void bindFromContactUriInternal(Uri contactUri, boolean resetQueryHandler) {
    443         mContactUri = contactUri;
    444         startContactQuery(contactUri, resetQueryHandler);
    445     }
    446 
    447     /**
    448      * Convenience method for binding all available data from an existing
    449      * contact.
    450      *
    451      * @param emailAddress The email address used to do a reverse lookup in
    452      * the contacts database. If more than one contact contains this email
    453      * address, one of them will be chosen to bind to.
    454      */
    455     public void bindFromEmail(String emailAddress) {
    456         resetAsyncQueryHandler();
    457 
    458         mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, emailAddress,
    459                 Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)),
    460                 EMAIL_LOOKUP_PROJECTION, null, null, null);
    461     }
    462 
    463     /**
    464      * Convenience method for binding all available data from an existing
    465      * contact.
    466      *
    467      * @param number The phone number used to do a reverse lookup in
    468      * the contacts database. If more than one contact contains this phone
    469      * number, one of them will be chosen to bind to.
    470      */
    471     public void bindFromPhoneNumber(String number) {
    472         resetAsyncQueryHandler();
    473 
    474         mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, number,
    475                 Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
    476                 PHONE_LOOKUP_PROJECTION, null, null, null);
    477     }
    478 
    479     /**
    480      * startContactQuery
    481      *
    482      * internal method to query contact by Uri.
    483      *
    484      * @param contactUri the contact uri
    485      * @param resetQueryHandler whether to use a new AsyncQueryHandler or not
    486      */
    487     private void startContactQuery(Uri contactUri, boolean resetQueryHandler) {
    488         if (resetQueryHandler) {
    489             resetAsyncQueryHandler();
    490         }
    491 
    492         mQueryHandler.startQuery(TOKEN_CONTACT_INFO, contactUri, contactUri, ContactQuery.COLUMNS,
    493                 null, null, null);
    494     }
    495 
    496     /**
    497      * startPhotoQuery
    498      *
    499      * internal method to query contact photo by photo id and uri.
    500      *
    501      * @param photoId the photo id.
    502      * @param lookupKey the lookup uri.
    503      * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
    504      */
    505     protected void startPhotoQuery(long photoId, Uri lookupKey, boolean resetQueryHandler) {
    506         if (resetQueryHandler) {
    507             resetAsyncQueryHandler();
    508         }
    509 
    510         mQueryHandler.startQuery(TOKEN_PHOTO_QUERY, lookupKey,
    511                 ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PhotoQuery.COLUMNS,
    512                 null, null, null);
    513     }
    514 
    515     /**
    516      * Method to force this widget to forget everything it knows about the contact.
    517      * We need to stop any existing async queries for phone, email, contact, and photos.
    518      */
    519     public void wipeClean() {
    520         resetAsyncQueryHandler();
    521 
    522         setDisplayName(null, null);
    523         setPhoto(loadPlaceholderPhoto(null));
    524         setSocialSnippet(null);
    525         setPresence(0);
    526         mContactUri = null;
    527         mExcludeMimes = null;
    528     }
    529 
    530 
    531     private void resetAsyncQueryHandler() {
    532         // the api AsyncQueryHandler.cancelOperation() doesn't really work. Since we really
    533         // need the old async queries to be cancelled, let's do it the hard way.
    534         mQueryHandler = new QueryHandler(mContentResolver);
    535     }
    536 
    537     /**
    538      * Bind the contact details provided by the given {@link Cursor}.
    539      */
    540     protected void bindContactInfo(Cursor c) {
    541         final String displayName = c.getString(ContactQuery.DISPLAY_NAME);
    542         final String phoneticName = c.getString(ContactQuery.PHONETIC_NAME);
    543         this.setDisplayName(displayName, phoneticName);
    544 
    545         final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
    546         mStarredView.setChecked(starred);
    547 
    548         //Set the presence status
    549         if (!c.isNull(ContactQuery.CONTACT_PRESENCE_STATUS)) {
    550             int presence = c.getInt(ContactQuery.CONTACT_PRESENCE_STATUS);
    551             mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
    552             mPresenceView.setVisibility(View.VISIBLE);
    553         } else {
    554             mPresenceView.setVisibility(View.GONE);
    555         }
    556 
    557         //Set the status update
    558         String status = c.getString(ContactQuery.CONTACT_STATUS);
    559         if (!TextUtils.isEmpty(status)) {
    560             mStatusView.setText(status);
    561             mStatusView.setVisibility(View.VISIBLE);
    562 
    563             CharSequence timestamp = null;
    564 
    565             if (!c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)) {
    566                 long date = c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
    567 
    568                 // Set the date/time field by mixing relative and absolute
    569                 // times.
    570                 int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
    571 
    572                 timestamp = DateUtils.getRelativeTimeSpanString(date,
    573                         System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags);
    574             }
    575 
    576             String label = null;
    577 
    578             if (!c.isNull(ContactQuery.CONTACT_STATUS_LABEL)) {
    579                 String resPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
    580                 int labelResource = c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
    581                 Resources resources;
    582                 if (TextUtils.isEmpty(resPackage)) {
    583                     resources = getResources();
    584                 } else {
    585                     PackageManager pm = getContext().getPackageManager();
    586                     try {
    587                         resources = pm.getResourcesForApplication(resPackage);
    588                     } catch (NameNotFoundException e) {
    589                         Log.w(TAG, "Contact status update resource package not found: "
    590                                 + resPackage);
    591                         resources = null;
    592                     }
    593                 }
    594 
    595                 if (resources != null) {
    596                     try {
    597                         label = resources.getString(labelResource);
    598                     } catch (NotFoundException e) {
    599                         Log.w(TAG, "Contact status update resource not found: " + resPackage + "@"
    600                                 + labelResource);
    601                     }
    602                 }
    603             }
    604 
    605             CharSequence attribution;
    606             if (timestamp != null && label != null) {
    607                 attribution = getContext().getString(
    608                         R.string.contact_status_update_attribution_with_date,
    609                         timestamp, label);
    610             } else if (timestamp == null && label != null) {
    611                 attribution = getContext().getString(
    612                         R.string.contact_status_update_attribution,
    613                         label);
    614             } else if (timestamp != null) {
    615                 attribution = timestamp;
    616             } else {
    617                 attribution = null;
    618             }
    619             if (attribution != null) {
    620                 mStatusAttributionView.setText(attribution);
    621                 mStatusAttributionView.setVisibility(View.VISIBLE);
    622             } else {
    623                 mStatusAttributionView.setVisibility(View.GONE);
    624             }
    625         } else {
    626             mStatusView.setVisibility(View.GONE);
    627             mStatusAttributionView.setVisibility(View.GONE);
    628         }
    629     }
    630 
    631     public void onClick(View view) {
    632         switch (view.getId()) {
    633             case R.id.star: {
    634                 // Toggle "starred" state
    635                 // Make sure there is a contact
    636                 if (mContactUri != null) {
    637                     final ContentValues values = new ContentValues(1);
    638                     values.put(Contacts.STARRED, mStarredView.isChecked());
    639                     mContentResolver.update(mContactUri, values, null, null);
    640                 }
    641                 break;
    642             }
    643             case R.id.photo: {
    644                 performPhotoClick();
    645                 break;
    646             }
    647             case R.id.name: {
    648                 performDisplayNameClick();
    649                 break;
    650             }
    651         }
    652     }
    653 
    654     private Bitmap loadPlaceholderPhoto(BitmapFactory.Options options) {
    655         if (mNoPhotoResource == 0) {
    656             return null;
    657         }
    658         return BitmapFactory.decodeResource(mContext.getResources(),
    659                 mNoPhotoResource, options);
    660     }
    661 }
    662