Add support for letter tile avatars to ContactsPhotoManager

* Add LetterTileDrawable, a custom drawable used to display letter tiles
for contacts without a contact photo, instead of static bitmap resource
drawables.

* Add a class DefaultImageRequest to ContactPhotoManager. This is essentially
a data holder object used to store and retrieve various parameters that
clients can use to configure the way they want their requested letter tile
avatars to be drawn.

* Add LetterTileImageProvider to ContactPhotoManager. LetterTileImageProvider
extends DefaultImageProvider, but returns instances of LetterTileDrawable
instead of BitmapDrawables when a default contact image is needed.

* Add two new APIs to ContactPhotoManager:
getDefaultAvatarForContact - This returns an instance of a LetterTileDrawable,
configured with the display parameters stored in the provided
DefaultImageRequest.
getDefaultAvatarUriForContact - This returns an uri which the
ContactPhotoManager can use to recreate a DefaultImageRequest and hence
a LetterTileDrawable.

* Modify ContactPhotoManager.loadPhoto and loadThumbnail to accept a
DefaultImageRequest as a parameter.

* Modify the following classes to use the new ContactPhotoManager APIs. These
affect both the Dialer and People apps.
ContactEntryListADapter
ContactListAdapter
ContactTileView
PhoneNumberListAdapter

Bug: 13101785
Change-Id: Id1a87b459d6e63c42049739059a3b3ee202af837
(cherry picked from commit 6084726fbdda78bdb16e2d4cc1c3b81c84fd5da1)
diff --git a/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java b/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java
index 1398626..32fb899 100644
--- a/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java
+++ b/TestCommon/src/com/android/contacts/common/test/mocks/MockContactPhotoManager.java
@@ -29,14 +29,14 @@
 public class MockContactPhotoManager extends ContactPhotoManager {
     @Override
     public void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
-            DefaultImageProvider defaultProvider) {
-        defaultProvider.applyDefaultImage(view, -1, darkTheme);
+            DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider) {
+        defaultProvider.applyDefaultImage(view, -1, darkTheme, null);
     }
 
     @Override
     public void loadPhoto(ImageView view, Uri photoUri, int requestedExtent, boolean darkTheme,
-            DefaultImageProvider defaultProvider) {
-        defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme);
+            DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider) {
+        defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme, null);
     }
 
     @Override
diff --git a/res/drawable-hdpi/ic_contact_picture_180_holo_dark.png b/res/drawable-hdpi/ic_contact_picture_180_holo_dark.png
deleted file mode 100644
index a17da61..0000000
--- a/res/drawable-hdpi/ic_contact_picture_180_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_contact_picture_180_holo_light.png b/res/drawable-hdpi/ic_contact_picture_180_holo_light.png
deleted file mode 100644
index 7195b07..0000000
--- a/res/drawable-hdpi/ic_contact_picture_180_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_contact_picture_holo_dark.png b/res/drawable-hdpi/ic_contact_picture_holo_dark.png
deleted file mode 100644
index 314fa00..0000000
--- a/res/drawable-hdpi/ic_contact_picture_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_contact_picture_holo_light.png b/res/drawable-hdpi/ic_contact_picture_holo_light.png
deleted file mode 100644
index 87447e5..0000000
--- a/res/drawable-hdpi/ic_contact_picture_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_list_item_avatar.png b/res/drawable-hdpi/ic_list_item_avatar.png
new file mode 100644
index 0000000..0a239d0
--- /dev/null
+++ b/res/drawable-hdpi/ic_list_item_avatar.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_list_item_businessavatar.png b/res/drawable-hdpi/ic_list_item_businessavatar.png
new file mode 100644
index 0000000..c943fc9
--- /dev/null
+++ b/res/drawable-hdpi/ic_list_item_businessavatar.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_voicemail_avatar.png b/res/drawable-hdpi/ic_voicemail_avatar.png
new file mode 100644
index 0000000..2fb7826
--- /dev/null
+++ b/res/drawable-hdpi/ic_voicemail_avatar.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture_180_holo_dark.png b/res/drawable-mdpi/ic_contact_picture_180_holo_dark.png
deleted file mode 100644
index acba333..0000000
--- a/res/drawable-mdpi/ic_contact_picture_180_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture_180_holo_light.png b/res/drawable-mdpi/ic_contact_picture_180_holo_light.png
deleted file mode 100644
index 70d9697..0000000
--- a/res/drawable-mdpi/ic_contact_picture_180_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture_holo_dark.png b/res/drawable-mdpi/ic_contact_picture_holo_dark.png
deleted file mode 100644
index 6876777..0000000
--- a/res/drawable-mdpi/ic_contact_picture_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture_holo_light.png b/res/drawable-mdpi/ic_contact_picture_holo_light.png
deleted file mode 100644
index c2aef96..0000000
--- a/res/drawable-mdpi/ic_contact_picture_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_list_item_avatar.png b/res/drawable-mdpi/ic_list_item_avatar.png
new file mode 100644
index 0000000..871212b
--- /dev/null
+++ b/res/drawable-mdpi/ic_list_item_avatar.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_list_item_businessavatar.png b/res/drawable-mdpi/ic_list_item_businessavatar.png
new file mode 100644
index 0000000..43135ea
--- /dev/null
+++ b/res/drawable-mdpi/ic_list_item_businessavatar.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_voicemail_avatar.png b/res/drawable-mdpi/ic_voicemail_avatar.png
new file mode 100644
index 0000000..4005f24
--- /dev/null
+++ b/res/drawable-mdpi/ic_voicemail_avatar.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture_180_holo_dark.png b/res/drawable-xhdpi/ic_contact_picture_180_holo_dark.png
deleted file mode 100644
index c4c001e..0000000
--- a/res/drawable-xhdpi/ic_contact_picture_180_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png b/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png
deleted file mode 100644
index 989844e..0000000
--- a/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture_holo_dark.png b/res/drawable-xhdpi/ic_contact_picture_holo_dark.png
deleted file mode 100644
index ddf797f..0000000
--- a/res/drawable-xhdpi/ic_contact_picture_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture_holo_light.png b/res/drawable-xhdpi/ic_contact_picture_holo_light.png
deleted file mode 100644
index 914d85c..0000000
--- a/res/drawable-xhdpi/ic_contact_picture_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_list_item_avatar.png b/res/drawable-xhdpi/ic_list_item_avatar.png
new file mode 100644
index 0000000..2a73a2e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_list_item_avatar.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_list_item_businessavatar.png b/res/drawable-xhdpi/ic_list_item_businessavatar.png
new file mode 100644
index 0000000..14742b5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_list_item_businessavatar.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_voicemail_avatar.png b/res/drawable-xhdpi/ic_voicemail_avatar.png
new file mode 100644
index 0000000..f24505d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_voicemail_avatar.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_contact_picture_180_holo_dark.png b/res/drawable-xxhdpi/ic_contact_picture_180_holo_dark.png
deleted file mode 100644
index 6e057ac..0000000
--- a/res/drawable-xxhdpi/ic_contact_picture_180_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_contact_picture_180_holo_light.png b/res/drawable-xxhdpi/ic_contact_picture_180_holo_light.png
deleted file mode 100644
index b04c82f..0000000
--- a/res/drawable-xxhdpi/ic_contact_picture_180_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_contact_picture_holo_dark.png b/res/drawable-xxhdpi/ic_contact_picture_holo_dark.png
deleted file mode 100644
index 52a69c3..0000000
--- a/res/drawable-xxhdpi/ic_contact_picture_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_contact_picture_holo_light.png b/res/drawable-xxhdpi/ic_contact_picture_holo_light.png
deleted file mode 100644
index 9150df2..0000000
--- a/res/drawable-xxhdpi/ic_contact_picture_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_list_item_avatar.png b/res/drawable-xxhdpi/ic_list_item_avatar.png
new file mode 100644
index 0000000..5665631
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_list_item_avatar.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_list_item_businessavatar.png b/res/drawable-xxhdpi/ic_list_item_businessavatar.png
new file mode 100644
index 0000000..484591c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_list_item_businessavatar.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_voicemail_avatar.png b/res/drawable-xxhdpi/ic_voicemail_avatar.png
new file mode 100644
index 0000000..182def8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_voicemail_avatar.png
Binary files differ
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 65abd95..3e5127e 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -47,4 +47,19 @@
 
     <color name="textColorIconOverlay">#fff</color>
     <color name="textColorIconOverlayShadow">#000</color>
+
+    <!-- Make sure to also update LetterTileProvider#NUM_OF_TILE_COLORS when adding or removing colors -->
+    <array name="letter_tile_colors">
+        <item>#f16364</item>
+        <item>#f58559</item>
+        <item>#f9a43e</item>
+        <item>#e4c62e</item>
+        <item>#67bf74</item>
+        <item>#59a2be</item>
+        <item>#2093cd</item>
+        <item>#ad62a7</item>
+    </array>
+    <color name="letter_tile_default_color">#cccccc</color>
+
+    <color name="letter_tile_font_color">#ffffff</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d407da3..efe56ff 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -81,4 +81,10 @@
     -->
     <dimen name="contact_phone_list_empty_description_size">20sp</dimen>
     <dimen name="contact_phone_list_empty_description_padding">10dip</dimen>
+
+    <!-- Dimensions for contact letter tiles -->
+    <dimen name="tile_letter_font_size">40dp</dimen>
+    <dimen name="tile_letter_font_size_small">20dp</dimen>
+    <dimen name="tile_divider_width">1dp</dimen>
+    <item name="letter_to_tile_ratio" type="dimen">67%</item>
 </resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 9a73195..093901b 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -40,4 +40,9 @@
     <item type="id" name="cliv_phoneticname_textview"/>
     <item type="id" name="cliv_label_textview"/>
     <item type="id" name="cliv_data_view"/>
+    
+    <!-- For tag ids used by ContactPhotoManager to tag views with contact details -->
+    <item type="id" name="tag_display_name"/>
+    <item type="id" name="tag_identifier"/>
+    <item type="id" name="tag_contact_type"/>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d4bb1ff..c63fb33 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -726,4 +726,7 @@
     <!-- Attribution of a contact status update, when the time of update is known -->
     <string name="contact_status_update_attribution_with_date"><xliff:g id="date" example="3 hours ago">%1$s</xliff:g> via <xliff:g id="source" example="Google Talk">%2$s</xliff:g></string>
 
+    <!-- Font family used when drawing letters for letter tile avatars.
+         Do not translate. -->
+    <string name="letter_tile_letter_font_family">sans-serif-thin</string>
 </resources>
diff --git a/src/com/android/contacts/common/ContactPhotoManager.java b/src/com/android/contacts/common/ContactPhotoManager.java
index 995201d..916c9b5 100644
--- a/src/com/android/contacts/common/ContactPhotoManager.java
+++ b/src/com/android/contacts/common/ContactPhotoManager.java
@@ -33,6 +33,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
 import android.net.Uri;
+import android.net.Uri.Builder;
 import android.os.Handler;
 import android.os.Handler.Callback;
 import android.os.HandlerThread;
@@ -48,9 +49,11 @@
 import android.util.TypedValue;
 import android.widget.ImageView;
 
+import com.android.contacts.common.lettertiles.LetterTileDrawable;
 import com.android.contacts.common.util.BitmapUtil;
 import com.android.contacts.common.util.MemoryUtils;
 import com.android.contacts.common.util.UriUtils;
+
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
@@ -73,48 +76,231 @@
     static final boolean DEBUG = false; // Don't submit with true
     static final boolean DEBUG_SIZES = false; // Don't submit with true
 
-    /** Caches 180dip in pixel. This is used to detect whether to show the hires or lores version
-     * of the default avatar */
-    private static int s180DipInPixel = -1;
+    /** Contact type constants used for default letter images */
+    public static final int TYPE_PERSON = LetterTileDrawable.TYPE_PERSON;
+    public static final int TYPE_BUSINESS = LetterTileDrawable.TYPE_BUSINESS;
+    public static final int TYPE_VOICEMAIL = LetterTileDrawable.TYPE_VOICEMAIL;
+    public static final int TYPE_DEFAULT = LetterTileDrawable.TYPE_DEFAULT;
+
+    /** Scale and offset default constants used for default letter images */
+    public static final float SCALE_DEFAULT = 1.0f;
+    public static final float OFFSET_DEFAULT = 0.0f;
+
+    /** Uri-related constants used for default letter images */
+    private static final String DISPLAY_NAME_PARAM_KEY = "display_name";
+    private static final String IDENTIFIER_PARAM_KEY = "identifier";
+    private static final String CONTACT_TYPE_PARAM_KEY = "contact_type";
+    private static final String SCALE_PARAM_KEY = "scale";
+    private static final String OFFSET_PARAM_KEY = "offset";
+    private static final String DEFAULT_IMAGE_URI_SCHEME = "defaultimage";
+    private static final Uri DEFAULT_IMAGE_URI = Uri.parse(DEFAULT_IMAGE_URI_SCHEME + "://");
 
     public static final String CONTACT_PHOTO_SERVICE = "contactPhotos";
 
-    /**
-     * Returns the resource id of the default avatar. Tries to find a resource that is bigger
-     * than the given extent (width or height). If extent=-1, a thumbnail avatar is returned
-     */
-    public static int getDefaultAvatarResId(Context context, int extent, boolean darkTheme) {
-        // TODO: Is it worth finding a nicer way to do hires/lores here? In practice, the
-        // default avatar doesn't look too different when stretched
-        if (s180DipInPixel == -1) {
-            Resources r = context.getResources();
-            s180DipInPixel = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 180,
-                    r.getDisplayMetrics());
-        }
+    // Static field used to cache the default letter avatar drawable that is created
+    // using a null {@link DefaultImageRequest}
+    private static Drawable sDefaultLetterAvatar = null;
 
-        final boolean hires = (extent != -1) && (extent > s180DipInPixel);
-        return getDefaultAvatarResId(hires, darkTheme);
+    /**
+     * Given a {@link DefaultImageRequest}, returns a {@link Drawable}, that when drawn, will
+     * draw a letter tile avatar based on the request parameters defined in the
+     * {@link DefaultImageRequest}.
+     */
+    public static Drawable getDefaultAvatarDrawableForContact(Resources resources, boolean hires,
+            DefaultImageRequest defaultImageRequest) {
+        if (defaultImageRequest == null) {
+            if (sDefaultLetterAvatar == null) {
+                // Cache and return the letter tile drawable that is created by a null request,
+                // so that it doesn't have to be recreated every time it is requested again.
+                sDefaultLetterAvatar = LetterTileDefaultImageProvider.getDefaultImageForContact(
+                        resources, null);
+            }
+            return sDefaultLetterAvatar;
+        }
+        return LetterTileDefaultImageProvider.getDefaultImageForContact(resources,
+                defaultImageRequest);
     }
 
-    public static int getDefaultAvatarResId(boolean hires, boolean darkTheme) {
-        if (hires && darkTheme) return R.drawable.ic_contact_picture_180_holo_dark;
-        if (hires) return R.drawable.ic_contact_picture_180_holo_light;
-        if (darkTheme) return R.drawable.ic_contact_picture_holo_dark;
-        return R.drawable.ic_contact_picture_holo_light;
+    /**
+     * Given a {@link DefaultImageRequest}, returns an Uri that can be used to request a
+     * letter tile avatar when passed to the {@link ContactPhotoManager}. The internal
+     * implementation of this uri is not guaranteed to remain the same across application
+     * versions, so the actual uri should never be persisted in long-term storage and reused.
+     *
+     * @param request A {@link DefaultImageRequest} object with the fields configured
+     * to return a
+     * @return A Uri that when later passed to the {@link ContactPhotoManager} via
+     * {@link #loadPhoto(ImageView, Uri, int, boolean, DefaultImageRequest)}, can be
+     * used to request a default contact image, drawn as a letter tile using the
+     * parameters as configured in the provided {@link DefaultImageRequest}
+     */
+    public static Uri getDefaultAvatarUriForContact(DefaultImageRequest request) {
+        final Builder builder = DEFAULT_IMAGE_URI.buildUpon();
+        if (request != null) {
+            if (!TextUtils.isEmpty(request.displayName)) {
+                builder.appendQueryParameter(DISPLAY_NAME_PARAM_KEY, request.displayName);
+            }
+            if (!TextUtils.isEmpty(request.identifier)) {
+                builder.appendQueryParameter(IDENTIFIER_PARAM_KEY, request.identifier);
+            }
+            if (request.contactType != TYPE_DEFAULT) {
+                builder.appendQueryParameter(CONTACT_TYPE_PARAM_KEY,
+                        String.valueOf(request.contactType));
+            }
+            if (request.scale != SCALE_DEFAULT) {
+                builder.appendQueryParameter(SCALE_PARAM_KEY, String.valueOf(request.scale));
+            }
+            if (request.offset != OFFSET_DEFAULT) {
+                builder.appendQueryParameter(OFFSET_PARAM_KEY, String.valueOf(request.offset));
+            }
+
+        }
+        return builder.build();
+    }
+
+    protected static DefaultImageRequest getDefaultImageRequestFromUri(Uri uri) {
+        final DefaultImageRequest request = new DefaultImageRequest(
+                uri.getQueryParameter(DISPLAY_NAME_PARAM_KEY),
+                uri.getQueryParameter(IDENTIFIER_PARAM_KEY));
+        try {
+            String contactType = uri.getQueryParameter(CONTACT_TYPE_PARAM_KEY);
+            if (!TextUtils.isEmpty(contactType)) {
+                request.contactType = Integer.valueOf(contactType);
+            }
+
+            String scale = uri.getQueryParameter(SCALE_PARAM_KEY);
+            if (!TextUtils.isEmpty(scale)) {
+                request.scale = Float.valueOf(scale);
+            }
+
+            String offset = uri.getQueryParameter(OFFSET_PARAM_KEY);
+            if (!TextUtils.isEmpty(offset)) {
+                request.offset = Float.valueOf(offset);
+            }
+        } catch (NumberFormatException e) {
+            Log.w(TAG, "Invalid DefaultImageRequest image parameters provided, ignoring and using "
+                    + "defaults.");
+        }
+
+        return request;
+    }
+
+    protected boolean isDefaultImageUri(Uri uri) {
+        return DEFAULT_IMAGE_URI_SCHEME.equals(uri.getScheme());
+    }
+
+    /**
+     * Contains fields used to contain contact details and other user-defined settings that might
+     * be used by the ContactPhotoManager to generate a default contact image. This contact image
+     * takes the form of a letter or bitmap drawn on top of a colored tile.
+     */
+    public static class DefaultImageRequest {
+        /**
+         * The contact's display name. The display name is used to
+         */
+        public String displayName;
+        /**
+         * A unique and deterministic string that can be used to identify this contact. This is
+         * usually the contact's lookup key, but other contact details can be used as well,
+         * especially for non-local or temporary contacts that might not have a lookup key. This
+         * is used to determine the color of the tile.
+         */
+        public String identifier;
+        /**
+         * The type of this contact. This contact type may be used to decide the kind of
+         * image to use in the case where a unique letter cannot be generated from the contact's
+         * display name and identifier. See:
+         * {@link #TYPE_PERSON}
+         * {@link #TYPE_BUSINESS}
+         * {@link #TYPE_PERSON}
+         * {@link #TYPE_DEFAULT}
+         */
+        public int contactType = TYPE_DEFAULT;
+        /**
+         * The amount to scale the letter or bitmap to, as a ratio of its default size (from a
+         * range of 0.0f to 2.0f). The default value is 1.0f.
+         */
+        public float scale = SCALE_DEFAULT;
+        /**
+         * The amount to vertically offset the letter or image to within the tile.
+         * The provided offset must be within the range of -0.5f to 0.5f.
+         * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of the canvas
+         * it is being drawn on, which means it will be drawn with the center of the letter starting
+         * at the top edge of the canvas.
+         * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of the
+         * canvas it is being drawn on, which means it will be drawn with the center of the letter
+         * starting at the bottom edge of the canvas.
+         * The default is 0.0f, which means the letter is drawn in the exact vertical center of
+         * the tile.
+         */
+        public float offset = OFFSET_DEFAULT;
+
+        public DefaultImageRequest() {
+        }
+
+        public DefaultImageRequest(String displayName, String identifier) {
+            this(displayName, identifier, TYPE_DEFAULT, SCALE_DEFAULT, OFFSET_DEFAULT);
+        }
+
+        public DefaultImageRequest(String displayName, String identifier, int contactType) {
+            this(displayName, identifier, contactType, SCALE_DEFAULT, OFFSET_DEFAULT);
+        }
+
+        public DefaultImageRequest(String displayName, String identifier, int contactType,
+                float scale, float offset) {
+            this.displayName = displayName;
+            this.identifier = identifier;
+            this.contactType = contactType;
+            this.scale = scale;
+            this.offset = offset;
+        }
     }
 
     public static abstract class DefaultImageProvider {
         /**
          * Applies the default avatar to the ImageView. Extent is an indicator for the size (width
          * or height). If darkTheme is set, the avatar is one that looks better on dark background
+         *
+         * @param defaultImageRequest {@link DefaultImageRequest} object that specifies how a
+         * default letter tile avatar should be drawn.
          */
-        public abstract void applyDefaultImage(ImageView view, int extent, boolean darkTheme);
+        public abstract void applyDefaultImage(ImageView view, int extent, boolean darkTheme,
+                DefaultImageRequest defaultImageRequest);
     }
 
-    private static class AvatarDefaultImageProvider extends DefaultImageProvider {
+    /**
+     * A default image provider that applies a letter tile consisting of a colored background
+     * and a letter in the foreground as the default image for a contact. The color of the
+     * background and the type of letter is decided based on the contact's details.
+     */
+    private static class LetterTileDefaultImageProvider extends DefaultImageProvider {
         @Override
-        public void applyDefaultImage(ImageView view, int extent, boolean darkTheme) {
-            view.setImageResource(getDefaultAvatarResId(view.getContext(), extent, darkTheme));
+        public void applyDefaultImage(ImageView view, int extent, boolean darkTheme,
+                DefaultImageRequest defaultImageRequest) {
+            final Drawable drawable = getDefaultImageForContact(view.getResources(),
+                    defaultImageRequest);
+            view.setImageDrawable(drawable);
+        }
+
+        public static Drawable getDefaultImageForContact(Resources resources,
+                DefaultImageRequest defaultImageRequest) {
+            final LetterTileDrawable drawable = new LetterTileDrawable(resources);
+            if (defaultImageRequest != null) {
+                // If the contact identifier is null or empty, fallback to the
+                // displayName. In that case, use {@code null} for the contact's
+                // display name so that a default bitmap will be used instead of a
+                // letter
+                if (TextUtils.isEmpty(defaultImageRequest.identifier)) {
+                    drawable.setContactDetails(null, defaultImageRequest.displayName);
+                } else {
+                    drawable.setContactDetails(defaultImageRequest.displayName,
+                            defaultImageRequest.identifier);
+                }
+                drawable.setContactType(defaultImageRequest.contactType);
+                drawable.setScale(defaultImageRequest.scale);
+                drawable.setOffset(defaultImageRequest.offset);
+            }
+            return drawable;
         }
     }
 
@@ -122,7 +308,8 @@
         private static Drawable sDrawable;
 
         @Override
-        public void applyDefaultImage(ImageView view, int extent, boolean darkTheme) {
+        public void applyDefaultImage(ImageView view, int extent, boolean darkTheme,
+                DefaultImageRequest defaultImageRequest) {
             if (sDrawable == null) {
                 Context context = view.getContext();
                 sDrawable = new ColorDrawable(context.getResources().getColor(
@@ -132,7 +319,7 @@
         }
     }
 
-    public static final DefaultImageProvider DEFAULT_AVATAR = new AvatarDefaultImageProvider();
+    public static DefaultImageProvider DEFAULT_AVATAR = new LetterTileDefaultImageProvider();
 
     public static final DefaultImageProvider DEFAULT_BLANK = new BlankDefaultImageProvider();
 
@@ -157,20 +344,23 @@
      * from the database.
      */
     public abstract void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
-            DefaultImageProvider defaultProvider);
+            DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider);
 
     /**
-     * Calls {@link #loadThumbnail(ImageView, long, boolean, DefaultImageProvider)} with
-     * {@link #DEFAULT_AVATAR}.
-     */
-    public final void loadThumbnail(ImageView view, long photoId, boolean darkTheme) {
-        loadThumbnail(view, photoId, darkTheme, DEFAULT_AVATAR);
+     * Calls {@link #loadThumbnail(ImageView, long, boolean, DefaultImageRequest,
+     * DefaultImageProvider)} using the {@link DefaultImageProvider} {@link #DEFAULT_AVATAR}.
+    */
+    public final void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
+            DefaultImageRequest defaultImageRequest) {
+        loadThumbnail(view, photoId, darkTheme, defaultImageRequest, DEFAULT_AVATAR);
     }
 
+
     /**
      * Load photo into the supplied image view. If the photo is already cached,
      * it is displayed immediately. Otherwise a request is sent to load the photo
      * from the location specified by the URI.
+     *
      * @param view The target view
      * @param photoUri The uri of the photo to load
      * @param requestedExtent Specifies an approximate Max(width, height) of the targetView.
@@ -178,27 +368,39 @@
      * is done using efficient sampling. If requestedExtent is specified, no sampling of the image
      * is performed
      * @param darkTheme Whether the background is dark. This is used for default avatars
+     * @param defaultImageRequest {@link DefaultImageRequest} object that specifies how a default
+     * letter tile avatar should be drawn.
      * @param defaultProvider The provider of default avatars (this is used if photoUri doesn't
      * refer to an existing image)
      */
     public abstract void loadPhoto(ImageView view, Uri photoUri, int requestedExtent,
-            boolean darkTheme, DefaultImageProvider defaultProvider);
+            boolean darkTheme, DefaultImageRequest defaultImageRequest,
+            DefaultImageProvider defaultProvider);
 
     /**
-     * Calls {@link #loadPhoto(ImageView, Uri, boolean, boolean, DefaultImageProvider)} with
-     * {@link #DEFAULT_AVATAR}.
+     * Calls {@link #loadPhoto(ImageView, Uri, int, boolean, DefaultImageRequest,
+     * DefaultImageProvider)} with {@link #DEFAULT_AVATAR} and {@code null} display names and
+     * lookup keys.
+     *
+     * @param defaultImageRequest {@link DefaultImageRequest} object that specifies how a default
+     * letter tile avatar should be drawn.
      */
     public final void loadPhoto(ImageView view, Uri photoUri, int requestedExtent,
-            boolean darkTheme) {
-        loadPhoto(view, photoUri, requestedExtent, darkTheme, DEFAULT_AVATAR);
+            boolean darkTheme, DefaultImageRequest defaultImageRequest) {
+        loadPhoto(view, photoUri, requestedExtent, darkTheme, defaultImageRequest, DEFAULT_AVATAR);
     }
 
     /**
-     * Calls {@link #loadPhoto(ImageView, Uri, boolean, boolean, DefaultImageProvider)} with
-     * {@link #DEFAULT_AVATAR} and with the assumption, that the image is a thumbnail
+     * Calls {@link #loadPhoto(ImageView, Uri, boolean, boolean, DefaultImageRequest,
+     * DefaultImageProvider)} with {@link #DEFAULT_AVATAR} and with the assumption, that
+     * the image is a thumbnail.
+     *
+     * @param defaultImageRequest {@link DefaultImageRequest} object that specifies how a default
+     * letter tile avatar should be drawn.
      */
-    public final void loadDirectoryPhoto(ImageView view, Uri photoUri, boolean darkTheme) {
-        loadPhoto(view, photoUri, -1, darkTheme, DEFAULT_AVATAR);
+    public final void loadDirectoryPhoto(ImageView view, Uri photoUri, boolean darkTheme,
+            DefaultImageRequest defaultImageRequest) {
+        loadPhoto(view, photoUri, -1, darkTheme, defaultImageRequest, DEFAULT_AVATAR);
     }
 
     /**
@@ -467,10 +669,10 @@
 
     @Override
     public void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
-            DefaultImageProvider defaultProvider) {
+            DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider) {
         if (photoId == 0) {
             // No photo is needed
-            defaultProvider.applyDefaultImage(view, -1, darkTheme);
+            defaultProvider.applyDefaultImage(view, -1, darkTheme, defaultImageRequest);
             mPendingRequests.remove(view);
         } else {
             if (DEBUG) Log.d(TAG, "loadPhoto request: " + photoId);
@@ -481,18 +683,31 @@
 
     @Override
     public void loadPhoto(ImageView view, Uri photoUri, int requestedExtent, boolean darkTheme,
-            DefaultImageProvider defaultProvider) {
+            DefaultImageRequest defaultImageRequest, DefaultImageProvider defaultProvider) {
         if (photoUri == null) {
             // No photo is needed
-            defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme);
+            defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme,
+                    defaultImageRequest);
             mPendingRequests.remove(view);
         } else {
             if (DEBUG) Log.d(TAG, "loadPhoto request: " + photoUri);
-            loadPhotoByIdOrUri(view, Request.createFromUri(photoUri, requestedExtent, darkTheme,
-                    defaultProvider));
+            if (isDefaultImageUri(photoUri)) {
+                createAndApplyDefaultImageForUri(view, photoUri, requestedExtent, darkTheme,
+                        defaultProvider);
+            } else {
+
+                loadPhotoByIdOrUri(view, Request.createFromUri(photoUri, requestedExtent,
+                        darkTheme, defaultProvider));
+            }
         }
     }
 
+    private void createAndApplyDefaultImageForUri(ImageView view, Uri uri, int requestedExtent,
+            boolean darkTheme, DefaultImageProvider defaultProvider) {
+        DefaultImageRequest request = getDefaultImageRequestFromUri(uri);
+        defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme, request);
+    }
+
     private void loadPhotoByIdOrUri(ImageView view, Request request) {
         boolean loaded = loadCachedPhoto(view, request, false);
         if (loaded) {
@@ -1219,7 +1434,7 @@
         }
 
         public void applyDefaultImage(ImageView view) {
-            mDefaultProvider.applyDefaultImage(view, mRequestedExtent, mDarkTheme);
+            mDefaultProvider.applyDefaultImage(view, mRequestedExtent, mDarkTheme, null);
         }
     }
 }
diff --git a/src/com/android/contacts/common/lettertiles/LetterTileDrawable.java b/src/com/android/contacts/common/lettertiles/LetterTileDrawable.java
new file mode 100644
index 0000000..9adcca6
--- /dev/null
+++ b/src/com/android/contacts/common/lettertiles/LetterTileDrawable.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.common.lettertiles;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.contacts.common.R;
+import com.android.contacts.common.util.BitmapUtil;
+
+import junit.framework.Assert;
+
+/**
+ * A drawable that encapsulates all the functionality needed to display a letter tile to
+ * represent a contact image.
+ */
+public class LetterTileDrawable extends Drawable {
+
+    private final String TAG = LetterTileDrawable.class.getSimpleName();
+
+    private final Paint mPaint;
+
+    /** Letter tile */
+    private static TypedArray sColors;
+    private static int sDefaultColor;
+    private static int sTileFontColor;
+    private static float sLetterToTileRatio;
+    private static Bitmap DEFAULT_PERSON_AVATAR;
+    private static Bitmap DEFAULT_BUSINESS_AVATAR;
+    private static Bitmap DEFAULT_VOICEMAIL_AVATAR;
+
+    /** Reusable components to avoid new allocations */
+    private static final Paint sPaint = new Paint();
+    private static final Rect sRect = new Rect();
+    private static final char[] sFirstChar = new char[1];
+
+    /** Contact type constants */
+    public static final int TYPE_PERSON = 1;
+    public static final int TYPE_BUSINESS = 2;
+    public static final int TYPE_VOICEMAIL = 3;
+    public static final int TYPE_DEFAULT = TYPE_PERSON;
+
+    private String mDisplayName;
+    private String mIdentifier;
+    private int mContactType = TYPE_DEFAULT;
+    private float mScale = 1.0f;
+    private float mOffset = 0.0f;
+
+    /** This should match the total number of colors defined in colors.xml for letter_tile_color */
+    private static final int NUM_OF_TILE_COLORS = 8;
+
+    public LetterTileDrawable(final Resources res) {
+        mPaint = new Paint();
+        mPaint.setFilterBitmap(true);
+        mPaint.setDither(true);
+
+        if (sColors == null) {
+            sColors = res.obtainTypedArray(R.array.letter_tile_colors);
+            sDefaultColor = res.getColor(R.color.letter_tile_default_color);
+            sTileFontColor = res.getColor(R.color.letter_tile_font_color);
+            sLetterToTileRatio = res.getFraction(R.dimen.letter_to_tile_ratio, 1, 1);
+            DEFAULT_PERSON_AVATAR = BitmapFactory.decodeResource(res,
+                    R.drawable.ic_list_item_avatar);
+            DEFAULT_BUSINESS_AVATAR = BitmapFactory.decodeResource(res,
+                    R.drawable.ic_list_item_businessavatar);
+            DEFAULT_VOICEMAIL_AVATAR = BitmapFactory.decodeResource(res,
+                    R.drawable.ic_voicemail_avatar);
+            sPaint.setTypeface(Typeface.create(
+                    res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL));
+            sPaint.setTextAlign(Align.CENTER);
+            sPaint.setAntiAlias(true);
+        }
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        final Rect bounds = getBounds();
+        if (!isVisible() || bounds.isEmpty()) {
+            return;
+        }
+        // Draw letter tile.
+        drawLetterTile(canvas);
+    }
+
+    /**
+     * Draw the bitmap onto the canvas at the current bounds taking into account the current scale.
+     */
+    private void drawBitmap(final Bitmap bitmap, final int width, final int height,
+            final Canvas canvas) {
+        // The bitmap should be drawn in the middle of the canvas without changing its width to
+        // height ratio.
+        final Rect destRect = copyBounds();
+
+        // Crop the destination bounds into a square, scaled and offset as appropriate
+        final int halfLength = (int) (mScale * Math.min(destRect.width(), destRect.height()) / 2);
+
+        destRect.set(destRect.centerX() - halfLength,
+                (int) (destRect.centerY() - halfLength + mOffset * destRect.height()),
+                destRect.centerX() + halfLength,
+                (int) (destRect.centerY() + halfLength + mOffset * destRect.height()));
+
+        // Source rectangle remains the entire bounds of the source bitmap.
+        sRect.set(0, 0, width, height);
+
+        canvas.drawBitmap(bitmap, sRect, destRect, mPaint);
+    }
+
+    private void drawLetterTile(final Canvas canvas) {
+        // Draw background color.
+        sPaint.setColor(pickColor(mIdentifier));
+
+        sPaint.setAlpha(mPaint.getAlpha());
+        canvas.drawRect(getBounds(), sPaint);
+
+        // Draw letter/digit only if the first character is an english letter
+        if (mDisplayName != null && isEnglishLetter(mDisplayName.charAt(0))) {
+            // Draw letter or digit.
+            sFirstChar[0] = Character.toUpperCase(mDisplayName.charAt(0));
+
+            // Scale text by canvas bounds and user selected scaling factor
+            final int minDimension = Math.min(getBounds().width(), getBounds().height());
+            sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension);
+            //sPaint.setTextSize(sTileLetterFontSize);
+            sPaint.getTextBounds(sFirstChar, 0, 1, sRect);
+            sPaint.setColor(sTileFontColor);
+            final Rect bounds = getBounds();
+
+            // Draw the letter in the canvas, vertically shifted up or down by the user-defined
+            // offset
+            canvas.drawText(sFirstChar, 0, 1, bounds.centerX(),
+                    bounds.centerY() + mOffset * bounds.height() + sRect.height() / 2,
+                    sPaint);
+        } else {
+            // Draw the default image if there is no letter/digit to be drawn
+            final Bitmap bitmap = getBitmapForContactType(mContactType);
+            drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(),
+                        canvas);
+        }
+    }
+
+    /**
+     * Returns a deterministic color based on the provided contact identifier string.
+     */
+    private int pickColor(final String identifier) {
+        if (TextUtils.isEmpty(identifier) || mContactType == TYPE_VOICEMAIL) {
+            return sDefaultColor;
+        }
+        // String.hashCode() implementation is not supposed to change across java versions, so
+        // this should guarantee the same email address always maps to the same color.
+        // The email should already have been normalized by the ContactRequest.
+        final int color = Math.abs(identifier.hashCode()) % NUM_OF_TILE_COLORS;
+        return sColors.getColor(color, sDefaultColor);
+    }
+
+    private static Bitmap getBitmapForContactType(int contactType) {
+        switch (contactType) {
+            case TYPE_PERSON:
+                return DEFAULT_PERSON_AVATAR;
+            case TYPE_BUSINESS:
+                return DEFAULT_BUSINESS_AVATAR;
+            case TYPE_VOICEMAIL:
+                return DEFAULT_VOICEMAIL_AVATAR;
+            default:
+                return DEFAULT_PERSON_AVATAR;
+        }
+    }
+
+    private static boolean isEnglishLetter(final char c) {
+        return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
+    }
+
+    @Override
+    public void setAlpha(final int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(final ColorFilter cf) {
+        mPaint.setColorFilter(cf);
+    }
+
+    @Override
+    public int getOpacity() {
+        return android.graphics.PixelFormat.OPAQUE;
+    }
+
+    /**
+     * Scale the drawn letter tile to a ratio of its default size
+     *
+     * @param scale The ratio the letter tile should be scaled to as a percentage of its default
+     * size, from a scale of 0 to 2.0f. The default is 1.0f.
+     */
+    public void setScale(float scale) {
+        mScale = scale;
+    }
+
+    /**
+     * Assigns the vertical offset of the position of the letter tile to the ContactDrawable
+     *
+     * @param offset The provided offset must be within the range of -0.5f to 0.5f.
+     * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of the canvas
+     * it is being drawn on, which means it will be drawn with the center of the letter starting
+     * at the top edge of the canvas.
+     * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of the canvas
+     * it is being drawn on, which means it will be drawn with the center of the letter starting
+     * at the bottom edge of the canvas.
+     * The default is 0.0f.
+     */
+    public void setOffset(float offset) {
+        Assert.assertTrue(offset >= -0.5f && offset <= 0.5f);
+        mOffset = offset;
+    }
+
+    public void setContactDetails(final String displayName, final String identifier) {
+        mDisplayName = displayName;
+        mIdentifier = identifier;
+    }
+
+    public void setContactType(int contactType) {
+        mContactType = contactType;
+    }
+}
diff --git a/src/com/android/contacts/common/list/ContactEntry.java b/src/com/android/contacts/common/list/ContactEntry.java
index 2683bef..43fc19d 100644
--- a/src/com/android/contacts/common/list/ContactEntry.java
+++ b/src/com/android/contacts/common/list/ContactEntry.java
@@ -29,7 +29,8 @@
     public String phoneLabel;
     public String phoneNumber;
     public Uri photoUri;
-    public Uri lookupKey;
+    public Uri lookupUri;
+    public String lookupKey;
     public Drawable presenceIcon;
     public long id;
     public int pinned = PinnedPositions.UNPINNED;
diff --git a/src/com/android/contacts/common/list/ContactEntryListAdapter.java b/src/com/android/contacts/common/list/ContactEntryListAdapter.java
index 0d13ec2..202f121 100644
--- a/src/com/android/contacts/common/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactEntryListAdapter.java
@@ -34,7 +34,9 @@
 import android.widget.TextView;
 
 import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.R;
+import com.android.contacts.common.list.ContactListAdapter.ContactQuery;
 import com.android.contacts.common.util.SearchUtil;
 
 import java.util.HashSet;
@@ -640,10 +642,11 @@
      * @param photoUriColumn Index of the photo uri column. Optional: Can be -1
      * @param contactIdColumn Index of the contact id column
      * @param lookUpKeyColumn Index of the lookup key column
+     * @param displayNameColumn Index of the display name column
      */
     protected void bindQuickContact(final ContactListItemView view, int partitionIndex,
             Cursor cursor, int photoIdColumn, int photoUriColumn, int contactIdColumn,
-            int lookUpKeyColumn) {
+            int lookUpKeyColumn, int displayNameColumn) {
         long photoId = 0;
         if (!cursor.isNull(photoIdColumn)) {
             photoId = cursor.getLong(photoIdColumn);
@@ -654,11 +657,16 @@
                 getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn));
 
         if (photoId != 0 || photoUriColumn == -1) {
-            getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme);
+            getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme, null);
         } else {
             final String photoUriString = cursor.getString(photoUriColumn);
             final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
-            getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme);
+            DefaultImageRequest request = null;
+            if (photoUri == null) {
+                request = getDefaultImageRequestFromCursor(cursor, displayNameColumn,
+                        lookUpKeyColumn);
+            }
+            getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme, request);
         }
 
     }
@@ -693,4 +701,21 @@
         return directoryId != Directory.DEFAULT
                 && directoryId != Directory.LOCAL_INVISIBLE;
     }
+
+    /**
+     * Retrieves the lookup key and display name from a cursor, and returns a
+     * {@link DefaultImageRequest} containing these contact details
+     *
+     * @param cursor Contacts cursor positioned at the current row to retrieve contact details for
+     * @param displayNameColumn Column index of the display name
+     * @param lookupKeyColumn Column index of the lookup key
+     * @return {@link DefaultImageRequest} with the displayName and identifier fields set to the
+     * display name and lookup key of the contact.
+     */
+    public static DefaultImageRequest getDefaultImageRequestFromCursor(Cursor cursor,
+            int displayNameColumn, int lookupKeyColumn) {
+        final String displayName = cursor.getString(displayNameColumn);
+        final String lookupKey = cursor.getString(lookupKeyColumn);
+        return new DefaultImageRequest(displayName, lookupKey);
+    }
 }
diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java
index 3ad1801..0750459 100644
--- a/src/com/android/contacts/common/list/ContactListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactListAdapter.java
@@ -28,6 +28,8 @@
 import android.view.ViewGroup;
 import android.widget.ListView;
 
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.R;
 
 /**
@@ -237,11 +239,17 @@
         }
 
         if (photoId != 0) {
-            getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false);
+            getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, null);
         } else {
             final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
             final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
-            getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false);
+            DefaultImageRequest request = null;
+            if (photoUri == null) {
+                String displayName = cursor.getString(ContactQuery.CONTACT_DISPLAY_NAME);
+                String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
+                request = new DefaultImageRequest(displayName, lookupKey);
+            }
+            getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false, request);
         }
     }
 
diff --git a/src/com/android/contacts/common/list/ContactTileAdapter.java b/src/com/android/contacts/common/list/ContactTileAdapter.java
index 66467a3..d9fbeac 100644
--- a/src/com/android/contacts/common/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/common/list/ContactTileAdapter.java
@@ -256,7 +256,8 @@
         contact.name = (name != null) ? name : mResources.getString(R.string.missing_name);
         contact.status = cursor.getString(mStatusIndex);
         contact.photoUri = (photoUri != null ? Uri.parse(photoUri) : null);
-        contact.lookupKey = ContentUris.withAppendedId(
+        contact.lookupKey = lookupKey;
+        contact.lookupUri = ContentUris.withAppendedId(
                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), id);
         contact.isFavorite = cursor.getInt(mStarredIndex) > 0;
 
diff --git a/src/com/android/contacts/common/list/ContactTileView.java b/src/com/android/contacts/common/list/ContactTileView.java
index c81a81c..740f75a 100644
--- a/src/com/android/contacts/common/list/ContactTileView.java
+++ b/src/com/android/contacts/common/list/ContactTileView.java
@@ -28,6 +28,7 @@
 import android.widget.TextView;
 
 import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.MoreContactUtils;
 import com.android.contacts.common.R;
 
@@ -94,7 +95,7 @@
 
         if (entry != null) {
             mName.setText(getNameForView(entry.name));
-            mLookupUri = entry.lookupKey;
+            mLookupUri = entry.lookupUri;
 
             if (mStatus != null) {
                 if (entry.status == null) {
@@ -124,9 +125,11 @@
             setVisibility(View.VISIBLE);
 
             if (mPhotoManager != null) {
+                DefaultImageRequest request = getDefaultImageRequest(entry.name, entry.lookupKey);
                 if (mPhoto != null) {
+
                     mPhotoManager.loadPhoto(mPhoto, entry.photoUri, getApproximateImageSize(),
-                            isDarkTheme());
+                            isDarkTheme(), request);
 
                     if (mQuickContact != null) {
                         mQuickContact.assignContactUri(mLookupUri);
@@ -134,7 +137,7 @@
                 } else if (mQuickContact != null) {
                     mQuickContact.assignContactUri(mLookupUri);
                     mPhotoManager.loadPhoto(mQuickContact, entry.photoUri,
-                            getApproximateImageSize(), isDarkTheme());
+                            getApproximateImageSize(), isDarkTheme(), request);
                 }
             } else {
                 Log.w(TAG, "contactPhotoManager not set");
@@ -182,6 +185,19 @@
 
     protected abstract boolean isDarkTheme();
 
+    /**
+     * Implemented by subclasses to allow them to return a {@link DefaultImageRequest} with the
+     * various image parameters defined to match their own layouts.
+     *
+     * @param displayName The display name of the contact
+     * @param lookupKey The lookup key of the contact
+     * @return A {@link DefaultImageRequest} object with each field configured by the subclass
+     * as desired, or {@code null}.
+     */
+    protected DefaultImageRequest getDefaultImageRequest(String displayName, String lookupKey) {
+        return new DefaultImageRequest(displayName, lookupKey);
+    }
+
     public interface Listener {
         /**
          * Notification that the contact was selected; no specific action is dictated.
diff --git a/src/com/android/contacts/common/list/DefaultContactListAdapter.java b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
index fb974b4..e6be0f2 100644
--- a/src/com/android/contacts/common/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
@@ -196,7 +196,7 @@
         if (isQuickContactEnabled()) {
             bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID,
                     ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID,
-                    ContactQuery.CONTACT_LOOKUP_KEY);
+                    ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME);
         } else {
             if (getDisplayPhotos()) {
                 bindPhoto(view, partition, cursor);
diff --git a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
index ad37035..88cc2b8 100644
--- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
@@ -35,6 +35,7 @@
 
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.R;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.extensions.ExtendedPhoneDirectoriesManager;
 import com.android.contacts.common.extensions.ExtensionsFactory;
 import com.android.contacts.common.util.Constants;
@@ -345,7 +346,7 @@
             if (isQuickContactEnabled()) {
                 bindQuickContact(view, partition, cursor, PhoneQuery.PHOTO_ID,
                         PhoneQuery.PHOTO_URI, PhoneQuery.CONTACT_ID,
-                        PhoneQuery.LOOKUP_KEY);
+                        PhoneQuery.LOOKUP_KEY, PhoneQuery.DISPLAY_NAME);
             } else {
                 if (getDisplayPhotos()) {
                     bindPhoto(view, partition, cursor);
@@ -420,11 +421,18 @@
         }
 
         if (photoId != 0) {
-            getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false);
+            getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, null);
         } else {
             final String photoUriString = cursor.getString(PhoneQuery.PHOTO_URI);
             final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
-            getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false);
+
+            DefaultImageRequest request = null;
+            if (photoUri == null) {
+                final String displayName = cursor.getString(PhoneQuery.DISPLAY_NAME);
+                final String lookupKey = cursor.getString(PhoneQuery.LOOKUP_KEY);
+                request = new DefaultImageRequest(displayName, lookupKey);
+            }
+            getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false, request);
         }
     }
 
diff --git a/src/com/android/contacts/common/model/Contact.java b/src/com/android/contacts/common/model/Contact.java
index d5ff0a3..74b5596 100644
--- a/src/com/android/contacts/common/model/Contact.java
+++ b/src/com/android/contacts/common/model/Contact.java
@@ -304,6 +304,14 @@
         return mDisplayNameSource;
     }
 
+    /**
+     * Used by various classes to determine whether or not this contact should be displayed as
+     * a business rather than a person.
+     */
+    public boolean isDisplayNameFromOrganization() {
+        return DisplayNameSources.ORGANIZATION == mDisplayNameSource;
+    }
+
     public long getPhotoId() {
         return mPhotoId;
     }