am 2b080e1e: Main changes to the chips library: Changed attribute styles - Added an avatar position (left, right) inside the chip - Added a disableDelete boolean - Removed chipsAlternateLayout

* commit '2b080e1e1e24a2096fea375d825aa19089fa3e32':
  Main changes to the chips library: Changed attribute styles - Added an avatar position (left, right) inside the chip - Added a disableDelete boolean - Removed chipsAlternateLayout
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 44c2500..6b26bcb 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -15,13 +15,17 @@
 -->
 <resources>
     <declare-styleable name="RecipientEditTextView">
-        <attr name="invalidChipBackground" format="reference" />
+        <attr name="avatarPosition">
+            <enum name="right" value="0" />
+            <enum name="left" value="1" />
+        </attr>
         <attr name="chipBackground" format="reference" />
         <attr name="chipBackgroundPressed" format="reference" />
         <attr name="chipDelete" format="reference" />
-        <attr name="chipAlternatesLayout" format="reference" />
-        <attr name="chipPadding" format="reference" />
-        <attr name="chipHeight" format="reference" />
         <attr name="chipFontSize" format="reference" />
+        <attr name="chipHeight" format="reference" />
+        <attr name="chipPadding" format="reference" />
+        <attr name="disableDelete" format="boolean" />
+        <attr name="invalidChipBackground" format="reference" />
     </declare-styleable>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/src/com/android/ex/chips/BaseRecipientAdapter.java b/src/com/android/ex/chips/BaseRecipientAdapter.java
index b779776..d52c7ca 100644
--- a/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -23,8 +23,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -43,12 +41,12 @@
 import android.widget.BaseAdapter;
 import android.widget.Filter;
 import android.widget.Filterable;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
 
 import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -460,6 +458,7 @@
     private final LayoutInflater mInflater;
     private Account mAccount;
     private final int mPreferredMaxResultCount;
+    private DropdownChipLayouter mDropdownChipLayouter;
 
     /**
      * {@link #mEntries} is responsible for showing every result for this Adapter. To
@@ -570,6 +569,15 @@
         return mQueryType;
     }
 
+    public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+        mDropdownChipLayouter = dropdownChipLayouter;
+        mDropdownChipLayouter.setQuery(mQuery);
+    }
+
+    public DropdownChipLayouter getDropdownChipLayouter() {
+        return mDropdownChipLayouter;
+    }
+
     /**
      * Set the account when known. Causes the search to prioritize contacts from that account.
      */
@@ -943,111 +951,9 @@
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         final RecipientEntry entry = getEntries().get(position);
-        String displayName = entry.getDisplayName();
-        String destination = entry.getDestination();
-        if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
-            displayName = destination;
 
-            // We only show the destination for secondary entries, so clear it
-            // only for the first level.
-            if (entry.isFirstLevel()) {
-                destination = null;
-            }
-        }
-
-        final View itemView = convertView != null ? convertView : mInflater.inflate(
-                getItemLayout(), parent, false);
-        final TextView displayNameView = (TextView) itemView.findViewById(getDisplayNameId());
-        final TextView destinationView = (TextView) itemView.findViewById(getDestinationId());
-        final TextView destinationTypeView = (TextView) itemView
-                .findViewById(getDestinationTypeId());
-        final ImageView imageView = (ImageView) itemView.findViewById(getPhotoId());
-        displayNameView.setText(displayName);
-        if (!TextUtils.isEmpty(destination)) {
-            destinationView.setText(destination);
-        } else {
-            destinationView.setText(null);
-        }
-        if (destinationTypeView != null) {
-            final CharSequence destinationType = mQuery
-                    .getTypeLabel(mContext.getResources(), entry.getDestinationType(),
-                            entry.getDestinationLabel()).toString().toUpperCase();
-
-            destinationTypeView.setText(destinationType);
-        }
-
-        if (entry.isFirstLevel()) {
-            displayNameView.setVisibility(View.VISIBLE);
-            if (imageView != null) {
-                imageView.setVisibility(View.VISIBLE);
-                final byte[] photoBytes = entry.getPhotoBytes();
-                if (photoBytes != null) {
-                    final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
-                            photoBytes.length);
-                    imageView.setImageBitmap(photo);
-                } else {
-                    imageView.setImageResource(getDefaultPhotoResource());
-                }
-            }
-        } else {
-            displayNameView.setVisibility(View.GONE);
-            if (imageView != null) {
-                imageView.setVisibility(View.INVISIBLE);
-            }
-        }
-        return itemView;
-    }
-
-    /**
-     * Returns a layout id for each item inside auto-complete list.
-     *
-     * Each View must contain two TextViews (for display name and destination) and one ImageView
-     * (for photo). Ids for those should be available via {@link #getDisplayNameId()},
-     * {@link #getDestinationId()}, and {@link #getPhotoId()}.
-     */
-    protected int getItemLayout() {
-        return R.layout.chips_recipient_dropdown_item;
-    }
-
-    /**
-     * Returns a resource ID representing an image which should be shown when ther's no relevant
-     * photo is available.
-     */
-    protected int getDefaultPhotoResource() {
-        return R.drawable.ic_contact_picture;
-    }
-
-    /**
-     * Returns an id for TextView in an item View for showing a display name. By default
-     * {@link android.R.id#title} is returned.
-     */
-    protected int getDisplayNameId() {
-        return android.R.id.title;
-    }
-
-    /**
-     * Returns an id for TextView in an item View for showing a destination
-     * (an email address or a phone number).
-     * By default {@link android.R.id#text1} is returned.
-     */
-    protected int getDestinationId() {
-        return android.R.id.text1;
-    }
-
-    /**
-     * Returns an id for TextView in an item View for showing the type of the destination.
-     * By default {@link android.R.id#text2} is returned.
-     */
-    protected int getDestinationTypeId() {
-        return android.R.id.text2;
-    }
-
-    /**
-     * Returns an id for ImageView in an item View for showing photo image for a person. In default
-     * {@link android.R.id#icon} is returned.
-     */
-    protected int getPhotoId() {
-        return android.R.id.icon;
+        return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
+                AdapterType.BASE_RECIPIENT, mCurrentConstraint.toString());
     }
 
     public Account getAccount() {
diff --git a/src/com/android/ex/chips/DropdownChipLayouter.java b/src/com/android/ex/chips/DropdownChipLayouter.java
new file mode 100644
index 0000000..aceefab
--- /dev/null
+++ b/src/com/android/ex/chips/DropdownChipLayouter.java
@@ -0,0 +1,274 @@
+package com.android.ex.chips;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.text.util.Rfc822Tokenizer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.ex.chips.Queries.Query;
+
+/**
+ * A class that inflates and binds the views in the dropdown list from
+ * RecipientEditTextView.
+ */
+public class DropdownChipLayouter {
+    /**
+     * The type of adapter that is requesting a chip layout.
+     */
+    public enum AdapterType {
+        BASE_RECIPIENT,
+        RECIPIENT_ALTERNATES,
+        SINGLE_RECIPIENT
+    }
+
+    private final LayoutInflater mInflater;
+    private final Context mContext;
+    private Query mQuery;
+
+    public DropdownChipLayouter(LayoutInflater inflater, Context context) {
+        mInflater = inflater;
+        mContext = context;
+    }
+
+    public void setQuery(Query query) {
+        mQuery = query;
+    }
+
+
+    /**
+     * Layouts and binds recipient information to the view. If convertView is null, inflates a new
+     * view with getItemLaytout().
+     *
+     * @param convertView The view to bind information to.
+     * @param parent The parent to bind the view to if we inflate a new view.
+     * @param entry The recipient entry to get information from.
+     * @param position The position in the list.
+     * @param type The adapter type that is requesting the bind.
+     * @param constraint The constraint typed in the auto complete view.
+     *
+     * @return A view ready to be shown in the drop down list.
+     */
+    public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
+        AdapterType type, String constraint) {
+        // Default to show all the information
+        String displayName = entry.getDisplayName();
+        String destination = entry.getDestination();
+        boolean showImage = true;
+        CharSequence destinationType = getDestinationType(entry);
+
+        final View itemView = reuseOrInflateView(convertView, parent, type);
+
+        final ViewHolder viewHolder = new ViewHolder(itemView);
+
+        // Hide some information depending on the entry type and adapter type
+        switch (type) {
+            case BASE_RECIPIENT:
+                if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
+                    displayName = destination;
+
+                    // We only show the destination for secondary entries, so clear it only for the
+                    // first level.
+                    if (entry.isFirstLevel()) {
+                        destination = null;
+                    }
+                }
+
+                if (!entry.isFirstLevel()) {
+                    displayName = null;
+                    showImage = false;
+                }
+                break;
+            case RECIPIENT_ALTERNATES:
+                if (position != 0) {
+                    displayName = null;
+                    showImage = false;
+                }
+                break;
+            case SINGLE_RECIPIENT:
+                destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress();
+                destinationType = null;
+        }
+
+        // Bind the information to the view
+        bindTextToView(displayName, viewHolder.displayNameView);
+        bindTextToView(destination, viewHolder.destinationView);
+        bindTextToView(destinationType, viewHolder.destinationTypeView);
+        bindIconToView(showImage, entry, viewHolder.imageView, type);
+
+        return itemView;
+    }
+
+    /**
+     * Returns a new view with {@link #getItemLayoutResId()}.
+     */
+    public View newView() {
+        return mInflater.inflate(getItemLayoutResId(), null);
+    }
+
+    /**
+     * Returns the same view, or inflates a new one if the given view was null.
+     */
+    protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) {
+        int itemLayout = getItemLayoutResId();
+        switch (type) {
+            case BASE_RECIPIENT:
+            case RECIPIENT_ALTERNATES:
+                break;
+            case SINGLE_RECIPIENT:
+                itemLayout = getAlternateItemLayoutResId();
+                break;
+        }
+        return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false);
+    }
+
+    /**
+     * Binds the text to the given text view. If the text was null, hides the text view.
+     */
+    protected void bindTextToView(CharSequence text, TextView view) {
+        if (view == null) {
+            return;
+        }
+
+        if (text != null) {
+            view.setText(text);
+            view.setVisibility(View.VISIBLE);
+        } else {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Binds the avatar icon to the image view. If we don't want to show the image, hides the
+     * image view.
+     */
+    protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
+        AdapterType type) {
+        if (view == null) {
+            return;
+        }
+
+        if (showImage) {
+            switch (type) {
+                case BASE_RECIPIENT:
+                    byte[] photoBytes = entry.getPhotoBytes();
+                    if (photoBytes != null && photoBytes.length > 0) {
+                        final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
+                            photoBytes.length);
+                        view.setImageBitmap(photo);
+                    } else {
+                        view.setImageResource(getDefaultPhotoResId());
+                    }
+                    break;
+                case RECIPIENT_ALTERNATES:
+                    Uri thumbnailUri = entry.getPhotoThumbnailUri();
+                    if (thumbnailUri != null) {
+                        // TODO: see if this needs to be done outside the main thread
+                        // as it may be too slow to get immediately.
+                        view.setImageURI(entry.getPhotoThumbnailUri());
+                    } else {
+                        view.setImageResource(getDefaultPhotoResId());
+                    }
+                    break;
+                case SINGLE_RECIPIENT:
+                default:
+                    break;
+            }
+            view.setVisibility(View.VISIBLE);
+        } else {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    protected CharSequence getDestinationType(RecipientEntry entry) {
+        return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(),
+            entry.getDestinationLabel()).toString().toUpperCase();
+    }
+
+    /**
+     * Returns a layout id for each item inside auto-complete list.
+     *
+     * Each View must contain two TextViews (for display name and destination) and one ImageView
+     * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+     * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+     */
+    protected int getItemLayoutResId() {
+        return R.layout.chips_recipient_dropdown_item;
+    }
+
+    /**
+     * Returns a layout id for each item inside alternate auto-complete list.
+     *
+     * Each View must contain two TextViews (for display name and destination) and one ImageView
+     * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+     * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+     */
+    protected int getAlternateItemLayoutResId() {
+        return R.layout.chips_alternate_item;
+    }
+
+    /**
+     * Returns a resource ID representing an image which should be shown when ther's no relevant
+     * photo is available.
+     */
+    protected int getDefaultPhotoResId() {
+        return R.drawable.ic_contact_picture;
+    }
+
+    /**
+     * Returns an id for TextView in an item View for showing a display name. By default
+     * {@link android.R.id#title} is returned.
+     */
+    protected int getDisplayNameResId() {
+        return android.R.id.title;
+    }
+
+    /**
+     * Returns an id for TextView in an item View for showing a destination
+     * (an email address or a phone number).
+     * By default {@link android.R.id#text1} is returned.
+     */
+    protected int getDestinationResId() {
+        return android.R.id.text1;
+    }
+
+    /**
+     * Returns an id for TextView in an item View for showing the type of the destination.
+     * By default {@link android.R.id#text2} is returned.
+     */
+    protected int getDestinationTypeResId() {
+        return android.R.id.text2;
+    }
+
+    /**
+     * Returns an id for ImageView in an item View for showing photo image for a person. In default
+     * {@link android.R.id#icon} is returned.
+     */
+    protected int getPhotoResId() {
+        return android.R.id.icon;
+    }
+
+    /**
+     * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
+     * corresponding views.
+     */
+    protected class ViewHolder {
+        public final TextView displayNameView;
+        public final TextView destinationView;
+        public final TextView destinationTypeView;
+        public final ImageView imageView;
+
+        public ViewHolder(View view) {
+            displayNameView = (TextView) view.findViewById(getDisplayNameResId());
+            destinationView = (TextView) view.findViewById(getDestinationResId());
+            destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
+            imageView = (ImageView) view.findViewById(getPhotoResId());
+        }
+    }
+}
diff --git a/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index b9a7c80..547a76b 100644
--- a/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -27,15 +27,13 @@
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
 import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
 import com.android.ex.chips.Queries.Query;
 
 import java.util.ArrayList;
@@ -51,7 +49,6 @@
  */
 public class RecipientAlternatesAdapter extends CursorAdapter {
     static final int MAX_LOOKUPS = 50;
-    private final LayoutInflater mLayoutInflater;
 
     private final long mCurrentId;
 
@@ -64,6 +61,7 @@
     public static final int QUERY_TYPE_EMAIL = 0;
     public static final int QUERY_TYPE_PHONE = 1;
     private Query mQuery;
+    private DropdownChipLayouter mDropdownChipLayouter;
 
     public interface RecipientMatchCallback {
         public void matchesFound(Map<String, RecipientEntry> results);
@@ -328,14 +326,9 @@
     }
 
     public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
-            OnCheckedItemChangedListener listener) {
-        this(context, contactId, currentId, QUERY_TYPE_EMAIL, listener);
-    }
-
-    public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
-            int queryMode, OnCheckedItemChangedListener listener) {
+            int queryMode, OnCheckedItemChangedListener listener,
+        DropdownChipLayouter dropdownChipLayouter) {
         super(context, getCursorForConstruction(context, contactId, queryMode), 0);
-        mLayoutInflater = LayoutInflater.from(context);
         mCurrentId = currentId;
         mCheckedItemChangedListener = listener;
 
@@ -347,6 +340,8 @@
             mQuery = Queries.EMAIL;
             Log.e(TAG, "Unsupported query type: " + queryMode);
         }
+
+        mDropdownChipLayouter = dropdownChipLayouter;
     }
 
     private static Cursor getCursorForConstruction(Context context, long contactId, int queryType) {
@@ -439,7 +434,7 @@
         Cursor cursor = getCursor();
         cursor.moveToPosition(position);
         if (convertView == null) {
-            convertView = newView();
+            convertView = mDropdownChipLayouter.newView();
         }
         if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
             mCheckedItemPosition = position;
@@ -451,44 +446,18 @@
         return convertView;
     }
 
-    // TODO: this is VERY similar to the BaseRecipientAdapter. Can we combine
-    // somehow?
     @Override
     public void bindView(View view, Context context, Cursor cursor) {
         int position = cursor.getPosition();
-
-        TextView display = (TextView) view.findViewById(android.R.id.title);
-        ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
         RecipientEntry entry = getRecipientEntry(position);
-        if (position == 0) {
-            display.setText(cursor.getString(Queries.Query.NAME));
-            display.setVisibility(View.VISIBLE);
-            // TODO: see if this needs to be done outside the main thread
-            // as it may be too slow to get immediately.
-            imageView.setImageURI(entry.getPhotoThumbnailUri());
-            imageView.setVisibility(View.VISIBLE);
-        } else {
-            display.setVisibility(View.GONE);
-            imageView.setVisibility(View.GONE);
-        }
-        TextView destination = (TextView) view.findViewById(android.R.id.text1);
-        destination.setText(cursor.getString(Queries.Query.DESTINATION));
 
-        TextView destinationType = (TextView) view.findViewById(android.R.id.text2);
-        if (destinationType != null) {
-            destinationType.setText(mQuery.getTypeLabel(context.getResources(),
-                    cursor.getInt(Queries.Query.DESTINATION_TYPE),
-                    cursor.getString(Queries.Query.DESTINATION_LABEL)).toString().toUpperCase());
-        }
+        mDropdownChipLayouter.bindView(view, null, entry, position,
+                AdapterType.RECIPIENT_ALTERNATES, null);
     }
 
     @Override
     public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        return newView();
-    }
-
-    private View newView() {
-        return mLayoutInflater.inflate(R.layout.chips_recipient_dropdown_item, null);
+        return mDropdownChipLayouter.newView();
     }
 
     /*package*/ static interface OnCheckedItemChangedListener {
diff --git a/src/com/android/ex/chips/RecipientEditTextView.java b/src/com/android/ex/chips/RecipientEditTextView.java
index 3cba87b..b355c1b 100644
--- a/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/src/com/android/ex/chips/RecipientEditTextView.java
@@ -30,6 +30,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
+import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -149,14 +150,24 @@
 
     private int mChipPadding;
 
+    /**
+     * Enumerator for avatar position. See attr.xml for more details.
+     * 0 for right, 1 for left.
+     */
+    private int mAvatarPosition;
+
+    private static final int AVATAR_POSITION_RIGHT = 0;
+
+    private static final int AVATAR_POSITION_LEFT = 1;
+
+    private boolean mDisableDelete;
+
     private Tokenizer mTokenizer;
 
     private Validator mValidator;
 
     private DrawableRecipientChip mSelectedChip;
 
-    private int mAlternatesLayout;
-
     private Bitmap mDefaultContactPhoto;
 
     private ImageSpan mMoreChip;
@@ -254,6 +265,8 @@
 
     private boolean mAttachedToWindow;
 
+    private DropdownChipLayouter mDropdownChipLayouter;
+
     public RecipientEditTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setChipDimensions(context, attrs);
@@ -293,6 +306,12 @@
         addTextChangedListener(mTextWatcher);
         mGestureDetector = new GestureDetector(context, this);
         setOnEditorActionListener(this);
+
+        setDropdownChipLayouter(new DropdownChipLayouter(LayoutInflater.from(context), context));
+    }
+
+    protected void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+        mDropdownChipLayouter = dropdownChipLayouter;
     }
 
     @Override
@@ -433,17 +452,18 @@
     @Override
     public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
         super.setAdapter(adapter);
-        ((BaseRecipientAdapter) adapter)
-                .registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
-                    @Override
-                    public void onChanged(List<RecipientEntry> entries) {
-                        // Scroll the chips field to the top of the screen so
-                        // that the user can see as many results as possible.
-                        if (entries != null && entries.size() > 0) {
-                            scrollBottomIntoView();
-                        }
-                    }
-                });
+        BaseRecipientAdapter baseAdapter = (BaseRecipientAdapter) adapter;
+        baseAdapter.registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
+            @Override
+            public void onChanged(List<RecipientEntry> entries) {
+                // Scroll the chips field to the top of the screen so
+                // that the user can see as many results as possible.
+                if (entries != null && entries.size() > 0) {
+                    scrollBottomIntoView();
+                }
+            }
+        });
+        baseAdapter.setDropdownChipLayouter(mDropdownChipLayouter);
     }
 
     private void scrollBottomIntoView() {
@@ -543,124 +563,129 @@
                 TextUtils.TruncateAt.END);
     }
 
+    /**
+     * Creates a bitmap of the given contact on a selected chip.
+     *
+     * @param contact The recipient entry to pull data from.
+     * @param paint The paint to use to draw the bitmap.
+     */
     private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint) {
-        // Ellipsize the text so that it takes AT MOST the entire width of the
-        // autocomplete text entry area. Make sure to leave space for padding
-        // on the sides.
-        int height = (int) mChipHeight;
-        int deleteWidth = height;
-        float[] widths = new float[1];
-        paint.getTextWidths(" ", widths);
-        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
-                calculateAvailableWidth() - deleteWidth - widths[0]);
-
-        // Make sure there is a minimum chip width so the user can ALWAYS
-        // tap a chip without difficulty.
-        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
-                ellipsizedText.length()))
-                + (mChipPadding * 2) + deleteWidth);
-
-        // Create the background of the chip.
-        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(tmpBitmap);
-        if (mChipBackgroundPressed != null) {
-            mChipBackgroundPressed.setBounds(0, 0, width, height);
-            mChipBackgroundPressed.draw(canvas);
-            paint.setColor(sSelectedTextColor);
-            // Vertically center the text in the chip.
-            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
-                    getTextYOffset((String) ellipsizedText, paint, height), paint);
-            // Make the delete a square.
-            Rect backgroundPadding = new Rect();
-            mChipBackgroundPressed.getPadding(backgroundPadding);
-            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
-                    0 + backgroundPadding.top,
-                    width - backgroundPadding.right,
-                    height - backgroundPadding.bottom);
-            mChipDelete.draw(canvas);
+        paint.setColor(sSelectedTextColor);
+        Bitmap photo;
+        if (mDisableDelete) {
+            // Show the avatar instead if we don't want to delete
+            photo = getAvatarIcon(contact);
         } else {
-            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+            photo = ((BitmapDrawable) mChipDelete).getBitmap();
         }
-        return tmpBitmap;
+        return createChipBitmap(contact, paint, photo, mChipBackgroundPressed);
     }
 
-
+    /**
+     * Creates a bitmap of the given contact on a selected chip.
+     *
+     * @param contact The recipient entry to pull data from.
+     * @param paint The paint to use to draw the bitmap.
+     */
+    // TODO: Is leaveBlankIconSpacer obsolete now that we have left and right attributes?
     private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
             boolean leaveBlankIconSpacer) {
+        Drawable background = getChipBackground(contact);
+        Bitmap photo = getAvatarIcon(contact);
+        paint.setColor(getContext().getResources().getColor(android.R.color.black));
+        return createChipBitmap(contact, paint, photo, background);
+    }
+
+    private Bitmap createChipBitmap(RecipientEntry contact, TextPaint paint, Bitmap icon,
+        Drawable background) {
+        if (background == null) {
+            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+            Bitmap.createBitmap((int) mChipHeight * 2, (int) mChipHeight, Bitmap.Config.ARGB_8888);
+        }
+
+        Rect backgroundPadding = new Rect();
+        background.getPadding(backgroundPadding);
+
         // Ellipsize the text so that it takes AT MOST the entire width of the
         // autocomplete text entry area. Make sure to leave space for padding
         // on the sides.
         int height = (int) mChipHeight;
-        int iconWidth = height;
+        // Since the icon is a square, it's width is equal to the maximum height it can be inside
+        // the chip.
+        int iconWidth = height - backgroundPadding.top - backgroundPadding.bottom;
         float[] widths = new float[1];
         paint.getTextWidths(" ", widths);
         CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
                 calculateAvailableWidth() - iconWidth - widths[0]);
+        int textWidth = (int) paint.measureText(ellipsizedText, 0, ellipsizedText.length());
+
         // Make sure there is a minimum chip width so the user can ALWAYS
         // tap a chip without difficulty.
-        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
-                ellipsizedText.length()))
-                + (mChipPadding * 2) + iconWidth);
+        int width = Math.max(iconWidth * 2, textWidth + (mChipPadding * 2) + iconWidth
+                + backgroundPadding.left + backgroundPadding.right);
 
         // Create the background of the chip.
         Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(tmpBitmap);
-        Drawable background = getChipBackground(contact);
-        if (background != null) {
-            background.setBounds(0, 0, width, height);
-            background.draw(canvas);
 
-            // Don't draw photos for recipients that have been typed in OR generated on the fly.
-            long contactId = contact.getContactId();
-            boolean drawPhotos = isPhoneQuery() ?
-                    contactId != RecipientEntry.INVALID_CONTACT
-                    : (contactId != RecipientEntry.INVALID_CONTACT
-                            && (contactId != RecipientEntry.GENERATED_CONTACT &&
-                                    !TextUtils.isEmpty(contact.getDisplayName())));
-            if (drawPhotos) {
-                byte[] photoBytes = contact.getPhotoBytes();
-                // There may not be a photo yet if anything but the first contact address
-                // was selected.
-                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
-                    // TODO: cache this in the recipient entry?
-                    getAdapter().fetchPhoto(contact, contact.getPhotoThumbnailUri());
-                    photoBytes = contact.getPhotoBytes();
-                }
-
-                Bitmap photo;
-                if (photoBytes != null) {
-                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
-                } else {
-                    // TODO: can the scaled down default photo be cached?
-                    photo = mDefaultContactPhoto;
-                }
-                // Draw the photo on the left side.
-                if (photo != null) {
-                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
-                    Rect backgroundPadding = new Rect();
-                    mChipBackground.getPadding(backgroundPadding);
-                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
-                            0 + backgroundPadding.top,
-                            width - backgroundPadding.right,
-                            height - backgroundPadding.bottom);
-                    Matrix matrix = new Matrix();
-                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
-                    canvas.drawBitmap(photo, matrix, paint);
-                }
-            } else if (!leaveBlankIconSpacer || isPhoneQuery()) {
-                iconWidth = 0;
-            }
-            paint.setColor(getContext().getResources().getColor(android.R.color.black));
-            // Vertically center the text in the chip.
-            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
-                    getTextYOffset((String)ellipsizedText, paint, height), paint);
-        } else {
-            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+        // Draw the background drawable
+        background.setBounds(0, 0, width, height);
+        background.draw(canvas);
+        // Draw the text vertically aligned
+        int textX = mAvatarPosition == AVATAR_POSITION_RIGHT ?
+                mChipPadding + backgroundPadding.left :
+                width - backgroundPadding.right - mChipPadding - textWidth;
+        canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
+                textX, getTextYOffset(ellipsizedText.toString(), paint, height), paint);
+        if (icon != null) {
+            // Draw the icon
+            int iconX = mAvatarPosition == AVATAR_POSITION_RIGHT ?
+                    width - backgroundPadding.right - iconWidth :
+                    backgroundPadding.left;
+            RectF src = new RectF(0, 0, icon.getWidth(), icon.getHeight());
+            RectF dst = new RectF(iconX,
+                    0 + backgroundPadding.top,
+                    iconX + iconWidth,
+                    height - backgroundPadding.bottom);
+            drawIconOnCanvas(icon, canvas, paint, src, dst);
         }
         return tmpBitmap;
     }
 
     /**
+     * Returns the avatar icon to use for this recipient entry. Returns null if we don't want to
+     * draw an icon for this recipient.
+     */
+    private Bitmap getAvatarIcon(RecipientEntry contact) {
+        // Don't draw photos for recipients that have been typed in OR generated on the fly.
+        long contactId = contact.getContactId();
+        boolean drawPhotos = isPhoneQuery() ?
+                contactId != RecipientEntry.INVALID_CONTACT
+                : (contactId != RecipientEntry.INVALID_CONTACT
+                        && (contactId != RecipientEntry.GENERATED_CONTACT &&
+                                !TextUtils.isEmpty(contact.getDisplayName())));
+
+        if (drawPhotos) {
+            byte[] photoBytes = contact.getPhotoBytes();
+            // There may not be a photo yet if anything but the first contact address
+            // was selected.
+            if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
+                // TODO: cache this in the recipient entry?
+                getAdapter().fetchPhoto(contact, contact.getPhotoThumbnailUri());
+                photoBytes = contact.getPhotoBytes();
+            }
+            if (photoBytes != null) {
+                return BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
+            } else {
+                // TODO: can the scaled down default photo be cached?
+                return mDefaultContactPhoto;
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * Get the background drawable for a RecipientChip.
      */
     // Visible for testing.
@@ -668,13 +693,26 @@
         return contact.isValid() ? mChipBackground : mInvalidChipBackground;
     }
 
-    private static float getTextYOffset(String text, TextPaint paint, int height) {
+    /**
+     * Given a height, returns a Y offset that will draw the text in the middle of the height.
+     */
+    protected float getTextYOffset(String text, TextPaint paint, int height) {
         Rect bounds = new Rect();
         paint.getTextBounds(text, 0, text.length(), bounds);
         int textHeight = bounds.bottom - bounds.top ;
         return height - ((height - textHeight) / 2) - (int)paint.descent();
     }
 
+    /**
+     * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination 
+     * rectangle of the canvas.
+     */
+    protected void drawIconOnCanvas(Bitmap icon, Canvas canvas, Paint paint, RectF src, RectF dst) {
+        Matrix matrix = new Matrix();
+        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
+        canvas.drawBitmap(icon, matrix, paint);
+    }
+
     private DrawableRecipientChip constructChipSpan(RecipientEntry contact, boolean pressed,
             boolean leaveIconSpace) throws NullPointerException {
         if (mChipBackground == null) {
@@ -749,11 +787,6 @@
         if (mChipPadding == -1) {
             mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
         }
-        mAlternatesLayout = a.getResourceId(R.styleable.RecipientEditTextView_chipAlternatesLayout,
-                -1);
-        if (mAlternatesLayout == -1) {
-            mAlternatesLayout = R.layout.chips_alternate_item;
-        }
 
         mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
 
@@ -772,6 +805,9 @@
         if (mInvalidChipBackground == null) {
             mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
         }
+        mAvatarPosition = a.getInt(R.styleable.RecipientEditTextView_avatarPosition, 0);
+        mDisableDelete = a.getBoolean(R.styleable.RecipientEditTextView_avatarPosition, false);
+
         mLineSpacingExtra =  r.getDimension(R.dimen.line_spacing_extra);
         mMaxLines = r.getInteger(R.integer.chips_max_lines);
         TypedValue tv = new TypedValue();
@@ -779,6 +815,7 @@
             mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
                     .getDisplayMetrics());
         }
+
         a.recycle();
     }
 
@@ -798,6 +835,10 @@
         mChipHeight = height;
     }
 
+    public float getChipHeight() {
+        return mChipHeight;
+    }
+
     /**
      * Set whether to shrink the recipients field such that at most
      * one line of recipients chips are shown when the field loses
@@ -1511,12 +1552,12 @@
 
     private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
         return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
-                getAdapter().getQueryType(), this);
+                getAdapter().getQueryType(), this, mDropdownChipLayouter);
     }
 
     private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) {
-        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
-                .getEntry());
+        return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(),
+                mDropdownChipLayouter);
     }
 
     @Override
@@ -2121,14 +2162,20 @@
         // Figure out the bounds of this chip and whether or not
         // the user clicked in the X portion.
         // TODO: Should x and y be used, or removed?
-        return chip.isSelected() && offset == getChipEnd(chip);
+        if (mDisableDelete) {
+            return false;
+        }
+
+        return chip.isSelected() &&
+                ((mAvatarPosition == 0 && offset == getChipEnd(chip)) ||
+                (mAvatarPosition != 0 && offset == getChipStart(chip)));
     }
 
     /**
      * Remove the chip and any text associated with it from the RecipientEditTextView.
      */
     // Visible for testing.
-    /*pacakge*/ void removeChip(DrawableRecipientChip chip) {
+    /* package */void removeChip(DrawableRecipientChip chip) {
         Spannable spannable = getSpannable();
         int spanStart = spannable.getSpanStart(chip);
         int spanEnd = spannable.getSpanEnd(chip);
diff --git a/src/com/android/ex/chips/SingleRecipientArrayAdapter.java b/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
index 0571a4e..985953f 100644
--- a/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
+++ b/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
@@ -17,47 +17,27 @@
 package com.android.ex.chips;
 
 import android.content.Context;
-import android.text.util.Rfc822Tokenizer;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
 
 class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry> {
-    private int mLayoutId;
+    private final DropdownChipLayouter mDropdownChipLayouter;
 
-    private final LayoutInflater mLayoutInflater;
-
-    public SingleRecipientArrayAdapter(Context context, int resourceId, RecipientEntry entry) {
-        super(context, resourceId, new RecipientEntry[] {
+    public SingleRecipientArrayAdapter(Context context, RecipientEntry entry,
+            DropdownChipLayouter dropdownChipLayouter) {
+        super(context, dropdownChipLayouter.getAlternateItemLayoutResId(), new RecipientEntry[] {
             entry
         });
-        mLayoutInflater = LayoutInflater.from(context);
-        mLayoutId = resourceId;
+
+        mDropdownChipLayouter = dropdownChipLayouter;
     }
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
-        if (convertView == null) {
-            convertView = newView();
-        }
-        bindView(convertView, getItem(position));
-        return convertView;
-    }
-
-    private View newView() {
-        return mLayoutInflater.inflate(mLayoutId, null);
-    }
-
-    private static void bindView(View view, RecipientEntry entry) {
-        TextView display = (TextView) view.findViewById(android.R.id.title);
-        ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
-        display.setText(entry.getDisplayName());
-        display.setVisibility(View.VISIBLE);
-        imageView.setVisibility(View.VISIBLE);
-        TextView destination = (TextView) view.findViewById(android.R.id.text1);
-        destination.setText(Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress());
+        return mDropdownChipLayouter.bindView(convertView, parent, getItem(position), position,
+                AdapterType.SINGLE_RECIPIENT, null);
     }
 }