Snap for 6258215 from b16e5474671e5aeedadc3949db5b807f07238288 to r-keystone-qcom-release

Change-Id: I10abc88e860be09b3f6912f7f5197c05d38b8fac
diff --git a/res/layout/compose_message_view.xml b/res/layout/compose_message_view.xml
index fd60e2a..8bb8249 100644
--- a/res/layout/compose_message_view.xml
+++ b/res/layout/compose_message_view.xml
@@ -88,8 +88,8 @@
             android:layout_height="wrap_content"
             android:orientation="horizontal">
 
-            <!-- Contains compose message bubble and character counter for SMS which should be left
-            aligned -->
+            <!-- Contains compose message bubble and character counter for SMS or attachments size
+                 for MMS which should be left aligned -->
             <LinearLayout
                 android:layout_width="0dp"
                 android:layout_weight="1"
@@ -128,8 +128,8 @@
                 </LinearLayout>
 
                 <TextView
-                    android:id="@+id/char_counter"
-                    style="@style/ComposeMessageViewTextCounterStyle"
+                    android:id="@+id/message_body_size"
+                    style="@style/ComposeMessageViewMessageBodySizeStyle"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:singleLine="true"
diff --git a/res/layout/gallery_grid_item_view.xml b/res/layout/gallery_grid_item_view.xml
index 8b7ee58..fc857ca 100644
--- a/res/layout/gallery_grid_item_view.xml
+++ b/res/layout/gallery_grid_item_view.xml
@@ -21,12 +21,67 @@
     android:background="@color/gallery_image_default_background"
     android:clickable="true">
 
+    <!-- Thumbnail for image and video contents. -->
     <com.android.messaging.ui.AsyncImageView
-        android:id="@+id/image"
+        android:id="@+id/thumbnail"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:scaleType="centerCrop" />
 
+    <!-- Additional info such as icon, name and etc. -->
+    <RelativeLayout
+        android:id="@+id/additional_info"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:orientation="vertical"
+        android:visibility="gone" >
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_above="@id/file_info" >
+            <ImageView
+                android:id="@+id/icon"
+                android:layout_width="@dimen/gallery_icon_size"
+                android:layout_height="@dimen/gallery_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitCenter"
+                android:background="@color/background_item_transparent"
+                android:visibility="gone" />
+        </FrameLayout>
+
+        <!-- File info for audio contents only -->
+        <LinearLayout
+            android:id="@+id/file_info"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="4dp"
+            android:paddingLeft="4dp"
+            android:paddingRight="4dp"
+            android:orientation="vertical"
+            android:visibility="gone" >
+
+            <TextView
+                android:id="@+id/file_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textAppearance="@android:style/TextAppearance.Material.Subhead" />
+
+            <TextView
+                android:id="@+id/file_type"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:gravity="right"
+                android:textAppearance="@android:style/TextAppearance.Material.Caption" />
+        </LinearLayout>
+    </RelativeLayout>
+
     <View
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -43,4 +98,5 @@
         android:paddingTop="4dp"
         android:visibility="gone"
         android:contentDescription="@string/gallery_checkbox_content_description" />
+
 </com.android.messaging.ui.mediapicker.GalleryGridItemView>
diff --git a/res/layout/mediapicker_image_chooser.xml b/res/layout/mediapicker_gallery_chooser.xml
similarity index 100%
rename from res/layout/mediapicker_image_chooser.xml
rename to res/layout/mediapicker_gallery_chooser.xml
diff --git a/res/values-ldrtl/styles.xml b/res/values-ldrtl/styles.xml
index bd270c7..de4d9e8 100644
--- a/res/values-ldrtl/styles.xml
+++ b/res/values-ldrtl/styles.xml
@@ -171,9 +171,9 @@
         <item name="android:layout_marginStart">4dp</item>
     </style>
 
-    <style name="ComposeMessageViewTextCounterStyle">
-        <item name="android:textColor">@color/message_text_counter_color</item>
-        <item name="android:textSize">@dimen/message_text_counter_size</item>
+    <style name="ComposeMessageViewMessageBodySizeStyle">
+        <item name="android:textColor">@color/message_body_size_text_color</item>
+        <item name="android:textSize">@dimen/message_body_size_text_size</item>
         <item name="android:fontFamily">sans-serif</item>
         <item name="android:layout_gravity">end|center_vertical</item>
         <item name="android:paddingEnd">@dimen/compose_message_text_box_padding_side</item>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f33e105..035ed81 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -128,7 +128,7 @@
     <color name="fab_pressed_color">#3ea4dc</color>
     <color name="fab_ripple">#40ffffff</color>
 
-    <color name="message_text_counter_color">#555555</color>
+    <color name="message_body_size_text_color">#555555</color>
     <color name="mms_indicator_color">#8BC34A</color>
     <color name="list_empty_text">#6d6d6d</color>
     <color name="low_storage_action_item_color">#ff000000</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5ff0eb7..d775c7e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,6 +85,7 @@
     <!-- Flings faster than this length / sec will go from fullscreen straight to closed -->
     <dimen name="mediapicker_big_fling_threshold">1000dp</dimen>
     <dimen name="gallery_image_cell_size">110dp</dimen>
+    <dimen name="gallery_icon_size">40dp</dimen>
     <dimen name="single_attachment_min_dimen">50dp</dimen>
     <dimen name="single_attachment_max_height">150dp</dimen>
     <dimen name="multiple_attachment_preview_height">130dp</dimen>
@@ -116,7 +117,7 @@
     <dimen name="fab_elevation_pressed">6dp</dimen>
     <dimen name="fab_bottom_margin">12dp</dimen>
     <dimen name="fab_left_right_margin">14dp</dimen>
-    <dimen name="message_text_counter_size">12sp</dimen>
+    <dimen name="message_body_size_text_size">12sp</dimen>
 
     <dimen name="vcard_detail_group_indicator_width">40dp</dimen>
     <dimen name="mms_indicator_size">12sp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0e91156..521b1fa 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -53,7 +53,7 @@
     <string name="mediapicker_cameraChooserDescription">Capture pictures or video</string>
     <string name="mediapicker_galleryChooserDescription">Choose images from this device</string>
     <string name="mediapicker_audioChooserDescription">Record audio</string>
-    <string name="mediapicker_gallery_title">Choose photo</string>
+    <string name="mediapicker_gallery_title">Choose media</string>
     <string name="mediapicker_gallery_item_selected_content_description">The media is selected.</string>
     <string name="mediapicker_gallery_item_unselected_content_description">The media is unselected.</string>
     <string name="mediapicker_gallery_title_selection"><xliff:g id="count">%d</xliff:g> selected</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 643d044..46533af 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -361,13 +361,13 @@
         <item name="android:background">@null</item>
     </style>
 
-    <style name="DropDownListViewStyle">
+    <style name="DropDownListViewStyle" parent="Widget.AppCompat.ListView.DropDown">
       <item name="android:dividerHeight">0dp</item>
     </style>
 
-    <style name="ComposeMessageViewTextCounterStyle">
-        <item name="android:textColor">@color/message_text_counter_color</item>
-        <item name="android:textSize">@dimen/message_text_counter_size</item>
+    <style name="ComposeMessageViewMessageBodySizeStyle">
+        <item name="android:textColor">@color/message_body_size_text_color</item>
+        <item name="android:textSize">@dimen/message_body_size_text_size</item>
         <item name="android:fontFamily">sans-serif</item>
         <item name="android:layout_gravity">right|center_vertical</item>
         <item name="android:paddingRight">@dimen/compose_message_text_box_padding_side</item>
diff --git a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
index 28ec303..1974a5e 100644
--- a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
+++ b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
@@ -20,7 +20,7 @@
 import android.net.Uri;
 import android.provider.MediaStore.Files;
 import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.Images.Media;
+import android.provider.MediaStore.MediaColumns;
 
 import com.android.messaging.datamodel.data.GalleryGridItemData;
 import com.android.messaging.datamodel.data.MessagePartData;
@@ -32,18 +32,22 @@
 public class GalleryBoundCursorLoader extends BoundCursorLoader {
     public static final String MEDIA_SCANNER_VOLUME_EXTERNAL = "external";
     private static final Uri STORAGE_URI = Files.getContentUri(MEDIA_SCANNER_VOLUME_EXTERNAL);
-    private static final String SORT_ORDER = Media.DATE_MODIFIED + " DESC";
-    private static final String IMAGE_SELECTION = createSelection(
-            MessagePartData.ACCEPTABLE_IMAGE_TYPES,
-            new Integer[] { FileColumns.MEDIA_TYPE_IMAGE });
+    private static final String SORT_ORDER = MediaColumns.DATE_MODIFIED + " DESC";
+    private static final String SELECTION = createSelection(
+            MessagePartData.ACCEPTABLE_GALLERY_MEDIA_TYPES,
+            new Integer[] {
+                FileColumns.MEDIA_TYPE_IMAGE,
+                FileColumns.MEDIA_TYPE_VIDEO,
+                FileColumns.MEDIA_TYPE_AUDIO
+            });
 
     public GalleryBoundCursorLoader(final String bindingId, final Context context) {
-        super(bindingId, context, STORAGE_URI, GalleryGridItemData.IMAGE_PROJECTION,
-                IMAGE_SELECTION, null, SORT_ORDER);
+        super(bindingId, context, STORAGE_URI, GalleryGridItemData.MEDIA_PROJECTION, SELECTION,
+                null, SORT_ORDER);
     }
 
     private static String createSelection(final String[] mimeTypes, Integer[] mediaTypes) {
-        return Media.MIME_TYPE + " IN ('" + Joiner.on("','").join(mimeTypes) + "') AND "
+        return MediaColumns.MIME_TYPE + " IN ('" + Joiner.on("','").join(mimeTypes) + "') AND "
                 + FileColumns.MEDIA_TYPE + " IN (" + Joiner.on(',').join(mediaTypes) + ")";
     }
 }
diff --git a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
index 8a41f4a..ecbec10 100644
--- a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
+++ b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
@@ -24,12 +24,14 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
 
 import com.android.messaging.Factory;
 import com.android.messaging.datamodel.BugleDatabaseOperations;
 import com.android.messaging.datamodel.DataModel;
 import com.android.messaging.datamodel.DatabaseHelper;
 import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
 import com.android.messaging.datamodel.DatabaseWrapper;
 import com.android.messaging.datamodel.MessagingContentProvider;
 import com.android.messaging.datamodel.data.MessageData;
@@ -45,6 +47,7 @@
 import com.android.messaging.util.PhoneUtils;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -218,13 +221,15 @@
         final DatabaseWrapper db = DataModel.get().getDatabase();
         final long now = System.currentTimeMillis();
 
-        final String toSendMessageId = findNextMessageToSend(db, now);
-        if (toSendMessageId != null) {
-            return true;
-        } else {
-            final String toDownloadMessageId = findNextMessageToDownload(db, now);
-            if (toDownloadMessageId != null) {
+        for (int subId : getActiveSubscriptionIds()) {
+            final String toSendMessageId = findNextMessageToSend(db, now, subId);
+            if (toSendMessageId != null) {
                 return true;
+            } else {
+                final String toDownloadMessageId = findNextMessageToDownload(db, now, subId);
+                if (toDownloadMessageId != null) {
+                    return true;
+                }
             }
         }
         // Messages may be in the process of sending/downloading even when there are no pending
@@ -232,6 +237,21 @@
         return false;
     }
 
+    private static int[] getActiveSubscriptionIds() {
+        if (!OsUtil.isAtLeastL_MR1()) {
+            return new int[] { ParticipantData.DEFAULT_SELF_SUB_ID };
+        }
+        List<SubscriptionInfo> subscriptions = PhoneUtils.getDefault().toLMr1()
+            .getActiveSubscriptionInfoList();
+
+        int numSubs = subscriptions.size();
+        int[] result = new int[numSubs];
+        for (int i = 0; i < numSubs; i++) {
+            result[i] = subscriptions.get(i).getSubscriptionId();
+        }
+        return result;
+    }
+
     /**
      * Queue any pending actions
      * @param actionState
@@ -240,37 +260,44 @@
     private boolean queueActions(final Action processingAction) {
         final DatabaseWrapper db = DataModel.get().getDatabase();
         final long now = System.currentTimeMillis();
-        boolean succeeded = true;
+        boolean succeeded = false;
 
-        // Will queue no more than one message to send plus one message to download
+        // Will queue no more than one message per subscription to send plus one message to download
         // This keeps outgoing messages "in order" but allow downloads to happen even if sending
         //  gets blocked until messages time out.  Manual resend bumps messages to head of queue.
-        final String toSendMessageId = findNextMessageToSend(db, now);
-        final String toDownloadMessageId = findNextMessageToDownload(db, now);
-        if (toSendMessageId != null) {
-            LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toSendMessageId
-                    + " for sending");
-            // This could queue nothing
-            if (!SendMessageAction.queueForSendInBackground(toSendMessageId, processingAction)) {
-                LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
-                        + toSendMessageId + " for sending");
-                succeeded = false;
+        for (int subId : getActiveSubscriptionIds()) {
+            final String toSendMessageId = findNextMessageToSend(db, now, subId);
+            final String toDownloadMessageId = findNextMessageToDownload(db, now, subId);
+            if (toSendMessageId != null) {
+                LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toSendMessageId
+                        + " for sending");
+                // This could queue nothing
+                if (!SendMessageAction.queueForSendInBackground(toSendMessageId,
+                            processingAction)) {
+                    LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
+                            + toSendMessageId + " for sending");
+                } else {
+                    succeeded = true;
+                }
             }
-        }
-        if (toDownloadMessageId != null) {
-            LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toDownloadMessageId
-                    + " for download");
-            // This could queue nothing
-            if (!DownloadMmsAction.queueMmsForDownloadInBackground(toDownloadMessageId,
-                    processingAction)) {
-                LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
+            if (toDownloadMessageId != null) {
+                LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message "
                         + toDownloadMessageId + " for download");
-                succeeded = false;
+                // This could queue nothing
+                if (!DownloadMmsAction.queueMmsForDownloadInBackground(toDownloadMessageId,
+                            processingAction)) {
+                    LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
+                            + toDownloadMessageId + " for download");
+                } else {
+                    succeeded = true;
+                }
+
             }
-        }
-        if (toSendMessageId == null && toDownloadMessageId == null) {
-            if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
-                LogUtil.d(TAG, "ProcessPendingMessagesAction: No messages to send or download");
+            if (toSendMessageId == null && toDownloadMessageId == null) {
+                if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+                    LogUtil.d(TAG, "ProcessPendingMessagesAction: No messages to send or download");
+                }
+                succeeded = true;
             }
         }
         return succeeded;
@@ -293,7 +320,21 @@
         return null;
     }
 
-    private static String findNextMessageToSend(final DatabaseWrapper db, final long now) {
+    private static String prefixColumnWithTable(final  String tableName, final String column) {
+        return tableName + "." + column;
+    }
+
+    private static String[] prefixProjectionWithTable(final String tableName,
+            final String[] projection) {
+        String[] result = new String[projection.length];
+        for (int i = 0; i < projection.length; i++) {
+            result[i] = prefixColumnWithTable(tableName, projection[i]);
+        }
+        return result;
+    }
+
+    private static String findNextMessageToSend(final DatabaseWrapper db, final long now,
+            final int subId) {
         String toSendMessageId = null;
         db.beginTransaction();
         Cursor sending = null;
@@ -302,12 +343,25 @@
         int pendingCnt = 0;
         int failedCnt = 0;
         try {
+            String[] projection = prefixProjectionWithTable(DatabaseHelper.MESSAGES_TABLE,
+                    MessageData.getProjection());
+            String subIdClause =
+                    prefixColumnWithTable(DatabaseHelper.MESSAGES_TABLE,
+                            MessageColumns.SELF_PARTICIPANT_ID)
+                    + " = "
+                    + prefixColumnWithTable(DatabaseHelper.PARTICIPANTS_TABLE,
+                            ParticipantColumns._ID)
+                    + " AND " + ParticipantColumns.SUB_ID + " =?";
+
             // First check to see if we have any messages already sending
-            sending = db.query(DatabaseHelper.MESSAGES_TABLE,
-                    MessageData.getProjection(),
-                    DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+            sending = db.query(DatabaseHelper.MESSAGES_TABLE + ","
+                            + DatabaseHelper.PARTICIPANTS_TABLE,
+                            projection,
+                    DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)"
+                            + " AND " + subIdClause,
                     new String[]{Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING),
-                           Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING)},
+                           Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING),
+                           Integer.toString(subId)},
                     null,
                     null,
                     DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
@@ -317,12 +371,14 @@
             final ContentValues values = new ContentValues();
             values.put(DatabaseHelper.MessageColumns.STATUS,
                     MessageData.BUGLE_STATUS_OUTGOING_FAILED);
-            cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
-                    MessageData.getProjection(),
+            cursor = db.query(DatabaseHelper.MESSAGES_TABLE + ","
+                            + DatabaseHelper.PARTICIPANTS_TABLE,
+                    projection,
                     DatabaseHelper.MessageColumns.STATUS + " IN ("
                             + MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ","
-                            + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ")",
-                    null,
+                            + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ")"
+                            + " AND " + subIdClause,
+                    new String[]{Integer.toString(subId)},
                     null,
                     null,
                     DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
@@ -388,31 +444,49 @@
         return toSendMessageId;
     }
 
-    private static String findNextMessageToDownload(final DatabaseWrapper db, final long now) {
+    private static String findNextMessageToDownload(final DatabaseWrapper db, final long now,
+            final int subId) {
         String toDownloadMessageId = null;
         db.beginTransaction();
         Cursor cursor = null;
         int downloadingCnt = 0;
         int pendingCnt = 0;
         try {
+            String[] projection = prefixProjectionWithTable(DatabaseHelper.MESSAGES_TABLE,
+                    MessageData.getProjection());
+            String subIdClause =
+                    prefixColumnWithTable(DatabaseHelper.MESSAGES_TABLE,
+                            MessageColumns.SELF_PARTICIPANT_ID)
+                    + " = "
+                    + prefixColumnWithTable(DatabaseHelper.PARTICIPANTS_TABLE,
+                            ParticipantColumns._ID)
+                    + " AND " + ParticipantColumns.SUB_ID + " =?";
+
             // First check if we have any messages already downloading
-            downloadingCnt = (int) db.queryNumEntries(DatabaseHelper.MESSAGES_TABLE,
-                    DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+            downloadingCnt = (int) db.queryNumEntries(DatabaseHelper.MESSAGES_TABLE
+                    + "," + DatabaseHelper.PARTICIPANTS_TABLE,
+                    DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)"
+                            + " AND " + subIdClause,
                     new String[] {
                         Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING),
-                        Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING)
+                        Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING),
+                        Integer.toString(subId)
                     });
 
             // TODO: This query is not actually needed if downloadingCnt == 0.
-            cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
-                    MessageData.getProjection(),
+            cursor = db.query(DatabaseHelper.MESSAGES_TABLE + ","
+                    + DatabaseHelper.PARTICIPANTS_TABLE,
+                    projection,
                     DatabaseHelper.MessageColumns.STATUS + " =? OR "
-                            + DatabaseHelper.MessageColumns.STATUS + " =?",
+                            + DatabaseHelper.MessageColumns.STATUS + " =?"
+                            + " AND " + subIdClause,
                     new String[]{
                             Integer.toString(
                                     MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD),
                             Integer.toString(
-                                    MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD)
+                                    MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD),
+                            Integer.toString(
+                                    subId)
                     },
                     null,
                     null,
diff --git a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
index 6649757..941d38d 100644
--- a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
+++ b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
@@ -20,25 +20,29 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.provider.BaseColumns;
-import android.provider.MediaStore.Images.Media;
+import android.provider.MediaStore.MediaColumns;
 import android.text.TextUtils;
 
 import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
 import com.android.messaging.datamodel.media.ImageRequest;
 import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.datamodel.media.VideoThumbnailRequestDescriptor;
 import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.UriUtil;
 
 /**
  * Provides data for GalleryGridItemView
  */
 public class GalleryGridItemData {
-    public static final String[] IMAGE_PROJECTION = new String[] {
-        Media._ID,
-        Media.DATA,
-        Media.WIDTH,
-        Media.HEIGHT,
-        Media.MIME_TYPE,
-        Media.DATE_MODIFIED};
+    public static final String[] MEDIA_PROJECTION = new String[] {
+        MediaColumns._ID,
+        MediaColumns.DATA,
+        MediaColumns.WIDTH,
+        MediaColumns.HEIGHT,
+        MediaColumns.MIME_TYPE,
+        MediaColumns.DATE_MODIFIED,
+        MediaColumns.DISPLAY_NAME};
 
     public static final String[] SPECIAL_ITEM_COLUMNS = new String[] {
         BaseColumns._ID
@@ -46,20 +50,23 @@
 
     private static final int INDEX_ID = 0;
 
-    // For local image gallery.
+    // For local media gallery.
     private static final int INDEX_DATA_PATH = 1;
     private static final int INDEX_WIDTH = 2;
     private static final int INDEX_HEIGHT = 3;
     private static final int INDEX_MIME_TYPE = 4;
     private static final int INDEX_DATE_MODIFIED = 5;
+    private static final int INDEX_DISPLAY_NAME = 6;
 
-    /** A special item's id for picking images from document picker */
+    /** A special item's id for picking a media from document picker */
     public static final String ID_DOCUMENT_PICKER_ITEM = "-1";
 
     private UriImageRequestDescriptor mImageData;
     private String mContentType;
     private boolean mIsDocumentPickerItem;
     private long mDateSeconds;
+    private String mFileName;
+    private Uri mAudioUri;
 
     public GalleryGridItemData() {
     }
@@ -71,29 +78,45 @@
             mImageData = null;
             mContentType = null;
         } else {
-            int sourceWidth = cursor.getInt(INDEX_WIDTH);
-            int sourceHeight = cursor.getInt(INDEX_HEIGHT);
-
-            // Guard against bad data
-            if (sourceWidth <= 0) {
-                sourceWidth = ImageRequest.UNSPECIFIED_SIZE;
-            }
-            if (sourceHeight <= 0) {
-                sourceHeight = ImageRequest.UNSPECIFIED_SIZE;
-            }
-
             mContentType = cursor.getString(INDEX_MIME_TYPE);
+            final String filePath = cursor.getString(INDEX_DATA_PATH);
             final String dateModified = cursor.getString(INDEX_DATE_MODIFIED);
             mDateSeconds = !TextUtils.isEmpty(dateModified) ? Long.parseLong(dateModified) : -1;
-            mImageData = new FileImageRequestDescriptor(
-                    cursor.getString(INDEX_DATA_PATH),
-                    desiredWidth,
-                    desiredHeight,
-                    sourceWidth,
-                    sourceHeight,
-                    true /* canUseThumbnail */,
-                    true /* allowCompression */,
-                    true /* isStatic */);
+            if (ContentType.isAudioType(mContentType)) {
+                mImageData = null;
+                mAudioUri = UriUtil.getUriForResourceFile(filePath);
+                mFileName = cursor.getString(INDEX_DISPLAY_NAME);
+            } else { // For image and video types
+                int sourceWidth = cursor.getInt(INDEX_WIDTH);
+                int sourceHeight = cursor.getInt(INDEX_HEIGHT);
+
+                // Guard against bad data
+                if (sourceWidth <= 0) {
+                    sourceWidth = ImageRequest.UNSPECIFIED_SIZE;
+                }
+                if (sourceHeight <= 0) {
+                    sourceHeight = ImageRequest.UNSPECIFIED_SIZE;
+                }
+
+                if (ContentType.isVideoType(mContentType)) {
+                    mImageData = new VideoThumbnailRequestDescriptor(
+                            cursor.getLong(INDEX_ID),
+                            desiredWidth,
+                            desiredHeight,
+                            sourceWidth,
+                            sourceHeight);
+                } else {
+                    mImageData = new FileImageRequestDescriptor(
+                            filePath,
+                            desiredWidth,
+                            desiredHeight,
+                            sourceWidth,
+                            sourceHeight,
+                            true /* canUseThumbnail */,
+                            true /* allowCompression */,
+                            true /* isStatic */);
+                }
+            }
         }
     }
 
@@ -102,7 +125,7 @@
     }
 
     public Uri getImageUri() {
-        return mImageData.uri;
+        return ContentType.isAudioType(mContentType) ? mAudioUri : mImageData.uri;
     }
 
     public UriImageRequestDescriptor getImageRequestDescriptor() {
@@ -111,8 +134,10 @@
 
     public MessagePartData constructMessagePartData(final Rect startRect) {
         Assert.isTrue(!mIsDocumentPickerItem);
-        return new MediaPickerMessagePartData(startRect, mContentType,
-                mImageData.uri, mImageData.sourceWidth, mImageData.sourceHeight);
+        return ContentType.isAudioType(mContentType)
+                ? new MediaPickerMessagePartData(startRect, mContentType, mAudioUri, 0, 0)
+                : new MediaPickerMessagePartData(startRect, mContentType, mImageData.uri,
+                        mImageData.sourceWidth, mImageData.sourceHeight);
     }
 
     /**
@@ -125,4 +150,8 @@
     public String getContentType() {
         return mContentType;
     }
+
+    public String getFileName() {
+        return mFileName;
+    }
 }
diff --git a/src/com/android/messaging/datamodel/data/MediaPickerData.java b/src/com/android/messaging/datamodel/data/MediaPickerData.java
index 7fef67f..5a2ef33 100644
--- a/src/com/android/messaging/datamodel/data/MediaPickerData.java
+++ b/src/com/android/messaging/datamodel/data/MediaPickerData.java
@@ -51,7 +51,7 @@
         mGalleryLoaderCallbacks = new GalleryLoaderCallbacks();
     }
 
-    public static final int GALLERY_IMAGE_LOADER = 1;
+    public static final int GALLERY_MEDIA_LOADER = 1;
 
     /**
      * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
@@ -63,7 +63,7 @@
             // Check if data still bound to the requesting ui element
             if (isBound(bindingId)) {
                 switch (id) {
-                    case GALLERY_IMAGE_LOADER:
+                    case GALLERY_MEDIA_LOADER:
                         return new GalleryBoundCursorLoader(bindingId, mContext);
 
                     default:
@@ -84,9 +84,9 @@
             final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
             if (isBound(cursorLoader.getBindingId())) {
                 switch (loader.getId()) {
-                    case GALLERY_IMAGE_LOADER:
+                    case GALLERY_MEDIA_LOADER:
                         mListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
-                                GALLERY_IMAGE_LOADER);
+                                GALLERY_MEDIA_LOADER);
                         break;
 
                     default:
@@ -106,9 +106,9 @@
             final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
             if (isBound(cursorLoader.getBindingId())) {
                 switch (loader.getId()) {
-                    case GALLERY_IMAGE_LOADER:
+                    case GALLERY_MEDIA_LOADER:
                         mListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
-                                GALLERY_IMAGE_LOADER);
+                                GALLERY_MEDIA_LOADER);
                         break;
 
                     default:
@@ -129,7 +129,7 @@
             args = new Bundle();
         }
         args.putString(BINDING_ID, binding.getBindingId());
-        if (loaderId == GALLERY_IMAGE_LOADER) {
+        if (loaderId == GALLERY_MEDIA_LOADER) {
             mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad();
         } else {
             Assert.fail("Unsupported loader id for media picker!");
@@ -149,7 +149,7 @@
     protected void unregisterListeners() {
         // This could be null if we bind but the caller doesn't init the BindableData
         if (mLoaderManager != null) {
-            mLoaderManager.destroyLoader(GALLERY_IMAGE_LOADER);
+            mLoaderManager.destroyLoader(GALLERY_MEDIA_LOADER);
             mLoaderManager = null;
         }
     }
@@ -172,4 +172,4 @@
                 selectedIndex);
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/com/android/messaging/datamodel/data/MessagePartData.java b/src/com/android/messaging/datamodel/data/MessagePartData.java
index fffaca8..ea24876 100644
--- a/src/com/android/messaging/datamodel/data/MessagePartData.java
+++ b/src/com/android/messaging/datamodel/data/MessagePartData.java
@@ -52,9 +52,22 @@
  */
 public class MessagePartData implements Parcelable {
     public static final int UNSPECIFIED_SIZE = MessagingContentProvider.UNSPECIFIED_SIZE;
-    public static final String[] ACCEPTABLE_IMAGE_TYPES =
-            new String[] { ContentType.IMAGE_JPEG, ContentType.IMAGE_JPG, ContentType.IMAGE_PNG,
-                ContentType.IMAGE_GIF };
+
+    public static final String[] ACCEPTABLE_GALLERY_MEDIA_TYPES =
+            new String[] {
+                // Acceptable image types
+                ContentType.IMAGE_JPEG, ContentType.IMAGE_JPG, ContentType.IMAGE_PNG,
+                ContentType.IMAGE_GIF, ContentType.IMAGE_WBMP, ContentType.IMAGE_X_MS_BMP,
+                // Acceptable video types
+                ContentType.VIDEO_3GP, ContentType.VIDEO_3GPP, ContentType.VIDEO_3G2,
+                ContentType.VIDEO_H263, ContentType.VIDEO_M4V, ContentType.VIDEO_MP4,
+                ContentType.VIDEO_MPEG, ContentType.VIDEO_MPEG4, ContentType.VIDEO_WEBM,
+                // Acceptable audio types
+                ContentType.AUDIO_MP3, ContentType.AUDIO_MP4, ContentType.AUDIO_MIDI,
+                ContentType.AUDIO_MID, ContentType.AUDIO_AMR, ContentType.AUDIO_X_WAV,
+                ContentType.AUDIO_AAC, ContentType.AUDIO_X_MIDI, ContentType.AUDIO_X_MID,
+                ContentType.AUDIO_X_MP3
+            };
 
     private static final String[] sProjection = {
         PartColumns._ID,
@@ -328,6 +341,11 @@
         return mHeight;
     }
 
+    public static boolean isSupportedMediaType(final String contentType) {
+        return ContentType.isVCardType(contentType)
+                || Arrays.asList(ACCEPTABLE_GALLERY_MEDIA_TYPES).contains(contentType);
+    }
+
     /**
     *
     * @return true if this part can only exist by itself, with no other attachments
@@ -492,16 +510,8 @@
             }
             // Other images should be arbitrarily resized by ImageResizer before sending.
             return MmsUtils.MIN_IMAGE_BYTE_SIZE;
-        } else if (isAudio()) {
-            // Audios are already recorded with the lowest sampling settings (AMR_NB), so just
-            // return the file size as the minimum size.
-            return UriUtil.getContentSize(mContentUri);
-        } else if (isVideo()) {
-            final int mediaDurationMs = UriUtil.getMediaDurationMs(mContentUri);
-            return MmsUtils.MIN_VIDEO_BYTES_PER_SECOND * mediaDurationMs
-                    / TimeUnit.SECONDS.toMillis(1);
-        } else if (isVCard()) {
-            // We can't compress vCards.
+        } else if (isMedia()) {
+            // We can't compress attachments except images.
             return UriUtil.getContentSize(mContentUri);
         } else {
             // This is some unknown media type that we don't know how to handle. Log an error
diff --git a/src/com/android/messaging/datamodel/media/AvatarRequest.java b/src/com/android/messaging/datamodel/media/AvatarRequest.java
index 22d5ccc..6a738c7 100644
--- a/src/com/android/messaging/datamodel/media/AvatarRequest.java
+++ b/src/com/android/messaging/datamodel/media/AvatarRequest.java
@@ -161,7 +161,7 @@
                 getBackgroundColor());
         final Resources resources = mContext.getResources();
         final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        paint.setTypeface(Typeface.create("sans-serif-thin", Typeface.NORMAL));
+        paint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
         paint.setColor(resources.getColor(R.color.letter_tile_font_color));
         final float letterToTileRatio = resources.getFraction(R.dimen.letter_to_tile_ratio, 1, 1);
         paint.setTextSize(letterToTileRatio * minOfWidthAndHeight);
diff --git a/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java
index c5685d1..dae293a 100644
--- a/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java
+++ b/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java
@@ -87,9 +87,4 @@
             return new NetworkUriImageRequest<UriImageRequestDescriptor>(context, this);
         }
     }
-
-    /** ID of the resource in MediaStore or null if this resource didn't come from MediaStore */
-    public Long getMediaStoreId() {
-        return null;
-    }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
index 219e0a6..73ce5e0 100644
--- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
+++ b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
@@ -16,15 +16,11 @@
 
 package com.android.messaging.datamodel.media;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.provider.MediaStore.Video.Thumbnails;
 
-import com.android.messaging.Factory;
 import com.android.messaging.util.MediaMetadataRetrieverWrapper;
 import com.android.messaging.util.MediaUtil;
-import com.android.messaging.util.OsUtil;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -57,19 +53,15 @@
 
     @Override
     protected Bitmap getBitmapForResource() throws IOException {
-        final Long mediaId = mDescriptor.getMediaStoreId();
         Bitmap bitmap = null;
-        if (mediaId != null) {
-            final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
-            bitmap = Thumbnails.getThumbnail(cr, mediaId, Thumbnails.MICRO_KIND, null);
-        } else {
-            final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
-            try {
-                retriever.setDataSource(mDescriptor.uri);
-                bitmap = retriever.getFrameAtTime();
-            } finally {
-                retriever.release();
-            }
+        // Get a thumbnail through MediaMetadataRetriever to get a representative frame at any time
+        // position instead.
+        final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
+        try {
+            retriever.setDataSource(mDescriptor.uri);
+            bitmap = retriever.getFrameAtTime();
+        } finally {
+            retriever.release();
         }
         if (bitmap != null) {
             mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight());
diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java
index 907bb8f..22f1871 100644
--- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java
+++ b/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java
@@ -21,24 +21,17 @@
 import com.android.messaging.util.UriUtil;
 
 public class VideoThumbnailRequestDescriptor extends UriImageRequestDescriptor {
-    protected final long mMediaId;
-    public VideoThumbnailRequestDescriptor(final long id, String path, int desiredWidth,
-            int desiredHeight, int sourceWidth, int sourceHeight) {
-        super(UriUtil.getUriForResourceFile(path), desiredWidth, desiredHeight, sourceWidth,
+    public VideoThumbnailRequestDescriptor(final long id, int desiredWidth, int desiredHeight,
+            int sourceWidth, int sourceHeight) {
+        super(UriUtil.getContentUriForMediaStoreId(id), desiredWidth, desiredHeight, sourceWidth,
                 sourceHeight, false /* canCompress */, false /* isStatic */,
                 false /* cropToCircle */,
                 ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
                 ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
-        mMediaId = id;
     }
 
     @Override
     public MediaRequest<ImageResource> buildSyncMediaRequest(Context context) {
         return new VideoThumbnailRequest(context, this);
     }
-
-    @Override
-    public Long getMediaStoreId() {
-        return mMediaId;
-    }
 }
diff --git a/src/com/android/messaging/ui/UIIntents.java b/src/com/android/messaging/ui/UIIntents.java
index e5f8a52..b09c457 100644
--- a/src/com/android/messaging/ui/UIIntents.java
+++ b/src/com/android/messaging/ui/UIIntents.java
@@ -44,8 +44,8 @@
     // Sending draft data (from share intent / message forwarding) to the ConversationActivity.
     public static final String UI_INTENT_EXTRA_DRAFT_DATA = "draft_data";
 
-    // The request code for picking image from the Document picker.
-    public static final int REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER = 1400;
+    // The request code for picking a media from the Document picker.
+    public static final int REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER = 1400;
 
     // Indicates what type of notification this applies to (See BugleNotifications:
     // UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, UPDATE_ALL)
@@ -166,7 +166,8 @@
     public abstract void launchAddContactActivity(final Context context, final String destination);
 
     /**
-     * Launch an activity to show the document picker to pick an image.
+     * Launch an activity to show the document picker to pick an image/video.
+     *
      * @param fragment the requesting fragment
      */
     public abstract void launchDocumentImagePicker(final Fragment fragment);
diff --git a/src/com/android/messaging/ui/UIIntentsImpl.java b/src/com/android/messaging/ui/UIIntentsImpl.java
index fb624ad..ac430cd 100644
--- a/src/com/android/messaging/ui/UIIntentsImpl.java
+++ b/src/com/android/messaging/ui/UIIntentsImpl.java
@@ -236,11 +236,11 @@
     @Override
     public void launchDocumentImagePicker(final Fragment fragment) {
         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-        intent.putExtra(Intent.EXTRA_MIME_TYPES, MessagePartData.ACCEPTABLE_IMAGE_TYPES);
+        intent.putExtra(Intent.EXTRA_MIME_TYPES, MessagePartData.ACCEPTABLE_GALLERY_MEDIA_TYPES);
         intent.addCategory(Intent.CATEGORY_OPENABLE);
-        intent.setType(ContentType.IMAGE_UNSPECIFIED);
+        intent.setType(ContentType.ANY_TYPE);
 
-        fragment.startActivityForResult(intent, REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER);
+        fragment.startActivityForResult(intent, REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER);
     }
 
     @Override
diff --git a/src/com/android/messaging/ui/conversation/ComposeMessageView.java b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
index 5db1292..0f36e9a 100644
--- a/src/com/android/messaging/ui/conversation/ComposeMessageView.java
+++ b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
@@ -27,6 +27,7 @@
 import android.text.InputFilter.LengthFilter;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.format.Formatter;
 import android.util.AttributeSet;
 import android.view.ContextThemeWrapper;
 import android.view.KeyEvent;
@@ -67,7 +68,9 @@
 import com.android.messaging.util.LogUtil;
 import com.android.messaging.util.MediaUtil;
 import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.SafeAsyncTask;
 import com.android.messaging.util.UiUtils;
+import com.android.messaging.util.UriUtil;
 
 import java.util.Collection;
 import java.util.List;
@@ -112,7 +115,7 @@
 
     private PlainTextEditText mComposeEditText;
     private PlainTextEditText mComposeSubjectText;
-    private TextView mCharCounter;
+    private TextView mMessageBodySize;
     private TextView mMmsIndicator;
     private SimIconView mSelfSendIcon;
     private ImageButton mSendButton;
@@ -171,7 +174,7 @@
 
         final int counterColor = mHost.overrideCounterColor();
         if (counterColor != -1) {
-            mCharCounter.setTextColor(counterColor);
+            mMessageBodySize.setTextColor(counterColor);
         }
     }
 
@@ -309,7 +312,7 @@
         mAttachmentPreview = (AttachmentPreview) findViewById(R.id.attachment_draft_view);
         mAttachmentPreview.setComposeMessageView(this);
 
-        mCharCounter = (TextView) findViewById(R.id.char_counter);
+        mMessageBodySize = (TextView) findViewById(R.id.message_body_size);
         mMmsIndicator = (TextView) findViewById(R.id.mms_indicator);
     }
 
@@ -480,6 +483,8 @@
         final String subject = data.getMessageSubject();
         final String message = data.getMessageText();
 
+        boolean hasAttachmentsChanged = false;
+
         if ((changeFlags & DraftMessageData.MESSAGE_SUBJECT_CHANGED) ==
                 DraftMessageData.MESSAGE_SUBJECT_CHANGED) {
             mComposeSubjectText.setText(subject);
@@ -500,12 +505,13 @@
                 DraftMessageData.ATTACHMENTS_CHANGED) {
             final boolean haveAttachments = mAttachmentPreview.onAttachmentsChanged(data);
             mHost.onAttachmentsChanged(haveAttachments);
+            hasAttachmentsChanged = true;
         }
 
         if ((changeFlags & DraftMessageData.SELF_CHANGED) == DraftMessageData.SELF_CHANGED) {
             updateOnSelfSubscriptionChange();
         }
-        updateVisualsOnDraftChanged();
+        updateVisualsOnDraftChanged(hasAttachmentsChanged);
     }
 
     @Override   // From DraftMessageDataListener
@@ -624,7 +630,44 @@
                 mConversationDataModel.getData().getParticipantsLoaded();
     }
 
+    private static class AsyncUpdateMessageBodySizeTask
+            extends SafeAsyncTask<List<MessagePartData>, Void, Long> {
+
+        private final Context mContext;
+        private final TextView mSizeTextView;
+
+        public AsyncUpdateMessageBodySizeTask(final Context context, final TextView tv) {
+            mContext = context;
+            mSizeTextView = tv;
+        }
+
+        @Override
+        protected Long doInBackgroundTimed(final List<MessagePartData>... params) {
+            final List<MessagePartData> attachments = params[0];
+            long totalSize = 0;
+            for (final MessagePartData attachment : attachments) {
+                final Uri contentUri = attachment.getContentUri();
+                if (contentUri != null) {
+                    totalSize += UriUtil.getContentSize(attachment.getContentUri());
+                }
+            }
+            return totalSize;
+        }
+
+        @Override
+        protected void onPostExecute(Long size) {
+            if (mSizeTextView != null) {
+                mSizeTextView.setText(Formatter.formatFileSize(mContext, size));
+                mSizeTextView.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
     private void updateVisualsOnDraftChanged() {
+        updateVisualsOnDraftChanged(false);
+    }
+
+    private void updateVisualsOnDraftChanged(boolean hasAttachmentsChanged) {
         final String messageText = mComposeEditText.getText().toString();
         final DraftMessageData draftMessageData = mBinding.getData();
         draftMessageData.setMessageText(messageText);
@@ -640,26 +683,39 @@
         final boolean hasWorkingDraft = hasMessageText || hasSubject ||
                 mBinding.getData().hasAttachments();
 
-        // Update the SMS text counter.
-        final int messageCount = draftMessageData.getNumMessagesToBeSent();
-        final int codePointsRemaining = draftMessageData.getCodePointsRemainingInCurrentMessage();
-        // Show the counter only if:
-        // - We are not in MMS mode
-        // - We are going to send more than one message OR we are getting close
-        boolean showCounter = false;
-        if (!draftMessageData.getIsMms() && (messageCount > 1 ||
-                 codePointsRemaining <= CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN)) {
-            showCounter = true;
-        }
-
-        if (showCounter) {
-            // Update the remaining characters and number of messages required.
-            final String counterText = messageCount > 1 ? codePointsRemaining + " / " +
-                    messageCount : String.valueOf(codePointsRemaining);
-            mCharCounter.setText(counterText);
-            mCharCounter.setVisibility(View.VISIBLE);
-        } else {
-            mCharCounter.setVisibility(View.INVISIBLE);
+        final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
+        if (draftMessageData.getIsMms()) { // MMS case
+            if (draftMessageData.hasAttachments()) {
+                if (hasAttachmentsChanged) {
+                    // Calculate message attachments size and show it.
+                    new AsyncUpdateMessageBodySizeTask(getContext(), mMessageBodySize)
+                            .executeOnThreadPool(attachments, null, null);
+                } else {
+                    // No update. Just show previous size.
+                    mMessageBodySize.setVisibility(View.VISIBLE);
+                }
+            } else {
+                mMessageBodySize.setVisibility(View.INVISIBLE);
+            }
+        } else { // SMS case
+            // Update the SMS text counter.
+            final int messageCount = draftMessageData.getNumMessagesToBeSent();
+            final int codePointsRemaining =
+                    draftMessageData.getCodePointsRemainingInCurrentMessage();
+            // Show the counter only if we are going to send more than one message OR we are getting
+            // close.
+            if (messageCount > 1
+                    || codePointsRemaining <= CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN) {
+                // Update the remaining characters and number of messages required.
+                final String counterText =
+                        messageCount > 1
+                                ? codePointsRemaining + " / " + messageCount
+                                : String.valueOf(codePointsRemaining);
+                mMessageBodySize.setText(counterText);
+                mMessageBodySize.setVisibility(View.VISIBLE);
+            } else {
+                mMessageBodySize.setVisibility(View.INVISIBLE);
+            }
         }
 
         // Update the send message button. Self icon uri might be null if self participant data
@@ -699,7 +755,6 @@
         }
 
         // Update the text hint on the message box depending on the attachment type.
-        final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
         final int attachmentCount = attachments.size();
         if (attachmentCount == 0) {
             final SubscriptionListEntry subscriptionListEntry =
diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java
index 77e33ac..5c97c1c 100644
--- a/src/com/android/messaging/ui/conversation/ConversationFragment.java
+++ b/src/com/android/messaging/ui/conversation/ConversationFragment.java
@@ -191,7 +191,8 @@
                     intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_SELF_ID);
             Assert.notNull(conversationId);
             Assert.notNull(selfId);
-            if (TextUtils.equals(mBinding.getData().getConversationId(), conversationId)) {
+            if (isBound() && TextUtils
+                    .equals(mBinding.getData().getConversationId(), conversationId)) {
                 mComposeMessageView.updateConversationSelfIdOnExternalChange(selfId);
             }
         }
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
index 25d5ea3..412177e 100644
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
+++ b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
@@ -103,7 +103,7 @@
                 }
                 mDraftMessage =
                         sharedText != null ? MessageData.createSharedMessage(sharedText) : null;
-            } else if (ContentType.isMediaType(contentType)) {
+            } else if (PendingAttachmentData.isSupportedMediaType(contentType)) {
                 if (contentUri != null) {
                     mDraftMessage = MessageData.createSharedMessage(null);
                     addSharedPartToDraft(contentType, contentUri);
@@ -139,7 +139,7 @@
                             }
                             strBuffer.append(sharedText);
                         }
-                    } else if (ContentType.isMediaType(actualContentType)) {
+                    } else if (PendingAttachmentData.isSupportedMediaType(actualContentType)) {
                         uriMap.put(uri, actualContentType);
                     } else {
                         // Unsupported content type.
diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
index bb267da..d6de128 100644
--- a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
+++ b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
@@ -30,8 +30,8 @@
 import com.android.messaging.util.SafeAsyncTask;
 
 /**
- * Wraps around the functionalities to allow the user to pick images from the document
- * picker.  Instances of this class must be tied to a Fragment which is able to delegate activity
+ * Wraps around the functionalities to allow the user to pick an image/video/audio from the document
+ * picker. Instances of this class must be tied to a Fragment which is able to delegate activity
  * result callbacks.
  */
 public class DocumentImagePicker {
@@ -79,8 +79,8 @@
      * Must be called from the fragment/activity's onActivityResult().
      */
     public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
-        if (requestCode == UIIntents.REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER &&
-                resultCode == Activity.RESULT_OK) {
+        if (requestCode == UIIntents.REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER
+                && resultCode == Activity.RESULT_OK) {
             // Sometimes called after media item has been picked from the document picker.
             String url = data.getStringExtra(EXTRA_PHOTO_URL);
             if (url == null) {
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
index 3d71fe6..48eaa5d 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.database.Cursor;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -24,13 +25,17 @@
 import android.view.View;
 import android.widget.CheckBox;
 import android.widget.FrameLayout;
-import android.widget.ImageView.ScaleType;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
 
 import com.android.messaging.R;
 import com.android.messaging.datamodel.DataModel;
 import com.android.messaging.datamodel.data.GalleryGridItemData;
 import com.android.messaging.ui.AsyncImageView;
 import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.util.ContentType;
 import com.google.common.annotations.VisibleForTesting;
 
 import java.util.concurrent.TimeUnit;
@@ -53,6 +58,11 @@
     GalleryGridItemData mData;
     private AsyncImageView mImageView;
     private CheckBox mCheckBox;
+    private RelativeLayout mAdditionalInfo;
+    private ImageView mIcon;
+    private LinearLayout mFileInfo;
+    private TextView mFileName;
+    private TextView mFileType;
     private HostInterface mHostInterface;
     private final OnClickListener mOnClickListener = new OnClickListener() {
         @Override
@@ -69,9 +79,14 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mImageView = (AsyncImageView) findViewById(R.id.image);
+        mImageView = (AsyncImageView) findViewById(R.id.thumbnail);
         mCheckBox = (CheckBox) findViewById(R.id.checkbox);
         mCheckBox.setOnClickListener(mOnClickListener);
+        mAdditionalInfo = (RelativeLayout) findViewById(R.id.additional_info);
+        mIcon = (ImageView) findViewById(R.id.icon);
+        mFileInfo = (LinearLayout) findViewById(R.id.file_info);
+        mFileName = (TextView) findViewById(R.id.file_name);
+        mFileType = (TextView) findViewById(R.id.file_type);
         setOnClickListener(mOnClickListener);
         final OnLongClickListener longClickListener = new OnLongClickListener() {
             @Override
@@ -136,24 +151,58 @@
 
     private void updateImageView() {
         if (mData.isDocumentPickerItem()) {
-            mImageView.setScaleType(ScaleType.CENTER);
             setBackgroundColor(ConversationDrawables.get().getConversationThemeColor());
-            mImageView.setImageResourceId(null);
-            mImageView.setImageResource(R.drawable.ic_photo_library_light);
-            mImageView.setContentDescription(getResources().getString(
-                    R.string.pick_image_from_document_library_content_description));
+            mIcon.setImageResource(R.drawable.ic_photo_library_light);
+            mIcon.clearColorFilter();
+            mImageView.setVisibility(GONE);
+            mIcon.setVisibility(VISIBLE);
+            mFileInfo.setVisibility(GONE);
+            mAdditionalInfo.setVisibility(VISIBLE);
         } else {
-            mImageView.setScaleType(ScaleType.CENTER_CROP);
-            setBackgroundColor(getResources().getColor(R.color.gallery_image_default_background));
-            mImageView.setImageResourceId(mData.getImageRequestDescriptor());
-            final long dateSeconds = mData.getDateSeconds();
-            final boolean isValidDate = (dateSeconds > 0);
-            final int templateId = isValidDate ?
-                    R.string.mediapicker_gallery_image_item_description :
-                    R.string.mediapicker_gallery_image_item_description_no_date;
-            String contentDescription = String.format(getResources().getString(templateId),
-                    dateSeconds * TimeUnit.SECONDS.toMillis(1));
-            mImageView.setContentDescription(contentDescription);
+            final String contentType = mData.getContentType();
+            if (ContentType.isAudioType(contentType)) {
+                final Context context = getContext();
+                setBackgroundColor(
+                        getResources().getColor(R.color.gallery_image_default_background));
+                mIcon.setImageDrawable(
+                        context.getContentResolver()
+                                .getTypeInfo(contentType)
+                                .getIcon()
+                                .loadDrawable(context));
+                mIcon.setColorFilter(
+                        ConversationDrawables.get().getConversationThemeColor(),
+                        PorterDuff.Mode.SRC_IN);
+                mFileName.setText(mData.getFileName());
+                String[] type = contentType.split("/");
+                mFileType.setText(type[1].toUpperCase() + " " + type[0]);
+                mImageView.setVisibility(GONE);
+                mIcon.setVisibility(VISIBLE);
+                mFileInfo.setVisibility(VISIBLE);
+                mAdditionalInfo.setVisibility(VISIBLE);
+            } else { // For image and video types
+                mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+                setBackgroundColor(
+                        getResources().getColor(R.color.gallery_image_default_background));
+                mImageView.setImageResourceId(mData.getImageRequestDescriptor());
+                mImageView.setVisibility(VISIBLE);
+                if (ContentType.isVideoType(mData.getContentType())) {
+                    mIcon.setImageResource(R.drawable.ic_video_play_light);
+                    mIcon.clearColorFilter();
+                    mIcon.setVisibility(VISIBLE);
+                } else {
+                    mIcon.setVisibility(GONE);
+                }
+                mFileInfo.setVisibility(GONE);
+                mAdditionalInfo.setVisibility(VISIBLE);
+                final long dateSeconds = mData.getDateSeconds();
+                final boolean isValidDate = (dateSeconds > 0);
+                final int templateId = isValidDate ?
+                        R.string.mediapicker_gallery_image_item_description :
+                        R.string.mediapicker_gallery_image_item_description_no_date;
+                String contentDescription = String.format(getResources().getString(templateId),
+                        dateSeconds * TimeUnit.SECONDS.toMillis(1));
+                mImageView.setContentDescription(contentDescription);
+            }
         }
     }
 }
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
index 2265dd5..39912f9 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
@@ -43,16 +43,16 @@
 import java.util.Map;
 
 /**
- * Shows a list of galley images from external storage in a GridView with multi-select
- * capabilities, and with the option to intent out to a standalone image picker.
+ * Shows a list of galley mediae from external storage in a GridView with multi-select capabilities,
+ * and with the option to intent out to a standalone media picker.
  */
 public class GalleryGridView extends MediaPickerGridView implements
         GalleryGridItemView.HostInterface,
         PersistentInstanceState,
         DraftMessageDataListener {
     /**
-     * Implemented by the owner of this GalleryGridView instance to communicate on image
-     * picking and multi-image selection events.
+     * Implemented by the owner of this GalleryGridView instance to communicate on media picking and
+     * multi-media selection events.
      */
     public interface GalleryGridViewListener {
         void onDocumentPickerItemClicked();
@@ -127,7 +127,7 @@
             final MessagePartData item = mSelectedImages.remove(data.getImageUri());
             mListener.onItemUnselected(item);
             if (mSelectedImages.size() == 0) {
-                // No image is selected any more, turn off multi-select mode.
+                // No media is selected any more, turn off multi-select mode.
                 setMultiSelectEnabled(false);
             }
         } else {
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
index 1b8c2dc..c9b544d 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
@@ -39,7 +39,7 @@
 import com.android.messaging.util.OsUtil;
 
 /**
- * Chooser which allows the user to select one or more existing images or videos
+ * Chooser which allows the user to select one or more existing images or videos or audios.
  */
 class GalleryMediaChooser extends MediaChooser implements
         GalleryGridView.GalleryGridViewListener, MediaPickerDataListener {
@@ -54,7 +54,9 @@
 
     @Override
     public int getSupportedMediaTypes() {
-        return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
+        return (MediaPicker.MEDIA_TYPE_IMAGE
+                | MediaPicker.MEDIA_TYPE_VIDEO
+                | MediaPicker.MEDIA_TYPE_AUDIO);
     }
 
     @Override
@@ -63,7 +65,7 @@
         mAdapter.setHostInterface(null);
         // The loader is started only if startMediaPickerDataLoader() is called
         if (OsUtil.hasStoragePermission()) {
-            mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_IMAGE_LOADER);
+            mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_MEDIA_LOADER);
         }
         return super.destroyView();
     }
@@ -121,7 +123,7 @@
     protected View createView(final ViewGroup container) {
         final LayoutInflater inflater = getLayoutInflater();
         final View view = inflater.inflate(
-                R.layout.mediapicker_image_chooser,
+                R.layout.mediapicker_gallery_chooser,
                 container /* root */,
                 false /* attachToRoot */);
 
@@ -167,7 +169,7 @@
     public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data,
             final int loaderId) {
         mBindingRef.ensureBound(mediaPickerData);
-        Assert.equals(MediaPickerData.GALLERY_IMAGE_LOADER, loaderId);
+        Assert.equals(MediaPickerData.GALLERY_MEDIA_LOADER, loaderId);
         Cursor rawCursor = null;
         if (data instanceof Cursor) {
             rawCursor = (Cursor) data;
@@ -202,8 +204,9 @@
     }
 
     private void startMediaPickerDataLoader() {
-        mBindingRef.getData().startLoader(MediaPickerData.GALLERY_IMAGE_LOADER, mBindingRef, null,
-                this);
+        mBindingRef
+                .getData()
+                .startLoader(MediaPickerData.GALLERY_MEDIA_LOADER, mBindingRef, null, this);
     }
 
     @Override
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
index 8e5198b..b8fce8f 100644
--- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java
+++ b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
@@ -159,7 +159,7 @@
     @VisibleForTesting
     final Binding<MediaPickerData> mBinding = BindingBase.createBinding(this);
 
-    /** Handles picking image from the document picker */
+    /** Handles picking a media from the document picker. */
     private DocumentImagePicker mDocumentImagePicker;
 
     /** Provides subscription-related data to access per-subscription configurations. */
diff --git a/src/com/android/messaging/util/UriUtil.java b/src/com/android/messaging/util/UriUtil.java
index 0e931c4..ceff50c 100644
--- a/src/com/android/messaging/util/UriUtil.java
+++ b/src/com/android/messaging/util/UriUtil.java
@@ -26,6 +26,7 @@
 import android.text.TextUtils;
 
 import com.android.messaging.Factory;
+import com.android.messaging.datamodel.GalleryBoundCursorLoader;
 import com.android.messaging.datamodel.MediaScratchFileProvider;
 import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
 import com.google.common.io.ByteStreams;
@@ -130,6 +131,18 @@
     }
 
     /**
+     * Gets the content:// style URI for the given MediaStore row Id in the files table on the
+     * external volume.
+     *
+     * @param id the MediaStore row Id to get the URI for
+     * @return the URI to the files table on the external storage.
+     */
+    public static Uri getContentUriForMediaStoreId(final long id) {
+        return MediaStore.Files.getContentUri(
+                GalleryBoundCursorLoader.MEDIA_SCANNER_VOLUME_EXTERNAL, id);
+    }
+
+    /**
      * Gets the size in bytes for the content uri. Currently we only support content in the
      * scratch space.
      */
diff --git a/src/com/android/messaging/widget/WidgetConversationListService.java b/src/com/android/messaging/widget/WidgetConversationListService.java
index 264b98c..b67bd79 100644
--- a/src/com/android/messaging/widget/WidgetConversationListService.java
+++ b/src/com/android/messaging/widget/WidgetConversationListService.java
@@ -159,10 +159,9 @@
                 // Error
                 // Only show the fail icon if it is not a group conversation.
                 // And also require that we be the default sms app.
-                final boolean showError = conv.getIsFailedStatus() &&
-                        isDefaultSmsApp;
-                final boolean showDraft = conv.getShowDraft() &&
-                        isDefaultSmsApp;
+                final boolean showError =
+                        conv.getIsFailedStatus() && !conv.getIsGroup() && isDefaultSmsApp;
+                final boolean showDraft = conv.getShowDraft() && isDefaultSmsApp;
                 remoteViews.setViewVisibility(R.id.conversation_failed_status_icon,
                         showError && includeAvatar ?
                         View.VISIBLE : View.GONE);
diff --git a/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java
index 8527e2b..033caa2 100644
--- a/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java
+++ b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java
@@ -279,7 +279,7 @@
                 new Object[] { Long.valueOf(1), "/sdcard/image2", 200, 200, "image/png" },
                 new Object[] { Long.valueOf(2), "/sdcard/image3", 300, 300, "image/jpeg" },
         };
-        return new FakeCursor(GalleryGridItemData.IMAGE_PROJECTION, sGalleryCursorColumns,
+        return new FakeCursor(GalleryGridItemData.MEDIA_PROJECTION, sGalleryCursorColumns,
                 cursorData);
     }
 
diff --git a/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java b/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java
index 83d8ac9..304cc74 100644
--- a/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java
+++ b/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java
@@ -55,7 +55,7 @@
             final String imageUrl,
             final boolean showCheckbox,
             final boolean isSelected) {
-        final AsyncImageView imageView = (AsyncImageView) view.findViewById(R.id.image);
+        final AsyncImageView imageView = (AsyncImageView) view.findViewById(R.id.thumbnail);
         final CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkbox);
 
         assertNotNull(imageView);
diff --git a/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java b/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java
index 4a7040e..eaf9338 100644
--- a/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java
+++ b/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java
@@ -97,7 +97,7 @@
     public void testDefaultTabs() {
         Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0);
         initFragment(MediaPicker.MEDIA_TYPE_ALL, new Integer[] {
-                MediaPickerData.GALLERY_IMAGE_LOADER },
+                MediaPickerData.GALLERY_MEDIA_LOADER },
                 false);
         final MediaPicker mediaPicker = getFragment();
         final View view = mediaPicker.getView();
@@ -114,7 +114,7 @@
     public void testFilterTabsBeforeAttach() {
         Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0);
         initFragment(MediaPicker.MEDIA_TYPE_IMAGE, new Integer[] {
-                MediaPickerData.GALLERY_IMAGE_LOADER },
+                MediaPickerData.GALLERY_MEDIA_LOADER },
                 true);
         final MediaPicker mediaPicker = getFragment();
         final View view = mediaPicker.getView();