Merge "make scroll position compensation more reliable" into jb-ub-mail-ur8
diff --git a/res/layout/actionbar_folder.xml b/res/layout/actionbar_folder.xml
deleted file mode 100644
index 2b38d91..0000000
--- a/res/layout/actionbar_folder.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="2.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2011 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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/folder_layout"
-    android:orientation="vertical"
-    android:gravity="center_vertical"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent"
-    android:layout_marginLeft="4dip"
-    android:visibility="gone" >
-    <TextView
-        android:id="@+id/folders"
-        style="@android:style/TextAppearance.Holo.Widget.ActionBar.Title"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:includeFontPadding="false"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/folders" />
-    <TextView
-        android:id="@+id/account"
-        style="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:includeFontPadding="false"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-</LinearLayout>
diff --git a/res/layout/actionbar_subject.xml b/res/layout/actionbar_subject.xml
index 1aa830c..e4b0fc5 100644
--- a/res/layout/actionbar_subject.xml
+++ b/res/layout/actionbar_subject.xml
@@ -16,14 +16,16 @@
      limitations under the License.
 -->
 
+<!-- WARNING: this view's styling must match styling in framework's ActionBarView and -->
+<!-- action_bar_title_item.xml -->
 <com.android.mail.browse.SnippetTextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/conversation_subject"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingRight="@dimen/actionbar_subject_padding_right"
-    android:includeFontPadding="false"
-    android:textSize="16dp"
+    android:visibility="gone"
+    android:paddingRight="8dp"
     android:gravity="center_vertical"
     android:maxLines="2"
-    android:ellipsize="end" />
+    android:ellipsize="end"
+    style="@android:style/TextAppearance.Holo.Widget.ActionBar.Title" />
diff --git a/res/layout/actionbar_view.xml b/res/layout/actionbar_view.xml
index dc2c6d8..bcad65b 100644
--- a/res/layout/actionbar_view.xml
+++ b/res/layout/actionbar_view.xml
@@ -33,8 +33,6 @@
         android:focusable="false"
         android:clickable="true"/>
 
-    <include layout="@layout/actionbar_folder" />
-
     <include layout="@layout/actionbar_subject" />
 
 </com.android.mail.ui.MailActionBarView>
diff --git a/res/layout/conversation_message_attachment_tile.xml b/res/layout/conversation_message_attachment_tile.xml
index 0feca5f..d659e4b 100644
--- a/res/layout/conversation_message_attachment_tile.xml
+++ b/res/layout/conversation_message_attachment_tile.xml
@@ -20,6 +20,7 @@
     android:id="@+id/attachment_tile"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
+    android:focusable="true"
     android:background="@drawable/attachment_bg_holo">
 
     <include layout="@layout/attachment_preview"/>
@@ -76,7 +77,7 @@
         android:id="@+id/attachment_tile_push_state"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:focusable="true"
+        android:duplicateParentState="true"
         android:background="?android:attr/selectableItemBackground" />
 
 </com.android.mail.browse.MessageAttachmentTile>
diff --git a/res/layout/search_actionbar_view.xml b/res/layout/search_actionbar_view.xml
index 19825a5..93d8de8 100644
--- a/res/layout/search_actionbar_view.xml
+++ b/res/layout/search_actionbar_view.xml
@@ -35,8 +35,6 @@
         android:focusable="false"
         android:clickable="true"/>
 
-    <include layout="@layout/actionbar_folder" />
-
     <include layout="@layout/actionbar_subject" />
 
 </com.android.mail.ui.SearchMailActionBarView>
\ No newline at end of file
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 9c99fbe..d9fc3f8 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -159,7 +159,7 @@
   </plurals>
     <string name="sending" msgid="8214361929125649771">"Senden..."</string>
     <string name="send_failed" msgid="750908595144529579">"Nicht gesendet"</string>
-    <string name="me" msgid="6480762904022198669">"Ich"</string>
+    <string name="me" msgid="6480762904022198669">"Mich"</string>
     <string name="show_all_folders" msgid="3281420732307737553">"Alle Ordner anzeigen"</string>
   <plurals name="confirm_delete_conversation">
     <item quantity="one" msgid="3731948757247905508">"Diese Konversation löschen?"</item>
diff --git a/res/values-land/dimen.xml b/res/values-land/dimen.xml
index b618322..0dc5d8b 100644
--- a/res/values-land/dimen.xml
+++ b/res/values-land/dimen.xml
@@ -16,7 +16,6 @@
      limitations under the License.
 -->
 <resources>
-    <dimen name="actionbar_subject_padding_right">0dp</dimen>
     <dimen name="compose_scrollview_width">800dp</dimen>
     <dimen name="subject_width">604dip</dimen>
 </resources>
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 7bbf99f..70cd453 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -30,7 +30,6 @@
     <dimen name="folders_min_height">18dp</dimen>
     <dimen name="date_font_size">12sp</dimen>
     <dimen name="account_dropdown_dropdownwidth">274dip</dimen>
-    <dimen name="actionbar_subject_padding_right">8dp</dimen>
     <dimen name="compose_scrollview_width">700dp</dimen>
     <dimen name="total_conversationbar_width">150dip</dimen>
     <dimen name="max_total_folder_width">64dip</dimen>
diff --git a/src/com/android/mail/browse/AttachmentActionHandler.java b/src/com/android/mail/browse/AttachmentActionHandler.java
index 7c24dda..9a359f5 100644
--- a/src/com/android/mail/browse/AttachmentActionHandler.java
+++ b/src/com/android/mail/browse/AttachmentActionHandler.java
@@ -41,7 +41,6 @@
 import com.android.mail.utils.Utils;
 
 import java.util.ArrayList;
-import java.util.List;
 
 public class AttachmentActionHandler {
     private static final String PROGRESS_FRAGMENT_TAG = "attachment-progress";
@@ -72,6 +71,10 @@
     }
 
     public void showAttachment(int destination) {
+        if (mView == null) {
+            return;
+        }
+
         // If the caller requested that this attachments be saved to the external storage, we should
         // verify that the it was saved there.
         if (mAttachment.isPresentLocally() &&
@@ -84,6 +87,10 @@
         }
     }
 
+    /**
+     * Start downloading the full size attachment set with
+     * {@link #setAttachment(Attachment)} immediately.
+     */
     public void startDownloadingAttachment(int destination) {
         startDownloadingAttachment(destination, UIProvider.AttachmentRendition.BEST, 0, false);
     }
@@ -146,6 +153,10 @@
      * previously brought up (by tapping 'View') and the download has now finished.
      */
     public void updateStatus(boolean loaderResult) {
+        if (mView == null) {
+            return;
+        }
+
         final boolean showProgress = mAttachment.shouldShowProgress();
 
         final AttachmentProgressDialogFragment dialog = (AttachmentProgressDialogFragment)
diff --git a/src/com/android/mail/browse/AttachmentViewInterface.java b/src/com/android/mail/browse/AttachmentViewInterface.java
index d56bdf4..6a8b217 100644
--- a/src/com/android/mail/browse/AttachmentViewInterface.java
+++ b/src/com/android/mail/browse/AttachmentViewInterface.java
@@ -17,10 +17,6 @@
 
 package com.android.mail.browse;
 
-import com.android.mail.providers.Attachment;
-
-import java.util.List;
-
 public interface AttachmentViewInterface {
 
     /**
@@ -32,7 +28,7 @@
 
     /**
      * Allows the view to know when it should update its progress.
-     * @param showProgress true if the the view should show a determinate
+     * @param showDeterminateProgress true if the the view should show a determinate
      * progress value
      */
     public void updateProgress(boolean showDeterminateProgress);
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index 28df1fc..6d0daec 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -276,7 +276,7 @@
         }
 
         public Set<Long> conversationIds() {
-            return mConversationIdPositionMap.keySet()   ;
+            return mConversationIdPositionMap.keySet();
         }
 
         public int getPosition(long conversationId) {
@@ -378,11 +378,11 @@
     private void resetCursor(UnderlyingCursorWrapper newCursorWrapper) {
         synchronized (mCacheMapLock) {
             // Walk through the cache
-            final Iterator<HashMap.Entry<String, ContentValues>> iter =
+            final Iterator<Map.Entry<String, ContentValues>> iter =
                     mCacheMap.entrySet().iterator();
             final long now = System.currentTimeMillis();
             while (iter.hasNext()) {
-                HashMap.Entry<String, ContentValues> entry = iter.next();
+                Map.Entry<String, ContentValues> entry = iter.next();
                 final ContentValues values = entry.getValue();
                 final String key = entry.getKey();
                 boolean withinTimeWindow = false;
@@ -441,10 +441,10 @@
         synchronized (mCacheMapLock) {
             // Walk through the cache and return the list of uris that have been deleted
             final Set<String> deletedItems = Sets.newHashSet();
-            final Iterator<HashMap.Entry<String, ContentValues>> iter =
+            final Iterator<Map.Entry<String, ContentValues>> iter =
                     mCacheMap.entrySet().iterator();
             while (iter.hasNext()) {
-                final HashMap.Entry<String, ContentValues> entry = iter.next();
+                final Map.Entry<String, ContentValues> entry = iter.next();
                 final ContentValues values = entry.getValue();
                 if (values.containsKey(DELETED_COLUMN)) {
                     // Since clients of the conversation cursor see conversation ConversationCursor
@@ -472,10 +472,10 @@
         // position, decrement the position
         synchronized (mCacheMapLock) {
             int updatedPosition = underlyingPosition;
-            final Iterator<HashMap.Entry<String, ContentValues>> iter =
+            final Iterator<Map.Entry<String, ContentValues>> iter =
                     mCacheMap.entrySet().iterator();
             while (iter.hasNext()) {
-                final HashMap.Entry<String, ContentValues> entry = iter.next();
+                final Map.Entry<String, ContentValues> entry = iter.next();
                 final ContentValues values = entry.getValue();
                 if (values.containsKey(DELETED_COLUMN)) {
                     // Since clients of the conversation cursor see conversation ConversationCursor
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 07392f9..6db0d1a 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -63,6 +63,7 @@
 import com.android.mail.perf.Timer;
 import com.android.mail.photomanager.ContactPhotoManager;
 import com.android.mail.photomanager.LetterTileProvider;
+import com.android.mail.preferences.MailPrefs;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.UIProvider;
@@ -176,6 +177,7 @@
     private TextView mSendersTextView;
     private TextView mDateTextView;
     private DividedImageCanvas mContactImagesHolder;
+    private boolean mConvListPhotosEnabled;
     private static int sFoldersLeftPadding;
     private static TextAppearanceSpan sDateTextAppearance;
     private static TextAppearanceSpan sSubjectTextUnreadSpan;
@@ -342,9 +344,9 @@
         setClickable(true);
         setLongClickable(true);
         mContext = context.getApplicationContext();
-        mTabletDevice = Utils.useTabletUI(mContext);
+        final Resources res = mContext.getResources();
+        mTabletDevice = Utils.useTabletUI(res);
         mAccount = account;
-        Resources res = mContext.getResources();
 
         if (CHECKMARK_OFF == null) {
             // Initialize static bitmaps.
@@ -384,8 +386,10 @@
                     R.style.SubjectAppearanceUnreadStyle);
             sSubjectTextReadSpan = new TextAppearanceSpan(mContext,
                     R.style.SubjectAppearanceReadStyle);
-            sSnippetTextUnreadSpan = new ForegroundColorSpan(R.color.snippet_text_color_unread);
-            sSnippetTextReadSpan = new ForegroundColorSpan(R.color.snippet_text_color_read);
+            sSnippetTextUnreadSpan =
+                    new ForegroundColorSpan(res.getColor(R.color.snippet_text_color_unread));
+            sSnippetTextReadSpan =
+                    new ForegroundColorSpan(res.getColor(R.color.snippet_text_color_read));
             sTouchSlop = res.getDimensionPixelSize(R.dimen.touch_slop);
             sStandardScaledDimen = res.getDimensionPixelSize(R.dimen.standard_scaled_dimen);
             sShrinkAnimationDuration = res.getInteger(R.integer.shrink_animation_duration);
@@ -455,6 +459,8 @@
         mSelectedConversationSet = set;
         mDisplayedFolder = folder;
         mCheckboxesEnabled = !checkboxesDisabled;
+        mConvListPhotosEnabled = MailPrefs.get(activity.getActivityContext())
+                .areConvListPhotosEnabled();
         mStarEnabled = folder != null && !folder.isTrash();
         mSwipeEnabled = swipeEnabled;
         mPriorityMarkersEnabled = priorityArrowEnabled;
@@ -515,7 +521,7 @@
             ConversationItemViewCoordinates.refreshConversationHeights(mContext);
         }
         mCoordinates = ConversationItemViewCoordinates.forWidth(mContext, mViewWidth, mMode,
-                mHeader.standardScaledDimen, mCheckboxesEnabled);
+                mHeader.standardScaledDimen, mConvListPhotosEnabled);
         calculateTextsAndBitmaps();
         calculateCoordinates();
 
@@ -569,7 +575,7 @@
         if (mSelectedConversationSet != null) {
             mChecked = mSelectedConversationSet.contains(mHeader.conversation);
         }
-        mHeader.checkboxVisible = mCheckboxesEnabled;
+        mHeader.checkboxVisible = mCheckboxesEnabled && !mConvListPhotosEnabled;
 
         final boolean isUnread = mHeader.unread;
         updateBackground(isUnread);
@@ -647,7 +653,7 @@
     }
 
     private void loadSenderImages() {
-        if (!mCheckboxesEnabled && mHeader.displayableSenderEmails != null
+        if (mConvListPhotosEnabled && mHeader.displayableSenderEmails != null
                 && mHeader.displayableSenderEmails.size() > 0) {
             mContactImagesHolder.setDimensions(mCoordinates.contactImagesWidth,
                     mCoordinates.contactImagesHeight);
@@ -687,51 +693,51 @@
 
         SpannableStringBuilder subjectText = new SpannableStringBuilder(
                 Conversation.getSubjectAndSnippetForDisplay(mContext, subject, snippet));
-        int subjectTextLength = Math.min(subjectText.length(), subject.length());
-             if (!TextUtils.isEmpty(subject)) {
-                 subjectText.setSpan(TextAppearanceSpan.wrap(isUnread ?
-                         sSubjectTextUnreadSpan : sSubjectTextReadSpan), 0, subjectTextLength,
-                         Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-             }
-             if (!TextUtils.isEmpty(snippet)) {
-                 final int startOffset = subjectTextLength;
-                 // Start after the end of the subject text; since the subject may be
-                 // "" or null, this could start at the 0th character in the
-                 // subectText string
-                 if (startOffset < subjectText.length()) {
-                     subjectText.setSpan(ForegroundColorSpan.wrap(isUnread ?
-                             sSnippetTextUnreadSpan : sSnippetTextReadSpan), startOffset,
-                             subjectText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                 }
-             }
-             layoutSubject(subjectText);
-    }
 
-
-    private void layoutSubject(SpannableStringBuilder subjectText) {
         int secondLineMaxWidth = EllipsizedMultilineTextView.ALL_AVAILABLE;
         if (!ConversationItemViewCoordinates.isWideMode(mMode) && mCoordinates.showFolders
-                && mHeader.folderDisplayer != null
-                && mHeader.folderDisplayer.hasVisibleFolders()) {
-            secondLineMaxWidth = mCoordinates.subjectWidth
-                    - Math.min(ConversationItemViewCoordinates.getFoldersWidth(mContext, mMode),
-                            mHeader.folderDisplayer.measureFolders(mMode))
-                    - sFoldersLeftPadding;
+                && mHeader.folderDisplayer != null && mHeader.folderDisplayer.hasVisibleFolders()) {
+            secondLineMaxWidth = mCoordinates.subjectWidth - Math.min(
+                    ConversationItemViewCoordinates.getFoldersWidth(mContext, mMode),
+                    mHeader.folderDisplayer.measureFolders(mMode)) - sFoldersLeftPadding;
         }
         EllipsizedMultilineTextView subjectLayout = mSubjectTextView;
         int subjectWidth = mCoordinates.subjectWidth;
-        int subjectHeight = (int) (subjectLayout.getLineHeight() * 2 + subjectLayout.getPaint()
-                .descent());
+        int subjectHeight =
+                (int) (subjectLayout.getLineHeight() * 2 + subjectLayout.getPaint().descent());
+        subjectLayout.measure(
+                MeasureSpec.makeMeasureSpec(subjectWidth, MeasureSpec.EXACTLY), subjectHeight);
+        subjectLayout.layout(0, 0, subjectWidth, subjectHeight);
+
+        final CharSequence displayedText = subjectLayout.setText(subjectText, secondLineMaxWidth);
+
+        final SpannableStringBuilder displayedStringBuilder =
+                new SpannableStringBuilder(displayedText);
+        int subjectTextLength =
+                Math.min(displayedText.length(), Math.min(subjectText.length(), subject.length()));
+        if (!TextUtils.isEmpty(subject)) {
+            displayedStringBuilder.setSpan(TextAppearanceSpan.wrap(
+                    isUnread ? sSubjectTextUnreadSpan : sSubjectTextReadSpan), 0, subjectTextLength,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+        if (!TextUtils.isEmpty(snippet)) {
+            final int startOffset = subjectTextLength;
+            // Start after the end of the subject text; since the subject may be
+            // "" or null, this could start at the 0th character in the subectText string
+            if (startOffset < displayedText.length()) {
+                displayedStringBuilder.setSpan(ForegroundColorSpan.wrap(
+                        isUnread ? sSnippetTextUnreadSpan : sSnippetTextReadSpan), startOffset,
+                        displayedText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
         if (isActivated() && showActivatedText()) {
-            subjectText.setSpan(sActivatedTextSpan, 0, subjectText.length(),
+            displayedStringBuilder.setSpan(sActivatedTextSpan, 0, displayedText.length(),
                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
         } else {
-            subjectText.removeSpan(sActivatedTextSpan);
+            displayedStringBuilder.removeSpan(sActivatedTextSpan);
         }
-        subjectLayout.measure(MeasureSpec.makeMeasureSpec(subjectWidth, MeasureSpec.EXACTLY),
-                subjectHeight);
-        subjectLayout.layout(0, 0, subjectWidth, subjectHeight);
-        subjectLayout.setText(subjectText, secondLineMaxWidth);
+
+        subjectLayout.setText(displayedStringBuilder);
     }
 
     /**
@@ -1023,13 +1029,13 @@
     @Override
     protected void onDraw(Canvas canvas) {
         // Check mark.
-        if (mHeader.checkboxVisible) {
-            Bitmap checkmark = mChecked ? CHECKMARK_ON : CHECKMARK_OFF;
-            canvas.drawBitmap(checkmark, mCoordinates.checkmarkX, mCoordinates.checkmarkY, sPaint);
-        } else {
+        if (mConvListPhotosEnabled) {
             canvas.save();
             drawContactImages(canvas);
             canvas.restore();
+        } else if (mHeader.checkboxVisible) {
+            Bitmap checkmark = mChecked ? CHECKMARK_ON : CHECKMARK_OFF;
+            canvas.drawBitmap(checkmark, mCoordinates.checkmarkX, mCoordinates.checkmarkY, sPaint);
         }
 
         // Personal Level.
@@ -1144,7 +1150,7 @@
 
     private void drawSenders(Canvas canvas) {
         int left;
-        if (!mCheckboxesEnabled && mCoordinates.inlinePersonalLevel) {
+        if (mConvListPhotosEnabled && mCoordinates.inlinePersonalLevel) {
             if (mCoordinates.showPersonalLevel && mHeader.personalLevelBitmap != null) {
                 left = mCoordinates.sendersX;
             } else {
@@ -1161,6 +1167,14 @@
         return mHeader.conversation.starred ? STAR_ON : STAR_OFF;
     }
 
+    /**
+     * Set the background for this item based on:
+     * 1. Read / Unread (unread messages have a lighter background)
+     * 2. Tablet / Phone
+     * 3. Checkbox checked / Unchecked (controls CAB color for item)
+     * 4. Activated / Not activated (controls the blue highlight on tablet)
+     * @param isUnread
+     */
     private void updateBackground(boolean isUnread) {
         if (mBackgroundOverride != -1) {
             // If the item is animating, we use a color to avoid shrinking a 9-patch
@@ -1169,42 +1183,44 @@
             return;
         }
         final boolean isListOnTablet = mTabletDevice && mActivity.getViewMode().isListMode();
+        final int background;
         if (isUnread) {
             if (isListOnTablet) {
                 if (mChecked) {
-                    setBackgroundResource(R.drawable.list_conversation_wide_unread_selected_holo);
+                    background = R.drawable.list_conversation_wide_unread_selected_holo;
                 } else {
-                    setBackgroundResource(R.drawable.conversation_wide_unread_selector);
+                    background = R.drawable.conversation_wide_unread_selector;
                 }
             } else {
                 if (mChecked) {
-                    setCheckedActivatedBackground();
+                    background = getCheckedActivatedBackground();
                 } else {
-                    setBackgroundResource(R.drawable.conversation_unread_selector);
+                    background = R.drawable.conversation_unread_selector;
                 }
             }
         } else {
             if (isListOnTablet) {
                 if (mChecked) {
-                    setBackgroundResource(R.drawable.list_conversation_wide_read_selected_holo);
+                    background = R.drawable.list_conversation_wide_read_selected_holo;
                 } else {
-                    setBackgroundResource(R.drawable.conversation_wide_read_selector);
+                    background = R.drawable.conversation_wide_read_selector;
                 }
             } else {
                 if (mChecked) {
-                    setCheckedActivatedBackground();
+                    background = getCheckedActivatedBackground();
                 } else {
-                    setBackgroundResource(R.drawable.conversation_read_selector);
+                    background = R.drawable.conversation_read_selector;
                 }
             }
         }
+        setBackgroundResource(background);
     }
 
-    private void setCheckedActivatedBackground() {
+    private final int getCheckedActivatedBackground() {
         if (isActivated() && mTabletDevice) {
-            setBackgroundResource(R.drawable.list_arrow_selected_holo);
+            return R.drawable.list_arrow_selected_holo;
         } else {
-            setBackgroundResource(R.drawable.list_selected_holo);
+            return R.drawable.list_selected_holo;
         }
     }
 
diff --git a/src/com/android/mail/browse/ConversationItemViewCoordinates.java b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
index aca8f43..dfce5d8 100644
--- a/src/com/android/mail/browse/ConversationItemViewCoordinates.java
+++ b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
@@ -168,8 +168,8 @@
     /**
      * Returns the layout id to be inflated in this mode.
      */
-    private static int getLayoutId(int mode, boolean checkboxesEnabled) {
-        if (checkboxesEnabled) {
+    private static int getLayoutId(int mode, boolean convListPhotosEnabled) {
+        if (!convListPhotosEnabled) {
             switch (mode) {
                 case WIDE_MODE:
                     return R.layout.conversation_item_view_wide;
@@ -339,17 +339,17 @@
      * the view width.
      */
     public static ConversationItemViewCoordinates forWidth(Context context, int width, int mode,
-            int standardScaledDimen, boolean checkboxesEnabled) {
+            int standardScaledDimen, boolean convListPhotosEnabled) {
         ConversationItemViewCoordinates coordinates = sCache.get(Objects.hashCode(width, mode,
-                checkboxesEnabled));
+                convListPhotosEnabled));
         if (coordinates == null) {
             coordinates = new ConversationItemViewCoordinates();
-            sCache.put(Objects.hashCode(width, mode, checkboxesEnabled), coordinates);
+            sCache.put(Objects.hashCode(width, mode, convListPhotosEnabled), coordinates);
 
             // Layout the appropriate view.
             int height = getHeight(context, mode);
-            View view = LayoutInflater.from(context).inflate(getLayoutId(mode, checkboxesEnabled),
-                    null);
+            View view = LayoutInflater.from(context).inflate(
+                    getLayoutId(mode, convListPhotosEnabled), null);
             int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
             int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
             Resources res = context.getResources();
@@ -442,7 +442,7 @@
             coordinates.paperclipY = getY(paperclip);
 
             // Contact images view
-            if (!checkboxesEnabled) {
+            if (convListPhotosEnabled) {
             View contactImagesView = view.findViewById(R.id.contact_image);
                 if (contactImagesView != null) {
                     coordinates.contactImagesWidth = contactImagesView.getWidth();
diff --git a/src/com/android/mail/browse/ConversationListFooterView.java b/src/com/android/mail/browse/ConversationListFooterView.java
index 0fb1c8c..0d72a90 100644
--- a/src/com/android/mail/browse/ConversationListFooterView.java
+++ b/src/com/android/mail/browse/ConversationListFooterView.java
@@ -57,7 +57,7 @@
 
     public ConversationListFooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mTabletDevice = Utils.useTabletUI(context);
+        mTabletDevice = Utils.useTabletUI(context.getResources());
     }
 
     @Override
diff --git a/src/com/android/mail/browse/ConversationPagerAdapter.java b/src/com/android/mail/browse/ConversationPagerAdapter.java
index 855aa13..a32b0a3 100644
--- a/src/com/android/mail/browse/ConversationPagerAdapter.java
+++ b/src/com/android/mail/browse/ConversationPagerAdapter.java
@@ -39,7 +39,6 @@
 import com.android.mail.utils.FragmentStatePagerAdapter2;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
 
 public class ConversationPagerAdapter extends FragmentStatePagerAdapter2
         implements ViewPager.OnPageChangeListener {
@@ -129,6 +128,11 @@
     }
 
     private ConversationCursor getCursor() {
+        if (mDetachedMode) {
+            // In detached mode, the pager is decoupled from the cursor. Nothing should rely on the
+            // cursor at this point.
+            return null;
+        }
         if (mController == null) {
             // Happens when someone calls setActivityController(null) on us. This is done in
             // ConversationPagerController.stopListening() to indicate that the Conversation View
@@ -278,6 +282,9 @@
             return;
         }
 
+        // If we are in detached mode, changes to the cursor are of no interest to us, but they may
+        // be to parent classes.
+
         // when the currently visible item disappears from the dataset:
         //   if the new version of the currently visible item has zero messages:
         //     notify the list controller so it can handle this 'current conversation gone' case
@@ -285,13 +292,14 @@
         //   else
         //     'detach' the conversation view from the cursor, keeping the current item as-is but
         //     disabling swipe (effectively the same as singleton mode)
-        if (mController != null) {
+        if (mController != null && !mDetachedMode) {
             final Conversation currConversation = mController.getCurrentConversation();
             final int pos = getConversationPosition(currConversation);
             if (pos == POSITION_NONE && getCursor() != null && currConversation != null) {
                 // enable detached mode and do no more here. the fragment itself will figure out
                 // if the conversation is empty (using message list cursor) and back out if needed.
                 mDetachedMode = true;
+                mController.setDetachedMode();
                 LogUtils.i(LOG_TAG, "CPA: current conv is gone, reverting to detached mode. c=%s",
                         currConversation.uri);
             } else {
@@ -332,10 +340,6 @@
 
     public int getConversationPosition(Conversation conv) {
         if (isPagingDisabled()) {
-            if (getCursor() == null) {
-                return POSITION_NONE;
-            }
-
             if (conv != getDefaultConversation()) {
                 LogUtils.d(LOG_TAG, "unable to find conversation in singleton mode. c=%s", conv);
                 return POSITION_NONE;
diff --git a/src/com/android/mail/photo/MailPhotoViewActivity.java b/src/com/android/mail/photo/MailPhotoViewActivity.java
index 7d597e1..6a17f37 100644
--- a/src/com/android/mail/photo/MailPhotoViewActivity.java
+++ b/src/com/android/mail/photo/MailPhotoViewActivity.java
@@ -36,6 +36,7 @@
 import com.android.mail.browse.AttachmentActionHandler;
 import com.android.mail.providers.Attachment;
 import com.android.mail.providers.UIProvider.AttachmentDestination;
+import com.android.mail.providers.UIProvider.AttachmentState;
 import com.android.mail.utils.AttachmentUtils;
 import com.android.mail.utils.Utils;
 import com.google.common.collect.Lists;
@@ -61,6 +62,7 @@
         super.onCreate(savedInstanceState);
 
         mActionHandler = new AttachmentActionHandler(this, null);
+        mActionHandler.initialize(getFragmentManager());
     }
 
     @Override
@@ -193,6 +195,12 @@
     @Override
     public void onFragmentVisible(PhotoViewFragment fragment) {
         super.onFragmentVisible(fragment);
+        Attachment attachment = getCurrentAttachment();
+        if (attachment.state == AttachmentState.PAUSED) {
+            mActionHandler.setAttachment(attachment);
+            mActionHandler.startDownloadingAttachment(attachment.destination);
+        }
+        return;
     }
 
     @Override
@@ -322,9 +330,8 @@
     }
 
     @Override
-    public void onNewPhotoLoaded() {
-        // Don't call the super class, as the default implementation calls setViewActivated()
-        // and we don't want to do that
+    public void onNewPhotoLoaded(int position) {
+        // do nothing
     }
 
     /**
diff --git a/src/com/android/mail/photomanager/LetterTileProvider.java b/src/com/android/mail/photomanager/LetterTileProvider.java
index ab40234..415ae9b 100644
--- a/src/com/android/mail/photomanager/LetterTileProvider.java
+++ b/src/com/android/mail/photomanager/LetterTileProvider.java
@@ -57,14 +57,14 @@
     private static int sTileFontColor;
     private static TextPaint sPaint = new TextPaint();
     private static int DEFAULT_AVATAR_DRAWABLE = R.drawable.ic_contact_picture;
-    private static final Pattern ALPHABET = Pattern.compile("^[a-zA-Z]+$");
+    private static final Pattern ALPHABET = Pattern.compile("^[a-zA-Z0-9]+$");
 
     public LetterTileProvider() {
         super();
         final float cacheSizeAdjustment =
                 (MemoryUtils.getTotalMemorySize() >= MemoryUtils.LARGE_RAM_THRESHOLD) ?
                         1.0f : 0.5f;
-        final int bitmapCacheSize = (int) (cacheSizeAdjustment * 26);
+        final int bitmapCacheSize = (int) (cacheSizeAdjustment * 36);
         mTileBitmapCache = new LruCache<Integer, Bitmap>(bitmapCacheSize);
     }
 
diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java
index 2f60c4e..1aa8ac0 100644
--- a/src/com/android/mail/preferences/MailPrefs.java
+++ b/src/com/android/mail/preferences/MailPrefs.java
@@ -45,6 +45,7 @@
 
     // Hidden preference to indicate what version a "What's New" dialog was last shown for.
     private static final String WHATS_NEW_LAST_SHOWN_VERSION = "whats-new-last-shown-version";
+    public static final String ENABLE_CONVLIST_PHOTOS = "enable-convlist-photos";
 
     public static MailPrefs get(Context c) {
         if (sInstance == null) {
@@ -61,6 +62,13 @@
         return PREFS_NAME;
     }
 
+    /**
+     * Set the value of a shared preference of type boolean.
+     */
+    public void setSharedBooleanPreference(String pref, boolean value) {
+        mPrefs.edit().putBoolean(pref, value).apply();
+    }
+
     public boolean isWidgetConfigured(int appWidgetId) {
         return mPrefs.contains(WIDGET_ACCOUNT_PREFIX + appWidgetId);
     }
@@ -76,6 +84,14 @@
         return mPrefs.getString(WIDGET_ACCOUNT_PREFIX + appWidgetId, null);
     }
 
+    /**
+     * Get whether to show the experimental inline contact photos in the
+     * conversation list.
+     */
+    public boolean areConvListPhotosEnabled() {
+        return mPrefs.getBoolean(ENABLE_CONVLIST_PHOTOS, false);
+    }
+
     private static String createWidgetPreferenceValue(Account account, Folder folder) {
         return account.uri.toString() +
                 ACCOUNT_FOLDER_PREFERENCE_SEPARATOR + folder.uri.toString();
diff --git a/src/com/android/mail/providers/Attachment.java b/src/com/android/mail/providers/Attachment.java
index 707619a..51f2607 100644
--- a/src/com/android/mail/providers/Attachment.java
+++ b/src/com/android/mail/providers/Attachment.java
@@ -256,7 +256,7 @@
     }
 
     public boolean isDownloading() {
-        return state == AttachmentState.DOWNLOADING;
+        return state == AttachmentState.DOWNLOADING || state == AttachmentState.PAUSED;
     }
 
     public boolean isSavedToExternal() {
@@ -268,7 +268,7 @@
     }
 
     public boolean shouldShowProgress() {
-        return state == AttachmentState.DOWNLOADING
+        return (state == AttachmentState.DOWNLOADING || state == AttachmentState.PAUSED)
                 && size > 0 && downloadedSize > 0 && downloadedSize <= size;
     }
 
diff --git a/src/com/android/mail/providers/MailAppProvider.java b/src/com/android/mail/providers/MailAppProvider.java
index 798c446..587170d 100644
--- a/src/com/android/mail/providers/MailAppProvider.java
+++ b/src/com/android/mail/providers/MailAppProvider.java
@@ -295,8 +295,8 @@
                     case UIProvider.ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN:
                         builder.add(Integer.valueOf(account.settings.replyBehavior));
                         break;
-                    case UIProvider.ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.showCheckboxes ? 1 : 0));
+                    case UIProvider.ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN:
+                        builder.add(Integer.valueOf(account.settings.hideCheckboxes ? 1 : 0));
                         break;
                     case UIProvider.ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN:
                         builder.add(Integer.valueOf(account.settings.confirmDelete ? 1 : 0));
diff --git a/src/com/android/mail/providers/Settings.java b/src/com/android/mail/providers/Settings.java
index d37bab1..b77a19d 100644
--- a/src/com/android/mail/providers/Settings.java
+++ b/src/com/android/mail/providers/Settings.java
@@ -66,7 +66,7 @@
     public final int messageTextSize;
     public final int snapHeaders;
     public final int replyBehavior;
-    public final boolean showCheckboxes;
+    public final boolean hideCheckboxes;
     public final boolean confirmDelete;
     public final boolean confirmArchive;
     public final boolean confirmSend;
@@ -101,7 +101,7 @@
         messageTextSize = MessageTextSize.NORMAL;
         snapHeaders = SnapHeaderValue.ALWAYS;
         replyBehavior = DefaultReplyBehavior.REPLY;
-        showCheckboxes = false;
+        hideCheckboxes = false;
         confirmDelete = false;
         confirmArchive = false;
         confirmSend = false;
@@ -122,7 +122,7 @@
         messageTextSize = inParcel.readInt();
         snapHeaders = inParcel.readInt();
         replyBehavior = inParcel.readInt();
-        showCheckboxes = inParcel.readInt() != 0;
+        hideCheckboxes = inParcel.readInt() != 0;
         confirmDelete = inParcel.readInt() != 0;
         confirmArchive = inParcel.readInt() != 0;
         confirmSend = inParcel.readInt() != 0;
@@ -143,7 +143,7 @@
         messageTextSize = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_MESSAGE_TEXT_SIZE_COLUMN);
         snapHeaders = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN);
         replyBehavior = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN);
-        showCheckboxes = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN) != 0;
+        hideCheckboxes = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN) != 0;
         confirmDelete = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN) != 0;
         confirmArchive = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_CONFIRM_ARCHIVE_COLUMN) != 0;
         confirmSend = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_CONFIRM_SEND_COLUMN) != 0;
@@ -173,8 +173,8 @@
                 sDefault.snapHeaders);
         replyBehavior = json.optInt(AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
                 sDefault.replyBehavior);
-        showCheckboxes = json.optBoolean(AccountColumns.SettingsColumns.SHOW_CHECKBOXES,
-                sDefault.showCheckboxes);
+        hideCheckboxes = json.optBoolean(AccountColumns.SettingsColumns.HIDE_CHECKBOXES,
+                sDefault.hideCheckboxes);
         confirmDelete = json.optBoolean(AccountColumns.SettingsColumns.CONFIRM_DELETE,
                 sDefault.confirmDelete);
         confirmArchive = json.optBoolean(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE,
@@ -228,7 +228,7 @@
             json.put(AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, messageTextSize);
             json.put(AccountColumns.SettingsColumns.SNAP_HEADERS, snapHeaders);
             json.put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR, replyBehavior);
-            json.put(AccountColumns.SettingsColumns.SHOW_CHECKBOXES, showCheckboxes);
+            json.put(AccountColumns.SettingsColumns.HIDE_CHECKBOXES, hideCheckboxes);
             json.put(AccountColumns.SettingsColumns.CONFIRM_DELETE, confirmDelete);
             json.put(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, confirmArchive);
             json.put(AccountColumns.SettingsColumns.CONFIRM_SEND, confirmSend);
@@ -298,7 +298,7 @@
         dest.writeInt(messageTextSize);
         dest.writeInt(snapHeaders);
         dest.writeInt(replyBehavior);
-        dest.writeInt(showCheckboxes ? 1 : 0);
+        dest.writeInt(hideCheckboxes ? 1 : 0);
         dest.writeInt(confirmDelete ? 1 : 0);
         dest.writeInt(confirmArchive? 1 : 0);
         dest.writeInt(confirmSend? 1 : 0);
@@ -391,7 +391,7 @@
                 && messageTextSize == that.messageTextSize
                 && snapHeaders == that.snapHeaders
                 && replyBehavior == that.replyBehavior
-                && showCheckboxes == that.showCheckboxes
+                && hideCheckboxes == that.hideCheckboxes
                 && confirmDelete == that.confirmDelete
                 && confirmArchive == that.confirmArchive
                 && confirmSend == that.confirmSend
@@ -420,7 +420,7 @@
     private final int calculateHashCode() {
         return super.hashCode()
                 ^ Objects.hashCode(signature, mAutoAdvance, mTransientAutoAdvance, messageTextSize,
-                        snapHeaders, replyBehavior, showCheckboxes, confirmDelete, confirmArchive,
+                        snapHeaders, replyBehavior, hideCheckboxes, confirmDelete, confirmArchive,
                         confirmSend, defaultInbox, forceReplyFromDefault, maxAttachmentSize, swipe,
                         priorityArrowsEnabled, setupIntentUri, conversationViewMode,
                         veiledAddressPattern);
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index 69b56aa..adee501 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -80,14 +80,8 @@
         public static boolean isSyncInProgress(int syncStatus) {
             return 0 != (syncStatus & (BACKGROUND_SYNC |
                     USER_REFRESH |
-                    LIVE_QUERY |
-                    USER_MORE_RESULTS));
+                    LIVE_QUERY));
         }
-        /**
-         * Unused currently, is not used by any provider.
-         * TODO(viki): Remove.
-         */
-        public static final int USER_MORE_RESULTS = 1<<5;
     }
 
     /**
@@ -157,7 +151,7 @@
             .put(AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, Integer.class)
             .put(AccountColumns.SettingsColumns.SNAP_HEADERS, Integer.class)
             .put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR, Integer.class)
-            .put(AccountColumns.SettingsColumns.SHOW_CHECKBOXES, Integer.class)
+            .put(AccountColumns.SettingsColumns.HIDE_CHECKBOXES, Integer.class)
             .put(AccountColumns.SettingsColumns.CONFIRM_DELETE, Integer.class)
             .put(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, Integer.class)
             .put(AccountColumns.SettingsColumns.CONFIRM_SEND, Integer.class)
@@ -209,7 +203,7 @@
     public static final int ACCOUNT_SETTINGS_MESSAGE_TEXT_SIZE_COLUMN = 28;
     public static final int ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN = 29;
     public static final int ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN = 30;
-    public static final int ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN = 31;
+    public static final int ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN = 31;
     public static final int ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN = 32;
     public static final int ACCOUNT_SETTINGS_CONFIRM_ARCHIVE_COLUMN = 33;
     public static final int ACCOUNT_SETTINGS_CONFIRM_SEND_COLUMN = 34;
@@ -534,9 +528,9 @@
 
             /**
              * Integer column containing the user's specified checkbox preference. A
-             * non zero value means to show checkboxes.
+             * non zero value means to hide checkboxes.
              */
-            public static final String SHOW_CHECKBOXES = "show_checkboxes";
+            public static final String HIDE_CHECKBOXES = "hide_checkboxes";
 
             /**
              * Integer column containing the user's specified confirm delete preference value.
@@ -644,8 +638,8 @@
             .put(AccountColumns.SettingsColumns.SNAP_HEADERS,ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN)
             .put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
                     ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN)
-            .put(AccountColumns.SettingsColumns.SHOW_CHECKBOXES,
-                    ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN)
+            .put(AccountColumns.SettingsColumns.HIDE_CHECKBOXES,
+                    ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN)
             .put(AccountColumns.SettingsColumns.CONFIRM_DELETE,
                     ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN)
             .put(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE,
@@ -1808,11 +1802,17 @@
          * setting this state will tell the provider to initiate a download to
          * the accompanying destination in {@link AttachmentColumns#DESTINATION}
          * .
-         * <p>
-         * Valid next states: {@link #NOT_SAVED}, {@link #FAILED},
-         * {@link #SAVED}
          */
         public static final int REDOWNLOADING = 4;
+        /**
+         * The attachment is either pending or paused in the download manager.
+         * {@link AttachmentColumns#DOWNLOADED_SIZE} should reflect the current
+         * download progress while in this state. This state may not be used as
+         * a command on its own.
+         * <p>
+         * Valid next states: {@link #DOWNLOADING}, {@link #FAILED}
+         */
+        public static final int PAUSED = 5;
 
         private AttachmentState() {}
     }
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index e736e28..1e3886b 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -36,6 +36,7 @@
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.Loader;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DataSetObservable;
 import android.database.DataSetObserver;
@@ -59,10 +60,9 @@
 import com.android.mail.R;
 import com.android.mail.browse.ConfirmDialogFragment;
 import com.android.mail.browse.ConversationCursor;
-import com.android.mail.browse.ConversationItemView;
+import com.android.mail.browse.ConversationCursor.ConversationOperation;
 import com.android.mail.browse.ConversationItemViewModel;
 import com.android.mail.browse.ConversationPagerController;
-import com.android.mail.browse.ConversationCursor.ConversationOperation;
 import com.android.mail.browse.MessageCursor.ConversationMessage;
 import com.android.mail.browse.SelectedConversationsActionMenu;
 import com.android.mail.browse.SyncErrorDialogFragment;
@@ -140,6 +140,8 @@
     private static final String SAVED_ACTION = "saved-action";
     /** Tag for {@link #mDialogFromSelectedSet} */
     private static final String SAVED_ACTION_FROM_SELECTED = "saved-action-from-selected";
+    /** Tag for {@link #mDetachedConvUri} */
+    private static final String SAVED_DETACHED_CONV_URI = "saved-detached-conv-uri";
 
     /** Tag  used when loading a wait fragment */
     protected static final String TAG_WAIT = "wait-fragment";
@@ -159,6 +161,10 @@
     protected final RecentFolderList mRecentFolderList;
     protected ConversationListContext mConvListContext;
     protected Conversation mCurrentConversation;
+    /**
+     * The hash of {@link #mCurrentConversation} in detached mode. 0 if we are not in detached mode.
+     */
+    private Uri mDetachedConvUri;
 
     /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
     private SuppressNotificationReceiver mNewEmailReceiver = null;
@@ -177,6 +183,9 @@
 
     private boolean mDestroyed;
 
+    /** True if running on tablet */
+    private final boolean mIsTablet;
+
     /**
      * Are we in a point in the Activity/Fragment lifecycle where it's safe to execute fragment
      * transactions? (including back stack manipulation)
@@ -206,6 +215,19 @@
         }
     };
 
+    /**
+     * Interface for actions that are deferred until after a load completes. This is for handling
+     * user actions which affect cursors (e.g. marking messages read or unread) that happen before
+     * that cursor is loaded.
+     */
+    private interface LoadFinishedCallback {
+        void onLoadFinished();
+    }
+
+    /** The deferred actions to execute when mConversationListCursor load completes. */
+    private final ArrayList<LoadFinishedCallback> mConversationListLoadFinishedCallbacks =
+            new ArrayList<LoadFinishedCallback>();
+
     private RefreshTimerTask mConversationListRefreshTask;
 
     /** Listeners that are interested in changes to the current account. */
@@ -339,11 +361,11 @@
         // aware of the selected set.
         mSelectedSet.addObserver(this);
 
-        mFolderItemUpdateDelayMs =
-                mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
-        mShowUndoBarDelay =
-                mContext.getResources().getInteger(R.integer.show_undo_bar_delay_ms);
+        final Resources r = mContext.getResources();
+        mFolderItemUpdateDelayMs = r.getInteger(R.integer.folder_item_refresh_delay_ms);
+        mShowUndoBarDelay = r.getInteger(R.integer.show_undo_bar_delay_ms);
         mVeiledMatcher = VeiledAddressMatcher.newInstance(activity.getResources());
+        mIsTablet = Utils.useTabletUI(r);
     }
 
     @Override
@@ -826,9 +848,7 @@
             if (savedState.containsKey(SAVED_ACTION)) {
                 mDialogAction = savedState.getInt(SAVED_ACTION);
             }
-            if (savedState.containsKey(SAVED_ACTION_FROM_SELECTED)) {
-                mDialogFromSelectedSet = savedState.getBoolean(SAVED_ACTION_FROM_SELECTED);
-            }
+            mDialogFromSelectedSet = savedState.getBoolean(SAVED_ACTION_FROM_SELECTED, false);
             mViewMode.handleRestore(savedState);
         } else if (intent != null) {
             handleIntent(intent);
@@ -874,7 +894,6 @@
 
     @Override
     public final boolean onKeyDown(int keyCode, KeyEvent event) {
-        // TODO(viki): Auto-generated method stub
         return false;
     }
 
@@ -1012,8 +1031,8 @@
     }
 
     @Override
-    public void markConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
-            byte[] originalConversationInfo) {
+    public void markConversationMessagesUnread(final Conversation conv,
+            final Set<Uri> unreadMessageUris, final byte[] originalConversationInfo) {
         // The only caller of this method is the conversation view, from where marking unread should
         // *always* take you back to list mode.
         showConversation(null);
@@ -1021,12 +1040,24 @@
         // locally mark conversation unread (the provider is supposed to propagate message unread
         // to conversation unread)
         conv.read = false;
-
         if (mConversationListCursor == null) {
-            LogUtils.e(LOG_TAG, "null ConversationCursor in markConversationMessagesUnread");
-            return;
-        }
+            LogUtils.d(LOG_TAG, "deferring markConversationMessagesUnread for id=%d", conv.id);
 
+            mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
+                @Override
+                public void onLoadFinished() {
+                    doMarkConversationMessagesUnread(conv, unreadMessageUris,
+                            originalConversationInfo);
+                }
+            });
+        } else {
+            doMarkConversationMessagesUnread(conv, unreadMessageUris, originalConversationInfo);
+        }
+    }
+
+    private void doMarkConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
+            byte[] originalConversationInfo) {
+        LogUtils.d(LOG_TAG, "performing markConversationMessagesUnread for id=%d", conv.id);
         // only do a granular 'mark unread' if a subset of messages are unread
         final int unreadCount = (unreadMessageUris == null) ? 0 : unreadMessageUris.size();
         final int numMessages = conv.getNumMessages();
@@ -1068,14 +1099,25 @@
     }
 
     @Override
-    public void markConversationsRead(Collection<Conversation> targets, boolean read,
-            boolean viewed) {
-        // We want to show the next conversation if we are marking unread.
-        markConversationsRead(targets, read, viewed, true);
+    public void markConversationsRead(final Collection<Conversation> targets, final boolean read,
+            final boolean viewed) {
+        if (mConversationListCursor == null) {
+            LogUtils.d(LOG_TAG, "deferring markConversationsRead");
+            mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
+                @Override
+                public void onLoadFinished() {
+                    markConversationsRead(targets, read, viewed, true);
+                }
+            });
+        } else {
+            // We want to show the next conversation if we are marking unread.
+            markConversationsRead(targets, read, viewed, true);
+        }
     }
 
     private void markConversationsRead(final Collection<Conversation> targets, final boolean read,
             final boolean markViewed, final boolean showNext) {
+        LogUtils.d(LOG_TAG, "performing markConversationsRead");
         // Auto-advance if requested and the current conversation is being marked unread
         if (showNext && !read) {
             final Runnable operation = new Runnable() {
@@ -1141,6 +1183,8 @@
      * <p>Does nothing if outside of conversation mode.</p>
      *
      * @param target the set of conversations being deleted/marked unread
+     * @param operation if auto-advance setting is unset, this operation is run after the user
+     *        is prompted to select a setting.
      * @return <code>false</code> if we aborted because the user has not yet specified a default
      *         action, <code>true</code> otherwise
      */
@@ -1154,7 +1198,7 @@
         if (currentConversationInView) {
             final int autoAdvanceSetting = mAccount.settings.getAutoAdvanceSetting();
 
-            if (autoAdvanceSetting == AutoAdvance.UNSET && Utils.useTabletUI(mContext)) {
+            if (autoAdvanceSetting == AutoAdvance.UNSET && mIsTablet) {
                 displayAutoAdvanceDialogAndPerformAction(operation);
                 return false;
             } else {
@@ -1389,6 +1433,9 @@
             outState.putInt(SAVED_ACTION, mDialogAction);
             outState.putBoolean(SAVED_ACTION_FROM_SELECTED, mDialogFromSelectedSet);
         }
+        if (mDetachedConvUri != null) {
+            outState.putParcelable(SAVED_DETACHED_CONV_URI, mDetachedConvUri);
+        }
         mSafeToModifyFragments = false;
         outState.putString(SAVED_HIERARCHICAL_FOLDER,
                 (mFolderListFolder != null) ? Folder.toString(mFolderListFolder) : null);
@@ -1523,7 +1570,7 @@
      */
     @Override
     public void onRestoreInstanceState(Bundle savedState) {
-        LogUtils.d(LOG_TAG, "IN AAC.onRestoreInstanceState");
+        mDetachedConvUri = savedState.getParcelable(SAVED_DETACHED_CONV_URI);
         if (savedState.containsKey(SAVED_CONVERSATION)) {
             // Open the conversation.
             final Conversation conversation = savedState.getParcelable(SAVED_CONVERSATION);
@@ -1765,7 +1812,7 @@
     public final void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) {
         // Only animate destructive actions if we are going to be showing the
         // conversation list when we show the next conversation.
-        commitDestructiveActions(Utils.useTabletUI(mContext));
+        commitDestructiveActions(mIsTablet);
         showConversation(conversation, inLoaderCallbacks);
     }
 
@@ -1782,7 +1829,14 @@
      */
     @Override
     public void setCurrentConversation(Conversation conversation) {
-        // Must be the first call because this sets conversation.position if a cursor is available.
+        // The controller should come out of detached mode if a new conversation is viewed, or if
+        if (conversation == null || (mDetachedConvUri != null
+                && !mDetachedConvUri.equals(conversation.uri))) {
+            clearDetachedMode();
+        }
+
+        // Must happen *before* setting mCurrentConversation because this sets
+        // conversation.position if a cursor is available.
         mTracker.initialize(conversation);
         mCurrentConversation = conversation;
 
@@ -2105,7 +2159,7 @@
                 // default recents. The default recents will not stomp on the existing value: it
                 // will be shown in addition to the default folders: the max number of recent
                 // folders is more than 1+num(defaultRecents).
-                if (data != null && data.getCount() <= 1 && !Utils.useTabletUI(mContext)) {
+                if (data != null && data.getCount() <= 1 && !mIsTablet) {
                     final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
                         @Override
                         protected Void doInBackground(Uri... uri) {
@@ -2502,7 +2556,7 @@
     @Override
     public void onSetPopulated(ConversationSelectionSet set) {
         mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set, mFolder);
-        if (mViewMode.isListMode()) {
+        if (mViewMode.isListMode() || (mIsTablet && mViewMode.isConversationMode())) {
             enableCabMode();
         }
     }
@@ -2765,6 +2819,11 @@
             mConversationListCursor.addListener(AbstractActivityController.this);
             mTracker.onCursorUpdated();
             mConversationListObservable.notifyChanged();
+            // Handle actions that were deferred until after the conversation list was loaded.
+            for (LoadFinishedCallback callback : mConversationListLoadFinishedCallbacks) {
+                callback.onLoadFinished();
+            }
+            mConversationListLoadFinishedCallbacks.clear();
 
             final ConversationListFragment convList = getConversationListFragment();
             if (isFragmentVisible(convList)) {
@@ -3173,7 +3232,7 @@
         // Clear the cache of objects.
         ConversationItemViewModel.onAccessibilityUpdated();
         // Re-render the list if it exists.
-        ConversationListFragment frag = getConversationListFragment();
+        final ConversationListFragment frag = getConversationListFragment();
         if (frag != null) {
             AnimatedAdapter adapter = frag.getAnimatedAdapter();
             if (adapter != null) {
@@ -3224,5 +3283,31 @@
     @Override
     public VeiledAddressMatcher getVeiledAddressMatcher() {
         return mVeiledMatcher;
-    };
+    }
+
+    @Override
+    public void setDetachedMode() {
+        // Tell the conversation list not to select anything.
+        final ConversationListFragment frag = getConversationListFragment();
+        if (frag != null) {
+            frag.setChoiceNone();
+        } else {
+            // How did we ever land here? Detached mode, and no CLF???
+            LogUtils.e(LOG_TAG, "AAC.setDetachedMode(): CLF = null!");
+        }
+        mDetachedConvUri = mCurrentConversation.uri;
+    }
+
+    private void clearDetachedMode() {
+        // Tell the conversation list to go back to its usual selection behavior.
+        final ConversationListFragment frag = getConversationListFragment();
+        if (frag != null) {
+            frag.revertChoiceMode();
+        } else {
+            // How did we ever land here? Detached mode, and no CLF???
+            LogUtils.e(LOG_TAG, "AAC.clearDetachedMode(): CLF = null!");
+        }
+        mDetachedConvUri = null;
+    }
+
 }
diff --git a/src/com/android/mail/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index 0c2e355..74a3d9a 100644
--- a/src/com/android/mail/ui/AbstractConversationViewFragment.java
+++ b/src/com/android/mail/ui/AbstractConversationViewFragment.java
@@ -63,6 +63,7 @@
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.ListParams;
 import com.android.mail.providers.UIProvider;
+import com.android.mail.providers.UIProvider.CursorStatus;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
@@ -114,13 +115,6 @@
      */
     protected ConversationViewState mViewState;
 
-    /**
-     * Handles a deferred 'mark read' operation, necessary when the conversation view has finished
-     * loading before the conversation cursor. Normally null unless this situation occurs.
-     * When finally able to 'mark read', this observer will also be unregistered and cleaned up.
-     */
-    private MarkReadObserver mMarkReadObserver;
-
     private long mLoadingShownTime = -1;
 
     private final Runnable mDelayedShow = new FragmentRunnable("mDelayedShow") {
@@ -424,11 +418,6 @@
     public void onDestroyView() {
         super.onDestroyView();
         mAccountObserver.unregisterAndDestroy();
-        if (mMarkReadObserver != null) {
-            mActivity.getConversationUpdater().unregisterConversationListObserver(
-                    mMarkReadObserver);
-            mMarkReadObserver = null;
-        }
     }
 
     /**
@@ -476,9 +465,9 @@
                     LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
                 }
 
-                // When the last cursor had message(s), and the new version has
-                // no messages, we need to exit conversation view.
-                if (messageCursor.getCount() == 0 && mCursor != null) {
+                // We have no messages: exit conversation view.
+                if (messageCursor.getCount() == 0
+                        && !CursorStatus.isWaitingForResults(messageCursor.getStatus())) {
                     if (mUserVisible) {
                         onError();
                     } else {
@@ -558,16 +547,14 @@
         // but we do want future re-renders to mark read (e.g. "New message from X" case)
         MessageCursor cursor = getMessageCursor();
         if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
-            final ConversationUpdater listController = activity.getConversationUpdater();
-            // The conversation cursor may not have finished loading by now (when launched via
-            // notification), so watch for when it finishes and mark it read then.
-            if (listController.getConversationListCursor() == null) {
-                LogUtils.i(LOG_TAG, "deferring conv mark read on open for id=%d",
-                        mConversation.id);
-                mMarkReadObserver = new MarkReadObserver(listController);
-                listController.registerConversationListObserver(mMarkReadObserver);
-            } else {
-                markReadOnSeen(listController);
+            // Mark the conversation viewed and read.
+            activity.getConversationUpdater().markConversationsRead(Arrays.asList(mConversation),
+                    true /* read */, true /* viewed */);
+
+            // and update the Message objects in the cursor so the next time a cursor update happens
+            // with these messages marked read, we know to ignore it
+            if (cursor != null) {
+                cursor.markMessagesRead();
             }
         }
 
@@ -576,19 +563,6 @@
         showAutoFitPrompt();
     }
 
-    protected void markReadOnSeen(ConversationUpdater listController) {
-        // Mark the conversation viewed and read.
-        listController.markConversationsRead(Arrays.asList(mConversation), true /* read */,
-                true /* viewed */);
-
-        // and update the Message objects in the cursor so the next time a cursor update happens
-        // with these messages marked read, we know to ignore it
-        MessageCursor cursor = getMessageCursor();
-        if (cursor != null) {
-            cursor.markMessagesRead();
-        }
-    }
-
     protected ConversationViewState getNewViewState() {
         return new ConversationViewState();
     }
@@ -751,27 +725,6 @@
         }
     }
 
-    private class MarkReadObserver extends DataSetObserver {
-        private final ConversationUpdater mListController;
-
-        private MarkReadObserver(ConversationUpdater listController) {
-            mListController = listController;
-        }
-
-        @Override
-        public void onChanged() {
-            if (mListController.getConversationListCursor() == null) {
-                // nothing yet, keep watching
-                return;
-            }
-            // done loading, safe to mark read now
-            mListController.unregisterConversationListObserver(this);
-            mMarkReadObserver = null;
-            LogUtils.i(LOG_TAG, "running deferred conv mark read on open, id=%d", mConversation.id);
-            markReadOnSeen(mListController);
-        }
-    }
-
     public abstract void onConversationUpdated(Conversation conversation);
 
     /**
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index e320ac3..abb1dd6 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -127,10 +127,10 @@
     private static final String LOG_TAG = LogTag.getLogTag();
     private static final int INCREASE_WAIT_COUNT = 2;
 
-    public AnimatedAdapter(Context context, int textViewResourceId, ConversationCursor cursor,
-            ConversationSelectionSet batch,
-            ControllableActivity activity, SwipeableListView listView) {
-        super(context, textViewResourceId, cursor, UIProvider.CONVERSATION_PROJECTION, null, 0);
+    public AnimatedAdapter(Context context, ConversationCursor cursor,
+            ConversationSelectionSet batch, ControllableActivity activity,
+            SwipeableListView listView) {
+        super(context, -1, cursor, UIProvider.CONVERSATION_PROJECTION, null, 0);
         mContext = context;
         mBatchConversations = batch;
         setAccount(mAccountListener.initialize(activity.getAccountController()));
@@ -211,7 +211,7 @@
             view = new SwipeableConversationItemView(context, mAccount.name);
         }
         view.bind(conv, mActivity, mBatchConversations, mFolder,
-                mAccount != null ? !mAccount.settings.showCheckboxes : false, mSwipeEnabled,
+                mAccount != null ? mAccount.settings.hideCheckboxes : false, mSwipeEnabled,
                 mPriorityMarkersEnabled, this);
         return view;
     }
@@ -541,7 +541,7 @@
             return;
         }
         ((SwipeableConversationItemView) view).bind(cursor, mActivity, mBatchConversations, mFolder,
-                mAccount != null ? !mAccount.settings.showCheckboxes : false,
+                mAccount != null ? mAccount.settings.hideCheckboxes : false,
                         mSwipeEnabled, mPriorityMarkersEnabled, this);
     }
 
@@ -551,7 +551,7 @@
                 position, null, parent);
         view.reset();
         view.bind(conversation, mActivity, mBatchConversations, mFolder,
-                mAccount != null ? !mAccount.settings.showCheckboxes : false, mSwipeEnabled,
+                mAccount != null ? mAccount.settings.hideCheckboxes : false, mSwipeEnabled,
                 mPriorityMarkersEnabled, this);
         mAnimatingViews.put(conversation.id, view);
         return view;
@@ -797,4 +797,4 @@
             item.cancelFadeOutText();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/mail/ui/ConversationListCallbacks.java b/src/com/android/mail/ui/ConversationListCallbacks.java
index 974de55..40e75cb 100644
--- a/src/com/android/mail/ui/ConversationListCallbacks.java
+++ b/src/com/android/mail/ui/ConversationListCallbacks.java
@@ -73,4 +73,9 @@
      * Detect if there are any animations occuring in the conversation list.
      */
     boolean isAnimating();
+
+    /**
+     * Tell the controller that the conversation view has entered detached mode.
+     */
+    void setDetachedMode();
 }
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 12f31fa..a54aab5 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -69,6 +69,8 @@
     private static final String LIST_STATE_KEY = "list-state";
 
     private static final String LOG_TAG = LogTag.getLogTag();
+    /** Key used to save the ListView choice mode, since ListView doesn't save it automatically! */
+    private static final String CHOICE_MODE_KEY = "choice-mode-key";
 
     // True if we are on a tablet device
     private static boolean mTabletDevice;
@@ -116,7 +118,7 @@
     private View mEmptyView;
     private ErrorListener mErrorListener;
     private DataSetObserver mFolderObserver;
-    private DataSetObserver mConversationListStatusObserver;
+    private DataSetObserver mConversationCursorObserver;
 
     private ConversationSelectionSet mSelectedSet;
     private final AccountObserver mAccountObserver = new AccountObserver() {
@@ -153,7 +155,7 @@
         }
     }
 
-    private class ConversationListStatusObserver extends DataSetObserver {
+    private class ConversationCursorObserver extends DataSetObserver {
         @Override
         public void onChanged() {
             onConversationListStatusUpdated();
@@ -233,8 +235,8 @@
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public void onActivityCreated(Bundle savedState) {
+        super.onActivityCreated(savedState);
         // Strictly speaking, we get back an android.app.Activity from
         // getActivity. However, the
         // only activity creating a ConversationListContext is a MailActivity
@@ -262,7 +264,7 @@
                 null);
         mFooterView.setClickListener(mActivity);
         final ConversationCursor conversationCursor = getConversationListCursor();
-        mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(), -1,
+        mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(),
                 conversationCursor, mActivity.getSelectedSet(), mActivity, mListView);
         mListAdapter.addFooter(mFooterView);
         mListView.setAdapter(mListAdapter);
@@ -271,10 +273,10 @@
         mListAdapter.hideFooter();
         mFolderObserver = new FolderObserver();
         mActivity.getFolderController().registerFolderObserver(mFolderObserver);
-        mConversationListStatusObserver = new ConversationListStatusObserver();
+        mConversationCursorObserver = new ConversationCursorObserver();
         mUpdater = mActivity.getConversationUpdater();
-        mUpdater.registerConversationListObserver(mConversationListStatusObserver);
-        mTabletDevice = Utils.useTabletUI(mActivity.getApplicationContext());
+        mUpdater.registerConversationListObserver(mConversationCursorObserver);
+        mTabletDevice = Utils.useTabletUI(mActivity.getApplicationContext().getResources());
         initializeUiForFirstDisplay();
         configureSearchResultHeader();
         // The onViewModeChanged callback doesn't get called when the mode
@@ -294,6 +296,21 @@
             conversationCursor.sync();
         }
 
+        // On a phone we never highlight a conversation, so the default is to select none.
+        // On a tablet, we highlight a SINGLE conversation in landscape conversation view.
+        int choice = getDefaultChoiceMode(mTabletDevice);
+        if (savedState != null) {
+            // Restore the choice mode if it was set earlier, or NONE if creating a fresh view.
+            // Choice mode here represents the current conversation only. CAB mode does not rely on
+            // the platform: checked state is a local variable {@link ConversationItemView#mChecked}
+            choice = savedState.getInt(CHOICE_MODE_KEY, choice);
+            if (savedState.containsKey(LIST_STATE_KEY)) {
+                // TODO: find a better way to unset the selected item when restoring
+                mListView.clearChoices();
+            }
+        }
+        setChoiceMode(choice);
+
         // Show list and start loading list.
         showList();
         ToastBarOperation pendingOp = mActivity.getPendingToastOperation();
@@ -304,6 +321,16 @@
         }
     }
 
+    /**
+     * Returns the default choice mode for the list based on whether the list is displayed on tablet
+     * or not.
+     * @param isTablet
+     * @return
+     */
+    private final static int getDefaultChoiceMode(boolean isTablet) {
+        return isTablet ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE;
+    }
+
     public AnimatedAdapter getAnimatedAdapter() {
         return mListAdapter;
     }
@@ -337,22 +364,57 @@
         mEmptyView = rootView.findViewById(R.id.empty_view);
         mListView = (SwipeableListView) rootView.findViewById(android.R.id.list);
         mListView.setHeaderDividersEnabled(false);
-        // Choice mode here represents the current conversation only. CAB mode does not rely on the
-        // platform: it is a local variable within conversation items.
-        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         mListView.setOnItemLongClickListener(this);
         mListView.enableSwipe(mAccount.supportsCapability(AccountCapabilities.UNDO));
         mListView.setSwipedListener(this);
 
-        // Restore the list state
         if (savedState != null && savedState.containsKey(LIST_STATE_KEY)) {
             mListView.onRestoreInstanceState(savedState.getParcelable(LIST_STATE_KEY));
-            // TODO: find a better way to unset the selected item when restoring
-            mListView.clearChoices();
         }
         return rootView;
     }
 
+    /**
+     * Sets the choice mode of the list view
+     * @param choiceMode ListView#
+     */
+    private final void setChoiceMode(int choiceMode) {
+        mListView.setChoiceMode(choiceMode);
+    }
+
+    /**
+     * Tell the list to select nothing.
+     */
+    public final void setChoiceNone() {
+        // On a phone, the default choice mode is already none, so nothing to do.
+        if (!mTabletDevice) {
+            return;
+        }
+        final int currentSelected = mListView.getCheckedItemPosition();
+        mListView.clearChoices();
+        // We use the activated state to show the blue highlight on tablet. Clearing the choices
+        // removes the checked state, but doesn't do anything to the activated state.  We must
+        // manually clear that.
+        if (currentSelected != ListView.INVALID_POSITION) {
+            final View v = mListView.getChildAt(currentSelected);
+            if (v != null) {
+                v.setActivated(false);
+            }
+        }
+        setChoiceMode(ListView.CHOICE_MODE_NONE);
+    }
+
+    /**
+     * Tell the list to get out of selecting none.
+     */
+    public final void revertChoiceMode() {
+        // On a phone, the default choice mode is always none, so nothing to do.
+        if (!mTabletDevice) {
+            return;
+        }
+        setChoiceMode(getDefaultChoiceMode(mTabletDevice));
+    }
+
     @Override
     public void onDestroy() {
         super.onDestroy();
@@ -370,9 +432,9 @@
             mActivity.getFolderController().unregisterFolderObserver(mFolderObserver);
             mFolderObserver = null;
         }
-        if (mConversationListStatusObserver != null) {
-            mUpdater.unregisterConversationListObserver(mConversationListStatusObserver);
-            mConversationListStatusObserver = null;
+        if (mConversationCursorObserver != null) {
+            mUpdater.unregisterConversationListObserver(mConversationCursorObserver);
+            mConversationCursorObserver = null;
         }
         mAccountObserver.unregisterAndDestroy();
         super.onDestroyView();
@@ -435,7 +497,7 @@
         if (!(view instanceof ToggleableItem)) {
             return;
         }
-        if (!mAccount.settings.showCheckboxes && !mSelectedSet.isEmpty()) {
+        if (mAccount.settings.hideCheckboxes && !mSelectedSet.isEmpty()) {
             ToggleableItem v = (ToggleableItem) view;
             v.toggleCheckMarkOrBeginDrag();
         } else {
@@ -444,7 +506,7 @@
         // When a new list item is clicked, commit any existing leave behind
         // items. Wait until we have opened the desired conversation to cause
         // any position changes.
-        commitDestructiveActions(Utils.useTabletUI(mActivity.getActivityContext()));
+        commitDestructiveActions(Utils.useTabletUI(mActivity.getActivityContext().getResources()));
     }
 
     @Override
@@ -457,6 +519,7 @@
         super.onSaveInstanceState(outState);
         if (mListView != null) {
             outState.putParcelable(LIST_STATE_KEY, mListView.onSaveInstanceState());
+            outState.putInt(CHOICE_MODE_KEY, mListView.getChoiceMode());
         }
     }
 
@@ -476,17 +539,13 @@
     public void onViewModeChanged(int newMode) {
         // Change the divider based on view mode.
         if (mTabletDevice) {
-            if (newMode == ViewMode.CONVERSATION) {
+            if (ViewMode.isConversationMode(newMode)) {
                 mListView.setBackgroundResource(R.drawable.panel_conversation_leftstroke);
-            } else if (newMode == ViewMode.CONVERSATION_LIST
-                    || newMode == ViewMode.SEARCH_RESULTS_LIST) {
-                // There are no selected conversations when in conversation
-                // list mode.
-                mListView.clearChoices();
+            } else if (ViewMode.isListMode(newMode)) {
+                // There are no selected conversations when in conversation list mode.
                 mListView.setBackgroundDrawable(null);
+                mListView.clearChoices();
             }
-        } else {
-            mListView.setBackgroundDrawable(null);
         }
         if (mFooterView != null) {
             mFooterView.onViewModeChanged(newMode);
@@ -510,7 +569,7 @@
      */
     protected void viewConversation(int position) {
         LogUtils.d(LOG_TAG, "ConversationListFragment.viewConversation(%d)", position);
-        setSelected(position);
+        setSelected(position, true);
         final ConversationCursor cursor = getConversationListCursor();
         if (cursor != null && cursor.moveToPosition(position)) {
             final Conversation conv = new Conversation(cursor);
@@ -519,8 +578,17 @@
         }
     }
 
-
+    /**
+     * Sets the selected conversation to the position given here.
+     * @param position
+     * @param different if the currently selected conversation is different from the one provided
+     * here.  This is a difference in conversations, not a difference in positions. For example, a
+     * conversation at position 2 can move to position 4 as a result of new mail.
+     */
     public void setSelected(int position, boolean different) {
+        if (mListView.getChoiceMode() == ListView.CHOICE_MODE_NONE) {
+            return;
+        }
         if (different) {
             mListView.smoothScrollToPosition(position);
         }
@@ -528,14 +596,9 @@
     }
 
     /**
-     * Sets the selected position (the highlighted conversation) to the position
-     * provided here.
-     * @param position
+     * Returns the cursor associated with the conversation list.
+     * @return
      */
-    protected final void setSelected(int position) {
-        setSelected(position, true);
-    }
-
     private ConversationCursor getConversationListCursor() {
         return mCallbacks != null ? mCallbacks.getConversationListCursor() : null;
     }
@@ -693,8 +756,9 @@
         if (conv == null) {
             return;
         }
-        if (mListView.getCheckedItemPosition() == -1) {
-            setSelected(conv.position);
+        if (mListView.getChoiceMode() != ListView.CHOICE_MODE_NONE
+                && mListView.getCheckedItemPosition() == -1) {
+            setSelected(conv.position, true);
         }
     }
 
diff --git a/src/com/android/mail/ui/ConversationPositionTracker.java b/src/com/android/mail/ui/ConversationPositionTracker.java
index 3d4f86f..46ca0a0 100644
--- a/src/com/android/mail/ui/ConversationPositionTracker.java
+++ b/src/com/android/mail/ui/ConversationPositionTracker.java
@@ -17,16 +17,12 @@
 
 package com.android.mail.ui;
 
-import android.database.Cursor;
-
 import com.android.mail.browse.ConversationCursor;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Settings;
 import com.android.mail.providers.UIProvider.AutoAdvance;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
-
 import java.util.Collection;
 
 /**
@@ -132,7 +128,7 @@
     public void initialize(Conversation conversation) {
         mConversation = conversation;
         mCursorDirty = true;
-        calculatePosition();
+        calculatePosition(); // Return value discarded. Running for side effects.
     }
 
     /** @return whether or not we have a valid cursor to check the position of. */
@@ -179,7 +175,7 @@
             return invalidPosition;
         }
         mCursorDirty = false;
-        final int listSize = (cursor == null) ? 0 : cursor.getCount();
+        final int listSize = cursor.getCount();
         if (!isDataLoaded(cursor) || listSize == 0) {
             return invalidPosition;
         }
@@ -198,21 +194,24 @@
         // If the conversation is no longer found in the list, try to save the same position if
         // it is still a valid position. Otherwise, go back to a valid position until we can
         // find a valid one.
-        int newPosition = foundPosition;
-        if (mConversation.position >= listSize || foundPosition >= listSize) {
+        final int newPosition;
+        if (foundPosition >= listSize) {
             // Go to the last position since our expected position is past this somewhere.
-            newPosition = cursor.getCount() - 1;
+            newPosition = listSize - 1;
+        } else {
+            newPosition = foundPosition;
         }
 
-        // Did not keep the same conversation, but could still be a valid conversation.
-        if (isDataLoaded(cursor)){
+        // Did not keep the current conversation, so let's try to load the conversation from the
+        // new position.
+        if (isDataLoaded(cursor) && newPosition >= 0){
             LogUtils.d(LOG_TAG, "ConversationPositionTracker: Could not find conversation %s" +
                     " in the cursor. Moving to position %d ", mConversation.toString(),
                     newPosition);
             cursor.moveToPosition(newPosition);
             mConversation = new Conversation(cursor);
+            mConversation.position = newPosition;
         }
-
         return newPosition;
     }
 
@@ -221,10 +220,10 @@
      * conversations available in the folder. If no next conversation can be found, this method
      * returns null.
      * @param autoAdvance the auto advance preference for the user as an
-     * {@link Settings#autoAdvance} value.
+     * {@link Settings#getAutoAdvanceSetting()} value.
      * @param mTarget conversations to overlook while finding the next conversation. (These are
      * usually the conversations to be deleted.)
-     * @return
+     * @return the next conversation to be shown, or null if no next conversation exists.
      */
     public Conversation getNextConversation(int autoAdvance, Collection<Conversation> mTarget) {
         final boolean getNewer = autoAdvance == AutoAdvance.NEWER;
diff --git a/src/com/android/mail/ui/EllipsizedMultilineTextView.java b/src/com/android/mail/ui/EllipsizedMultilineTextView.java
index 0f83f27..fca39a9 100644
--- a/src/com/android/mail/ui/EllipsizedMultilineTextView.java
+++ b/src/com/android/mail/ui/EllipsizedMultilineTextView.java
@@ -64,17 +64,18 @@
      * @param avail available width in pixels for the last line
      * @param paint Paint that has the proper properties set to measure the text
      *            for this view
+     * @return the {@link CharSequence} that was set on the {@link TextView}
      */
-    public void setText(final CharSequence text, int avail) {
+    public CharSequence setText(final CharSequence text, int avail) {
         if (text == null || text.length() == 0) {
-            return;
+            return text;
         }
 
         setEllipsize(null);
         setText(text, TextView.BufferType.SPANNABLE);
 
         if (avail == ALL_AVAILABLE) {
-            return;
+            return text;
         }
         Layout layout = getLayout();
 
@@ -85,7 +86,7 @@
 
         if (layout == null) {
             // Bail
-            return;
+            return text;
         }
 
         CharSequence remainder;
@@ -104,5 +105,7 @@
             builder.append(remainder);
         }
         setText(builder, TextView.BufferType.SPANNABLE);
+
+        return builder;
     }
 }
\ No newline at end of file
diff --git a/src/com/android/mail/ui/MailActionBarView.java b/src/com/android/mail/ui/MailActionBarView.java
index ea52172..ac83a2f 100644
--- a/src/com/android/mail/ui/MailActionBarView.java
+++ b/src/com/android/mail/ui/MailActionBarView.java
@@ -22,6 +22,7 @@
 import android.app.SearchableInfo;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DataSetObserver;
 import android.os.Bundle;
@@ -33,7 +34,6 @@
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.SearchView;
-import android.widget.TextView;
 import android.widget.SearchView.OnQueryTextListener;
 import android.widget.SearchView.OnSuggestionListener;
 
@@ -61,10 +61,12 @@
 public class MailActionBarView extends LinearLayout implements ViewMode.ModeChangeListener,
         OnQueryTextListener, OnSuggestionListener, MenuItem.OnActionExpandListener,
         SubjectDisplayChanger {
+
+    private static final int DISPLAY_TITLE_MULTIPLE_LINES = 0x20;
+
     protected ActionBar mActionBar;
     protected ControllableActivity mActivity;
     protected ActivityController mController;
-    private View mFolderView;
     /**
      * The current mode of the ActionBar. This references constants in {@link ViewMode}
      */
@@ -106,19 +108,16 @@
         }
     };
     private final boolean mShowConversationSubject;
-    private TextView mFolderAccountName;
     private DataSetObserver mFolderObserver;
 
     private final AccountObserver mAccountObserver = new AccountObserver() {
         @Override
         public void onChanged(Account newAccount) {
             updateAccount(newAccount);
-            if (mFolderAccountName != null) {
-                mFolderAccountName.setText(mAccount.name);
-            }
             mSpinner.setAccount(mAccount);
         }
     };
+
     /** True if the application has more than one account. */
     private boolean mHasManyAccounts;
 
@@ -132,8 +131,9 @@
 
     public MailActionBarView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mShowConversationSubject = getResources().getBoolean(R.bool.show_conversation_subject);
-        mIsOnTablet = Utils.useTabletUI(context);
+        final Resources r = getResources();
+        mShowConversationSubject = r.getBoolean(R.bool.show_conversation_subject);
+        mIsOnTablet = Utils.useTabletUI(r);
     }
 
     // update the pager title strip as the Folder's conversation count changes
@@ -149,8 +149,6 @@
         super.onFinishInflate();
 
         mSubjectView = (SnippetTextView) findViewById(R.id.conversation_subject);
-        mFolderView = findViewById(R.id.folder_layout);
-        mFolderAccountName = (TextView) mFolderView.findViewById(R.id.account);
     }
 
     /**
@@ -238,8 +236,9 @@
         mFolderObserver = new FolderObserver();
         mController.registerFolderObserver(mFolderObserver);
         // We don't want to include the "Show all folders" menu item on tablet devices
-        final boolean showAllFolders = !Utils.useTabletUI(getContext());
-        mSpinnerAdapter = new AccountSpinnerAdapter(activity, getContext(), showAllFolders);
+        final Context context = getContext();
+        final boolean showAllFolders = !Utils.useTabletUI(context.getResources());
+        mSpinnerAdapter = new AccountSpinnerAdapter(activity, context, showAllFolders);
         mSpinner = (MailSpinner) findViewById(R.id.account_spinner);
         mSpinner.setAdapter(mSpinnerAdapter);
         mSpinner.setController(mController);
@@ -331,8 +330,6 @@
             mSpinnerAdapter.disableRecentFolders();
         }
 
-        boolean showFolderView = false;
-
         switch (mMode) {
             case ViewMode.UNKNOWN:
                 if (mSearch != null) {
@@ -347,13 +344,12 @@
                 if (!mShowConversationSubject) {
                     showNavList();
                 } else {
-                    setStandardMode();
+                    setSnippetMode();
                 }
                 break;
             case ViewMode.FOLDER_LIST:
                 mActionBar.setDisplayHomeAsUpEnabled(true);
-                setStandardMode();
-                showFolderView = true;
+                setFoldersMode();
                 break;
             case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
                 // We want the user to be able to switch accounts while waiting for an account
@@ -361,7 +357,6 @@
                 showNavList();
                 break;
         }
-        mFolderView.setVisibility(showFolderView ? VISIBLE : GONE);
     }
 
     protected int getMode() {
@@ -425,27 +420,35 @@
      * Put the ActionBar in List navigation mode. This starts the spinner up if it is missing.
      */
     private void showNavList() {
+        setTitleModeFlags(ActionBar.DISPLAY_SHOW_CUSTOM);
         mSpinner.setVisibility(View.VISIBLE);
-        mFolderView.setVisibility(View.GONE);
-        mFolderAccountName.setVisibility(View.GONE);
+        mSubjectView.setVisibility(View.GONE);
     }
 
     /**
-     * Set the actionbar mode to standard mode: no list navigation.
+     * Set the actionbar mode to "snippet" mode: no list navigation, show what looks like 2-line
+     * "standard" snippet. Later on, {@link #getUnshownSubject(String)} will seamlessly switch
+     * back to bog-standard SHOW_TITLE mode once the text remainders can safely be determined.
      */
-    private void setStandardMode() {
+    private void setSnippetMode() {
+        setTitleModeFlags(ActionBar.DISPLAY_SHOW_CUSTOM);
         mSpinner.setVisibility(View.GONE);
-        mFolderView.setVisibility(View.VISIBLE);
-        mFolderAccountName.setVisibility(View.VISIBLE);
+        mSubjectView.setVisibility(View.VISIBLE);
+    }
+
+    private void setFoldersMode() {
+        setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE);
+        mActionBar.setTitle(R.string.folders);
+        mActionBar.setSubtitle(mAccount.name);
     }
 
     /**
      * Set the actionbar mode to empty: no title, no custom content.
      */
     protected void setEmptyMode() {
+        setTitleModeFlags(ActionBar.DISPLAY_SHOW_CUSTOM);
         mSpinner.setVisibility(View.GONE);
-        mFolderView.setVisibility(View.GONE);
-        mFolderAccountName.setVisibility(View.GONE);
+        mSubjectView.setVisibility(View.GONE);
     }
 
     public void removeBackButton() {
@@ -596,12 +599,21 @@
         return true;
     }
 
+    private void setTitleModeFlags(int enabledFlags) {
+        final int mask = ActionBar.DISPLAY_SHOW_TITLE
+                | ActionBar.DISPLAY_SHOW_CUSTOM | DISPLAY_TITLE_MULTIPLE_LINES;
+
+        mActionBar.setDisplayOptions(enabledFlags, mask);
+    }
+
     @Override
     public void setSubject(String subject) {
         if (!mShowConversationSubject) {
             return;
         }
 
+        mActionBar.setTitle(subject);
+        mActionBar.setSubtitle(null);
         mSubjectView.setText(subject);
     }
 
@@ -611,6 +623,8 @@
             return;
         }
 
+        mActionBar.setTitle(null);
+        mActionBar.setSubtitle(null);
         mSubjectView.setText(null);
     }
 
@@ -620,7 +634,33 @@
             return subject;
         }
 
-        return mSubjectView.getTextRemainder(subject);
+        final String remainder = mSubjectView.getTextRemainder(subject);
+
+        if (actionBarReportsMultipleLineTitle(mActionBar)) {
+            // Switch over to title mode when the first fragment asks for a subject remainder.
+            // We assume that layout has happened by now, so the SnippetTextView already has
+            // measurements it needs to calculate remainders, and it's safe to switch over to
+            // TITLE mode to inherit standard system behaviors.
+            setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE | DISPLAY_TITLE_MULTIPLE_LINES);
+
+            // Work around a bug where the title's container is stuck GONE when a title is set while
+            // in CUSTOM mode.
+            mActionBar.setTitle(mActionBar.getTitle());
+        }
+
+        return remainder;
+    }
+
+    private static boolean actionBarReportsMultipleLineTitle(ActionBar bar) {
+        boolean reports = false;
+        try {
+            if (bar != null) {
+                reports = (ActionBar.class.getField("DISPLAY_TITLE_MULTIPLE_LINES") != null);
+            }
+        } catch (NoSuchFieldException e) {
+            // stay false
+        }
+        return reports;
     }
 
     public void setCurrentConversation(Conversation conversation) {
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index 470fb16..dc1e452 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -144,7 +144,7 @@
         super.onCreate(savedState);
 
         mViewMode = new ViewMode(this);
-        final boolean tabletUi = Utils.useTabletUI(this);
+        final boolean tabletUi = Utils.useTabletUI(this.getResources());
         mController = ControllerFactory.forActivity(this, mViewMode, tabletUi);
         mController.onCreate(savedState);
 
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index e097ad8..1a5e257 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -77,7 +77,6 @@
     @Override
     public void onRestoreInstanceState(Bundle inState) {
         super.onRestoreInstanceState(inState);
-        // TODO(mindyp) handle saved state.
         if (inState == null) {
             return;
         }
@@ -95,7 +94,6 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        // TODO(mindyp) handle saved state.
         outState.putInt(FOLDER_LIST_TRANSACTION_KEY, mLastFolderListTransactionId);
         outState.putInt(INBOX_CONVERSATION_LIST_TRANSACTION_KEY,
                 mLastInboxConversationListTransactionId);
@@ -170,7 +168,6 @@
 
         // When entering conversation list mode, hide and clean up any currently visible
         // conversation.
-        // TODO: improve this transition
         if (ViewMode.isListMode(newMode)) {
             mPagerController.hide(true /* changeVisibility */);
         }
@@ -185,15 +182,11 @@
     public void showConversationList(ConversationListContext listContext) {
         super.showConversationList(listContext);
         enableCabMode();
-        // TODO(viki): Check if the account has been changed since the previous
-        // time.
         if (ConversationListContext.isSearchResult(listContext)) {
             mViewMode.enterSearchResultsListMode();
         } else {
             mViewMode.enterConversationListMode();
         }
-        // TODO(viki): This account transition looks strange in two pane mode.
-        // Revisit as the app is coming together and improve the look and feel.
         final int transition = mConversationListNeverShown
                 ? FragmentTransaction.TRANSIT_FRAGMENT_FADE
                 : FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
@@ -248,7 +241,6 @@
         // activity, as when the transaction is popped off, the FragmentManager will attempt to
         // readd the same fragment twice
         if (f != null && f.isAdded()) {
-            // TODO: improve this transition
             ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
             ft.remove(f);
             ft.commitAllowingStateLoss();
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 77f9f67..0aa9a43 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -37,8 +37,6 @@
  * Controller for two-pane Mail activity. Two Pane is used for tablets, where screen real estate
  * abounds.
  */
-
-// Called TwoPaneActivityController in Gmail.
 public final class TwoPaneController extends AbstractActivityController {
     private TwoPaneLayout mLayout;
     private Conversation mConversationToShow;
@@ -121,9 +119,8 @@
         }
         fragmentTransaction.replace(R.id.content_pane, folderListFragment, TAG_FOLDER_LIST);
         fragmentTransaction.commitAllowingStateLoss();
-        // Since we are showing the folder list, we are at the start of the view
-        // stack.
-        // TODO(viki): We don't need this call. Evaluate and remove.
+        // We only set the action bar if the viewmode has been set previously. Otherwise, we leave
+        // the action bar in the state it is currently in.
         if (mViewMode.getMode() != ViewMode.UNKNOWN) {
             resetActionBarIcon();
         }
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 3ab91ac..5b0bab1 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -514,11 +514,10 @@
     /**
      * Returns a boolean indicating whether the table UI should be shown.
      */
-    public static boolean useTabletUI(Context context) {
-        return context.getResources().getInteger(R.integer.use_tablet_ui) != 0;
+    public static boolean useTabletUI(Resources res) {
+        return res.getInteger(R.integer.use_tablet_ui) != 0;
     }
 
-
     /**
      * Returns a boolean indicating whether or not we should animate in the
      * folder list fragment.