Merge "Compose now shows images as tiled attachments." into jb-ub-mail
diff --git a/res/layout/compose.xml b/res/layout/compose.xml
index 0d0b680..acbdc1f 100644
--- a/res/layout/compose.xml
+++ b/res/layout/compose.xml
@@ -56,8 +56,17 @@
                 android:paddingTop="2dip"
                 android:paddingRight="5dip"
                 android:paddingBottom="0dip"
-                android:paddingLeft="5dip" />
-
+                android:paddingLeft="5dip">
+                <com.android.mail.ui.AttachmentTileGrid
+                    android:id="@+id/attachment_tile_grid"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+                <LinearLayout
+                    android:id="@+id/attachment_bar_list"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:orientation="vertical" />
+            </com.android.mail.compose.AttachmentsView>
             <!-- Body -->
             <include layout="@layout/compose_body"/>
 
diff --git a/res/layout/compose_attachment_tile.xml b/res/layout/compose_attachment_tile.xml
new file mode 100644
index 0000000..4ee830d
--- /dev/null
+++ b/res/layout/compose_attachment_tile.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2012 Google Inc.
+     Licensed to 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.
+-->
+<com.android.mail.compose.ComposeAttachmentTile
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/attachment_tile"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@drawable/attachment_bg_holo">
+
+    <ImageView
+        android:id="@+id/attachment_tile_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerCrop" />
+
+    <View
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
+        android:background="@color/black_semi_transparent" />
+    <ImageButton
+        android:id="@+id/attachment_tile_close_button"
+        android:src="@drawable/ic_cancel_holo_light"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
+        android:paddingTop="12dip"
+        android:paddingRight="12dip"
+        android:paddingBottom="12dip"
+        android:paddingLeft="12dip"
+        android:focusable="true"
+        android:background="?android:attr/selectableItemBackground" />
+
+</com.android.mail.compose.ComposeAttachmentTile>
diff --git a/res/layout/conversation_message_footer.xml b/res/layout/conversation_message_footer.xml
index 7ceedc7..b8b6ace 100644
--- a/res/layout/conversation_message_footer.xml
+++ b/res/layout/conversation_message_footer.xml
@@ -38,7 +38,7 @@
         android:layout_marginBottom="2dip"
         android:background="@android:color/holo_blue_light"
         android:visibility="gone"/>
-    <com.android.mail.browse.AttachmentTileGrid
+    <com.android.mail.ui.AttachmentTileGrid
         android:id="@+id/attachment_tile_grid"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 3f71f9a..77fd91d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -44,6 +44,7 @@
     <!-- Must match the quoted_text_background_color_string -->
     <color name="compose_background_color">#ebebeb</color>
     <string name="quoted_text_font_color_string" translatable="false">#777777</string>
+    <color name="black_semi_transparent">#77000000</color>
 
     <color name="conv_header_add_label_text">@android:color/black</color>
     <color name="conv_header_add_label_background">#eeeeee</color>
diff --git a/src/com/android/mail/browse/MessageAttachmentTile.java b/src/com/android/mail/browse/MessageAttachmentTile.java
index aaec720..cda46bb 100644
--- a/src/com/android/mail/browse/MessageAttachmentTile.java
+++ b/src/com/android/mail/browse/MessageAttachmentTile.java
@@ -20,20 +20,12 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.PopupMenu.OnMenuItemClickListener;
-import android.widget.RelativeLayout;
 
 import com.android.mail.R;
 import com.android.mail.photo.Intents;
@@ -43,100 +35,25 @@
 import com.android.mail.providers.Attachment;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.providers.UIProvider.AttachmentDestination;
+import com.android.mail.ui.AttachmentTile;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 
-import java.io.IOException;
-
 /**
  * View for a single attachment in conversation view. Shows download status and allows launching
  * intents to act on an attachment.
  *
  */
-public class MessageAttachmentTile extends RelativeLayout implements OnClickListener,
-        OnMenuItemClickListener, AttachmentViewInterface {
+public class MessageAttachmentTile extends AttachmentTile implements OnClickListener,
+        AttachmentViewInterface {
 
-    private Attachment mAttachment;
-    private ImageView mIcon;
-    private ImageView.ScaleType mIconScaleType;
     private int mPhotoIndex;
     private Uri mAttachmentsListUri;
 
     private final AttachmentActionHandler mActionHandler;
 
-    private ThumbnailLoadTask mThumbnailTask;
-
     private static final String LOG_TAG = new LogUtils().getLogTag();
 
-    private class ThumbnailLoadTask extends AsyncTask<Uri, Void, Bitmap> {
-
-        private final int mWidth;
-        private final int mHeight;
-
-        public ThumbnailLoadTask(int width, int height) {
-            mWidth = width;
-            mHeight = height;
-        }
-
-        @Override
-        protected Bitmap doInBackground(Uri... params) {
-            final Uri thumbnailUri = params[0];
-
-            AssetFileDescriptor fd = null;
-            Bitmap result = null;
-
-            try {
-                fd = getContext().getContentResolver().openAssetFileDescriptor(thumbnailUri, "r");
-                if (isCancelled() || fd == null) {
-                    return null;
-                }
-
-                final BitmapFactory.Options opts = new BitmapFactory.Options();
-                opts.inJustDecodeBounds = true;
-
-                BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts);
-                if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) {
-                    return null;
-                }
-
-                opts.inJustDecodeBounds = false;
-
-                LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d",
-                        opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize);
-
-                result = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts);
-
-            } catch (Throwable t) {
-                LogUtils.e(LOG_TAG, t, "Unable to decode thumbnail %s", thumbnailUri);
-            } finally {
-                if (fd != null) {
-                    try {
-                        fd.close();
-                    } catch (IOException e) {
-                        LogUtils.e(LOG_TAG, e, "");
-                    }
-                }
-            }
-
-            return result;
-        }
-
-        @Override
-        protected void onPostExecute(Bitmap result) {
-            if (result == null) {
-                LogUtils.d(LOG_TAG, "back in UI thread, decode failed");
-                setThumbnailToDefault();
-                return;
-            }
-
-            LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(),
-                    result.getHeight());
-            mIcon.setImageBitmap(result);
-            mIcon.setScaleType(mIconScaleType);
-        }
-
-    }
-
     public MessageAttachmentTile(Context context) {
         this(context, null);
     }
@@ -147,12 +64,6 @@
         mActionHandler = new AttachmentActionHandler(context, this);
     }
 
-    public static MessageAttachmentTile inflate(LayoutInflater inflater, ViewGroup parent) {
-        MessageAttachmentTile view = (MessageAttachmentTile) inflater.inflate(
-                R.layout.conversation_message_attachment_tile, parent, false);
-        return view;
-    }
-
     /**
      * Render or update an attachment's view. This happens immediately upon instantiation, and
      * repeatedly as status updates stream in, so only properties with new or changed values will
@@ -160,58 +71,27 @@
      *
      */
     public void render(Attachment attachment, Uri attachmentsListUri, int index) {
-        if (attachment == null) {
-            setVisibility(View.INVISIBLE);
-            return;
-        }
+        super.render(attachment, attachmentsListUri, index);
 
-        final Attachment prevAttachment = mAttachment;
-        mAttachment = attachment;
-        mActionHandler.setAttachment(mAttachment);
         mAttachmentsListUri = attachmentsListUri;
         mPhotoIndex = index;
 
-        LogUtils.d(LOG_TAG, "got attachment list row: name=%s state/dest=%d/%d dled=%d" +
-                " contentUri=%s MIME=%s", attachment.name, attachment.state,
-                attachment.destination, attachment.downloadedSize, attachment.contentUri,
-                attachment.contentType);
-
-        final Uri imageUri = attachment.getImageUri();
-        final Uri prevImageUri = (prevAttachment == null) ? null : prevAttachment.getImageUri();
-        // begin loading a thumbnail if this is an image and either the thumbnail or the original
-        // content is ready (and different from any existing image)
-        if (imageUri != null && (prevImageUri == null || !imageUri.equals(prevImageUri))) {
-            // cancel/dispose any existing task and start a new one
-            if (mThumbnailTask != null) {
-                mThumbnailTask.cancel(true);
-            }
-            mThumbnailTask = new ThumbnailLoadTask(mIcon.getWidth(), mIcon.getHeight());
-            mThumbnailTask.execute(imageUri);
-        } else if (imageUri == null) {
-            // not an image, or no thumbnail exists. fall back to default.
-            // async image load must separately ensure the default appears upon load failure.
-            setThumbnailToDefault();
-        }
-
+        mActionHandler.setAttachment(mAttachment);
         mActionHandler.updateStatus();
     }
 
-    private void setThumbnailToDefault() {
-        mIcon.setImageResource(R.drawable.ic_menu_attachment_holo_light);
-        mIcon.setScaleType(ImageView.ScaleType.CENTER);
+    public static MessageAttachmentTile inflate(LayoutInflater inflater, ViewGroup parent) {
+        MessageAttachmentTile view = (MessageAttachmentTile) inflater.inflate(
+                R.layout.conversation_message_attachment_tile, parent, false);
+        return view;
     }
 
 
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mIcon = (ImageView) findViewById(R.id.attachment_tile_image);
-
         setOnClickListener(this);
-
-        mIconScaleType = mIcon.getScaleType();
     }
 
     @Override
@@ -219,11 +99,6 @@
         onClick(v.getId(), v);
     }
 
-    @Override
-    public boolean onMenuItemClick(MenuItem item) {
-        return onClick(item.getItemId(), null);
-    }
-
     private boolean onClick(int res, View v) {
         mActionHandler.showAttachment(AttachmentDestination.CACHE);
 
diff --git a/src/com/android/mail/browse/MessageFooterView.java b/src/com/android/mail/browse/MessageFooterView.java
index a925292..ca114e4 100644
--- a/src/com/android/mail/browse/MessageFooterView.java
+++ b/src/com/android/mail/browse/MessageFooterView.java
@@ -32,9 +32,10 @@
 import com.android.mail.browse.AttachmentLoader.AttachmentCursor;
 import com.android.mail.browse.ConversationContainer.DetachListener;
 import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
-import com.android.mail.photo.util.ImageUtils;
 import com.android.mail.providers.Attachment;
 import com.android.mail.providers.Message;
+import com.android.mail.ui.AttachmentTile;
+import com.android.mail.ui.AttachmentTileGrid;
 import com.android.mail.utils.LogUtils;
 import com.google.common.collect.Lists;
 
@@ -163,7 +164,7 @@
         List<Attachment> barAttachments = new ArrayList<Attachment>(maxSize);
 
         for (Attachment attachment : attachments) {
-            if (isTiledAttachment(attachment)) {
+            if (AttachmentTile.isTiledAttachment(attachment)) {
                 tiledAttachments.add(attachment);
             } else {
                 barAttachments.add(attachment);
@@ -202,10 +203,6 @@
         }
     }
 
-    private boolean isTiledAttachment(final Attachment attachment) {
-        return ImageUtils.isImageMimeType(attachment.contentType);
-    }
-
     private Integer getAttachmentLoaderId() {
         Integer id = null;
         final Message msg = mMessageHeaderItem == null ? null : mMessageHeaderItem.message;
diff --git a/src/com/android/mail/compose/AttachmentComposeView.java b/src/com/android/mail/compose/AttachmentComposeView.java
index 783b601..2067561 100644
--- a/src/com/android/mail/compose/AttachmentComposeView.java
+++ b/src/com/android/mail/compose/AttachmentComposeView.java
@@ -33,7 +33,7 @@
  * This view is used in the ComposeActivity to display an attachment along with its name/size
  * and a Remove button.
  */
-class AttachmentComposeView extends LinearLayout {
+class AttachmentComposeView extends LinearLayout implements AttachmentDeletionInterface {
     private final Attachment mAttachment;
     private final static String LOG_TAG = new LogUtils().getLogTag();
 
diff --git a/src/com/android/mail/compose/AttachmentDeletionInterface.java b/src/com/android/mail/compose/AttachmentDeletionInterface.java
new file mode 100644
index 0000000..c41a3f8
--- /dev/null
+++ b/src/com/android/mail/compose/AttachmentDeletionInterface.java
@@ -0,0 +1,18 @@
+
+package com.android.mail.compose;
+
+import android.view.View.OnClickListener;
+
+/**
+ * Interface for views in the compose layout that represent
+ * attachments so that the larger attachments view can set
+ * the onClickListener for the close button to remove the attachment
+ * from the draft.
+ */
+interface AttachmentDeletionInterface {
+    /**
+     * Sets the onClickListener for the close button on the attachment view.
+     * @param clickListener the listener to which the close button should be set
+     */
+    public void addDeleteListener(OnClickListener clickListener);
+}
diff --git a/src/com/android/mail/compose/AttachmentsView.java b/src/com/android/mail/compose/AttachmentsView.java
index 5cb4413..d1615c2 100644
--- a/src/com/android/mail/compose/AttachmentsView.java
+++ b/src/com/android/mail/compose/AttachmentsView.java
@@ -15,15 +15,6 @@
  */
 package com.android.mail.compose;
 
-import com.android.mail.R;
-import com.android.mail.providers.Account;
-import com.android.mail.providers.Attachment;
-import com.android.mail.providers.Message;
-import com.android.mail.providers.UIProvider;
-import com.android.mail.utils.LogUtils;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
@@ -35,9 +26,21 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.Toast;
 
+import com.android.mail.R;
+import com.android.mail.providers.Account;
+import com.android.mail.providers.Attachment;
+import com.android.mail.providers.Message;
+import com.android.mail.providers.UIProvider;
+import com.android.mail.ui.AttachmentTile;
+import com.android.mail.ui.AttachmentTileGrid;
+import com.android.mail.utils.LogUtils;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -49,6 +52,8 @@
     private static final String LOG_TAG = new LogUtils().getLogTag();
     private ArrayList<Attachment> mAttachments;
     private AttachmentDeletedListener mChangeListener;
+    private AttachmentTileGrid mTileGrid;
+    private LinearLayout mAttachmentLayout;
 
     public AttachmentsView(Context context) {
         this(context, null);
@@ -59,6 +64,14 @@
         mAttachments = Lists.newArrayList();
     }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mTileGrid = (AttachmentTileGrid) findViewById(R.id.attachment_tile_grid);
+        mAttachmentLayout = (LinearLayout) findViewById(R.id.attachment_bar_list);
+    }
+
     /**
      * Set a listener for changes to the attachments.
      * @param listener
@@ -77,27 +90,42 @@
         }
         mAttachments.add(attachment);
 
-        final AttachmentComposeView attachmentView =
-            new AttachmentComposeView(getContext(), attachment);
+        // If we have an attachment that should be shown in a tiled look,
+        // set up the tile and add it to the tile grid.
+        if (AttachmentTile.isTiledAttachment(attachment)) {
+            final ComposeAttachmentTile attachmentTile =
+                    mTileGrid.addComposeTileFromAttachment(attachment);
+            attachmentTile.addDeleteListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    deleteAttachment(attachmentTile, attachment);
+                }
+            });
+        // Otherwise, use the old bar look and add it to the new
+        // inner LinearLayout.
+        } else {
+            final AttachmentComposeView attachmentView =
+                new AttachmentComposeView(getContext(), attachment);
 
-        attachmentView.addDeleteListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                deleteAttachment(attachmentView, attachment);
-            }
-        });
+            attachmentView.addDeleteListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    deleteAttachment(attachmentView, attachment);
+                }
+            });
 
 
-        addView(attachmentView, new LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.MATCH_PARENT,
-                LinearLayout.LayoutParams.MATCH_PARENT));
+            mAttachmentLayout.addView(attachmentView, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.MATCH_PARENT,
+                    LinearLayout.LayoutParams.MATCH_PARENT));
+        }
     }
 
     @VisibleForTesting
-    protected void deleteAttachment(final AttachmentComposeView attachmentView,
+    protected void deleteAttachment(final View attachmentView,
             final Attachment attachment) {
         mAttachments.remove(attachment);
-        removeView(attachmentView);
+        ((ViewGroup) attachmentView.getParent()).removeView(attachmentView);
         if (mChangeListener != null) {
             mChangeListener.onAttachmentDeleted();
         }
@@ -119,7 +147,8 @@
      */
     public void deleteAllAttachments() {
         mAttachments.clear();
-        removeAllViews();
+        mTileGrid.removeAllViews();
+        mAttachmentLayout.removeAllViews();
     }
 
     /**
diff --git a/src/com/android/mail/compose/ComposeAttachmentTile.java b/src/com/android/mail/compose/ComposeAttachmentTile.java
new file mode 100644
index 0000000..0ece410
--- /dev/null
+++ b/src/com/android/mail/compose/ComposeAttachmentTile.java
@@ -0,0 +1,39 @@
+package com.android.mail.compose;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import com.android.mail.R;
+import com.android.mail.ui.AttachmentTile;
+
+public class ComposeAttachmentTile extends AttachmentTile implements AttachmentDeletionInterface {
+    private ImageButton mDeleteButton;
+
+    public ComposeAttachmentTile(Context context) {
+        this(context, null);
+    }
+
+    public ComposeAttachmentTile(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public static ComposeAttachmentTile inflate(LayoutInflater inflater, ViewGroup parent) {
+        ComposeAttachmentTile view = (ComposeAttachmentTile) inflater.inflate(
+                R.layout.compose_attachment_tile, parent, false);
+        return view;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mDeleteButton = (ImageButton) findViewById(R.id.attachment_tile_close_button);
+    }
+
+    public void addDeleteListener(OnClickListener clickListener) {
+        mDeleteButton.setOnClickListener(clickListener);
+    }
+}
diff --git a/src/com/android/mail/ui/AttachmentTile.java b/src/com/android/mail/ui/AttachmentTile.java
new file mode 100644
index 0000000..bfb9e83
--- /dev/null
+++ b/src/com/android/mail/ui/AttachmentTile.java
@@ -0,0 +1,174 @@
+package com.android.mail.ui;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import com.android.mail.R;
+import com.android.mail.photo.util.ImageUtils;
+import com.android.mail.providers.Attachment;
+import com.android.mail.utils.LogUtils;
+
+import java.io.IOException;
+
+/**
+ * Base class for attachment tiles that handles the work
+ * of fetching and displaying the bitmaps for the tiles.
+ */
+public class AttachmentTile extends RelativeLayout {
+    protected Attachment mAttachment;
+    private ImageView mIcon;
+    private ImageView.ScaleType mIconScaleType;
+    private ThumbnailLoadTask mThumbnailTask;
+
+    private static final String LOG_TAG = new LogUtils().getLogTag();
+
+    /**
+     * Returns true if the attachment should be rendered as a tile.
+     * with a large image preview.
+     * @param attachment the attachment to render
+     * @return true if the attachment should be rendered as a tile
+     */
+    public static boolean isTiledAttachment(final Attachment attachment) {
+        return ImageUtils.isImageMimeType(attachment.contentType);
+    }
+
+    public AttachmentTile(Context context) {
+        this(context, null);
+    }
+
+    public AttachmentTile(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mIcon = (ImageView) findViewById(R.id.attachment_tile_image);
+        mIconScaleType = mIcon.getScaleType();
+    }
+
+    /**
+     * Render or update an attachment's view. This happens immediately upon instantiation, and
+     * repeatedly as status updates stream in, so only properties with new or changed values will
+     * cause sub-views to update.
+     *
+     */
+    public void render(Attachment attachment, Uri attachmentsListUri, int index) {
+        if (attachment == null) {
+            setVisibility(View.INVISIBLE);
+            return;
+        }
+
+        final Attachment prevAttachment = mAttachment;
+        mAttachment = attachment;
+
+        LogUtils.d(LOG_TAG, "got attachment list row: name=%s state/dest=%d/%d dled=%d" +
+                " contentUri=%s MIME=%s", attachment.name, attachment.state,
+                attachment.destination, attachment.downloadedSize, attachment.contentUri,
+                attachment.contentType);
+
+        setupThumbnailPreview(attachment, prevAttachment);
+    }
+
+    private void setupThumbnailPreview(Attachment attachment, final Attachment prevAttachment) {
+        final Uri imageUri = attachment.getImageUri();
+        final Uri prevImageUri = (prevAttachment == null) ? null : prevAttachment.getImageUri();
+        // begin loading a thumbnail if this is an image and either the thumbnail or the original
+        // content is ready (and different from any existing image)
+        if (imageUri != null && (prevImageUri == null || !imageUri.equals(prevImageUri))) {
+            // cancel/dispose any existing task and start a new one
+            if (mThumbnailTask != null) {
+                mThumbnailTask.cancel(true);
+            }
+            mThumbnailTask = new ThumbnailLoadTask(mIcon.getWidth(), mIcon.getHeight());
+            mThumbnailTask.execute(imageUri);
+        } else if (imageUri == null) {
+            // not an image, or no thumbnail exists. fall back to default.
+            // async image load must separately ensure the default appears upon load failure.
+            setThumbnailToDefault();
+        }
+    }
+
+    private void setThumbnailToDefault() {
+        mIcon.setImageResource(R.drawable.ic_menu_attachment_holo_light);
+        mIcon.setScaleType(ImageView.ScaleType.CENTER);
+    }
+
+    private class ThumbnailLoadTask extends AsyncTask<Uri, Void, Bitmap> {
+
+        private final int mWidth;
+        private final int mHeight;
+
+        public ThumbnailLoadTask(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        @Override
+        protected Bitmap doInBackground(Uri... params) {
+            final Uri thumbnailUri = params[0];
+
+            AssetFileDescriptor fd = null;
+            Bitmap result = null;
+
+            try {
+                fd = getContext().getContentResolver().openAssetFileDescriptor(thumbnailUri, "r");
+                if (isCancelled() || fd == null) {
+                    return null;
+                }
+
+                final BitmapFactory.Options opts = new BitmapFactory.Options();
+                opts.inJustDecodeBounds = true;
+
+                BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts);
+                if (isCancelled() || opts.outWidth == -1 || opts.outHeight == -1) {
+                    return null;
+                }
+
+                opts.inJustDecodeBounds = false;
+
+                LogUtils.d(LOG_TAG, "in background, src w/h=%d/%d dst w/h=%d/%d, divider=%d",
+                        opts.outWidth, opts.outHeight, mWidth, mHeight, opts.inSampleSize);
+
+                result = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, opts);
+
+            } catch (Throwable t) {
+                LogUtils.e(LOG_TAG, t, "Unable to decode thumbnail %s", thumbnailUri);
+            } finally {
+                if (fd != null) {
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                        LogUtils.e(LOG_TAG, e, "");
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        @Override
+        protected void onPostExecute(Bitmap result) {
+            if (result == null) {
+                LogUtils.d(LOG_TAG, "back in UI thread, decode failed");
+                setThumbnailToDefault();
+                return;
+            }
+
+            LogUtils.d(LOG_TAG, "back in UI thread, decode success, w/h=%d/%d", result.getWidth(),
+                    result.getHeight());
+            mIcon.setImageBitmap(result);
+            mIcon.setScaleType(mIconScaleType);
+        }
+
+    }
+}
diff --git a/src/com/android/mail/browse/AttachmentTileGrid.java b/src/com/android/mail/ui/AttachmentTileGrid.java
similarity index 87%
rename from src/com/android/mail/browse/AttachmentTileGrid.java
rename to src/com/android/mail/ui/AttachmentTileGrid.java
index 84d483b..491d0e4 100644
--- a/src/com/android/mail/browse/AttachmentTileGrid.java
+++ b/src/com/android/mail/ui/AttachmentTileGrid.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.mail.browse;
+package com.android.mail.ui;
 
 import android.content.Context;
 import android.net.Uri;
@@ -24,12 +24,14 @@
 import android.widget.FrameLayout;
 
 import com.android.mail.R;
+import com.android.mail.browse.MessageAttachmentTile;
+import com.android.mail.compose.ComposeAttachmentTile;
 import com.android.mail.providers.Attachment;
 
 import java.util.List;
 
 /**
- * Acts as a grid composed of {@link MessageAttachmentTile}s.
+ * Acts as a grid composed of {@link AttachmentTile}s.
  */
 public class AttachmentTileGrid extends FrameLayout {
     private LayoutInflater mInflater;
@@ -52,23 +54,33 @@
         // Adding tiles to grid and filling in attachment information
         int index = 0;
         for (Attachment attachment : list) {
-            addTileFromAttachment(attachment, index++);
+            addMessageTileFromAttachment(attachment, index++);
         }
     }
 
-    private void addTileFromAttachment(Attachment attachment, int index) {
-        final MessageAttachmentTile attachmentTile;
+    private void addMessageTileFromAttachment(Attachment attachment, int index) {
+        final AttachmentTile attachmentTile;
 
         if (getChildCount() <= index) {
             attachmentTile = MessageAttachmentTile.inflate(mInflater, this);
             addView(attachmentTile);
         } else {
-            attachmentTile = (MessageAttachmentTile) getChildAt(index);
+            attachmentTile = (AttachmentTile) getChildAt(index);
         }
 
         attachmentTile.render(attachment, mAttachmentsListUri, index);
     }
 
+    public ComposeAttachmentTile addComposeTileFromAttachment(Attachment attachment) {
+        final ComposeAttachmentTile attachmentTile =
+                ComposeAttachmentTile.inflate(mInflater, this);
+
+        addView(attachmentTile);
+        attachmentTile.render(attachment, null, -1);
+
+        return attachmentTile;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         onMeasureForTiles(widthMeasureSpec);