Clean up header view. Speed up image loading by not spawning yet another async-task

Bug:2733187
Change-Id: Ibb59d9866af0b86e7dfbc89eecfec2b95091f889
diff --git a/res/layout/contact_detail_header_view.xml b/res/layout/contact_detail_header_view.xml
index dc8239a..3e47d51 100644
--- a/res/layout/contact_detail_header_view.xml
+++ b/res/layout/contact_detail_header_view.xml
@@ -36,32 +36,16 @@
         android:orientation="vertical"
         android:layout_gravity="center_vertical" >
 
-        <LinearLayout
+        <TextView android:id="@+id/name"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:id="@+id/aggregate_badge"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingRight="3dip"
-                android:paddingTop="3dip"
-                android:src="@drawable/ic_aggregated"
-                android:visibility="gone"
-            />
-
-            <TextView android:id="@+id/name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:textAppearance="?android:attr/textAppearanceMedium"
-                android:textStyle="bold"
-                android:shadowColor="#BB000000"
-                android:shadowRadius="2.75"
-                />
-        </LinearLayout>
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textStyle="bold"
+            android:shadowColor="#BB000000"
+            android:shadowRadius="2.75"
+         />
 
         <TextView android:id="@+id/phonetic_name"
             android:layout_width="match_parent"
@@ -120,7 +104,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:visibility="gone"
         android:contentDescription="@string/description_star"
         style="?android:attr/starStyle" />
 </LinearLayout>
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index f005efa..bfbd4d7 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -166,11 +166,10 @@
         final View view = inflater.inflate(R.layout.contact_detail_fragment, container, false);
 
         setHasOptionsMenu(true);
-        
+
         mInflater = inflater;
 
         mHeaderView = (ContactDetailHeaderView) view.findViewById(R.id.contact_header_widget);
-        mHeaderView.showStar(true);
         mHeaderView.setExcludeMimes(new String[] {
             Contacts.CONTENT_ITEM_TYPE
         });
@@ -238,15 +237,7 @@
 
     private void bindData() {
         // Set the header
-        mHeaderView.setContactUri(mContactData.getLookupUri());
-        mHeaderView.setDisplayName(mContactData.getDisplayName(),
-                mContactData.getPhoneticName());
-        mHeaderView.setPhotoId(mContactData.getPhotoId(), mContactData.getLookupUri());
-        mHeaderView.setStared(mContactData.getStarred());
-        mHeaderView.setPresence(mContactData.getPresence());
-        mHeaderView.setStatus(
-                mContactData.getStatus(), mContactData.getStatusTimestamp(),
-                mContactData.getStatusLabel(), mContactData.getStatusResPackage());
+        mHeaderView.loadData(mContactData);
 
         // Build up the contact entries
         buildEntries();
diff --git a/src/com/android/contacts/views/detail/ContactDetailHeaderView.java b/src/com/android/contacts/views/detail/ContactDetailHeaderView.java
index fcff6c1..85afd2d 100644
--- a/src/com/android/contacts/views/detail/ContactDetailHeaderView.java
+++ b/src/com/android/contacts/views/detail/ContactDetailHeaderView.java
@@ -17,27 +17,24 @@
 package com.android.contacts.views.detail;
 
 import com.android.contacts.R;
+import com.android.contacts.views.ContactLoader;
 
-import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
-import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Entity;
+import android.content.Entity.NamedContentValues;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.SystemClock;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.StatusUpdates;
-import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -54,29 +51,24 @@
 
 /**
  * Header for displaying a title bar with contact info. You
- * can bind specific values on the header
+ * can bind specific values by calling
+ * {@link ContactDetailHeaderView#loadData(com.android.contacts.views.ContactLoader.Result)}
  */
 public class ContactDetailHeaderView extends FrameLayout implements View.OnClickListener {
 
     private static final String TAG = "ContactDetailHeaderView";
 
     private TextView mDisplayNameView;
-    private View mAggregateBadge;
     private TextView mPhoneticNameView;
     private CheckBox mStarredView;
     private QuickContactBadge mPhotoView;
     private ImageView mPresenceView;
     private TextView mStatusView;
     private TextView mStatusAttributionView;
-    private int mNoPhotoResource;
-    private QueryHandler mQueryHandler;
     private ImageButton mEditButton;
 
     private Uri mContactUri;
-
-    protected String[] mExcludeMimes = null;
-
-    protected ContentResolver mContentResolver;
+    private Listener mListener;
 
     /**
      * Interface for callbacks invoked when the user interacts with a header.
@@ -87,73 +79,6 @@
         public void onEditClicked();
     }
 
-    private Listener mListener;
-
-
-    private interface ContactQuery {
-        //Projection used for the summary info in the header.
-        String[] COLUMNS = new String[] {
-            Contacts._ID,
-            Contacts.LOOKUP_KEY,
-            Contacts.PHOTO_ID,
-            Contacts.DISPLAY_NAME,
-            Contacts.PHONETIC_NAME,
-            Contacts.STARRED,
-            Contacts.CONTACT_PRESENCE,
-            Contacts.CONTACT_STATUS,
-            Contacts.CONTACT_STATUS_TIMESTAMP,
-            Contacts.CONTACT_STATUS_RES_PACKAGE,
-            Contacts.CONTACT_STATUS_LABEL,
-        };
-        int _ID = 0;
-        int LOOKUP_KEY = 1;
-        int PHOTO_ID = 2;
-        int DISPLAY_NAME = 3;
-        int PHONETIC_NAME = 4;
-        //TODO: We need to figure out how we're going to get the phonetic name.
-        //static final int HEADER_PHONETIC_NAME_COLUMN_INDEX
-        int STARRED = 5;
-        int CONTACT_PRESENCE_STATUS = 6;
-        int CONTACT_STATUS = 7;
-        int CONTACT_STATUS_TIMESTAMP = 8;
-        int CONTACT_STATUS_RES_PACKAGE = 9;
-        int CONTACT_STATUS_LABEL = 10;
-    }
-
-    private interface PhotoQuery {
-        String[] COLUMNS = new String[] {
-            Photo.PHOTO
-        };
-
-        int PHOTO = 0;
-    }
-
-    //Projection used for looking up contact id from phone number
-    protected static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
-        PhoneLookup._ID,
-        PhoneLookup.LOOKUP_KEY,
-    };
-    protected static final int PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
-    protected static final int PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
-
-    //Projection used for looking up contact id from email address
-    protected static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
-        RawContacts.CONTACT_ID,
-        Contacts.LOOKUP_KEY,
-    };
-    protected static final int EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
-    protected static final int EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
-
-    protected static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
-        Contacts._ID,
-    };
-    protected static final int CONTACT_LOOKUP_ID_COLUMN_INDEX = 0;
-
-    private static final int TOKEN_CONTACT_INFO = 0;
-    private static final int TOKEN_PHONE_LOOKUP = 1;
-    private static final int TOKEN_EMAIL_LOOKUP = 2;
-    private static final int TOKEN_PHOTO_QUERY = 3;
-
     public ContactDetailHeaderView(Context context) {
         this(context, null);
     }
@@ -165,14 +90,11 @@
     public ContactDetailHeaderView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        mContentResolver = mContext.getContentResolver();
-
         final LayoutInflater inflater =
             (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         inflater.inflate(R.layout.contact_detail_header_view, this);
 
         mDisplayNameView = (TextView) findViewById(R.id.name);
-        mAggregateBadge = findViewById(R.id.aggregate_badge);
 
         mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
 
@@ -188,27 +110,49 @@
 
         mStatusView = (TextView)findViewById(R.id.status);
         mStatusAttributionView = (TextView)findViewById(R.id.status_date);
-
-        // Set the photo with a random "no contact" image
-        long now = SystemClock.elapsedRealtime();
-        int num = (int) now & 0xf;
-        if (num < 9) {
-            // Leaning in from right, common
-            mNoPhotoResource = R.drawable.ic_contact_picture;
-        } else if (num < 14) {
-            // Leaning in from left uncommon
-            mNoPhotoResource = R.drawable.ic_contact_picture_2;
-        } else {
-            // Coming in from the top, rare
-            mNoPhotoResource = R.drawable.ic_contact_picture_3;
-        }
-
-        resetAsyncQueryHandler();
     }
 
-    public void enableClickListeners() {
-        mDisplayNameView.setOnClickListener(this);
-        mPhotoView.setOnClickListener(this);
+    /**
+     * Loads the data from the Loader-Result. This is the only function that has to be called
+     * from the outside to fully setup the View
+     */
+    public void loadData(ContactLoader.Result contactData) {
+        mContactUri = contactData.getLookupUri();
+        mPhotoView.assignContactUri(contactData.getLookupUri());
+
+        setDisplayName(contactData.getDisplayName(), contactData.getPhoneticName());
+        setPhoto(findPhoto(contactData));
+        setStared(contactData.getStarred());
+        setPresence(contactData.getPresence());
+        setStatus(
+                contactData.getStatus(), contactData.getStatusTimestamp(),
+                contactData.getStatusLabel(), contactData.getStatusResPackage());
+    }
+
+    /**
+     * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
+     * not found, returns null
+     */
+    private Bitmap findPhoto(ContactLoader.Result contactData) {
+        final long photoId = contactData.getPhotoId();
+
+        for (Entity entity : contactData.getEntities()) {
+            for (NamedContentValues subValue : entity.getSubValues()) {
+                final ContentValues entryValues = subValue.values;
+                final long dataId = entryValues.getAsLong(Data._ID);
+                final String mimeType = entryValues.getAsString(Data.MIMETYPE);
+
+                if (dataId == photoId) {
+                    // Correct Data Id but incorrect MimeType? Don't load
+                    if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) return null;
+                    final byte[] binaryData = entryValues.getAsByteArray(Photo.PHOTO);
+                    if (binaryData == null) return null;
+                    return BitmapFactory.decodeByteArray(binaryData, 0, binaryData.length);
+                }
+            }
+        }
+
+        return null;
     }
 
     /**
@@ -236,211 +180,51 @@
         }
     }
 
-    private class QueryHandler extends AsyncQueryHandler {
-
-        public QueryHandler(ContentResolver cr) {
-            super(cr);
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            try{
-                if (this != mQueryHandler) {
-                    Log.d(TAG, "onQueryComplete: discard result, the query handler is reset!");
-                    return;
-                }
-
-                switch (token) {
-                    case TOKEN_PHOTO_QUERY: {
-                        //Set the photo
-                        Bitmap photoBitmap = null;
-                        if (cursor != null && cursor.moveToFirst()
-                                && !cursor.isNull(PhotoQuery.PHOTO)) {
-                            byte[] photoData = cursor.getBlob(PhotoQuery.PHOTO);
-                            photoBitmap = BitmapFactory.decodeByteArray(photoData, 0,
-                                    photoData.length, null);
-                        }
-
-                        if (photoBitmap == null) {
-                            photoBitmap = loadPlaceholderPhoto(null);
-                        }
-                        setPhoto(photoBitmap);
-                        if (cookie != null && cookie instanceof Uri) {
-                            mPhotoView.assignContactUri((Uri) cookie);
-                        }
-                        invalidate();
-                        break;
-                    }
-                    case TOKEN_CONTACT_INFO: {
-                        if (cursor != null && cursor.moveToFirst()) {
-                            bindContactInfo(cursor);
-                            final Uri lookupUri = Contacts.getLookupUri(
-                                    cursor.getLong(ContactQuery._ID),
-                                    cursor.getString(ContactQuery.LOOKUP_KEY));
-
-                            final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
-
-                            setPhotoId(photoId, lookupUri);
-                        } else {
-                            // shouldn't really happen
-                            setDisplayName(null, null);
-                            setSocialSnippet(null);
-                            setPhoto(loadPlaceholderPhoto(null));
-                        }
-                        break;
-                    }
-                    case TOKEN_PHONE_LOOKUP: {
-                        if (cursor != null && cursor.moveToFirst()) {
-                            long contactId = cursor.getLong(PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX);
-                            String lookupKey = cursor.getString(
-                                    PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
-                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
-                                    false /* don't reset query handler */);
-                        } else {
-                            String phoneNumber = (String) cookie;
-                            setDisplayName(phoneNumber, null);
-                            setSocialSnippet(null);
-                            setPhoto(loadPlaceholderPhoto(null));
-                            mPhotoView.assignContactFromPhone(phoneNumber, true);
-                        }
-                        break;
-                    }
-                    case TOKEN_EMAIL_LOOKUP: {
-                        if (cursor != null && cursor.moveToFirst()) {
-                            long contactId = cursor.getLong(EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX);
-                            String lookupKey = cursor.getString(
-                                    EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
-                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
-                                    false /* don't reset query handler */);
-                        } else {
-                            String emailAddress = (String) cookie;
-                            setDisplayName(emailAddress, null);
-                            setSocialSnippet(null);
-                            setPhoto(loadPlaceholderPhoto(null));
-                            mPhotoView.assignContactFromEmail(emailAddress, true);
-                        }
-                        break;
-                    }
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        }
-    }
-
     /**
-     * Turn on/off showing of the aggregate badge element.
+     * Set the starred state of this header widget.
      */
-    public void showAggregateBadge(boolean showBagde) {
-        mAggregateBadge.setVisibility(showBagde ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Turn on/off showing of the star element.
-     */
-    public void showStar(boolean showStar) {
-        mStarredView.setVisibility(showStar ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Manually set the starred state of this header widget. This doesn't change
-     * the underlying {@link Contacts} value, only the UI state.
-     */
-    public void setStared(boolean starred) {
+    private void setStared(boolean starred) {
         mStarredView.setChecked(starred);
     }
 
     /**
-     * Manually set the presence.
+     * Set the presence. If presence is null, it is hidden.
      */
-    public void setPresence(int presence) {
-        mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
-    }
-
-    /**
-     * Manually set the presence. If presence is null, it is hidden.
-     * This doesn't change the underlying {@link Contacts} value, only the UI state.
-     * @hide
-     */
-    public void setPresence(Integer presence) {
+    private void setPresence(Integer presence) {
         if (presence == null) {
-            showPresence(false);
+            mPresenceView.setVisibility(View.GONE);
         } else {
-            showPresence(true);
-            setPresence(presence.intValue());
+            mPresenceView.setVisibility(View.VISIBLE);
+            mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(
+                    presence.intValue()));
         }
     }
 
     /**
-     * Turn on/off showing the presence.
-     * @hide this is here for consistency with setStared/showStar and should be public
+     * Set the photo to display in the header. If bitmap is null, the default placeholder
+     * image is shown
      */
-    public void showPresence(boolean showPresence) {
-        mPresenceView.setVisibility(showPresence ? View.VISIBLE : View.GONE);
+    private void setPhoto(Bitmap bitmap) {
+        mPhotoView.setImageBitmap(bitmap == null ? loadPlaceholderPhoto() : bitmap);
     }
 
     /**
-     * Manually set the contact uri without loading any data
+     * Set the display name and phonetic name to show in the header.
      */
-    public void setContactUri(Uri uri) {
-        setContactUri(uri, true);
-    }
-
-    /**
-     * Manually set the contact uri without loading any data
-     */
-    public void setContactUri(Uri uri, boolean sendToQuickContact) {
-        mContactUri = uri;
-        if (sendToQuickContact) {
-            mPhotoView.assignContactUri(uri);
-        }
-    }
-
-    /**
-     * Manually set the photo to display in the header. This doesn't change the
-     * underlying {@link Contacts}, only the UI state.
-     */
-    public void setPhoto(Bitmap bitmap) {
-        mPhotoView.setImageBitmap(bitmap);
-    }
-
-    /**
-     * Manually set the photo given its id. If the id is 0, a placeholder picture will
-     * be loaded. For any other Id, an async query is started
-     * @hide
-     */
-    public void setPhotoId(final long photoId, final Uri lookupUri) {
-        if (photoId == 0) {
-            setPhoto(loadPlaceholderPhoto(null));
-            mPhotoView.assignContactUri(lookupUri);
-            invalidate();
-        } else {
-            startPhotoQuery(photoId, lookupUri,
-                    false /* don't reset query handler */);
-        }
-    }
-
-    /**
-     * Manually set the display name and phonetic name to show in the header.
-     * This doesn't change the underlying {@link Contacts}, only the UI state.
-     */
-    public void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
+    private void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
         mDisplayNameView.setText(displayName);
-        if (!TextUtils.isEmpty(phoneticName)) {
+        if (TextUtils.isEmpty(phoneticName)) {
+            mPhoneticNameView.setVisibility(View.GONE);
+        } else {
             mPhoneticNameView.setText(phoneticName);
             mPhoneticNameView.setVisibility(View.VISIBLE);
-        } else {
-            mPhoneticNameView.setVisibility(View.GONE);
         }
     }
 
     /**
-     * Manually set the social snippet text to display in the header. This doesn't change the
-     * underlying {@link Contacts}, only the UI state.
+     * Set the social snippet text to display in the header.
      */
-    public void setSocialSnippet(CharSequence snippet) {
+    private void setSocialSnippet(CharSequence snippet) {
         if (snippet == null) {
             mStatusView.setVisibility(View.GONE);
             mStatusAttributionView.setVisibility(View.GONE);
@@ -451,16 +235,14 @@
     }
 
     /**
-     * Manually set the status attribution text to display in the header.
-     * This doesn't change the underlying {@link Contacts}, only the UI state.
-     * @hide
+     * Set the status attribution text to display in the header.
      */
-    public void setStatusAttribution(CharSequence attribution) {
-        if (attribution != null) {
+    private void setStatusAttribution(CharSequence attribution) {
+        if (attribution == null) {
+            mStatusAttributionView.setVisibility(View.GONE);
+        } else {
             mStatusAttributionView.setText(attribution);
             mStatusAttributionView.setVisibility(View.VISIBLE);
-        } else {
-            mStatusAttributionView.setVisibility(View.GONE);
         }
     }
 
@@ -470,14 +252,11 @@
      * profile icon.
      */
     public void setExcludeMimes(String[] excludeMimes) {
-        mExcludeMimes = excludeMimes;
         mPhotoView.setExcludeMimes(excludeMimes);
     }
 
     /**
-     * Manually set all the status values to display in the header.
-     * This doesn't change the underlying {@link Contacts}, only the UI state.
-     * @hide
+     * Set all the status values to display in the header.
      * @param status             The status of the contact. If this is either null or empty,
      *                           the status is cleared and the other parameters are ignored.
      * @param statusTimestamp    The timestamp (retrieved via a call to
@@ -488,7 +267,7 @@
      * @param statusResPackage   The name of the resource package containing the resource string
      *                           referenced in the parameter statusLabel.
      */
-    public void setStatus(final String status, final Long statusTimestamp,
+    private void setStatus(final String status, final Long statusTimestamp,
             final Integer statusLabel, final String statusResPackage) {
         if (TextUtils.isEmpty(status)) {
             setSocialSnippet(null);
@@ -556,141 +335,6 @@
         setStatusAttribution(attribution);
     }
 
-    /**
-     * Convenience method for binding all available data from an existing
-     * contact.
-     *
-     * @param contactUri a {Contacts.CONTENT_URI} style URI.
-     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
-     */
-    private void bindFromContactUriInternal(Uri contactUri, boolean resetQueryHandler) {
-        mContactUri = contactUri;
-        startContactQuery(contactUri, resetQueryHandler);
-    }
-
-    /**
-     * Convenience method for binding all available data from an existing
-     * contact.
-     *
-     * @param emailAddress The email address used to do a reverse lookup in
-     * the contacts database. If more than one contact contains this email
-     * address, one of them will be chosen to bind to.
-     */
-    public void bindFromEmail(String emailAddress) {
-        resetAsyncQueryHandler();
-
-        mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, emailAddress,
-                Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)),
-                EMAIL_LOOKUP_PROJECTION, null, null, null);
-    }
-
-    /**
-     * Convenience method for binding all available data from an existing
-     * contact.
-     *
-     * @param number The phone number used to do a reverse lookup in
-     * the contacts database. If more than one contact contains this phone
-     * number, one of them will be chosen to bind to.
-     */
-    public void bindFromPhoneNumber(String number) {
-        resetAsyncQueryHandler();
-
-        mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, number,
-                Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
-                PHONE_LOOKUP_PROJECTION, null, null, null);
-    }
-
-    /**
-     * startContactQuery
-     *
-     * internal method to query contact by Uri.
-     *
-     * @param contactUri the contact uri
-     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not
-     */
-    private void startContactQuery(Uri contactUri, boolean resetQueryHandler) {
-        if (resetQueryHandler) {
-            resetAsyncQueryHandler();
-        }
-
-        mQueryHandler.startQuery(TOKEN_CONTACT_INFO, contactUri, contactUri, ContactQuery.COLUMNS,
-                null, null, null);
-    }
-
-    /**
-     * startPhotoQuery
-     *
-     * internal method to query contact photo by photo id and uri.
-     *
-     * @param photoId the photo id.
-     * @param lookupKey the lookup uri.
-     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
-     */
-    protected void startPhotoQuery(long photoId, Uri lookupKey, boolean resetQueryHandler) {
-        if (resetQueryHandler) {
-            resetAsyncQueryHandler();
-        }
-
-        mQueryHandler.startQuery(TOKEN_PHOTO_QUERY, lookupKey,
-                ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PhotoQuery.COLUMNS,
-                null, null, null);
-    }
-
-    /**
-     * Method to force this widget to forget everything it knows about the contact.
-     * We need to stop any existing async queries for phone, email, contact, and photos.
-     */
-    public void wipeClean() {
-        resetAsyncQueryHandler();
-
-        setDisplayName(null, null);
-        setPhoto(loadPlaceholderPhoto(null));
-        setSocialSnippet(null);
-        setPresence(0);
-        mContactUri = null;
-        mExcludeMimes = null;
-    }
-
-
-    private void resetAsyncQueryHandler() {
-        // the api AsyncQueryHandler.cancelOperation() doesn't really work. Since we really
-        // need the old async queries to be cancelled, let's do it the hard way.
-        mQueryHandler = new QueryHandler(mContentResolver);
-    }
-
-    /**
-     * Bind the contact details provided by the given {@link Cursor}.
-     */
-    protected void bindContactInfo(Cursor c) {
-        final String displayName = c.getString(ContactQuery.DISPLAY_NAME);
-        final String phoneticName = c.getString(ContactQuery.PHONETIC_NAME);
-        this.setDisplayName(displayName, phoneticName);
-
-        final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
-        setStared(starred);
-
-        //Set the presence status
-        if (!c.isNull(ContactQuery.CONTACT_PRESENCE_STATUS)) {
-            int presence = c.getInt(ContactQuery.CONTACT_PRESENCE_STATUS);
-            setPresence(presence);
-            showPresence(true);
-        } else {
-            showPresence(false);
-        }
-
-        //Set the status update
-        final String status = c.getString(ContactQuery.CONTACT_STATUS);
-        final Long statusTimestamp = c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
-                ? null
-                : c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
-        final Integer statusLabel = c.isNull(ContactQuery.CONTACT_STATUS_LABEL)
-                ? null
-                : c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
-        final String statusResPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
-
-        setStatus(status, statusTimestamp, statusLabel, statusResPackage);
-    }
-
     public void onClick(View view) {
         switch (view.getId()) {
             case R.id.edit: {
@@ -701,9 +345,10 @@
                 // Toggle "starred" state
                 // Make sure there is a contact
                 if (mContactUri != null) {
+                    // TODO: This should be done in the background
                     final ContentValues values = new ContentValues(1);
                     values.put(Contacts.STARRED, mStarredView.isChecked());
-                    mContentResolver.update(mContactUri, values, null, null);
+                    mContext.getContentResolver().update(mContactUri, values, null, null);
                 }
                 break;
             }
@@ -718,11 +363,22 @@
         }
     }
 
-    private Bitmap loadPlaceholderPhoto(BitmapFactory.Options options) {
-        if (mNoPhotoResource == 0) {
-            return null;
+    private Bitmap loadPlaceholderPhoto() {
+        // Set the photo with a random "no contact" image
+        final long now = SystemClock.elapsedRealtime();
+        final int num = (int) now & 0xf;
+        final int resourceId;
+        if (num < 9) {
+            // Leaning in from right, common
+            resourceId = R.drawable.ic_contact_picture;
+        } else if (num < 14) {
+            // Leaning in from left uncommon
+            resourceId = R.drawable.ic_contact_picture_2;
+        } else {
+            // Coming in from the top, rare
+            resourceId = R.drawable.ic_contact_picture_3;
         }
-        return BitmapFactory.decodeResource(mContext.getResources(),
-                mNoPhotoResource, options);
+
+        return BitmapFactory.decodeResource(mContext.getResources(), resourceId);
     }
 }