am 661e4783: (-s ours) DO NOT MERGE Prevent crash with bad Intent

* commit '661e4783a7c57f42849489ed54cf5495ada26314':
  DO NOT MERGE Prevent crash with bad Intent
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8ddad7e..24fa2b2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -143,12 +143,6 @@
         <provider android:name="com.android.mail.providers.SuggestionsProvider"
             android:authorities="com.android.mail.suggestionsprovider" />
 
-        <receiver android:name=".providers.protos.boot.AccountReceiver">
-          <intent-filter>
-              <action android:name="com.android.mail.providers.protos.boot.intent.ACTION_PROVIDER_CREATED" />
-          </intent-filter>
-        </receiver>
-
         <service android:name=".compose.EmptyService"/>
 
         <!-- Widget -->
@@ -165,6 +159,7 @@
         <service android:name=".widget.WidgetService"
                  android:permission="android.permission.BIND_REMOTEVIEWS"
                  android:exported="false" />
+        <service android:name=".MailLogService"/>
 
     </application>
 
diff --git a/assets/script.js b/assets/script.js
index 215349d..51a9d69 100644
--- a/assets/script.js
+++ b/assets/script.js
@@ -24,6 +24,15 @@
 var gScaleInfo;
 
 /**
+ * Only revert transforms that do an imperfect job of shrinking content if they fail
+ * to shrink by this much. Expressed as a ratio of:
+ * (original width difference : width difference after transforms);
+ */
+TRANSFORM_MINIMUM_EFFECTIVE_RATIO = 0.7;
+
+var gTransformText = {};
+
+/**
  * Returns the page offset of an element.
  *
  * @param {Element} element The element to return the page offset for.
@@ -136,10 +145,6 @@
     var contentValues;
     var isEmpty;
 
-    if (!NORMALIZE_MESSAGE_WIDTHS) {
-        return;
-    }
-
     expandedBodyDivs = document.querySelectorAll(".expanded > .mail-message-content");
 
     isEmpty = isConversationEmpty(expandedBodyDivs);
@@ -169,10 +174,6 @@
     var documentWidth;
     var newZoom, oldZoom;
 
-    if (!NORMALIZE_MESSAGE_WIDTHS) {
-        return;
-    }
-
     documentWidth = document.body.offsetWidth;
 
     for (i = 0; i < elements.length; i++) {
@@ -183,10 +184,235 @@
             el.style.zoom = 1;
         }
         newZoom = documentWidth / el.scrollWidth;
-        el.style.zoom = newZoom;
+        transformContent(el, documentWidth, el.scrollWidth);
+        newZoom = documentWidth / el.scrollWidth;
+        if (NORMALIZE_MESSAGE_WIDTHS) {
+            el.style.zoom = newZoom;
+        }
     }
 }
 
+function transformContent(el, docWidth, elWidth) {
+    var nodes;
+    var i, len;
+    var index;
+    var newWidth = elWidth;
+    var wStr;
+    var touched;
+    // the format of entries in this array is:
+    // entry := [ undoFunction, undoFunctionThis, undoFunctionParamArray ]
+    var actionLog = [];
+    var node;
+    var done = false;
+    var msgId;
+    var transformText;
+    var existingText;
+    var textElement;
+    var start;
+    if (elWidth <= docWidth) {
+        return;
+    }
+
+    start = Date.now();
+
+    if (el.parentElement.classList.contains("mail-message")) {
+        msgId = el.parentElement.id;
+        transformText = "[origW=" + elWidth + "/" + docWidth;
+    }
+
+    // Try munging all divs or textareas with inline styles where the width
+    // is wider than docWidth, and change it to be a max-width.
+    touched = false;
+    nodes = ENABLE_MUNGE_TABLES ? el.querySelectorAll("div[style], textarea[style]") : [];
+    touched = transformBlockElements(nodes, docWidth, actionLog);
+    if (touched) {
+        newWidth = el.scrollWidth;
+        console.log("ran div-width munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth
+            + " docW=" + docWidth);
+        if (msgId) {
+            transformText += " DIV:newW=" + newWidth;
+        }
+        if (newWidth <= docWidth) {
+            done = true;
+        }
+    }
+
+    if (!done) {
+        // OK, that wasn't enough. Find images with widths and override their widths.
+        nodes = ENABLE_MUNGE_IMAGES ? el.querySelectorAll("img") : [];
+        touched = transformImages(nodes, docWidth, actionLog);
+        if (touched) {
+            newWidth = el.scrollWidth;
+            console.log("ran img munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth
+                + " docW=" + docWidth);
+            if (msgId) {
+                transformText += " IMG:newW=" + newWidth;
+            }
+            if (newWidth <= docWidth) {
+                done = true;
+            }
+        }
+    }
+
+    if (!done) {
+        // OK, that wasn't enough. Find tables with widths and override their widths.
+        nodes = ENABLE_MUNGE_TABLES ? el.querySelectorAll("table") : [];
+        touched = addClassToElements(nodes, shouldMungeTable, "munged",
+            actionLog);
+        if (touched) {
+            newWidth = el.scrollWidth;
+            console.log("ran table munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth
+                + " docW=" + docWidth);
+            if (msgId) {
+                transformText += " TABLE:newW=" + newWidth;
+            }
+            if (newWidth <= docWidth) {
+                done = true;
+            }
+        }
+    }
+
+    if (!done) {
+        // OK, that wasn't enough. Try munging all <td> to override any width and nowrap set.
+        nodes = ENABLE_MUNGE_TABLES ? el.querySelectorAll("td") : [];
+        touched = addClassToElements(nodes, null /* mungeAll */, "munged",
+            actionLog);
+        if (touched) {
+            newWidth = el.scrollWidth;
+            console.log("ran td munger on el=" + el + " oldW=" + elWidth + " newW=" + newWidth
+                + " docW=" + docWidth);
+            if (msgId) {
+                transformText += " TD:newW=" + newWidth;
+            }
+            if (newWidth <= docWidth) {
+                done = true;
+            }
+        }
+    }
+
+    // If the transformations shrank the width significantly enough, leave them in place.
+    // We figure that in those cases, the benefits outweight the risk of rendering artifacts.
+    if (!done && (elWidth - newWidth) / (elWidth - docWidth) >
+            TRANSFORM_MINIMUM_EFFECTIVE_RATIO) {
+        console.log("transform(s) deemed effective enough");
+        done = true;
+    }
+
+    if (done) {
+        if (msgId) {
+            transformText += "]";
+            existingText = gTransformText[msgId];
+            if (!existingText) {
+                transformText = "Message transforms: " + transformText;
+            } else {
+                transformText = existingText + " " + transformText;
+            }
+            gTransformText[msgId] = transformText;
+            window.mail.onMessageTransform(msgId, transformText);
+            textElement = el.firstChild;
+            if (!textElement.classList || !textElement.classList.contains("transform-text")) {
+                textElement = document.createElement("div");
+                textElement.classList.add("transform-text");
+                textElement.style.fontSize = "10px";
+                textElement.style.color = "#ccc";
+                el.insertBefore(textElement, el.firstChild);
+            }
+            textElement.innerHTML = transformText + "<br>";
+        }
+        console.log("munger(s) succeeded, elapsed time=" + (Date.now() - start));
+        return;
+    }
+
+    // reverse all changes if the width is STILL not narrow enough
+    // (except the width->maxWidth change, which is not particularly destructive)
+    for (i = 0, len = actionLog.length; i < len; i++) {
+        actionLog[i][0].apply(actionLog[i][1], actionLog[i][2]);
+    }
+    if (actionLog.length > 0) {
+        console.log("all mungers failed, changes reversed. elapsed time=" + (Date.now() - start));
+    }
+}
+
+function addClassToElements(nodes, conditionFn, classToAdd, actionLog) {
+    var i, len;
+    var node;
+    var added = false;
+    for (i = 0, len = nodes.length; i < len; i++) {
+        node = nodes[i];
+        if (!conditionFn || conditionFn(node)) {
+            if (node.classList.contains(classToAdd)) {
+                continue;
+            }
+            node.classList.add(classToAdd);
+            added = true;
+            actionLog.push([node.classList.remove, node.classList, [classToAdd]]);
+        }
+    }
+    return added;
+}
+
+function transformBlockElements(nodes, docWidth, actionLog) {
+    var i, len;
+    var node;
+    var wStr;
+    var index;
+    var touched = false;
+
+    for (i = 0, len = nodes.length; i < len; i++) {
+        node = nodes[i];
+        wStr = node.style.width || node.style.minWidth;
+        index = wStr ? wStr.indexOf("px") : -1;
+        if (index >= 0 && wStr.slice(0, index) > docWidth) {
+            saveStyleProperty(node, "width", actionLog);
+            saveStyleProperty(node, "minWidth", actionLog);
+            saveStyleProperty(node, "maxWidth", actionLog);
+            node.style.width = "100%";
+            node.style.minWidth = "";
+            node.style.maxWidth = wStr;
+            touched = true;
+        }
+    }
+    return touched;
+}
+
+function transformImages(nodes, docWidth, actionLog) {
+    var i, len;
+    var node;
+    var w, h;
+    var touched = false;
+
+    for (i = 0, len = nodes.length; i < len; i++) {
+        node = nodes[i];
+        w = node.offsetWidth;
+        h = node.offsetHeight;
+        // shrink w/h proportionally if the img is wider than available width
+        if (w > docWidth) {
+            saveStyleProperty(node, "maxWidth", actionLog);
+            saveStyleProperty(node, "width", actionLog);
+            saveStyleProperty(node, "height", actionLog);
+            node.style.maxWidth = docWidth + "px";
+            node.style.width = "100%";
+            node.style.height = "auto";
+            touched = true;
+        }
+    }
+    return touched;
+}
+
+function saveStyleProperty(node, property, actionLog) {
+    var savedName = "data-" + property;
+    node.setAttribute(savedName, node.style[property]);
+    actionLog.push([undoSetProperty, node, [property, savedName]]);
+}
+
+function undoSetProperty(property, savedProperty) {
+    this.style[property] = savedProperty ? this.getAttribute(savedProperty) : "";
+}
+
+function shouldMungeTable(table) {
+    return table.hasAttribute("width") || table.style.width;
+}
+
 function hideAllUnsafeImages() {
     hideUnsafeImages(document.getElementsByClassName("mail-message-content"));
 }
diff --git a/res/drawable-hdpi/ic_menu_search_holo_light.png b/res/drawable-hdpi/ic_menu_search_holo_light.png
index dae6979..1cb61fa 100644
--- a/res/drawable-hdpi/ic_menu_search_holo_light.png
+++ b/res/drawable-hdpi/ic_menu_search_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/stat_notify_email.png b/res/drawable-hdpi/stat_notify_email.png
index 276d429..d40e2fe 100644
--- a/res/drawable-hdpi/stat_notify_email.png
+++ b/res/drawable-hdpi/stat_notify_email.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_search_holo_light.png b/res/drawable-mdpi/ic_menu_search_holo_light.png
index 2769fd2..2369d03 100644
--- a/res/drawable-mdpi/ic_menu_search_holo_light.png
+++ b/res/drawable-mdpi/ic_menu_search_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/stat_notify_email.png b/res/drawable-mdpi/stat_notify_email.png
deleted file mode 100644
index f808685..0000000
--- a/res/drawable-mdpi/stat_notify_email.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/stat_notify_email.png b/res/drawable-sw600dp-hdpi/stat_notify_email.png
deleted file mode 100644
index b40df8e..0000000
--- a/res/drawable-sw600dp-hdpi/stat_notify_email.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/stat_notify_email.png b/res/drawable-sw600dp-mdpi/stat_notify_email.png
deleted file mode 100644
index 1c0ecfa..0000000
--- a/res/drawable-sw600dp-mdpi/stat_notify_email.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/stat_notify_email.png b/res/drawable-sw600dp-xhdpi/stat_notify_email.png
deleted file mode 100644
index 09fa242..0000000
--- a/res/drawable-sw600dp-xhdpi/stat_notify_email.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_search_holo_light.png b/res/drawable-xhdpi/ic_menu_search_holo_light.png
index d285976..578cb24 100644
--- a/res/drawable-xhdpi/ic_menu_search_holo_light.png
+++ b/res/drawable-xhdpi/ic_menu_search_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/stat_notify_email.png b/res/drawable-xhdpi/stat_notify_email.png
index 8b6d94a..0317760 100644
--- a/res/drawable-xhdpi/stat_notify_email.png
+++ b/res/drawable-xhdpi/stat_notify_email.png
Binary files differ
diff --git a/res/layout-sw600dp/account_switch_spinner_item.xml b/res/layout-sw600dp/account_switch_spinner_item.xml
deleted file mode 100644
index 6247095..0000000
--- a/res/layout-sw600dp/account_switch_spinner_item.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.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.
--->
-
-<!-- View shown in the navigation spinner in the actionbar.  -->
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="match_parent"
-    android:paddingLeft="0dip"
-    android:layout_marginLeft="0dip"
-    android:layout_width="match_parent">
-
-    <LinearLayout
-        android:layout_height="match_parent"
-        android:id="@+id/account_spinner_container"
-        android:orientation="vertical"
-        android:gravity="center_vertical"
-        android:background="@drawable/spinner_ab_holo_light"
-        android:duplicateParentState="false"
-        android:layout_alignParentLeft="true"
-        style="@style/AccountSpinnerStyle">
-        <TextView
-            android:id="@+id/account_first"
-            style="@style/AccountSpinnerAnchorTextPrimary"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:includeFontPadding="false"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-        <TextView
-            android:id="@+id/account_second"
-            style="@style/AccountSpinnerAnchorTextSecondary"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:includeFontPadding="false"
-            android:layout_marginRight="4dp"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-    </LinearLayout>
-
-    <TextView
-        android:id="@+id/account_unread"
-        android:layout_toRightOf="@id/account_spinner_container"
-        style="@style/unreadCountActionBarTablet"/>
-</RelativeLayout>
diff --git a/res/layout/account_switch_spinner_item.xml b/res/layout/account_switch_spinner_item.xml
deleted file mode 100644
index 0ab8b62..0000000
--- a/res/layout/account_switch_spinner_item.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.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.
--->
-
-<!-- View shown in the navigation spinner in the actionbar.  -->
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="match_parent"
-    android:paddingLeft="0dip"
-    android:layout_marginLeft="0dip"
-    android:layout_width="match_parent">
-
-    <!-- Place the unread count first, taking all the space it needs
-         to fit its content -->
-    <TextView
-        android:id="@+id/account_unread"
-        android:layout_alignParentRight="true"
-        android:layout_width="wrap_content"
-        android:layout_marginLeft="4dp"
-        style="@style/UnreadCountActionBar"/>
-
-    <!-- Container to soak up space and ensure that the caret attaches
-         to a short label name. This container should be anonymous because nothing
-         should reference it.
-         Phone only: On tablets, the width of the spinner is a constant for each
-         orientation. -->
-    <LinearLayout
-        android:layout_height="match_parent"
-        android:orientation="horizontal"
-        android:layout_width="fill_parent"
-        android:gravity="center_vertical"
-        android:clickable="false"
-        android:focusable="false"
-        android:layout_toLeftOf="@id/account_unread">
-
-        <!-- In the container, the label name takes up as much space
-             as it needs to show its content: this is to ensure that the
-             dropdown caret is flush with the label name. -->
-        <LinearLayout
-            android:layout_height="match_parent"
-            android:id="@+id/account_spinner_container"
-            android:orientation="vertical"
-            android:gravity="center_vertical"
-            android:background="@drawable/spinner_ab_holo_light"
-            style="@style/AccountSpinnerStyle">
-            <TextView
-                android:id="@+id/account_first"
-                style="@style/AccountSpinnerAnchorTextPrimary"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:includeFontPadding="false"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-            <TextView
-                android:id="@+id/account_second"
-                style="@style/AccountSpinnerAnchorTextSecondary"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:includeFontPadding="false"
-                android:layout_marginRight="4dp"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-        </LinearLayout>
-    </LinearLayout>
-</RelativeLayout>
diff --git a/res/layout/actionbar_view.xml b/res/layout/actionbar_view.xml
index bcad65b..7d4b792 100644
--- a/res/layout/actionbar_view.xml
+++ b/res/layout/actionbar_view.xml
@@ -21,18 +21,10 @@
     label, and subject).
 -->
 <com.android.mail.ui.MailActionBarView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
+    android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:orientation="horizontal" >
-
-    <com.android.mail.ui.MailSpinner
-        android:id="@+id/account_spinner"
-        android:layout_height="match_parent"
-        style="@style/AccountSwitchSpinnerItem"
-        android:focusableInTouchMode="false"
-        android:focusable="false"
-        android:clickable="true"/>
-
+    <!-- Only used for displaying a subject view -->
     <include layout="@layout/actionbar_subject" />
 
 </com.android.mail.ui.MailActionBarView>
diff --git a/res/layout/child_folder_item.xml b/res/layout/child_folder_item.xml
index 70793e4..b8dc6c6 100644
--- a/res/layout/child_folder_item.xml
+++ b/res/layout/child_folder_item.xml
@@ -34,7 +34,7 @@
         android:src="@drawable/folder_parent_icon" />
 
     <ImageView
-        android:id="@+id/folder_box"
+        android:id="@+id/color_block"
         style="@style/FolderItemIcon"
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true" />
@@ -48,19 +48,37 @@
         android:layout_toLeftOf="@id/folder_parent_icon"
         android:textColor="@color/folder_name_color_primary_invertible" />
 
+    <TextView
+        android:id="@+id/unseen"
+        style="@style/UnseenCount"
+        android:layout_marginRight="@dimen/folder_list_item_right_margin"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_alignParentRight="true"
+        android:layout_toLeftOf="@id/folder_parent_icon" />
+
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_centerVertical="true"
         android:layout_toLeftOf="@id/unread"
+        android:layout_alignWithParentIfMissing="true"
         android:layout_marginLeft="@dimen/folder_list_item_left_margin"
         android:layout_marginTop="@dimen/folder_swatch_height"
         android:layout_marginBottom="@dimen/folder_swatch_height">
 
+        <ImageView
+            android:id="@+id/folder_icon"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_centerVertical="true"
+            android:layout_marginRight="10dp"
+            android:visibility="gone" />
+
         <TextView
             android:id="@+id/name"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
+            android:layout_toRightOf="@id/folder_icon"
             android:maxLines="2"
             android:ellipsize="end"
             android:textColor="@color/folder_name_color_primary_invertible"
@@ -71,6 +89,7 @@
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:layout_below="@id/name"
+            android:layout_toRightOf="@id/folder_icon"
             android:textColor="@color/folder_name_color_primary_invertible"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:visibility="gone" />
diff --git a/res/layout/folder_expand_item.xml b/res/layout/folder_expand_item.xml
new file mode 100644
index 0000000..5041582
--- /dev/null
+++ b/res/layout/folder_expand_item.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2013 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.
+-->
+
+<!-- This is a button for expanding/collapsing emails in FLF  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/folder_expand_item"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical">
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/message_details_header_bottom_border_height"
+        android:visibility="visible"
+        android:background="@color/conv_subject_border" />
+
+    <TextView
+        android:id="@+id/folder_expand_text"
+        android:layout_width="match_parent"
+        android:layout_height="48dip"
+        android:layout_marginLeft="@dimen/folder_list_item_left_margin"
+        android:gravity="center_vertical"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAllCaps="true"
+        android:textColor="@color/folder_list_heading_text_color"/>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/message_details_header_bottom_border_height"
+        android:visibility="visible"
+        android:background="@color/conv_subject_border" />
+
+    <ImageView
+        android:layout_gravity="top|right"
+        android:layout_marginTop="14dip"
+        android:layout_marginRight="14dip"
+        android:id="@+id/details_expander"
+        style="@style/MessageHeaderExpanderMinimizedStyle" />
+</FrameLayout>
diff --git a/res/layout/folder_item.xml b/res/layout/folder_item.xml
index 2426a77..c68e442 100644
--- a/res/layout/folder_item.xml
+++ b/res/layout/folder_item.xml
@@ -47,6 +47,14 @@
         android:layout_toLeftOf="@id/folder_parent_icon"
         android:textColor="@color/folder_name_color_primary_invertible" />
 
+    <TextView
+        android:id="@+id/unseen"
+        style="@style/UnseenCount"
+        android:layout_marginRight="@dimen/folder_list_item_right_margin"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_alignParentRight="true"
+        android:layout_toLeftOf="@id/folder_parent_icon" />
+
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -57,10 +65,19 @@
         android:layout_marginTop="@dimen/folder_swatch_height"
         android:layout_marginBottom="@dimen/folder_swatch_height">
 
+        <ImageView
+            android:id="@+id/folder_icon"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_centerVertical="true"
+            android:layout_marginRight="10dp"
+            android:visibility="gone" />
+
         <TextView
             android:id="@+id/name"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
+            android:layout_toRightOf="@id/folder_icon"
             android:maxLines="2"
             android:ellipsize="end"
             android:textColor="@color/folder_name_color_primary_invertible"
@@ -71,6 +88,7 @@
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:layout_below="@id/name"
+            android:layout_toRightOf="@id/folder_icon"
             android:textColor="@color/folder_name_color_primary_invertible"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:visibility="gone" />
diff --git a/res/layout/one_pane_activity.xml b/res/layout/one_pane_activity.xml
index 07974b7..eb5b3c6 100644
--- a/res/layout/one_pane_activity.xml
+++ b/res/layout/one_pane_activity.xml
@@ -15,20 +15,34 @@
      limitations under the License.
 -->
 
-<com.android.mail.ui.OnePaneRoot xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/one_pane_root"
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/drawer_container"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent">
+
+    <com.android.mail.ui.OnePaneRoot
+        android:id="@+id/one_pane_root"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+
+        <FrameLayout
+            android:id="@+id/content_pane"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+        <include layout="@layout/conversation_pager" />
+
+        <com.android.mail.ui.ActionableToastBar
+            android:id="@+id/toast_bar"
+            style="@style/ToastBarStyle" />
+
+    </com.android.mail.ui.OnePaneRoot>
 
     <FrameLayout
-        android:id="@+id/content_pane"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:id="@+id/drawer_pullout"
+        android:layout_width="300dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        style="@style/FolderListStyle"/>
 
-    <include layout="@layout/conversation_pager" />
-
-    <com.android.mail.ui.ActionableToastBar
-        android:id="@+id/toast_bar"
-        style="@style/ToastBarStyle" />
-
-</com.android.mail.ui.OnePaneRoot>
\ No newline at end of file
+</android.support.v4.widget.DrawerLayout>
diff --git a/res/layout/search_actionbar_view.xml b/res/layout/search_actionbar_view.xml
index 93d8de8..613d8c4 100644
--- a/res/layout/search_actionbar_view.xml
+++ b/res/layout/search_actionbar_view.xml
@@ -26,15 +26,6 @@
     android:layout_height="match_parent"
     android:orientation="horizontal" >
 
-    <com.android.mail.ui.MailSpinner
-        android:id="@+id/account_spinner"
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        style="@style/PlainSpinnerDropdown"
-        android:focusableInTouchMode="false"
-        android:focusable="false"
-        android:clickable="true"/>
-
     <include layout="@layout/actionbar_subject" />
 
 </com.android.mail.ui.SearchMailActionBarView>
\ No newline at end of file
diff --git a/res/layout/secure_conversation_view.xml b/res/layout/secure_conversation_view.xml
index 56679f2..da1dae3 100644
--- a/res/layout/secure_conversation_view.xml
+++ b/res/layout/secure_conversation_view.xml
@@ -26,21 +26,16 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:orientation="vertical">
-            <RelativeLayout android:id="@+id/header"
+            <include layout="@layout/conversation_view_header"
+                android:id="@+id/conv_header"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <include layout="@layout/conversation_message_header"
+                android:id="@+id/message_header"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:background="@android:color/white">
-                <include layout="@layout/conversation_view_header"
-                    android:id="@+id/conv_header"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-
-                <include layout="@layout/conversation_message_header"
-                    android:id="@+id/message_header"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_below="@id/conv_header" />
-            </RelativeLayout>
+                android:layout_below="@id/conv_header" />
             <!-- base WebView layer -->
             <WebView
                 android:id="@+id/webview"
@@ -54,12 +49,6 @@
                 android:visibility="gone" />
         </LinearLayout>
     </ScrollView>
-    <FrameLayout
-            android:id="@+id/conversation_topmost_overlay"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-            <!-- TODO: scroll indicators go HERE on top of all other layers -->
-    </FrameLayout>
 
     <include layout="@layout/conversation_load_spinner"/>
 
diff --git a/res/menu-sw600dp-port/conversation_actions.xml b/res/menu-sw600dp-port/conversation_actions.xml
index 0762dae..170551a 100644
--- a/res/menu-sw600dp-port/conversation_actions.xml
+++ b/res/menu-sw600dp-port/conversation_actions.xml
@@ -62,6 +62,10 @@
         android:icon="@drawable/ic_menu_mark_unread_holo_light" />
 
     <item
+        android:id="@+id/move_to"
+        android:title="@string/menu_move_to" />
+
+    <item
         android:id="@+id/mark_important"
         android:showAsAction="never"
         android:title="@string/mark_important" />
diff --git a/res/menu-sw600dp-port/conversation_list_search_results_actions.xml b/res/menu-sw600dp-port/conversation_list_search_results_actions.xml
index 357ba1b..ba722e6 100644
--- a/res/menu-sw600dp-port/conversation_list_search_results_actions.xml
+++ b/res/menu-sw600dp-port/conversation_list_search_results_actions.xml
@@ -35,11 +35,6 @@
         android:icon="@drawable/ic_menu_refresh_holo_light"
         android:alphabeticShortcut="@string/trigger_refresh_char" />
 
-    <!-- Available for Folders with SUPPORTS_SETTINGS capability -->
-    <item android:id="@+id/folder_options"
-        android:title="@string/menu_folder_options"
-        android:showAsAction="never"  />
-
     <item android:id="@+id/settings"
         android:title="@string/menu_settings"
         android:showAsAction="never" />
diff --git a/res/menu-sw600dp-port/conversation_search_results_actions.xml b/res/menu-sw600dp-port/conversation_search_results_actions.xml
index ca13500..e228059 100644
--- a/res/menu-sw600dp-port/conversation_search_results_actions.xml
+++ b/res/menu-sw600dp-port/conversation_search_results_actions.xml
@@ -51,11 +51,6 @@
         android:title="@string/report_spam"
         android:showAsAction="never"/>
 
-    <!-- Available for Folders with SUPPORTS_SETTINGS capability -->
-    <item android:id="@+id/folder_options"
-        android:title="@string/menu_folder_options"
-        android:showAsAction="never"  />
-
     <item android:id="@+id/settings"
         android:title="@string/menu_settings"
         android:showAsAction="never" />
@@ -72,4 +67,4 @@
         android:icon="@android:drawable/ic_menu_help"
         android:title="@string/help_and_info" />
 
-</menu>
\ No newline at end of file
+</menu>
diff --git a/res/menu-sw600dp/conversation_actions.xml b/res/menu-sw600dp/conversation_actions.xml
index 48359cc..fce09e0 100644
--- a/res/menu-sw600dp/conversation_actions.xml
+++ b/res/menu-sw600dp/conversation_actions.xml
@@ -75,6 +75,10 @@
         android:icon="@drawable/ic_menu_mark_unread_holo_light" />
 
     <item
+        android:id="@+id/move_to"
+        android:title="@string/menu_move_to" />
+
+    <item
         android:id="@+id/mark_important"
         android:showAsAction="never"
         android:title="@string/mark_important" />
diff --git a/res/menu-sw600dp/conversation_list_selection_actions_menu.xml b/res/menu-sw600dp/conversation_list_selection_actions_menu.xml
index 64b1b8c..2563f54 100644
--- a/res/menu-sw600dp/conversation_list_selection_actions_menu.xml
+++ b/res/menu-sw600dp/conversation_list_selection_actions_menu.xml
@@ -80,6 +80,11 @@
         android:icon="@drawable/ic_menu_star_off_holo_light" />
 
     <item
+        android:id="@+id/move_to"
+        android:title="@string/menu_move_to"
+        android:showAsAction="never" />
+
+    <item
         android:id="@+id/mark_important"
         android:title="@string/mark_important"
         android:showAsAction="never"
diff --git a/res/menu/conversation_actions.xml b/res/menu/conversation_actions.xml
index 7a24d8d..91dda4e 100644
--- a/res/menu/conversation_actions.xml
+++ b/res/menu/conversation_actions.xml
@@ -64,6 +64,11 @@
 
     <!-- Always available -->
     <item
+        android:id="@+id/move_to"
+        android:title="@string/menu_move_to" />
+
+    <!-- Always available -->
+    <item
         android:id="@+id/mark_important"
         android:title="@string/mark_important"
         android:icon="@drawable/ic_email_caret_double" />
diff --git a/res/menu/conversation_list_menu.xml b/res/menu/conversation_list_menu.xml
index 589097a..76ae716 100644
--- a/res/menu/conversation_list_menu.xml
+++ b/res/menu/conversation_list_menu.xml
@@ -34,7 +34,7 @@
     <!-- Always available -->
     <item android:id="@+id/show_all_folders"
         android:title="@string/show_all_folders"
-        android:showAsAction="ifRoom"
+        android:showAsAction="never"
         android:icon="@drawable/ic_menu_folders_holo_light" />
 
     <!-- Always available -->
diff --git a/res/menu/conversation_list_selection_actions_menu.xml b/res/menu/conversation_list_selection_actions_menu.xml
index 2b1c149..c08cf92 100644
--- a/res/menu/conversation_list_selection_actions_menu.xml
+++ b/res/menu/conversation_list_selection_actions_menu.xml
@@ -80,6 +80,11 @@
         android:icon="@drawable/ic_menu_star_off_holo_light" />
 
     <item
+        android:id="@+id/move_to"
+        android:title="@string/menu_move_to"
+        android:showAsAction="never" />
+
+    <item
         android:id="@+id/mark_important"
         android:title="@string/mark_important"
         android:showAsAction="never"
diff --git a/res/menu/email_copy_context_menu.xml b/res/menu/email_copy_context_menu.xml
new file mode 100644
index 0000000..d40be51
--- /dev/null
+++ b/res/menu/email_copy_context_menu.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2013 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/mail_context_menu_id"
+        android:title="@string/contextmenu_send_mail"/>
+    <item android:id="@+id/copy_mail_context_menu_id"
+        android:title="@string/contextmenu_copy"/>
+</menu>
+
diff --git a/res/menu/message_header_overflow_menu.xml b/res/menu/message_header_overflow_menu.xml
index e3e9f0b..035eca2 100644
--- a/res/menu/message_header_overflow_menu.xml
+++ b/res/menu/message_header_overflow_menu.xml
@@ -26,4 +26,10 @@
     <item android:id="@+id/forward"
           android:icon="@drawable/ic_forward_holo_dark"
           android:title="@string/forward" />
+    <item android:id="@+id/report_rendering_problem"
+          android:icon="@drawable/ic_forward_holo_dark"
+          android:title="@string/report_rendering_problem" />
+    <item android:id="@+id/report_rendering_improvement"
+          android:icon="@drawable/ic_forward_holo_dark"
+          android:title="@string/report_rendering_improvement" />
 </menu>
diff --git a/res/raw/template_conversation_lower.html b/res/raw/template_conversation_lower.html
index 6cc1934..b128077 100644
--- a/res/raw/template_conversation_lower.html
+++ b/res/raw/template_conversation_lower.html
@@ -9,6 +9,8 @@
   var WIDE_VIEWPORT_WIDTH = %s;
   var ENABLE_CONTENT_READY = %s;
   var NORMALIZE_MESSAGE_WIDTHS = %s;
+  var ENABLE_MUNGE_TABLES = %s;
+  var ENABLE_MUNGE_IMAGES = %s;
 </script>
 <script type="text/javascript" src="file:///android_asset/script.js"></script>
 </html>
diff --git a/res/raw/template_conversation_upper.html b/res/raw/template_conversation_upper.html
index df844f8..6222762 100644
--- a/res/raw/template_conversation_upper.html
+++ b/res/raw/template_conversation_upper.html
@@ -21,6 +21,19 @@
     body {
         font-size: 80%%;
     }
+    blockquote {
+        margin-left: 0.8ex !important;
+        margin-right: 0 !important;
+        border-left:1px #ccc solid !important;
+        padding-left: 1ex !important;
+    }
+    table.munged {
+        width: auto !important;
+    }
+    td.munged {
+        width: auto !important;
+        white-space: normal !important;
+    }
     .initial-load {
         /* 0x0 and 1x1 may be short-circuited by WebView */
         width: 2px;
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 44d74bb..e2e55c0 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Stuur aan"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Skryf"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Verander vouers"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Skuif na"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Vouerinstellings"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Vouers"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sinkroniseer en stel in kennis"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Vouerinstellings"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Rekeninginstellings"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Het vouer verander."</item>
     <item quantity="other" msgid="8918589141287976985">"Het vouers verander."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultate"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Search word nie ondersteun op hierdie rekening nie."</string>
     <string name="searchMode" msgid="3329807422114758583">"Soekmodus"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> nuwe boodskappe"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Stil"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NUUT"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 6fc5026..5b832d4 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"አስተላልፍ"</string>
     <string name="menu_compose" msgid="6274193058224230645">"አዲስ ጻፍ"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"አቃፊዎችን ቀይር"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"አንቀሳቅስ ወደ"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"የአቃፊ ቅንብሮች"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"አቃፊዎች"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"አመሳስልና አሳውቅ"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"የአቃፊ ቅንብሮች"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"የመለያ ቅንብሮች"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"የተቀየረ አቃፊ። የተለወጠ አቃፊ።"</item>
     <item quantity="other" msgid="8918589141287976985">"የተቀየሩ አቃፊዎች።"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"ውጤቶች"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"ፍለጋ በዚህ መለያ አይደገፍም።"</string>
     <string name="searchMode" msgid="3329807422114758583">"የፍለጋ ሁናቴ"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> አዲስ መልዕክቶች"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"ፀጥታ"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> አዲስ"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index da73a7a..cd7f37c 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"إعادة توجيه"</string>
     <string name="menu_compose" msgid="6274193058224230645">"إنشاء"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"تغيير المجلدات"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"نقل إلى"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"إعدادات المجلدات"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"المجلدات"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"المزامنة والإشعارات"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"إعدادات المجلد"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"إعدادات الحساب"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"تم تغيير المجلد."</item>
     <item quantity="other" msgid="8918589141287976985">"تم تغيير المجلدات."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"النتائج"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"لا يمكن استخدام البحث على هذا الحساب."</string>
     <string name="searchMode" msgid="3329807422114758583">"وضع البحث"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> من الرسائل الجديدة"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"صامت"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> جديدة"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 79128ca..d119d6c 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Пераслаць"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Напісаць"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Змена тэчкі"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Перанесці ў"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Налады тэчкi"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Тэчкі"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Сінхранізацыя і апавяшчэнне"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Налады тэчак"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Налады ўліковага запісу"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Змененая тэчка."</item>
     <item quantity="other" msgid="8918589141287976985">"Змененыя тэчкі."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Вынікі"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"У гэтым уліковым запісе пошук не падтрымліваецца."</string>
     <string name="searchMode" msgid="3329807422114758583">"Рэжым пошуку"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Новых паведамленняў: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Ціхі рэжым"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"НОВЫХ: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 4f88f4f..f35f63d 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Препращане"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Ново съобщение"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Промяна на папките"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Преместване във:"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Настройки за папките"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Папки"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Синхронизиране и известяване"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Настройки за папката"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Настройки на профила"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Променихте папката."</item>
     <item quantity="other" msgid="8918589141287976985">"Променихте папките."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Резултати"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Търсенето не се поддържа за този профил."</string>
     <string name="searchMode" msgid="3329807422114758583">"Режим на търсене"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> нови съобщения"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Тих режим"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> НОВИ"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index c6c6211..8b30f07 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Reenvia"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Redacta"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Canvia les carpetes"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Mou a"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Configuració de la carpeta"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Carpetes"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sincronitza i notifica"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Configuració de la carpeta"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Configuració del compte"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"S\'ha canviat la carpeta."</item>
     <item quantity="other" msgid="8918589141287976985">"S\'han canviat les carpetes."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultats"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"La cerca no és compatible en aquest compte."</string>
     <string name="searchMode" msgid="3329807422114758583">"Mode de cerca"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> missatges nous"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silenci"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"NOUS: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 592bb38..487aaad 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Přeposlat"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Nová zpráva"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Změnit složky"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Přesunout do"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Nastavení složek"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Složky"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synchronizace a upozornění"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Nastavení složek"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Nastavení účtu"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Složka změněna."</item>
     <item quantity="other" msgid="8918589141287976985">"Složky změněny."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Výsledky"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Vyhledávání není v tomto účtu podporováno."</string>
     <string name="searchMode" msgid="3329807422114758583">"Režim vyhledávání"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Počet nových zpráv: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Tichý režim"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"NOVÉ: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 7966bdd..ae7c73a 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Videresend"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Skriv"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Skift mapper"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Flyt til"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Indstillinger for mapper"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mapper"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synkroniser og underret"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Indstillinger for mapper"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Kontoindstillinger"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Ændret mappe."</item>
     <item quantity="other" msgid="8918589141287976985">"Ændrede mapper."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultater"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Søgningen understøttes ikke på denne konto."</string>
     <string name="searchMode" msgid="3329807422114758583">"Søgetilstand"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> nye beskeder"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Lydløs"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NYE"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index f3d15b1..2ca30c3 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Weiterleiten"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Schreiben"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Ordner ändern"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Verschieben nach"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Ordnereinstellungen"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Ordner"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synchron. &amp;  benachrichtigen"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Ordnereinstellungen"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Kontoeinstellungen"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Ordner geändert"</item>
     <item quantity="other" msgid="8918589141287976985">"Ordner geändert"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Ergebnisse"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Die Suche wird für dieses Konto nicht unterstützt."</string>
     <string name="searchMode" msgid="3329807422114758583">"Suchmodus"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> neue Nachrichten"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Lautlos"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> neu"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index d0a6118..59f1748 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Προώθηση"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Σύνταξη"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Αλλαγή φακέλων"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Μετακίνηση σε"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Ρυθμίσεις φακέλων"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Φάκελοι"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Συγχρονισμός και ειδοποίηση"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Ρυθμίσεις φακέλου..."</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Ρυθμίσεις Λογαριασμού"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Αλλαγή φακέλου."</item>
     <item quantity="other" msgid="8918589141287976985">"Αλλαγή φακέλων."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Αποτελέσματα"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Η αναζήτηση δεν υποστηρίζεται σε αυτόν τον λογαριασμό."</string>
     <string name="searchMode" msgid="3329807422114758583">"Λειτουργία αναζήτησης"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> νέα μηνύματα"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Σίγαση"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> ΝΕΑ"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 261c001..b3e54f6 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Forward"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Compose"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Change folders"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Move to"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Folder settings"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Folders"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sync &amp; notify"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Folder settings"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Account settings"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Changed folder."</item>
     <item quantity="other" msgid="8918589141287976985">"Changed folders."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Results"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Search is not supported on this account."</string>
     <string name="searchMode" msgid="3329807422114758583">"Search Mode"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> new messages"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silent"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NEW"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 11ffc7d..3a845c4 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Reenviar"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Redactar"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Cambiar carpetas"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Mover a"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Configuración de carpetas"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Carpetas"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sincronizar y notificar"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Configuración de la carpeta"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Configuración de la cuenta"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Carpeta modificada"</item>
     <item quantity="other" msgid="8918589141287976985">"Carpetas modificadas"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultados"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Esta cuenta no admite la función de búsqueda."</string>
     <string name="searchMode" msgid="3329807422114758583">"Buscar modo"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> mensajes nuevos"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silencio"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NUEVOS"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 05d640e..f6f692c 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Reenviar"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Redactar"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Cambiar carpetas"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Mover a"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Ajustes de carpeta"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Carpetas"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sincronizar y notificar"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Ajustes de carpeta"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Ajustes de cuenta"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Carpeta cambiada"</item>
     <item quantity="other" msgid="8918589141287976985">"Carpetas cambiadas"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultados"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Esta cuenta no admite la función de búsqueda."</string>
     <string name="searchMode" msgid="3329807422114758583">"Modo de búsqueda"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> mensajes nuevos"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silencio"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NUEVOS"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 21c6445..857e31f 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Edasta"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Koosta"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Muuda kaustu"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Teisalda asukohta"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Kausta seaded"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Kaustad"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sünkroonimine ja teavitamine"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Kausta seaded"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Konto seaded"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Muudetud kaust."</item>
     <item quantity="other" msgid="8918589141287976985">"Muudetud kaustad."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Tulemused"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Sellel kontol ei toetata otsingut."</string>
     <string name="searchMode" msgid="3329807422114758583">"Otsimisrežiim"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> uut sõnumit"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Hääletu"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"UUSI: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 98aea75..444a3c4 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"باز ارسال"</string>
     <string name="menu_compose" msgid="6274193058224230645">"نوشتن نامه"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"تغییر پوشه‌ها"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"انتقال به"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"تنظیمات پوشه"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"پوشه‌ها"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"همگام‌سازی و اعلان"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"تنظیمات پوشه"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"تنظیمات حساب"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"پوشه تغییر کرد."</item>
     <item quantity="other" msgid="8918589141287976985">"پوشه‌ها تغییر کردند."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"نتایج"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"جستجو در این حساب پشتیبانی نمی‌شود."</string>
     <string name="searchMode" msgid="3329807422114758583">"حالت جستجو"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> پیام جدید"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"بیصدا"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> پیام جدید"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index f956ba2..994a196 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Lähetä edelleen"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Viestin kirjoitus"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Vaihda kansiota"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Siirrä kansioon"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Kansion asetukset"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Kansiot"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synkronoi ja ilmoita"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Kansion asetukset..."</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Tilin asetukset"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Kansiota muutettu."</item>
     <item quantity="other" msgid="8918589141287976985">"Kansioita muutettu."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Tulokset"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Hakua ei voi käyttää tällä tilillä."</string>
     <string name="searchMode" msgid="3329807422114758583">"Hakutapa"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> uutta viestiä"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Äänetön"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> UUTTA"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f2a2836..f1a7e0f 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Transférer"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Nouveau message"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Modifier les dossiers"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Déplacer vers"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Paramètres des dossiers"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Dossiers"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synchroniser et signaler"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Paramètres du dossier"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Paramètres de compte"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Dossier modifié."</item>
     <item quantity="other" msgid="8918589141287976985">"Dossiers modifiés."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Résultats"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"La fonctionnalité de recherche n\'est pas compatible avec ce compte."</string>
     <string name="searchMode" msgid="3329807422114758583">"Mode Recherche"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g> : <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> nouveaux messages"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g> : <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Mode silencieux"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NOUVEAUX"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index d0fda64..b1f8f3d 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"अग्रेषित करें"</string>
     <string name="menu_compose" msgid="6274193058224230645">"लिखें"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"फ़ोल्डर बदलें"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"इसमें ले जाएं"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"फ़ोल्डर सेटिंग"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"फ़ोल्डर"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"समन्वयित करें और सूचित करें"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"फ़ोल्डर सेटिंग"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"खाता सेटिंग"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"फ़ोल्‍डर बदला गया."</item>
     <item quantity="other" msgid="8918589141287976985">"फ़ोल्डर बदले गए."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"परिणाम"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"इस खाते पर खोज समर्थित नहीं है."</string>
     <string name="searchMode" msgid="3329807422114758583">"खोज मोड"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> नए संदेश"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"मौन"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> नए"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index d02a35f..ea7f309 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Dalje"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Nova poruka"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Promjena mapa"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Premjesti u/na"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Postavke mapa"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mape"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sinkroniziraj i obavijesti"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Postavke mape"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Postavke računa"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Promijenjena mapa."</item>
     <item quantity="other" msgid="8918589141287976985">"Promijenjene mape."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Rezultati"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Pretraživanje nije podržano na ovom računu."</string>
     <string name="searchMode" msgid="3329807422114758583">"Način pretraživanja"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Novih poruka: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Bešumno"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"NOVE PORUKE (<xliff:g id="NUMBER">%d</xliff:g>)"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index b6f519c..e1f12a9 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Továbbítás"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Levélírás"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Mappaváltás"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Áthelyezés ide:"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Mappabeállítások"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mappák"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Szinkronizálás és értesítés"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Mappabeállítások"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Fiókbeállítások"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"A mappa módosult."</item>
     <item quantity="other" msgid="8918589141287976985">"A mappák módosultak."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Találatok"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"A keresés nem támogatott ebben a fiókban."</string>
     <string name="searchMode" msgid="3329807422114758583">"Keresési mód"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> új üzenet"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Néma"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> ÚJ"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index dd5ba36..50d0352 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Teruskan"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Tulis"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Ganti folder"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Pindahkan ke"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Setelan folder"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Folder"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sinkronkan &amp; beri tahu"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Setelan folder"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Pengaturan akun"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Folder diubah."</item>
     <item quantity="other" msgid="8918589141287976985">"Folder diubah."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Hasil"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Penelusuran tidak didukung pada akun ini."</string>
     <string name="searchMode" msgid="3329807422114758583">"Mode Penelusuran"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> pesan baru"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Tidak Berbunyi"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> BARU"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8122943..2e17b51 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Inoltra"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Scrivi"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Cambia cartelle"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Sposta in"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Impostazioni cartella"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Cartelle"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sincronizza e notifica"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Impostazioni cartella"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Impostazioni account"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Cartella cambiata."</item>
     <item quantity="other" msgid="8918589141287976985">"Cartelle cambiate."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Risultati"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"La ricerca non è supportata per l\'account in uso."</string>
     <string name="searchMode" msgid="3329807422114758583">"Modalità di ricerca"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> nuovi messaggi"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silenzioso"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NUOVI"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7ff0529..dd9dd84 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"העבר"</string>
     <string name="menu_compose" msgid="6274193058224230645">"כתוב"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"שנה תיקיות"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"העבר אל"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"הגדרות תיקיה"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"תיקיות"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"סנכרן ושלח התראה"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"הגדרות תיקיה"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"הגדרות חשבון"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"התיקיה שונתה."</item>
     <item quantity="other" msgid="8918589141287976985">"התיקיות שונו."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"תוצאות"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"חיפוש אינו נתמך בחשבון זה."</string>
     <string name="searchMode" msgid="3329807422114758583">"מצב חיפוש"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>‏: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> הודעות חדשות"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>‏: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"שקט"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> חדשות"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index bd4a14b..6bb7191 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"転送"</string>
     <string name="menu_compose" msgid="6274193058224230645">"作成"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"フォルダを変更"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"移動"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"フォルダの設定"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"フォルダ"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"同期と通知"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"フォルダの設定"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"アカウント設定"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"フォルダを変更しました。"</item>
     <item quantity="other" msgid="8918589141287976985">"フォルダを変更しました。"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"検索結果"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"このアカウントでは検索をご利用いただけません。"</string>
     <string name="searchMode" msgid="3329807422114758583">"検索モード"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g>件の新着メッセージ"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"マナーモード"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g>件の新着"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index cbb9812..842dbb1 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"전달"</string>
     <string name="menu_compose" msgid="6274193058224230645">"편지쓰기"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"폴더 변경"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"이동"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"폴더 설정"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"폴더"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"동기화 및 알림"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"폴더 설정"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"계정 설정"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"폴더가 변경되었습니다."</item>
     <item quantity="other" msgid="8918589141287976985">"폴더가 변경되었습니다."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"검색결과"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"이 계정에서는 검색이 지원되지 않습니다."</string>
     <string name="searchMode" msgid="3329807422114758583">"검색 모드"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"새 메시지 <xliff:g id="COUNT">%1$d</xliff:g>개"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"무음"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g>개의 새 이메일"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 5240e9a..23944b8 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Persiųsti"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Sukurti"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Keisti aplankus"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Perkelti į"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Aplankų nustatymai"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Aplankai"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sinchronizuoti ir pranešti"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Aplankų nustatymai"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Paskyros nustatymai"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Aplankas pakeistas."</item>
     <item quantity="other" msgid="8918589141287976985">"Aplankai pakeisti."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Rezultatai"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Šioje paskyroje paieška nepalaikoma."</string>
     <string name="searchMode" msgid="3329807422114758583">"Paieškos režimas"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Naujų pranešimų: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Tylus"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"NAUJA: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 343c348..bef8502 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Pārsūtīt"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Rakstīt"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Mainīt mapes"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Pārvietot uz:"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Mapju iestatījumi"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mapes"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sinhronizācija un paziņojumi"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Mapes iestatījumi"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Konta iestatījumi"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Mape ir mainīta."</item>
     <item quantity="other" msgid="8918589141287976985">"Mapes ir mainītas."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Rezultāti"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Šajā kontā netiek atbalstīta meklēšana."</string>
     <string name="searchMode" msgid="3329807422114758583">"Meklēšanas režīms"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> jauni ziņojumi"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Klusums"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"JAUNI (<xliff:g id="NUMBER">%d</xliff:g>)"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 9f0664e..0c364af 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Kirim semula"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Karang"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Tukar folder"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Alih ke"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Tetapan folder"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Folder"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Segerakkan &amp; beritahu"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Tetapan folder"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Tetapan akaun"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Folder yang diubah."</item>
     <item quantity="other" msgid="8918589141287976985">"Folder yang diubah."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Hasil"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Carian tidak disokong pada akaun ini."</string>
     <string name="searchMode" msgid="3329807422114758583">"Cari dalam Mod"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> mesej baharu"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Senyap"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> BAHARU"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 01af04e..656ebd4 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Videresend"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Skriv ny"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Endre mapper"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Flytt til"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Mappeinnstillinger"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mapper"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synkronisering og varsler"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Mappeinnstillinger"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Kontoinnstillinger"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Endret mappe."</item>
     <item quantity="other" msgid="8918589141287976985">"Endret mapper."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultater"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Søk støttes ikke på denne kontoen."</string>
     <string name="searchMode" msgid="3329807422114758583">"Søkemodus"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> nye e-poster"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Stille"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NYE"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 0c64057..cc91bda 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Doorsturen"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Opstellen"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Mappen wijzigen"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Verplaatsen naar"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Mapinstellingen"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mappen"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synchroniseren en melden"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Mapinstellingen"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Accountinstellingen"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Map gewijzigd."</item>
     <item quantity="other" msgid="8918589141287976985">"Mappen gewijzigd."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultaten"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Zoeken wordt niet ondersteund in dit account."</string>
     <string name="searchMode" msgid="3329807422114758583">"Zoekmodus"</string>
@@ -276,7 +283,7 @@
   <string-array name="sync_status">
     <item msgid="2446076619901049026">"Geslaagd"</item>
     <item msgid="7109065688039971961">"Geen verbinding."</item>
-    <item msgid="8437496123716232060">"Kan niet aanmelden."</item>
+    <item msgid="8437496123716232060">"Kan niet inloggen."</item>
     <item msgid="1651266301325684887">"Beveiligingsfout."</item>
     <item msgid="1461520171154288533">"Kan niet synchroniseren."</item>
     <item msgid="4779810016424303449">"Interne fout"</item>
@@ -322,7 +329,7 @@
     <item msgid="6593672292311851204">"Veelgebruikt"</item>
     <item msgid="3584541772344786752">"Alle mappen"</item>
   </string-array>
-    <string name="signin" msgid="8958889809095796177">"Aanmelden"</string>
+    <string name="signin" msgid="8958889809095796177">"Inloggen"</string>
     <string name="info" msgid="6009817562073541204">"Info"</string>
     <string name="report" msgid="5417082746232614958">"Rapporteren"</string>
     <string name="sync_error" msgid="7368819509040597851">"Kan niet synchroniseren."</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> nieuwe berichten"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Stil"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NIEUW"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index eaa4452..45efbaf 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Przekaż dalej"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Utwórz"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Zmień foldery"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Przenieś do"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Ustawienia folderów"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Foldery"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synchronizuj i powiadamiaj"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Ustawienia folderu"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Ustawienia konta"</string>
@@ -218,6 +223,8 @@
     <item quantity="one" msgid="4930161390461457462">"Zmieniono folder."</item>
     <item quantity="other" msgid="8918589141287976985">"Zmieniono foldery."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Wyniki"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Wyszukiwanie nie jest obsługiwane na tym koncie."</string>
     <string name="searchMode" msgid="3329807422114758583">"Tryb wyszukiwania"</string>
@@ -365,4 +372,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Nowe wiadomości: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Cichy"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"NOWE: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 3fdb5f4..b33baa4 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Encaminhar"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Compor"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Alterar pastas"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Mover para"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Definições da pasta"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Pastas"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sincronizar e notificar"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Definições da pasta"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Definições da conta"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Pasta alterada."</item>
     <item quantity="other" msgid="8918589141287976985">"Pastas alteradas."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultados"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"A pesquisa não é suportada nesta conta."</string>
     <string name="searchMode" msgid="3329807422114758583">"Modo de Pesquisa"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> mensagens novas"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silencioso"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NOVAS"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index a3a4c4f..ee01f8d 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Encaminhar"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Escrever"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Alterar pastas"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Mover para"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Configurações de pastas"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Pastas"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sincronizar e notificar"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Configurações da pasta"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Configurações da conta"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Pasta alterada."</item>
     <item quantity="other" msgid="8918589141287976985">"Pastas alteradas."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultados"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Não há suporte para pesquisa nesta conta."</string>
     <string name="searchMode" msgid="3329807422114758583">"Modo de pesquisa"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> novas mensagens"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silencioso"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NOVAS"</string>
 </resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index 981ccbc..a47b6ce 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -123,10 +123,16 @@
     <skip />
     <!-- no translation found for menu_change_folders (1542713666608888717) -->
     <skip />
+    <!-- no translation found for menu_move_to (9138296669516358542) -->
+    <skip />
     <!-- no translation found for menu_manage_folders (6755623004628177492) -->
     <skip />
     <!-- no translation found for folder_list_title (4276644062440415214) -->
     <skip />
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <!-- no translation found for manage_folders_subtitle (7702199674083260433) -->
     <skip />
     <!-- no translation found for menu_folder_options (8897520487430647932) -->
@@ -319,6 +325,8 @@
     <skip />
     <!-- no translation found for conversation_folder_changed:one (4930161390461457462) -->
     <!-- no translation found for conversation_folder_changed:other (8918589141287976985) -->
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <!-- no translation found for search_results_header (4669917471897026269) -->
     <skip />
     <!-- no translation found for search_unsupported (4654227193354052607) -->
@@ -570,4 +578,14 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <!-- no translation found for label_notification_ticker (1684732605316462621) -->
+    <skip />
+    <!-- no translation found for new_messages (4419173946074516420) -->
+    <skip />
+    <!-- no translation found for single_new_message_notification_title (4138237430881084155) -->
+    <skip />
+    <!-- no translation found for silent_ringtone (5856834572357761687) -->
+    <skip />
+    <!-- no translation found for inbox_unseen_banner (4561582938706814621) -->
+    <skip />
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index c609858..f537450 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -63,8 +63,8 @@
     <string name="report_spam" msgid="6467567747975393907">"Raportaţi ca spam"</string>
     <string name="mark_not_spam" msgid="694891665407228160">"Nu este spam"</string>
     <string name="report_phishing" msgid="5714205737453138338">"Raportaţi phishing"</string>
-    <string name="delete" msgid="844871204175957681">"Ştergeţi"</string>
-    <string name="discard_drafts" msgid="6862272443470085375">"Ştergeţi mesajele nefinalizate"</string>
+    <string name="delete" msgid="844871204175957681">"Ștergeţi"</string>
+    <string name="discard_drafts" msgid="6862272443470085375">"Ștergeţi mesajele nefinalizate"</string>
     <string name="next" msgid="4674401197968248302">"Mai vechi"</string>
     <string name="previous" msgid="309943944831349924">"Mai noi"</string>
     <string name="refresh" msgid="490989798005710951">"Actualizaţi"</string>
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Redirecţionaţi"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Scrieţi"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Modificaţi dosarele"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Mutați în"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Setări pentru dosare"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Dosare"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sincronizare şi notificare"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Setări pentru dosar"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Setările contului"</string>
@@ -166,16 +171,16 @@
     <string name="me" msgid="6480762904022198669">"eu"</string>
     <string name="show_all_folders" msgid="3281420732307737553">"Afişaţi toate dosarele"</string>
   <plurals name="confirm_delete_conversation">
-    <item quantity="one" msgid="3731948757247905508">"Ştergeţi această conversaţie?"</item>
-    <item quantity="other" msgid="930334208937121234">"Ştergeţi aceste <xliff:g id="COUNT">%1$d</xliff:g> (de) conversaţii?"</item>
+    <item quantity="one" msgid="3731948757247905508">"Ștergeţi această conversaţie?"</item>
+    <item quantity="other" msgid="930334208937121234">"Ștergeţi aceste <xliff:g id="COUNT">%1$d</xliff:g> (de) conversaţii?"</item>
   </plurals>
   <plurals name="confirm_archive_conversation">
     <item quantity="one" msgid="2990537295519552069">"Arhivaţi această conversaţie?"</item>
     <item quantity="other" msgid="4713469868399246772">"Arhivaţi aceste <xliff:g id="COUNT">%1$d</xliff:g> (de) conversaţii?"</item>
   </plurals>
   <plurals name="confirm_discard_drafts_conversation">
-    <item quantity="one" msgid="5974090449454432874">"Ştergeţi mesajele nefinalizate din conversaţie?"</item>
-    <item quantity="other" msgid="4173815457177336569">"Ştergeţi mesajele nefinalizate din <xliff:g id="COUNT">%1$d</xliff:g> conversaţii?"</item>
+    <item quantity="one" msgid="5974090449454432874">"Ștergeţi mesajele nefinalizate din conversaţie?"</item>
+    <item quantity="other" msgid="4173815457177336569">"Ștergeţi mesajele nefinalizate din <xliff:g id="COUNT">%1$d</xliff:g> conversaţii?"</item>
   </plurals>
     <string name="confirm_discard_text" msgid="1149834186404614612">"Renunţaţi la acest mesaj?"</string>
     <string name="loading_conversations" msgid="2649440958602369555">"Se încarcă…"</string>
@@ -213,13 +218,15 @@
     <item quantity="one" msgid="4398693029405479323">"&lt;b&gt;<xliff:g id="COUNT">%1$d</xliff:g>&lt;/b&gt; a fost ştearsă."</item>
     <item quantity="other" msgid="8630099095360065837">"&lt;b&gt;<xliff:g id="COUNT">%1$d</xliff:g>&lt;/b&gt; au fost şterse."</item>
   </plurals>
-    <string name="deleted" msgid="2757349161107268029">"Ştearsă"</string>
+    <string name="deleted" msgid="2757349161107268029">"Ștearsă"</string>
     <string name="archived" msgid="7533995360704366325">"Arhivată"</string>
     <string name="folder_removed" msgid="1047474677580149436">"Eliminat din <xliff:g id="FOLDERNAME">%1$s</xliff:g>"</string>
   <plurals name="conversation_folder_changed">
     <item quantity="one" msgid="4930161390461457462">"Dosar modificat."</item>
     <item quantity="other" msgid="8918589141287976985">"Dosare modificate."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Rezultate"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Căutarea nu este acceptată pentru acest cont."</string>
     <string name="searchMode" msgid="3329807422114758583">"Modul Căutare"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> (de) mesaje noi"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Silențios"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NOI"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 0f6ce66..47726e7 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Переслать"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Написать"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Изменить папки"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Переместить в"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Настройки папок"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Папки"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Синхронизация и уведомления"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Настройки папки"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Настройки аккаунта"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Папка изменена."</item>
     <item quantity="other" msgid="8918589141287976985">"Папки изменены."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Результаты"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"В этом аккаунте не поддерживается поиск."</string>
     <string name="searchMode" msgid="3329807422114758583">"Режим поиска"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Новых сообщений: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Без звука"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"НОВЫХ: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index b798a24..534430b 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Poslať ďalej"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Napísať správu"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Zmeniť priečinky"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Presunúť do"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Nastavenia priečinka"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Priečinky"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synchronizovať a upozorniť"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Nastavenia priečinka"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Nastavenia účtu"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Zmenený priečinok."</item>
     <item quantity="other" msgid="8918589141287976985">"Zmenené priečinky."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Výsledky"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Hľadanie nie je v tomto účte podporované."</string>
     <string name="searchMode" msgid="3329807422114758583">"Režim vyhľadávania"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Počet nových správ: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Tichý režim"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"NOVÉ: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 0f940dd..d406dc9 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Posreduj"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Novo"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Spremenite mape"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Premakni v"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Nastavitve mape"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mape"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sinhronizacija in obveščanje"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Nastavitve mape"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Nastavitve računa"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Spremenjena mapa."</item>
     <item quantity="other" msgid="8918589141287976985">"Spremenjene mape."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Rezultati"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Iskanje ni podprto za ta račun."</string>
     <string name="searchMode" msgid="3329807422114758583">"Način iskanja"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Št. novih sporočil: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Tiho"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"ŠT. NOVIH: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 5ec567f..77bb3f2 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Проследи"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Нова порука"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Промени директоријуме"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Премести у"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Подешавања директоријума"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Директоријуми"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Синхронизовање и обавештавање"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Подешавања директоријума"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Подешавања налога"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Директоријум је промењен."</item>
     <item quantity="other" msgid="8918589141287976985">"Директоријуми су промењени."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Резултати"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Претрага није подржана на овом налогу."</string>
     <string name="searchMode" msgid="3329807422114758583">"Режим претраге"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Нових порука: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Нечујно"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"НОВИХ: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 1687f8b..021a3e8 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Vidarebefordra"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Skriv"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Byt mapp"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Flytta till"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Mappinställningar"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mappar"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Synka och meddela"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Mappinställningar"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Kontoinställningar"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Mappen har ändrats."</item>
     <item quantity="other" msgid="8918589141287976985">"Mapparna har ändrats."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Resultat"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Det går inte att söka i det här kontot."</string>
     <string name="searchMode" msgid="3329807422114758583">"Sökläge"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> nya meddelanden"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Tyst"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> NYA"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 7c852c0..1ddf455 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Sambaza"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Tunga"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Badilisha folda"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Hamisha hadi"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Mipangilio ya folda"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Folda"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Sawazisha na uarifu"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Mipangilio ya folda"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Mipangilio ya akaunti"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Folda iliyobadilishwa."</item>
     <item quantity="other" msgid="8918589141287976985">"Folda zilizobadilishwa."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Matokeo"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Utafutaji hauauniwi kwenye akaunti hii."</string>
     <string name="searchMode" msgid="3329807422114758583">"Hali ya Utafutaji"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Ujumbe <xliff:g id="COUNT">%1$d</xliff:g> mpya"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Kimya"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> MPYA"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8d7aab0..a638516 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"ส่งต่อ"</string>
     <string name="menu_compose" msgid="6274193058224230645">"เขียน"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"เปลี่ยนโฟลเดอร์"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"ย้ายไปที่"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"การตั้งค่าโฟลเดอร์"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"โฟลเดอร์"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"ซิงค์และแจ้งเตือน"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"การตั้งค่าโฟลเดอร์"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"การตั้งค่าบัญชี"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"เปลี่ยนโฟลเดอร์แล้ว"</item>
     <item quantity="other" msgid="8918589141287976985">"เปลี่ยนโฟลเดอร์แล้ว"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"ผลการค้นหา"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"บัญชีนี้ไม่สนับสนุนการค้นหา"</string>
     <string name="searchMode" msgid="3329807422114758583">"โหมดการค้นหา"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> ข้อความใหม่"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"เงียบ"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> ข้อความใหม่"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index f2ae5ec..5c00f6f 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Ipasa"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Bumuo"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Baguhin ang mga folder"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Ilipat sa"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Mga setting ng folder"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Mga Folder"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"I-sync at i-notify"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Mga setting ng folder"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Mga setting ng account"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Binagong folder."</item>
     <item quantity="other" msgid="8918589141287976985">"Mga binagong folder."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Mga resulta"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Hindi sinusuportahan ang paghahanap sa account na ito."</string>
     <string name="searchMode" msgid="3329807422114758583">"Mode ng Paghahanap"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> (na) bagong mensahe"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Naka-silent"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> (na) BAGO"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index b8a6c79..27233f2 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Yönlendir"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Oluştur"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Klasörleri değiştir"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Taşı"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Klasör ayarları"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Klasörler"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Senkronize et ve bildir"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Klasör ayarları"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Hesap ayarları"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Klasör değişti."</item>
     <item quantity="other" msgid="8918589141287976985">"Klasörler değişti."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Sonuçlar"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Bu hesapta arama desteklenmiyor"</string>
     <string name="searchMode" msgid="3329807422114758583">"Arama Modu"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> yeni ileti"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Sessiz"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> YENİ"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 67cc4e5..7986e2e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Переслати"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Написати"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Змінити папки"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Перемістити в"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Налаштування папки"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Папки"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Синхронізація та сповіщення"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Налаштування папки"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Налаштування облікового запису"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Змінено папку."</item>
     <item quantity="other" msgid="8918589141287976985">"Змінено папки."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Результати"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Функція пошуку в цьому обліковому записі не підтримується."</string>
     <string name="searchMode" msgid="3329807422114758583">"Режим пошуку"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"Нових повідомлень: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Без звуку"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"НОВИХ: <xliff:g id="NUMBER">%d</xliff:g>"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index f8a95e4..6aa6ac7 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Chuyển tiếp"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Soạn thư"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Thay đổi thư mục"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Di chuyển tới"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Cài đặt thư mục"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Thư mục"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Đồng bộ hóa và thông báo"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Cài đặt thư mục"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Cài đặt tài khoản"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Đã thay đổi thư mục."</item>
     <item quantity="other" msgid="8918589141287976985">"Đã thay đổi thư mục."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Kết quả"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Không hỗ trợ tính năng tìm kiếm trên tài khoản này."</string>
     <string name="searchMode" msgid="3329807422114758583">"Chế độ tìm kiếm"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>: <xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> tin nhắn mới"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Im lặng"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> MỚI"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 9711d1e..a3ffadb 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"转发"</string>
     <string name="menu_compose" msgid="6274193058224230645">"写邮件"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"更改文件夹"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"移至"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"文件夹设置"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"文件夹"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"同步与通知"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"文件夹设置"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"帐户设置"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"已更改文件夹。"</item>
     <item quantity="other" msgid="8918589141287976985">"已更改文件夹。"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"结果"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"此帐户不支持搜索。"</string>
     <string name="searchMode" msgid="3329807422114758583">"搜索模式"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>:<xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g>封新邮件"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>:<xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"静音"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g>封新邮件"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 765be73..ee35ca4 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"轉寄"</string>
     <string name="menu_compose" msgid="6274193058224230645">"撰寫"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"變更資料夾"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"移至"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"資料夾設定"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"資料夾"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"同步處理並通知"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"資料夾設定"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"帳戶設定"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"已變更資料夾。"</item>
     <item quantity="other" msgid="8918589141287976985">"已變更資料夾。"</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"搜尋結果"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"這個帳戶不支援搜尋功能。"</string>
     <string name="searchMode" msgid="3329807422114758583">"搜尋模式"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g>:<xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> 封新郵件"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>:<xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"靜音"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> 封新郵件"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 044a4eb..ed52d66 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -74,8 +74,13 @@
     <string name="forward" msgid="6822914459902983767">"Dlulisela"</string>
     <string name="menu_compose" msgid="6274193058224230645">"Bhala"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"Shintsha amafolda"</string>
+    <string name="menu_move_to" msgid="9138296669516358542">"Hambisa ku-"</string>
     <string name="menu_manage_folders" msgid="6755623004628177492">"Izilungiselelo zefolda"</string>
     <string name="folder_list_title" msgid="4276644062440415214">"Amafolda"</string>
+    <!-- no translation found for folder_list_more (537172187223133825) -->
+    <skip />
+    <!-- no translation found for folder_list_show_all_accounts (8054807182336991835) -->
+    <skip />
     <string name="manage_folders_subtitle" msgid="7702199674083260433">"Ukuvumelanisa  &amp; ukuqaphelisa"</string>
     <string name="menu_folder_options" msgid="8897520487430647932">"Izilungiselelo zefolda"</string>
     <string name="menu_account_settings" msgid="8230989362863431918">"Izilungiselelo ze-akhawunti"</string>
@@ -220,6 +225,8 @@
     <item quantity="one" msgid="4930161390461457462">"Ifolda eshintshiwe."</item>
     <item quantity="other" msgid="8918589141287976985">"Amafolda ashintshiwe."</item>
   </plurals>
+    <!-- no translation found for conversation_folder_moved (297469098857964678) -->
+    <skip />
     <string name="search_results_header" msgid="4669917471897026269">"Imiphumela"</string>
     <string name="search_unsupported" msgid="4654227193354052607">"Usesho alusekelwe kule akhawunti."</string>
     <string name="searchMode" msgid="3329807422114758583">"Imodi yokusesha"</string>
@@ -367,4 +374,9 @@
     <string name="veiled_alternate_text" msgid="7370933826442034211"></string>
     <string name="veiled_alternate_text_unknown_person" msgid="5132515097905273819"></string>
     <string name="veiled_summary_unknown_person" msgid="4030928895738205054"></string>
+    <string name="label_notification_ticker" msgid="1684732605316462621">"<xliff:g id="LABEL">%s</xliff:g><xliff:g id="NOTIFICATION">%s</xliff:g>"</string>
+    <string name="new_messages" msgid="4419173946074516420">"<xliff:g id="COUNT">%1$d</xliff:g> imilayezo emisha"</string>
+    <string name="single_new_message_notification_title" msgid="4138237430881084155">"<xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="SUBJECT">%2$s</xliff:g>"</string>
+    <string name="silent_ringtone" msgid="5856834572357761687">"Thulile"</string>
+    <string name="inbox_unseen_banner" msgid="4561582938706814621">"<xliff:g id="NUMBER">%d</xliff:g> OKUSHA"</string>
 </resources>
diff --git a/res/values/accountprovider.xml b/res/values/accountprovider.xml
new file mode 100644
index 0000000..742a7e5
--- /dev/null
+++ b/res/values/accountprovider.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2013 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.
+-->
+<resources>
+    <!-- List of content provider uris for  -->
+    <string-array name="account_providers" translatable="false">
+        <!-- mock account list -->
+        <item>content://com.android.mail.mockprovider/accounts</item>
+    </string-array>
+
+</resources>
\ No newline at end of file
diff --git a/res/values/constants.xml b/res/values/constants.xml
index 5cd820f..84bfa4f 100644
--- a/res/values/constants.xml
+++ b/res/values/constants.xml
@@ -114,4 +114,5 @@
     <!-- Whether to show the priority indicator inline with the senders in conversation list view -->
     <bool name="inline_personal_level">true</bool>
 
+    <integer name="swipe_senders_length">25</integer>
 </resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 84042ce..0039aab 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -21,4 +21,5 @@
     <item type="id" name="reply_state" />
     <item type="id" name="manage_folders_item"/>
     <item type="id" name="contact_image" />
+    <item type="id" name="move_folder" />
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c939677..dee3df6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -67,7 +67,7 @@
         <item>Forward</item>
     </string-array>
         <!-- Formatting string for the subject when it contains a reply or forward identifier. Do not translate.-->
-    <string name="formatted_subject"><xliff:g id="prefix">%1$s</xliff:g> <xliff:g id="subject">%2$s</xliff:g></string>
+    <string name="formatted_subject" translatable="false"><xliff:g id="prefix">%1$s</xliff:g> <xliff:g id="subject">%2$s</xliff:g></string>
     <!-- Compose screen, prefixed to the subject of a message when replying to it (if not already present). Do not translate. -->
     <string name="reply_subject_label" translatable="false">Re:</string>
     <!-- Compose screen, Prefix to forwarded message subject. Do not translate. -->
@@ -149,10 +149,28 @@
     <string name="menu_compose">Compose</string>
     <!-- Menu item: change the folders for this conversation. -->
     <string name="menu_change_folders">Change folders</string>
+    <!-- Menu item: moves to folders for selected conversation(s). [CHAR LIMIT = 30] -->
+    <string name="menu_move_to">Move to</string>
     <!-- Menu item: manages the folders for this account. [CHAR LIMIT = 30] -->
     <string name="menu_manage_folders">Folder settings</string>
+    <!-- Menu item: report an email was not readable or poorly rendered -->
+    <string name="report_rendering_problem" translatable="false">Looks bad</string>
+    <!-- Menu item: report an email's readability has improved -->
+    <string name="report_rendering_improvement" translatable="false">Looks good</string>
+    <!-- Temporary text used for reporting rendering issues Googlers see in testing -->
+    <string name="report_rendering_problem_desc" translatable="false">
+        This message looks bad.
+    </string>
+    <!-- Temporary text used for reporting rendering improvements Googlers see in testing -->
+    <string name="report_rendering_improvement_desc" translatable="false">
+        This message looks good.
+    </string>
     <!-- Title for the Folder list screen. [CHAR LIMIT = 30] -->
     <string name="folder_list_title">Folders</string>
+    <!-- Folder list item: show more folders. [CHAR LIMIT = 30] -->
+    <string name="folder_list_more">More</string>
+    <!-- Folder list item: show all accounts. [CHAR LIMIT = 30] -->
+    <string name="folder_list_show_all_accounts">More accounts</string>
     <!-- action bar sub title for manage folder mode. [CHAR LIMIT = 30] -->
     <string name="manage_folders_subtitle">Sync &amp; notify</string>
     <!-- Menu item: options for this folder. When source text cannot be translated within the char limit, please translate the shorter "Folder options" instead. [CHAR LIMIT = 30] -->
@@ -438,6 +456,9 @@
         <item quantity="other">Changed folders.</item>
     </plurals>
 
+    <!-- Displayed after moving a conversation to a different folder. [CHAR LIMIT=100] -->
+    <string name="conversation_folder_moved">Moved to <xliff:g id="folderName">%1$s</xliff:g></string>
+
     <!-- Search Results: Text for header that is shown above search results [CHAR LIMIT=30] -->
     <string name="search_results_header">Results</string>
     <!-- Toast shown when the user taps the search hard key when viewing an account that does not support search [CHAR LIMIT=100] -->
@@ -787,11 +808,26 @@
     <string name="notification_action_preference_summary_not_set">Not set</string>
 
     <!-- Regex that specifies veiled addresses. These are all empty because this is disabled currently. -->
-    <string name="veiled_address"></string>
+    <string name="veiled_address"/>
     <!-- String to be shown instead of a veiled addresses. [CHAR LIMIT=50] -->
-    <string name="veiled_alternate_text"></string>
+    <string name="veiled_alternate_text"/>
     <!-- String to be shown instead of a veiled addresses. [CHAR LIMIT=50] -->
-    <string name="veiled_alternate_text_unknown_person"></string>
+    <string name="veiled_alternate_text_unknown_person"/>
     <!-- Summary string to be shown instead of a veiled recipient. [CHAR LIMIT=50] -->
-    <string name="veiled_summary_unknown_person"></string>
+    <string name="veiled_summary_unknown_person"/>
+
+    <!-- Notification ticker text for per-label notification [CHAR LIMIT=30]-->
+    <string name="label_notification_ticker">"<xliff:g id="label">%s</xliff:g>: <xliff:g id="notification">%s</xliff:g>"</string>
+
+    <!-- Notification message to the user upon new messages for a conversation. [CHAR LIMIT=120] -->
+    <string name="new_messages"><xliff:g id="count">%1$d</xliff:g> new messages</string>
+
+    <!-- Format string used when displaying the title of a notification that was triggered by a single new conversation. [CHAR LIMIT=120] -->
+    <string name="single_new_message_notification_title"><xliff:g id="sender">%1$s</xliff:g>: <xliff:g id="subject">%2$s</xliff:g></string>
+
+    <!-- Settings screen, what to display for Ringtone when the user chooses "silent" [CHAR LIMIT=100]-->
+    <string name="silent_ringtone">Silent</string>
+
+    <!-- Label list screen, banner for number of unseen messages [CHAR LIMIT=15]  -->
+    <string name="inbox_unseen_banner"><xliff:g id="number" example="7">%d</xliff:g> NEW</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ab7e47a..054d3be 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -179,6 +179,23 @@
         <item name="android:gravity">center_vertical|right</item>
     </style>
 
+    <style name="UnseenCount">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:textSize">13sp</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:paddingLeft">8dp</item>
+        <item name="android:paddingRight">10dp</item>
+        <item name="android:paddingTop">4dp</item>
+        <item name="android:paddingBottom">4dp</item>
+    </style>
+
     <!-- No change in the default case. -->
     <style name="AccountSpinnerAnchorTextPrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
     </style>
diff --git a/src/com/android/mail/EmailAddress.java b/src/com/android/mail/EmailAddress.java
new file mode 100644
index 0000000..1218da9
--- /dev/null
+++ b/src/com/android/mail/EmailAddress.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.mail;
+
+import android.text.Html;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class representing a single email address.  Thread-safe, immutable value class suitable for
+ * caching.
+ * TODO(pwestbro): move to provider
+ */
+public class EmailAddress {
+
+    private final String mName;
+
+    private final String mAddress;
+
+    private static final Matcher sEmailMatcher =
+            Pattern.compile("\\\"?([^\"<]*?)\\\"?\\s*<(.*)>").matcher("");
+
+    private EmailAddress(String name, String address) {
+        mName = name;
+        mAddress = address;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * @return either the parsed out e-mail address, or the full raw address if it is not in
+     *     an expected format. This is not guaranteed to be HTML safe.
+     */
+    public String getAddress() {
+        return mAddress;
+    }
+
+    // TODO (pwestbro): move to provider
+    public static synchronized EmailAddress getEmailAddress(String rawAddress) {
+        String name, address;
+        Matcher m = sEmailMatcher.reset(rawAddress);
+        if (m.matches()) {
+            name = m.group(1);
+            address = m.group(2);
+            if (name == null) {
+                name = "";
+            } else {
+                name = Html.fromHtml(name.trim()).toString();
+            }
+            if (address == null) {
+                address = "";
+            } else {
+                address = Html.fromHtml(address).toString();
+            }
+        } else {
+            // Try and tokenize the string
+            final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(rawAddress);
+            if (tokens.length > 0) {
+                final String tokenizedName = tokens[0].getName();
+                name = tokenizedName != null ? Html.fromHtml(tokenizedName.trim()).toString() : "";
+                address = Html.fromHtml(tokens[0].getAddress()).toString();
+            } else {
+                name = "";
+                address = rawAddress == null ? "" : Html.fromHtml(rawAddress).toString();
+            }
+        }
+        return new EmailAddress(name, address);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/mail/MailIntentService.java b/src/com/android/mail/MailIntentService.java
index 08b9175..bd74c32 100644
--- a/src/com/android/mail/MailIntentService.java
+++ b/src/com/android/mail/MailIntentService.java
@@ -18,20 +18,32 @@
 import com.android.mail.utils.StorageLowState;
 
 import android.app.IntentService;
+import android.content.Context;
 import android.content.Intent;
 
+import com.android.mail.providers.Account;
+import com.android.mail.providers.Folder;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.NotificationUtils;
+
 /**
  * A service to handle various intents asynchronously.
  */
 public class MailIntentService extends IntentService {
+    private static final String LOG_TAG = LogTag.getLogTag();
+
     public static final String ACTION_RESEND_NOTIFICATIONS =
             "com.android.mail.action.RESEND_NOTIFICATIONS";
     public static final String ACTION_CLEAR_NEW_MAIL_NOTIFICATIONS =
             "com.android.mail.action.CLEAR_NEW_MAIL_NOTIFICATIONS";
     public static final String ACTION_MARK_SEEN = "com.android.mail.action.MARK_SEEN";
+    public static final String ACTION_BACKUP_DATA_CHANGED =
+            "com.android.mail.action.BACKUP_DATA_CHANGED";
 
     public static final String ACCOUNT_EXTRA = "account";
     public static final String FOLDER_EXTRA = "folder";
+    public static final String CONVERSATION_EXTRA = "conversation";
 
     public MailIntentService() {
         super("MailIntentService");
@@ -43,13 +55,42 @@
 
     @Override
     protected void onHandleIntent(final Intent intent) {
-        // The storage_low state is recorded centrally even though no handler might be present to
-        // change application state based on state changes.
+        // UnifiedEmail does not handle all Intents
+
+        LogUtils.v(LOG_TAG, "Handling intent %s", intent);
+
         final String action = intent.getAction();
-        if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+
+        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+            handleLocaleChanged();
+        } else if (ACTION_CLEAR_NEW_MAIL_NOTIFICATIONS.equals(action)) {
+            final Account account = intent.getParcelableExtra(ACCOUNT_EXTRA);
+            final Folder folder = intent.getParcelableExtra(FOLDER_EXTRA);
+
+            NotificationUtils.clearFolderNotification(this, account, folder);
+        } else if (ACTION_RESEND_NOTIFICATIONS.equals(action)) {
+            NotificationUtils.resendNotifications(this, false);
+        } else if (ACTION_MARK_SEEN.equals(action)) {
+            final Folder folder = intent.getParcelableExtra(FOLDER_EXTRA);
+
+            NotificationUtils.markSeen(this, folder);
+        } else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+            // The storage_low state is recorded centrally even though
+            // no handler might be present to change application state
+            // based on state changes.
             StorageLowState.setIsStorageLow(true);
         } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
             StorageLowState.setIsStorageLow(false);
         }
     }
+
+    public static void broadcastBackupDataChanged(final Context context) {
+        final Intent intent = new Intent(ACTION_BACKUP_DATA_CHANGED);
+        context.startService(intent);
+    }
+
+    private void handleLocaleChanged() {
+        // Cancel all notifications. The correct ones will be recreated when the app starts back up
+        NotificationUtils.cancelAndResendNotifications(this);
+    }
 }
diff --git a/src/com/android/mail/MailLogService.java b/src/com/android/mail/MailLogService.java
new file mode 100644
index 0000000..bd0cfbf
--- /dev/null
+++ b/src/com/android/mail/MailLogService.java
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ *      Copyright (C) 2013 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.
+ *******************************************************************************/
+
+package com.android.mail;
+
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+
+/**
+ * A write-only device for sensitive logs. Turned on only during debugging.
+ *
+ * Dump valuable system state by sending a local broadcast to the associated activity.
+ * Broadcast receivers are responsible for dumping state as they see fit.
+ * This service is only started when the log level is high, so there is no risk of user
+ * data being logged by mistake.
+ *
+ * To add logging to this service, call {@link #log(String, String, Object...)} with a tag name,
+ * which is a class name, like "AbstractActivityController", which is a unique ID. Then, add to the
+ * resulting buffer any information of interest at logging time. This is kept in a ring buffer,
+ * which is overwritten with new information.
+ */
+public class MailLogService extends Service {
+    /**
+     * This is the top level flag that enables this service.
+     * STOPSHIP: Turn to false before a release.
+     */
+    public static boolean DEBUG_ENABLED = true;
+
+    /** The tag which needs to be turned to DEBUG to get logging going. */
+    protected static final String LOG_TAG = LogTag.getLogTag();
+
+    /**
+     * A circular buffer of {@value #SIZE} lines.  To  insert into this buffer,
+     * call the {@link #put(String)} method.  To retrieve the most recent logs,
+     * call the {@link #toString()} method.
+     */
+    private static class CircularBuffer {
+        // We accept fifty lines of input.
+        public static final int SIZE = 50;
+        /** The actual list of strings to be printed. */
+        final Queue<Pair<Long, String>> mList = new LinkedList<Pair<Long, String>>();
+        /** The current size of the buffer */
+        int mCurrentSize = 0;
+
+        /** Create an empty log buffer. */
+        private CircularBuffer() {
+            // Do nothing
+        }
+
+        /** Get the current timestamp */
+        private static String dateToString(long timestamp) {
+            final Date d = new Date(timestamp);
+            return String.format("%d-%d %d:%d:%d: ", d.getDay(), d.getMonth(), d.getHours(),
+                    d.getMinutes(), d.getSeconds());
+        }
+
+        /**
+         * Insert a log message into the buffer. This might evict the oldest message if the log
+         * is at capacity.
+         * @param message a log message for this buffer.
+         */
+        private synchronized void put(String message) {
+            if (mCurrentSize == SIZE) {
+                // At capacity, we'll remove the head, and add to the tail. Size is unchanged.
+                mList.remove();
+            } else {
+                // Less than capacity. Adding a new element at the end.
+                mCurrentSize++;
+            }
+            // Add the current timestamp along with the message.
+            mList.add(new Pair<Long, String>(System.currentTimeMillis(), message));
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder();
+            for (final Pair<Long, String> s : mList) {
+                // Print the timestamp as an actual date, and then the message.
+                builder.append(dateToString(s.first));
+                builder.append(s.second);
+                // Put a newline at the end of each log line.
+                builder.append("\n");
+            }
+            return builder.toString();
+        }
+    }
+
+    /** Header printed at the start of the dump. */
+    private static final String HEADER = "**** MailLogService ***\n";
+    /** Map of current tag -> log. */
+    private static final Map<String, CircularBuffer> sLogs = new HashMap<String, CircularBuffer>();
+
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    /**
+     * Return the circular buffer associated with this tag, or create a new buffer if none is
+     * currently associated.
+     * @param tag a string to identify a unique tag.
+     * @return a circular buffer associated with a string tag.
+     */
+    private static CircularBuffer getOrCreate(String tag) {
+        if (sLogs.containsKey(tag)) {
+            return sLogs.get(tag);
+        }
+        // Create a new CircularBuffer with this tag
+        final CircularBuffer buffer = new CircularBuffer();
+        sLogs.put(tag, buffer);
+        return buffer;
+    }
+
+    /**
+     * Return true if the logging level is high enough for this service to function.
+     * @return true if this service is functioning at the current log level. False otherwise.
+     */
+    public static boolean isLoggingLevelHighEnough() {
+        return LogUtils.isLoggable(LOG_TAG, Log.DEBUG);
+    }
+
+    /**
+     * Add to the log for the tag given.
+     * @param tag a unique tag to add the message to
+     * @param format a string format for the message
+     * @param args optional list of arguments for the format.
+     */
+    public static void log(String tag, String format, Object... args) {
+        if (!DEBUG_ENABLED || !isLoggingLevelHighEnough()) {
+            return;
+        }
+        // The message we are printing.
+        final String logMessage = String.format(format, args);
+        // Find the circular buffer to go with this tag, or create a new one.
+        getOrCreate(tag).put(logMessage);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (!DEBUG_ENABLED) {
+            return;
+        }
+        writer.print(HEADER);
+        // Go through all the tags, and write them all out sequentially.
+        for (final String tag : sLogs.keySet()) {
+            // Write out a sub-header: Logging for tag "MyModuleName"
+            writer.append("Logging for tag: \"");
+            writer.append(tag);
+            writer.append("\"\n");
+
+            writer.append(sLogs.get(tag).toString());
+        }
+        // Go through all the buffers.
+        super.dump(fd, writer,args);
+    }
+}
diff --git a/src/com/android/mail/adapter/DrawerItem.java b/src/com/android/mail/adapter/DrawerItem.java
new file mode 100644
index 0000000..84b6540
--- /dev/null
+++ b/src/com/android/mail/adapter/DrawerItem.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.mail.adapter;
+
+import com.android.mail.R;
+import com.android.mail.providers.Account;
+import com.android.mail.providers.Folder;
+import com.android.mail.ui.ControllableActivity;
+import com.android.mail.ui.FolderItemView;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/** An account, a system folder, a recent folder, or a header (a resource string) */
+public class DrawerItem {
+
+    private static final String LOG_TAG = LogTag.getLogTag();
+    public int mPosition;
+    public final Folder mFolder;
+    public final Account mAccount;
+    public final int mResource;
+    /** True if expand item view for expanding accounts. False otherwise */
+    public final boolean mIsExpandForAccount;
+    /** Either {@link #VIEW_ACCOUNT}, {@link #VIEW_FOLDER} or {@link #VIEW_HEADER} */
+    public final int mType;
+    /** A normal folder, also a child, if a parent is specified. */
+    public static final int VIEW_FOLDER = 0;
+    /** A text-label which serves as a header in sectioned lists. */
+    public static final int VIEW_HEADER = 1;
+    /** An account object, which allows switching accounts rather than folders. */
+    public static final int VIEW_ACCOUNT = 2;
+    /** An expandable object for expanding/collapsing more of the list */
+    public static final int VIEW_MORE = 3;
+    /** TODO: On adding another type, be sure to change getViewTypes() */
+
+    /** The parent activity */
+    private final ControllableActivity mActivity;
+    private final LayoutInflater mInflater;
+
+    /**
+     * Either {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or {@link #FOLDER_USER} when
+     * {@link #mType} is {@link #VIEW_FOLDER}, or an {@link #ACCOUNT} in the case of
+     * accounts, {@link #EXPAND} for expand blocks, and {@link #INERT_HEADER} otherwise.
+     */
+    public final int mFolderType;
+    /** An unclickable text-header visually separating the different types. */
+    public static final int INERT_HEADER = 0;
+    /** A system-defined folder: Inbox/Drafts, ...*/
+    public static final int FOLDER_SYSTEM = 1;
+    /** A folder from whom a conversation was recently viewed */
+    public static final int FOLDER_RECENT = 2;
+    /** A user created folder */
+    public static final int FOLDER_USER = 3;
+    /** An entry for the accounts the user has on the device. */
+    public static final int ACCOUNT = 4;
+    /** A clickable block to expand list as requested */
+    public static final int EXPAND = 5;
+
+    /** True if this view is enabled, false otherwise. */
+    private boolean isEnabled = false;
+
+    /**
+     * Create a folder item with the given type.
+     * @param folder a folder that this item represents
+     * @param folderType one of {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or
+     * {@link #FOLDER_USER}
+     */
+    public DrawerItem(ControllableActivity activity, Folder folder, int folderType,
+            int cursorPosition) {
+        mActivity = activity;
+        mInflater = LayoutInflater.from(mActivity.getActivityContext());
+        mFolder = folder;
+        mAccount = null;
+        mResource = -1;
+        mType = VIEW_FOLDER;
+        mFolderType = folderType;
+        mPosition = cursorPosition;
+        mIsExpandForAccount = false;
+    }
+
+    /**
+     * Creates an item from an account.
+     * @param account an account that this item represents.
+     */
+    public DrawerItem(ControllableActivity activity, Account account, int count) {
+        mActivity = activity;
+        mInflater = LayoutInflater.from(mActivity.getActivityContext());
+        mFolder = null;
+        mType = VIEW_ACCOUNT;
+        mResource = count;
+        mFolderType = ACCOUNT;
+        mAccount = account;
+        mIsExpandForAccount = false;
+    }
+
+    /**
+     * Create a header item with a string resource.
+     * @param resource the string resource: R.string.all_folders_heading
+     */
+    public DrawerItem(ControllableActivity activity, int resource) {
+        mActivity = activity;
+        mInflater = LayoutInflater.from(mActivity.getActivityContext());
+        mFolder = null;
+        mResource = resource;
+        mType = VIEW_HEADER;
+        mFolderType = INERT_HEADER;
+        mAccount = null;
+        mIsExpandForAccount = false;
+    }
+
+    /**
+     * Creates an item for expanding or contracting for emails/items
+     * @param resource the string resource: R.string.folder_list_*
+     * @param isExpand true if "more" and false if "less"
+     */
+    public DrawerItem(ControllableActivity activity, int resource, boolean isExpandForAccount) {
+        mActivity = activity;
+        mInflater = LayoutInflater.from(mActivity.getActivityContext());
+        mFolder = null;
+        mType = VIEW_MORE;
+        mResource = resource;
+        mFolderType = EXPAND;
+        mAccount = null;
+        mIsExpandForAccount = isExpandForAccount;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final View result;
+        switch (mType) {
+            case VIEW_FOLDER:
+                result = getFolderView(position, convertView, parent);
+                break;
+            case VIEW_HEADER:
+                result = getHeaderView(position, convertView, parent);
+                break;
+            case VIEW_ACCOUNT:
+                result = getAccountView(position, convertView, parent);
+                break;
+            case VIEW_MORE:
+                result = getExpandView(position, convertView, parent);
+                break;
+            default:
+                LogUtils.wtf(LOG_TAG, "DrawerItem.getView(%d) for an invalid type!", mType);
+                result = null;
+        }
+        return result;
+    }
+
+    /**
+     * Book-keeping for how many different view types there are. Be sure to
+     * increment this appropriately once adding more types as drawer items
+     * @return number of different types of view items
+     */
+    public static int getViewTypes() {
+        return VIEW_MORE + 1;
+    }
+
+    /**
+     * Returns whether this view is enabled or not.
+     * @return
+     */
+    public boolean isItemEnabled(Uri currentAccountUri) {
+        switch (mType) {
+            case VIEW_HEADER :
+                // Headers are never enabled.
+                return false;
+            case VIEW_FOLDER :
+                // Folders are always enabled.
+                return true;
+            case VIEW_ACCOUNT:
+                // Accounts are only enabled if they are not the current account.
+                return !currentAccountUri.equals(mAccount.uri);
+            case VIEW_MORE:
+                // 'Expand/Collapse' items are always enabled.
+                return true;
+            default:
+                LogUtils.wtf(LOG_TAG, "DrawerItem.isItemEnabled() for invalid type %d", mType);
+                return false;
+        }
+    }
+
+    /**
+     * Returns whether this view is highlighted or not.
+     *
+     * @param currentFolder
+     * @param currentType
+     * @return
+     */
+    public boolean isHighlighted(Folder currentFolder, int currentType){
+        switch (mType) {
+            case VIEW_HEADER :
+                // Headers are never highlighted
+                return false;
+            case VIEW_FOLDER :
+                return (mFolderType == currentType) && mFolder.uri.equals(currentFolder.uri);
+            case VIEW_ACCOUNT:
+                // Accounts are never highlighted
+                return false;
+            case VIEW_MORE:
+                // Expand/Collapse items are never highlighted
+                return false;
+            default:
+                LogUtils.wtf(LOG_TAG, "DrawerItem.isHighlighted() for invalid type %d", mType);
+                return false;
+        }
+    }
+
+    /**
+     * Return a view for an account object.
+     * @param position a zero indexed position in to the list.
+     * @param convertView a view, possibly null, to be recycled.
+     * @param parent the parent viewgroup to attach to.
+     * @return a view to display at this position.
+     */
+    private View getAccountView(int position, View convertView, ViewGroup parent) {
+        // Shoe-horn an account object into a Folder DrawerItem for now.
+        // TODO(viki): Stop this ugly shoe-horning and use a real layout.
+        final FolderItemView folderItemView;
+        if (convertView != null) {
+            folderItemView = (FolderItemView) convertView;
+        } else {
+            folderItemView =
+                    (FolderItemView) mInflater.inflate(R.layout.folder_item, null, false);
+        }
+        // Temporary. Ideally we want a totally different item.
+        folderItemView.bind(mAccount, mActivity, mResource);
+        View v = folderItemView.findViewById(R.id.color_block);
+        v.setBackgroundColor(mAccount.color);
+        v = folderItemView.findViewById(R.id.folder_icon);
+        v.setVisibility(View.GONE);
+        return folderItemView;
+    }
+
+    /**
+     * Returns a text divider between sections.
+     * @param convertView a previous view, perhaps null
+     * @param parent the parent of this view
+     * @return a text header at the given position.
+     */
+    private View getHeaderView(int position, View convertView, ViewGroup parent) {
+        final TextView headerView;
+        if (convertView != null) {
+            headerView = (TextView) convertView;
+        } else {
+            headerView = (TextView) mInflater.inflate(
+                    R.layout.folder_list_header, parent, false);
+        }
+        headerView.setText(mResource);
+        return headerView;
+    }
+
+    /**
+     * Return a folder: either a parent folder or a normal (child or flat)
+     * folder.
+     * @param position a zero indexed position into the top level list.
+     * @param convertView a view, possibly null, to be recycled.
+     * @param parent the parent hosting this view.
+     * @return a view showing a folder at the given position.
+     */
+    private View getFolderView(int position, View convertView, ViewGroup parent) {
+        final FolderItemView folderItemView;
+        if (convertView != null) {
+            folderItemView = (FolderItemView) convertView;
+        } else {
+            folderItemView =
+                    (FolderItemView) mInflater.inflate(R.layout.folder_item, null, false);
+        }
+        folderItemView.bind(mFolder, mActivity);
+        Folder.setFolderBlockColor(mFolder, folderItemView.findViewById(R.id.color_block));
+        Folder.setIcon(mFolder, (ImageView) folderItemView.findViewById(R.id.folder_icon));
+        return folderItemView;
+    }
+
+    /**
+     * Return a view for the 'Expand/Collapse' item.
+     * @param position a zero indexed position into the top level list.
+     * @param convertView a view, possibly null, to be recycled.
+     * @param parent the parent hosting this view.
+     * @return a view showing an item for folder/account expansion at given position.
+     */
+    private View getExpandView(int position, View convertView, ViewGroup parent) {
+        final ViewGroup headerView;
+        if (convertView != null) {
+            headerView = (ViewGroup) convertView;
+        } else {
+            headerView = (ViewGroup) mInflater.inflate(
+                    R.layout.folder_expand_item, parent, false);
+        }
+        TextView direction =
+                (TextView)headerView.findViewById(R.id.folder_expand_text);
+        if(direction != null) {
+            direction.setText(mResource);
+        }
+        return headerView;
+    }
+}
+
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index beab823..b3c9f79 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -35,6 +35,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.support.v4.util.SparseArrayCompat;
 import android.text.TextUtils;
 import android.util.Log;
@@ -54,12 +55,14 @@
 import com.android.mail.utils.Utils;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -71,7 +74,10 @@
  * caching for quick UI response. This is effectively a singleton class, as the cache is
  * implemented as a static HashMap.
  */
-public final class ConversationCursor implements Cursor {
+public final class ConversationCursor implements Cursor, ConversationCursorMarkSeenListener {
+
+    private static final boolean ENABLE_CONVERSATION_PRECACHING = true;
+
     private static final String LOG_TAG = LogTag.getLogTag();
     /** Turn to true for debugging. */
     private static final boolean DEBUG = false;
@@ -133,6 +139,8 @@
     private final String mName;
     /** Column names for this cursor */
     private String[] mColumnNames;
+    // Column names as above, as a Set for quick membership checking
+    private Set<String> mColumnNameSet;
     /** An observer on the underlying cursor (so we can detect changes from outside the UI) */
     private final CursorObserver mCursorObserver;
     /** Whether our observer is currently registered with the underlying cursor */
@@ -162,6 +170,11 @@
             close();
         }
         mColumnNames = cursor.getColumnNames();
+        ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+        for (String name : mColumnNames) {
+            builder.add(name);
+        }
+        mColumnNameSet = builder.build();
         mRefreshRequired = false;
         mRefreshReady = false;
         mRefreshTask = null;
@@ -244,42 +257,87 @@
         return mUnderlyingCursor != null ? mUnderlyingCursor.conversationIds() : null;
     }
 
+    private static class UnderlyingRowData {
+        public final String wrappedUri;
+        public final String innerUri;
+        public final Conversation conversation;
+
+        public UnderlyingRowData(String wrappedUri, String innerUri, Conversation conversation) {
+            this.wrappedUri = wrappedUri;
+            this.innerUri = innerUri;
+            this.conversation = conversation;
+        }
+    }
+
     /**
      * Simple wrapper for a cursor that provides methods for quickly determining
      * the existence of a row.
      */
-    private class UnderlyingCursorWrapper extends CursorWrapper {
+    private static class UnderlyingCursorWrapper extends CursorWrapper {
         // Ideally these two objects could be combined into a Map from
         // conversationId -> position, but the cached values uses the conversation
         // uri as a key.
         private final Map<String, Integer> mConversationUriPositionMap;
         private final Map<Long, Integer> mConversationIdPositionMap;
+        private final List<UnderlyingRowData> mRowCache;
 
         public UnderlyingCursorWrapper(Cursor result) {
             super(result);
+            long start = SystemClock.uptimeMillis();
             final ImmutableMap.Builder<String, Integer> conversationUriPositionMapBuilder =
                     new ImmutableMap.Builder<String, Integer>();
             final ImmutableMap.Builder<Long, Integer> conversationIdPositionMapBuilder =
                     new ImmutableMap.Builder<Long, Integer>();
+            final UnderlyingRowData[] cache;
+            final int count;
             if (result != null && result.moveToFirst()) {
+                count = result.getCount();
+                cache = new UnderlyingRowData[count];
                 // We don't want iterating over this cursor to trigger a network
                 // request
                 final boolean networkWasEnabled =
                         Utils.disableConversationCursorNetworkAccess(result);
+                int i = 0;
                 do {
-                    final int position = result.getPosition();
-                    conversationUriPositionMapBuilder.put(
-                            result.getString(URI_COLUMN_INDEX), position);
-                    conversationIdPositionMapBuilder.put(
-                            result.getLong(UIProvider.CONVERSATION_ID_COLUMN), position);
-                } while (result.moveToNext());
+                    final Conversation c;
+                    final String innerUriString;
+                    final String wrappedUriString;
+                    final long convId;
+
+                    if (ENABLE_CONVERSATION_PRECACHING) {
+                        c = new Conversation(this);
+                        innerUriString = c.uri.toString();
+                        wrappedUriString = uriToCachingUriString(c.uri);
+                        convId = c.id;
+                    } else {
+                        c = null;
+                        innerUriString = result.getString(URI_COLUMN_INDEX);
+                        wrappedUriString = uriToCachingUriString(Uri.parse(innerUriString));
+                        convId = result.getLong(UIProvider.CONVERSATION_ID_COLUMN);
+                    }
+                    conversationUriPositionMapBuilder.put(innerUriString, i);
+                    conversationIdPositionMapBuilder.put(convId, i);
+                    cache[i] = new UnderlyingRowData(
+                            wrappedUriString,
+                            innerUriString,
+                            c);
+
+                } while (result.moveToPosition(++i));
 
                 if (networkWasEnabled) {
                     Utils.enableConversationCursorNetworkAccess(result);
                 }
+            } else {
+                count = 0;
+                cache = new UnderlyingRowData[0];
             }
             mConversationUriPositionMap = conversationUriPositionMapBuilder.build();
             mConversationIdPositionMap = conversationIdPositionMapBuilder.build();
+            mRowCache = Collections.unmodifiableList(Arrays.asList(cache));
+            long end = SystemClock.uptimeMillis();
+            LogUtils.i(LOG_TAG, "*** ConversationCursor pre-loading took" +
+                    " %sms n=%s CONV_PRECACHING=%s",
+                    (end-start), count, ENABLE_CONVERSATION_PRECACHING);
         }
 
         public boolean contains(String uri) {
@@ -299,6 +357,18 @@
             final Integer position = mConversationUriPositionMap.get(conversationUri);
             return position != null ? position.intValue() : -1;
         }
+
+        public String getWrappedUri() {
+            return mRowCache.get(getPosition()).wrappedUri;
+        }
+
+        public String getInnerUri() {
+            return mRowCache.get(getPosition()).innerUri;
+        }
+
+        public Conversation getConversation() {
+            return mRowCache.get(getPosition()).conversation;
+        }
     }
 
     /**
@@ -629,22 +699,7 @@
                     return;
                 }
             }
-            // ContentValues has no generic "put", so we must test.  For now, the only classes
-            // of values implemented are Boolean/Integer/String/Blob, though others are trivially
-            // added
-            if (value instanceof Boolean) {
-                map.put(columnName, ((Boolean) value).booleanValue() ? 1 : 0);
-            } else if (value instanceof Integer) {
-                map.put(columnName, (Integer) value);
-            } else if (value instanceof String) {
-                map.put(columnName, (String) value);
-            } else if (value instanceof byte[]) {
-                map.put(columnName, (byte[])value);
-            } else {
-                final String cname = value.getClass().getName();
-                throw new IllegalArgumentException("Value class not compatible with cache: "
-                        + cname);
-            }
+            putInValues(map, columnName, value);
             map.put(UPDATE_TIME_COLUMN, System.currentTimeMillis());
             if (DEBUG && (columnName != DELETED_COLUMN)) {
                 LogUtils.i(LOG_TAG, "Caching value for %s: %s", uriString, columnName);
@@ -658,7 +713,7 @@
      * @return the cached value for this column, or null if there is none
      */
     private Object getCachedValue(int columnIndex) {
-        String uri = mUnderlyingCursor.getString(URI_COLUMN_INDEX);
+        final String uri = mUnderlyingCursor.getInnerUri();
         return getCachedValue(uri, columnIndex);
     }
 
@@ -1021,8 +1076,7 @@
         // If we're asking for the Uri for the conversation list, we return a forwarding URI
         // so that we can intercept update/delete and handle it ourselves
         if (columnIndex == URI_COLUMN_INDEX) {
-            Uri uri = Uri.parse(mUnderlyingCursor.getString(columnIndex));
-            return uriToCachingUriString(uri);
+            return mUnderlyingCursor.getWrappedUri();
         }
         Object obj = getCachedValue(columnIndex);
         if (obj != null) return (String)obj;
@@ -1036,6 +1090,56 @@
         return mUnderlyingCursor.getBlob(columnIndex);
     }
 
+    public Conversation getConversation() {
+        Conversation c = mUnderlyingCursor.getConversation();
+
+        if (c == null) {
+            // not pre-cached. fall back to just-in-time construction.
+            c = new Conversation(this);
+        } else {
+            // apply any cached values
+            // but skip over any cached values that aren't part of the cursor projection
+            final ContentValues values = mCacheMap.get(mUnderlyingCursor.getInnerUri());
+            if (values != null) {
+                final ContentValues queryableValues = new ContentValues();
+                for (String key : values.keySet()) {
+                    if (!mColumnNameSet.contains(key)) {
+                        continue;
+                    }
+                    putInValues(queryableValues, key, values.get(key));
+                }
+                if (queryableValues.size() > 0) {
+                    // copy-on-write to help ensure the underlying cached Conversation is immutable
+                    // of course, any callers this method should also try not to modify them
+                    // overmuch...
+                    c = new Conversation(c);
+                    c.applyCachedValues(queryableValues);
+                }
+            }
+        }
+
+        return c;
+    }
+
+    private static void putInValues(ContentValues dest, String key, Object value) {
+        // ContentValues has no generic "put", so we must test.  For now, the only classes
+        // of values implemented are Boolean/Integer/String/Blob, though others are trivially
+        // added
+        if (value instanceof Boolean) {
+            dest.put(key, ((Boolean) value).booleanValue() ? 1 : 0);
+        } else if (value instanceof Integer) {
+            dest.put(key, (Integer) value);
+        } else if (value instanceof String) {
+            dest.put(key, (String) value);
+        } else if (value instanceof byte[]) {
+            dest.put(key, (byte[])value);
+        } else {
+            final String cname = value.getClass().getName();
+            throw new IllegalArgumentException("Value class not compatible with cache: "
+                    + cname);
+        }
+    }
+
     /**
      * Observer of changes to underlying data
      */
@@ -1997,4 +2101,12 @@
             });
         }
     }
+
+    /**
+     * Marks all contents of this cursor as seen. This may have no effect with certain providers.
+     */
+    @Override
+    public void markContentsSeen() {
+        ConversationCursorMarkSeenListener.MarkSeenHelper.markContentsSeen(mUnderlyingCursor);
+    }
 }
diff --git a/src/com/android/mail/browse/ConversationCursorMarkSeenListener.java b/src/com/android/mail/browse/ConversationCursorMarkSeenListener.java
new file mode 100644
index 0000000..0956859
--- /dev/null
+++ b/src/com/android/mail/browse/ConversationCursorMarkSeenListener.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ *      Copyright (C) 2013 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.
+ *******************************************************************************/
+package com.android.mail.browse;
+
+import android.database.Cursor;
+import android.database.CursorWrapper;
+
+public interface ConversationCursorMarkSeenListener {
+    /**
+     * Marks all contents of this cursor as seen.
+     */
+    void markContentsSeen();
+
+    public class MarkSeenHelper {
+        /**
+         * Invokes {@link ConversationCursorMarkSeenListener#markContentsSeen(Cursor)} on the
+         * specified {@link Cursor}, recursively calls {@link #markContentsSeen(Cursor)} on a
+         * wrapped cursor, or returns.
+         */
+        public static void markContentsSeen(final Cursor cursor) {
+            if (cursor == null) {
+                return;
+            }
+
+            if (cursor instanceof ConversationCursorMarkSeenListener) {
+                ((ConversationCursorMarkSeenListener) cursor).markContentsSeen();
+            } else if (cursor instanceof CursorWrapper) {
+                markContentsSeen(((CursorWrapper) cursor).getWrappedCursor());
+            }
+        }
+    }
+}
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index b3abaf2..5165610 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -37,6 +37,7 @@
 import android.graphics.Shader;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.text.Layout.Alignment;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -71,7 +72,6 @@
 import com.android.mail.ui.AnimatedAdapter;
 import com.android.mail.ui.ControllableActivity;
 import com.android.mail.ui.ConversationSelectionSet;
-
 import com.android.mail.ui.DividedImageCanvas;
 import com.android.mail.ui.DividedImageCanvas.InvalidateCallback;
 import com.android.mail.ui.EllipsizedMultilineTextView;
@@ -213,8 +213,8 @@
         }
 
         @Override
-        public void loadConversationFolders(Conversation conv, Folder ignoreFolder) {
-            super.loadConversationFolders(conv, ignoreFolder);
+        public void loadConversationFolders(Conversation conv, final Uri ignoreFolderUri) {
+            super.loadConversationFolders(conv, ignoreFolderUri);
 
             mFoldersCount = mFoldersSortedSet.size();
             mHasMoreFolders = mFoldersCount > MAX_DISPLAYED_FOLDERS_COUNT;
@@ -569,7 +569,8 @@
             } else {
                 mHeader.folderDisplayer.reset();
             }
-            mHeader.folderDisplayer.loadConversationFolders(mHeader.conversation, mDisplayedFolder);
+            mHeader.folderDisplayer.loadConversationFolders(mHeader.conversation,
+                    mDisplayedFolder.uri);
         }
 
         if (mSelectedConversationSet != null) {
@@ -587,7 +588,7 @@
         if (mHeader.conversation.conversationInfo != null) {
             Context context = getContext();
             mHeader.messageInfoString = SendersView
-                    .createMessageInfo(context, mHeader.conversation);
+                    .createMessageInfo(context, mHeader.conversation, true);
             int maxChars = ConversationItemViewCoordinates.getSendersLength(context,
                     ConversationItemViewCoordinates.getMode(context, mActivity.getViewMode()),
                     mHeader.conversation.hasAttachments);
@@ -596,11 +597,12 @@
             mHeader.styledSenders = new ArrayList<SpannableString>();
             SendersView.format(context, mHeader.conversation.conversationInfo,
                     mHeader.messageInfoString.toString(), maxChars, mHeader.styledSenders,
-                    mHeader.displayableSenderNames, mHeader.displayableSenderEmails, mAccount);
+                    mHeader.displayableSenderNames, mHeader.displayableSenderEmails, mAccount,
+                    true);
             // If we have displayable sendres, load their thumbnails
             loadSenderImages();
         } else {
-            SendersView.formatSenders(mHeader, getContext());
+            SendersView.formatSenders(mHeader, getContext(), true);
         }
 
         mHeader.dateText = DateUtils.getRelativeTimeSpanString(mContext,
@@ -1217,6 +1219,7 @@
      * Toggle the check mark on this view and update the conversation or begin
      * drag, if drag is enabled.
      */
+    @Override
     public void toggleCheckMarkOrBeginDrag() {
         ViewMode mode = mActivity.getViewMode();
         if (!mTabletDevice || !mode.isListMode()) {
@@ -1264,9 +1267,11 @@
         postInvalidate(mCoordinates.starX, mCoordinates.starY, mCoordinates.starX
                 + starBitmap.getWidth(),
                 mCoordinates.starY + starBitmap.getHeight());
-        ConversationCursor cursor = (ConversationCursor)mAdapter.getCursor();
-        cursor.updateBoolean(mContext, mHeader.conversation, ConversationColumns.STARRED,
-                mHeader.conversation.starred);
+        ConversationCursor cursor = (ConversationCursor) mAdapter.getCursor();
+        if (cursor != null) {
+            cursor.updateBoolean(mContext, mHeader.conversation, ConversationColumns.STARRED,
+                    mHeader.conversation.starred);
+        }
     }
 
     private boolean isTouchInCheckmark(float x, float y) {
diff --git a/src/com/android/mail/browse/ConversationListFooterView.java b/src/com/android/mail/browse/ConversationListFooterView.java
index 0d72a90..f6eb098 100644
--- a/src/com/android/mail/browse/ConversationListFooterView.java
+++ b/src/com/android/mail/browse/ConversationListFooterView.java
@@ -111,6 +111,7 @@
         mErrorStatus = extras.containsKey(UIProvider.CursorExtraKeys.EXTRA_ERROR) ?
                 extras.getInt(UIProvider.CursorExtraKeys.EXTRA_ERROR)
                 : UIProvider.LastSyncResult.SUCCESS;
+        final int totalCount = extras.getInt(UIProvider.CursorExtraKeys.EXTRA_TOTAL_COUNT);
         if (UIProvider.CursorStatus.isWaitingForResults(cursorStatus)) {
             mLoading.setVisibility(View.VISIBLE);
             mNetworkError.setVisibility(View.GONE);
@@ -151,7 +152,7 @@
             }
             mErrorActionButton.setText(actionTextResourceId);
 
-        } else if (mLoadMoreUri != null) {
+        } else if (mLoadMoreUri != null && cursor.getCount() < totalCount) {
             mLoading.setVisibility(View.GONE);
             mNetworkError.setVisibility(View.GONE);
             mLoadMore.setVisibility(View.VISIBLE);
diff --git a/src/com/android/mail/browse/ConversationPagerAdapter.java b/src/com/android/mail/browse/ConversationPagerAdapter.java
index 6ba7886..239d6dc 100644
--- a/src/com/android/mail/browse/ConversationPagerAdapter.java
+++ b/src/com/android/mail/browse/ConversationPagerAdapter.java
@@ -31,6 +31,7 @@
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
+import com.android.mail.providers.FolderObserver;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.ui.AbstractConversationViewFragment;
 import com.android.mail.ui.ActivityController;
@@ -44,7 +45,12 @@
         implements ViewPager.OnPageChangeListener {
 
     private final DataSetObserver mListObserver = new ListObserver();
-    private final DataSetObserver mFolderObserver = new FolderObserver();
+    private final FolderObserver mFolderObserver = new FolderObserver() {
+        @Override
+        public void onChanged(Folder newFolder) {
+            notifyDataSetChanged();
+        }
+    };
     private ActivityController mController;
     private final Bundle mCommonFragmentArgs;
     private final Conversation mInitialConversation;
@@ -208,6 +214,13 @@
     @Override
     public int getCount() {
         if (mStopListeningMode) {
+            if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
+                final Cursor cursor = getCursor();
+                LogUtils.d(LOG_TAG,
+                        "IN CPA.getCount stopListeningMode, returning lastKnownCount=%d."
+                        + " cursor=%s real count=%s", mLastKnownCount, cursor,
+                        (cursor != null) ? cursor.getCount() : "N/A");
+            }
             return mLastKnownCount;
         }
 
@@ -420,7 +433,7 @@
         mController = controller;
         if (mController != null && !mStopListeningMode) {
             mController.registerConversationListObserver(mListObserver);
-            mController.registerFolderObserver(mFolderObserver);
+            mFolderObserver.initialize(mController);
 
             notifyDataSetChanged();
         } else {
@@ -441,13 +454,14 @@
 
         // disable the observer, but save off the current count, in case the Pager asks for it
         // from now until imminent destruction
-        mStopListeningMode = true;
 
         if (mController != null) {
             mController.unregisterConversationListObserver(mListObserver);
-            mController.unregisterFolderObserver(mFolderObserver);
+            mFolderObserver.unregisterAndDestroy();
         }
         mLastKnownCount = getCount();
+        mStopListeningMode = true;
+        LogUtils.d(LOG_TAG, "CPA.stopListening, recording lastKnownCount=%d", mLastKnownCount);
     }
 
     @Override
@@ -476,14 +490,6 @@
         // no-op
     }
 
-    // update the pager title strip as the Folder's conversation count changes
-    private class FolderObserver extends DataSetObserver {
-        @Override
-        public void onChanged() {
-            notifyDataSetChanged();
-        }
-    }
-
     // update the pager dataset as the Controller's cursor changes
     private class ListObserver extends DataSetObserver {
         @Override
diff --git a/src/com/android/mail/browse/ConversationPagerController.java b/src/com/android/mail/browse/ConversationPagerController.java
index 6c2e4ec..21cf6b9 100644
--- a/src/com/android/mail/browse/ConversationPagerController.java
+++ b/src/com/android/mail/browse/ConversationPagerController.java
@@ -37,6 +37,7 @@
 import com.android.mail.ui.SubjectDisplayChanger;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
 
 /**
  * A simple controller for a {@link ViewPager} of conversations.
@@ -128,6 +129,7 @@
         mPagerAdapter.setPager(mPager);
         LogUtils.d(LOG_TAG, "IN CPC.show, adapter=%s", mPagerAdapter);
 
+        Utils.sConvLoadTimer.mark("pager init");
         LogUtils.d(LOG_TAG, "init pager adapter, count=%d initialConv=%s", mPagerAdapter.getCount(),
                 initialConversation);
         mPager.setAdapter(mPagerAdapter);
@@ -140,6 +142,7 @@
                 mPager.setCurrentItem(initialPos);
             }
         }
+        Utils.sConvLoadTimer.mark("pager setAdapter");
 
         mShown = true;
     }
diff --git a/src/com/android/mail/browse/EmailCopyContextMenu.java b/src/com/android/mail/browse/EmailCopyContextMenu.java
new file mode 100644
index 0000000..5441c9f
--- /dev/null
+++ b/src/com/android/mail/browse/EmailCopyContextMenu.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+package com.android.mail.browse;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.view.ContextMenu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.webkit.WebView;
+import android.widget.TextView;
+
+import com.android.mail.R;
+
+/**
+ * <p>
+ * Handles display and behavior for long clicking on expanded messages' headers.
+ * Requires a context to use for inflation and clipboard copying.
+ * </p>
+ * <br>
+ * Dependencies:
+ * <ul>
+ * <li>res/menu/email_copy_context_menu.xml</li>
+ * </ul>
+ */
+public class EmailCopyContextMenu implements OnCreateContextMenuListener{
+
+    // IDs for displaying in the menu
+    private static final int SEND_EMAIL_ITEM = R.id.mail_context_menu_id;
+    private static final int COPY_CONTACT_ITEM = R.id.copy_mail_context_menu_id;
+
+    // Reference to context for layout inflation & copying the email
+    private final Context mContext;
+    private CharSequence mAddress;
+
+    public EmailCopyContextMenu(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Copy class for handling click event on the "Copy" button and storing
+     * copied text.
+     */
+    private class Copy implements MenuItem.OnMenuItemClickListener {
+        private final CharSequence mText;
+
+        public Copy(CharSequence text) {
+            mText = text;
+        }
+
+        @Override
+        public boolean onMenuItemClick(MenuItem item) {
+            copy(mText);
+            //Handled & consumed event
+            return true;
+        }
+    }
+
+    /**
+     * Copy the input text sequence to the system clipboard.
+     * @param text CharSequence to be copied.
+     */
+    private void copy(CharSequence text) {
+        ClipboardManager clipboard =
+                (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+        clipboard.setPrimaryClip(ClipData.newPlainText(null, text));
+    }
+
+    public void setAddress(CharSequence address) {
+        this.mAddress = address;
+    }
+
+    /**
+     * Creates context menu via MenuInflater and populates with items defined
+     * in res/menu/email_copy_context_menu.xml
+     */
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info) {
+        if(!TextUtils.isEmpty(mAddress)) {
+            MenuInflater inflater = new MenuInflater(mContext);
+            inflater.inflate(getMenuResourceId(), menu);
+
+            // Create menu and bind listener/intent
+            menu.setHeaderTitle(mAddress);
+            menu.findItem(SEND_EMAIL_ITEM).setIntent(new Intent(Intent.ACTION_VIEW,
+                    Uri.parse(WebView.SCHEME_MAILTO + mAddress)));
+            menu.findItem(COPY_CONTACT_ITEM).setOnMenuItemClickListener(
+                    new Copy(mAddress));
+        }
+    }
+
+    // Location of Context Menu layout
+    private int getMenuResourceId() {
+        return R.menu.email_copy_context_menu;
+    }
+}
diff --git a/src/com/android/mail/browse/MessageCursor.java b/src/com/android/mail/browse/MessageCursor.java
index 7a78f33..6c6d4ec 100644
--- a/src/com/android/mail/browse/MessageCursor.java
+++ b/src/com/android/mail/browse/MessageCursor.java
@@ -216,8 +216,13 @@
         return mStatus;
     }
 
+    /**
+     * Returns true if the cursor is fully loaded. Returns false if the cursor is expected to get
+     * new messages.
+     * @return
+     */
     public boolean isLoaded() {
-        return getStatus() >= CursorStatus.LOADED || getCount() > 0; // FIXME: remove count hack
+        return !CursorStatus.isWaitingForResults(getStatus());
     }
 
     public String getDebugDump() {
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index b75a9c2..8bd6981 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -16,13 +16,12 @@
 
 package com.android.mail.browse;
 
-import android.app.Dialog;
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.content.AsyncQueryHandler;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.res.Resources;
 import android.database.DataSetObserver;
 import android.graphics.Typeface;
@@ -33,12 +32,11 @@
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
+import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupMenu;
@@ -55,6 +53,7 @@
 import com.android.mail.browse.MessageCursor.ConversationMessage;
 import com.android.mail.compose.ComposeActivity;
 import com.android.mail.perf.Timer;
+import com.android.mail.preferences.MailPrefs;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Address;
 import com.android.mail.providers.Folder;
@@ -64,7 +63,6 @@
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 import com.android.mail.utils.VeiledAddressMatcher;
-
 import com.google.common.annotations.VisibleForTesting;
 
 import java.io.IOException;
@@ -72,7 +70,7 @@
 import java.util.Map;
 
 public class MessageHeaderView extends LinearLayout implements OnClickListener,
-        OnLongClickListener, OnMenuItemClickListener, ConversationContainer.DetachListener {
+        OnMenuItemClickListener, ConversationContainer.DetachListener {
 
     /**
      * Cap very long recipient lists during summary construction for efficiency.
@@ -99,6 +97,10 @@
 
     public static final int POPUP_MODE = 1;
 
+    // This is a debug only feature
+    public static final boolean ENABLE_REPORT_RENDERING_PROBLEM =
+            MailPrefs.SHOW_EXPERIMENTAL_PREFS;
+
     private MessageHeaderViewCallbacks mCallbacks;
 
     private ViewGroup mUpperHeaderView;
@@ -125,6 +127,7 @@
     private View mAttachmentIcon;
     private View mLeftSpacer;
     private View mRightSpacer;
+    private final EmailCopyContextMenu mEmailCopyMenu;
 
     // temporary fields to reference raw data between initial render and details
     // expansion
@@ -226,6 +229,9 @@
         void showExternalResources(Message msg);
 
         void showExternalResources(String senderRawAddress);
+
+        boolean supportsMessageTransforms();
+        String getMessageTransforms(Message msg);
     }
 
     public MessageHeaderView(Context context) {
@@ -239,6 +245,7 @@
     public MessageHeaderView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
+        mEmailCopyMenu = new EmailCopyContextMenu(getContext());
         mInflater = LayoutInflater.from(context);
         mMyName = context.getString(R.string.me);
     }
@@ -282,6 +289,8 @@
 
         registerMessageClickTargets(R.id.reply, R.id.reply_all, R.id.forward, R.id.star,
                 R.id.edit_draft, R.id.overflow, R.id.upper_header);
+
+        mUpperHeaderView.setOnCreateContextMenuListener(mEmailCopyMenu);
     }
 
     private void registerMessageClickTargets(int... ids) {
@@ -289,7 +298,6 @@
             View v = findViewById(id);
             if (v != null) {
                 v.setOnClickListener(this);
-                v.setOnLongClickListener(this);
             }
         }
     }
@@ -458,6 +466,7 @@
 
         mSenderNameView.setText(getHeaderTitle());
         mSenderEmailView.setText(getHeaderSubtitle());
+        setAddressOnContextMenu();
 
         if (mUpperDateView != null) {
             mUpperDateView.setText(mTimestampShort);
@@ -477,6 +486,14 @@
         t.pause(HEADER_RENDER_TAG);
     }
 
+    /**
+     * Update context menu's address field for when the user long presses
+     * on the message header and attempts to copy/send email.
+     */
+    private void setAddressOnContextMenu() {
+        mEmailCopyMenu.setAddress(mSender.getAddress());
+    }
+
     public boolean isBoundTo(ConversationOverlayItem item) {
         return item == mMessageHeaderItem;
     }
@@ -888,11 +905,6 @@
         onClick(v, v.getId());
     }
 
-    @Override
-    public boolean onLongClick(View v) {
-        return onLongClick(v.getId());
-    }
-
     /**
      * Handles clicks on either views or menu items. View parameter can be null
      * for menu item clicks.
@@ -923,6 +935,16 @@
             case R.id.forward:
                 ComposeActivity.forward(getContext(), getAccount(), mMessage);
                 break;
+            case R.id.report_rendering_problem:
+                String text = getContext().getString(R.string.report_rendering_problem_desc);
+                ComposeActivity.reportRenderingFeedback(getContext(), getAccount(), mMessage,
+                    text + "\n\n" + mCallbacks.getMessageTransforms(mMessage));
+                break;
+            case R.id.report_rendering_improvement:
+                text = getContext().getString(R.string.report_rendering_improvement_desc);
+                ComposeActivity.reportRenderingFeedback(getContext(), getAccount(), mMessage,
+                    text + "\n\n" + mCallbacks.getMessageTransforms(mMessage));
+                break;
             case R.id.star: {
                 final boolean newValue = !v.isSelected();
                 v.setSelected(newValue);
@@ -941,8 +963,14 @@
                 }
                 final boolean defaultReplyAll = getAccount().settings.replyBehavior
                         == UIProvider.DefaultReplyBehavior.REPLY_ALL;
-                mPopup.getMenu().findItem(R.id.reply).setVisible(defaultReplyAll);
-                mPopup.getMenu().findItem(R.id.reply_all).setVisible(!defaultReplyAll);
+                final Menu m = mPopup.getMenu();
+                m.findItem(R.id.reply).setVisible(defaultReplyAll);
+                m.findItem(R.id.reply_all).setVisible(!defaultReplyAll);
+
+                final boolean reportRendering = ENABLE_REPORT_RENDERING_PROBLEM
+                    && mCallbacks.supportsMessageTransforms();
+                m.findItem(R.id.report_rendering_improvement).setVisible(reportRendering);
+                m.findItem(R.id.report_rendering_problem).setVisible(reportRendering);
 
                 mPopup.show();
                 break;
@@ -965,30 +993,6 @@
         return handled;
     }
 
-    /**
-     * Handles long click on message upper header to show dialog for copying
-     * the email address. Does not consume the click, otherwise.
-     */
-    private boolean onLongClick(int id) {
-        if (id == R.id.upper_header) {
-            if(isExpanded()) {
-                mCopyAddress = mSender.getAddress();
-                mEmailCopyPopup = new Dialog(getContext());
-                mEmailCopyPopup.setTitle(mCopyAddress);
-                mEmailCopyPopup.setContentView(R.layout.copy_chip_dialog_layout);
-                mEmailCopyPopup.setCancelable(true);
-                mEmailCopyPopup.setCanceledOnTouchOutside(true);
-                Button button =
-                      (Button)mEmailCopyPopup.findViewById(android.R.id.button1);
-                button.setOnClickListener(this);
-                button.setText(R.string.copy_email);
-                mEmailCopyPopup.show();
-                return true;
-            }
-        }
-        return false;
-    }
-
     public void setExpandable(boolean expandable) {
         mExpandable = expandable;
     }
@@ -1056,6 +1060,7 @@
             hideSpamWarning();
             hideShowImagePrompt();
             hideInvite();
+            mUpperHeaderView.setOnCreateContextMenuListener(null);
         } else {
             setMessageDetailsExpanded(mMessageHeaderItem.detailsExpanded);
             if (mMessage.spamWarningString == null) {
@@ -1077,6 +1082,7 @@
             } else {
                 hideInvite();
             }
+            mUpperHeaderView.setOnCreateContextMenuListener(mEmailCopyMenu);
         }
         if (mBottomBorderView != null) {
             mBottomBorderView.setVisibility(vis);
diff --git a/src/com/android/mail/browse/SelectedConversationsActionMenu.java b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
index 5fd1414..43efc86 100644
--- a/src/com/android/mail/browse/SelectedConversationsActionMenu.java
+++ b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
@@ -163,6 +163,8 @@
                     starConversations(false);
                 }
                 break;
+            case R.id.move_to:
+                /* fall through */
             case R.id.change_folder:
                 boolean cantMove = false;
                 Account acct = mAccount;
@@ -187,7 +189,8 @@
                 }
                 if (!cantMove) {
                     final FolderSelectionDialog dialog = FolderSelectionDialog.getInstance(
-                            mContext, acct, mUpdater, mSelectionSet.values(), true, mFolder);
+                            mContext, acct, mUpdater, mSelectionSet.values(), true, mFolder,
+                            item.getItemId() == R.id.move_to);
                     if (dialog != null) {
                         dialog.show();
                     }
@@ -369,10 +372,14 @@
         // 3) If we show neither archive or remove folder, then show a disabled
         // archive icon if the setting for that is true.
         final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
+        final MenuItem moveTo = menu.findItem(R.id.move_to);
         final boolean showRemoveFolder = mFolder != null && mFolder.type == FolderType.DEFAULT
                 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
                 && !mFolder.isProviderFolder();
+        final boolean showMoveTo = mFolder != null
+                && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION);
         removeFolder.setVisible(showRemoveFolder);
+        moveTo.setVisible(showMoveTo);
         if (mFolder != null && showRemoveFolder) {
             removeFolder.setTitle(mActivity.getActivityContext().getString(R.string.remove_folder,
                     mFolder.name));
diff --git a/src/com/android/mail/browse/SendersView.java b/src/com/android/mail/browse/SendersView.java
index b6870a9..4f43326 100644
--- a/src/com/android/mail/browse/SendersView.java
+++ b/src/com/android/mail/browse/SendersView.java
@@ -95,13 +95,14 @@
         return isUnread ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT;
     }
 
-    private static void getSenderResources(Context context) {
-        if (sConfigurationChangedReceiver == null) {
+    private static synchronized void getSenderResources(
+            Context context, final boolean resourceCachingRequired) {
+        if (sConfigurationChangedReceiver == null && resourceCachingRequired) {
             sConfigurationChangedReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
                     sDraftSingularString = null;
-                    getSenderResources(context);
+                    getSenderResources(context, true);
                 }
             };
             context.registerReceiver(sConfigurationChangedReceiver, new IntentFilter(
@@ -127,87 +128,108 @@
         }
     }
 
-    public static SpannableStringBuilder createMessageInfo(Context context, Conversation conv) {
-        ConversationInfo conversationInfo = conv.conversationInfo;
-        int sendingStatus = conv.sendingState;
+    public static SpannableStringBuilder createMessageInfo(Context context, Conversation conv,
+            final boolean resourceCachingRequired) {
         SpannableStringBuilder messageInfo = new SpannableStringBuilder();
-        boolean hasSenders = false;
-        // This covers the case where the sender is "me" and this is a draft
-        // message, which means this will only run once most of the time.
-        for (MessageInfo m : conversationInfo.messageInfos) {
-            if (!TextUtils.isEmpty(m.sender)) {
-                hasSenders = true;
-                break;
+
+        try {
+            ConversationInfo conversationInfo = conv.conversationInfo;
+            int sendingStatus = conv.sendingState;
+            boolean hasSenders = false;
+            // This covers the case where the sender is "me" and this is a draft
+            // message, which means this will only run once most of the time.
+            for (MessageInfo m : conversationInfo.messageInfos) {
+                if (!TextUtils.isEmpty(m.sender)) {
+                    hasSenders = true;
+                    break;
+                }
+            }
+            getSenderResources(context, resourceCachingRequired);
+            if (conversationInfo != null) {
+                int count = conversationInfo.messageCount;
+                int draftCount = conversationInfo.draftCount;
+                boolean showSending = sendingStatus == UIProvider.ConversationSendingState.SENDING;
+                if (count > 1) {
+                    messageInfo.append(count + "");
+                }
+                messageInfo.setSpan(CharacterStyle.wrap(
+                        conv.read ? sMessageInfoReadStyleSpan : sMessageInfoUnreadStyleSpan),
+                        0, messageInfo.length(), 0);
+                if (draftCount > 0) {
+                    // If we are showing a message count or any draft text and there
+                    // is at least 1 sender, prepend the sending state text with a
+                    // comma.
+                    if (hasSenders || count > 1) {
+                        messageInfo.append(sSendersSplitToken);
+                    }
+                    SpannableStringBuilder draftString = new SpannableStringBuilder();
+                    if (draftCount == 1) {
+                        draftString.append(sDraftSingularString);
+                    } else {
+                        draftString.append(sDraftPluralString
+                                + String.format(sDraftCountFormatString, draftCount));
+                    }
+                    draftString.setSpan(CharacterStyle.wrap(sDraftsStyleSpan), 0,
+                            draftString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    messageInfo.append(draftString);
+                }
+                if (showSending) {
+                    // If we are showing a message count or any draft text, prepend
+                    // the sending state text with a comma.
+                    if (count > 1 || draftCount > 0) {
+                        messageInfo.append(sSendersSplitToken);
+                    }
+                    SpannableStringBuilder sending = new SpannableStringBuilder();
+                    sending.append(sSendingString);
+                    sending.setSpan(sSendingStyleSpan, 0, sending.length(), 0);
+                    messageInfo.append(sending);
+                }
+                // Prepend a space if we are showing other message info text.
+                if (count > 1 || (draftCount > 0 && hasSenders) || showSending) {
+                    messageInfo = new SpannableStringBuilder(sMessageCountSpacerString)
+                            .append(messageInfo);
+                }
+            }
+        } finally {
+            if (!resourceCachingRequired) {
+                clearResourceCache();
             }
         }
-        getSenderResources(context);
-        if (conversationInfo != null) {
-            int count = conversationInfo.messageCount;
-            int draftCount = conversationInfo.draftCount;
-            boolean showSending = sendingStatus == UIProvider.ConversationSendingState.SENDING;
-            if (count > 1) {
-                messageInfo.append(count + "");
-            }
-            messageInfo.setSpan(CharacterStyle.wrap(
-                    conv.read ? sMessageInfoReadStyleSpan : sMessageInfoUnreadStyleSpan),
-                    0, messageInfo.length(), 0);
-            if (draftCount > 0) {
-                // If we are showing a message count or any draft text and there
-                // is at least 1 sender, prepend the sending state text with a
-                // comma.
-                if (hasSenders || count > 1) {
-                    messageInfo.append(sSendersSplitToken);
-                }
-                SpannableStringBuilder draftString = new SpannableStringBuilder();
-                if (draftCount == 1) {
-                    draftString.append(sDraftSingularString);
-                } else {
-                    draftString.append(sDraftPluralString
-                            + String.format(sDraftCountFormatString, draftCount));
-                }
-                draftString.setSpan(CharacterStyle.wrap(sDraftsStyleSpan), 0, draftString.length(),
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                messageInfo.append(draftString);
-            }
-            if (showSending) {
-                // If we are showing a message count or any draft text, prepend
-                // the sending state text with a comma.
-                if (count > 1 || draftCount > 0) {
-                    messageInfo.append(sSendersSplitToken);
-                }
-                SpannableStringBuilder sending = new SpannableStringBuilder();
-                sending.append(sSendingString);
-                sending.setSpan(sSendingStyleSpan, 0, sending.length(), 0);
-                messageInfo.append(sending);
-            }
-            // Prepend a space if we are showing other message info text.
-            if (count > 1 || (draftCount > 0 && hasSenders) || showSending) {
-                messageInfo = new SpannableStringBuilder(sMessageCountSpacerString)
-                        .append(messageInfo);
-            }
-        }
+
         return messageInfo;
     }
 
     public static void format(Context context, ConversationInfo conversationInfo,
             String messageInfo, int maxChars, ArrayList<SpannableString> styledSenders,
             ArrayList<String> displayableSenderNames, ArrayList<String> displayableSenderEmails,
-            String account) {
-        getSenderResources(context);
-        format(context, conversationInfo, messageInfo, maxChars, styledSenders,
-                displayableSenderNames, displayableSenderEmails, account,
-                sUnreadStyleSpan, sReadStyleSpan);
+            String account, final boolean resourceCachingRequired) {
+        try {
+            getSenderResources(context, resourceCachingRequired);
+            format(context, conversationInfo, messageInfo, maxChars, styledSenders,
+                    displayableSenderNames, displayableSenderEmails, account,
+                    sUnreadStyleSpan, sReadStyleSpan, resourceCachingRequired);
+        } finally {
+            if (!resourceCachingRequired) {
+                clearResourceCache();
+            }
+        }
     }
 
     public static void format(Context context, ConversationInfo conversationInfo,
             String messageInfo, int maxChars, ArrayList<SpannableString> styledSenders,
             ArrayList<String> displayableSenderNames, ArrayList<String> displayableSenderEmails,
             String account, final TextAppearanceSpan notificationUnreadStyleSpan,
-            final CharacterStyle notificationReadStyleSpan) {
-        getSenderResources(context);
-        handlePriority(context, maxChars, messageInfo, conversationInfo, styledSenders,
-                displayableSenderNames, displayableSenderEmails, account,
-                notificationUnreadStyleSpan, notificationReadStyleSpan);
+            final CharacterStyle notificationReadStyleSpan, final boolean resourceCachingRequired) {
+        try {
+            getSenderResources(context, resourceCachingRequired);
+            handlePriority(context, maxChars, messageInfo, conversationInfo, styledSenders,
+                    displayableSenderNames, displayableSenderEmails, account,
+                    notificationUnreadStyleSpan, notificationReadStyleSpan);
+        } finally {
+            if (!resourceCachingRequired) {
+                clearResourceCache();
+            }
+        }
     }
 
     public static void handlePriority(Context context, int maxChars, String messageInfoString,
@@ -363,25 +385,32 @@
     }
 
     private static void formatDefault(ConversationItemViewModel header, String sendersString,
-            Context context, final CharacterStyle readStyleSpan) {
-        getSenderResources(context);
-        // Clear any existing sender fragments; we must re-make all of them.
-        header.senderFragments.clear();
-        String[] senders = TextUtils.split(sendersString, Address.ADDRESS_DELIMETER);
-        String[] namesOnly = new String[senders.length];
-        Rfc822Token[] senderTokens;
-        String display;
-        for (int i = 0; i < senders.length; i++) {
-            senderTokens = Rfc822Tokenizer.tokenize(senders[i]);
-            if (senderTokens != null && senderTokens.length > 0) {
-                display = senderTokens[0].getName();
-                if (TextUtils.isEmpty(display)) {
-                    display = senderTokens[0].getAddress();
+            Context context, final CharacterStyle readStyleSpan,
+            final boolean resourceCachingRequired) {
+        try {
+            getSenderResources(context, resourceCachingRequired);
+            // Clear any existing sender fragments; we must re-make all of them.
+            header.senderFragments.clear();
+            String[] senders = TextUtils.split(sendersString, Address.ADDRESS_DELIMETER);
+            String[] namesOnly = new String[senders.length];
+            Rfc822Token[] senderTokens;
+            String display;
+            for (int i = 0; i < senders.length; i++) {
+                senderTokens = Rfc822Tokenizer.tokenize(senders[i]);
+                if (senderTokens != null && senderTokens.length > 0) {
+                    display = senderTokens[0].getName();
+                    if (TextUtils.isEmpty(display)) {
+                        display = senderTokens[0].getAddress();
+                    }
+                    namesOnly[i] = display;
                 }
-                namesOnly[i] = display;
+            }
+            generateSenderFragments(header, namesOnly, readStyleSpan);
+        } finally {
+            if (!resourceCachingRequired) {
+                clearResourceCache();
             }
         }
-        generateSenderFragments(header, namesOnly, readStyleSpan);
     }
 
     private static void generateSenderFragments(ConversationItemViewModel header, String[] names,
@@ -391,13 +420,31 @@
                 true);
     }
 
-    public static void formatSenders(ConversationItemViewModel header, Context context) {
-        getSenderResources(context);
-        formatSenders(header, context, sReadStyleSpan);
+    public static void formatSenders(ConversationItemViewModel header, Context context,
+            final boolean resourceCachingRequired) {
+        try {
+            getSenderResources(context, resourceCachingRequired);
+            formatSenders(header, context, sReadStyleSpan, resourceCachingRequired);
+        } finally {
+            if (!resourceCachingRequired) {
+                clearResourceCache();
+            }
+        }
     }
 
     public static void formatSenders(ConversationItemViewModel header, Context context,
-            final CharacterStyle readStyleSpan) {
-        formatDefault(header, header.conversation.senders, context, readStyleSpan);
+            final CharacterStyle readStyleSpan, final boolean resourceCachingRequired) {
+        try {
+            formatDefault(header, header.conversation.senders, context, readStyleSpan,
+                    resourceCachingRequired);
+        } finally {
+            if (!resourceCachingRequired) {
+                clearResourceCache();
+            }
+        }
+    }
+
+    private static void clearResourceCache() {
+        sDraftSingularString = null;
     }
 }
diff --git a/src/com/android/mail/compose/ComposeActivity.java b/src/com/android/mail/compose/ComposeActivity.java
index acab1cc..77c29d7 100644
--- a/src/com/android/mail/compose/ComposeActivity.java
+++ b/src/com/android/mail/compose/ComposeActivity.java
@@ -38,8 +38,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Parcelable;
 import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.provider.BaseColumns;
 import android.text.Editable;
 import android.text.Html;
@@ -70,6 +70,7 @@
 import com.android.ex.chips.RecipientEditTextView;
 import com.android.mail.MailIntentService;
 import com.android.mail.R;
+import com.android.mail.browse.MessageHeaderView;
 import com.android.mail.compose.AttachmentsView.AttachmentAddedOrDeletedListener;
 import com.android.mail.compose.AttachmentsView.AttachmentFailureException;
 import com.android.mail.compose.FromAddressSpinner.OnAccountChangedListener;
@@ -86,10 +87,10 @@
 import com.android.mail.providers.UIProvider;
 import com.android.mail.providers.UIProvider.AccountCapabilities;
 import com.android.mail.providers.UIProvider.DraftType;
+import com.android.mail.ui.AttachmentTile.AttachmentPreview;
 import com.android.mail.ui.FeedbackEnabledActivity;
 import com.android.mail.ui.MailActivity;
 import com.android.mail.ui.WaitFragment;
-import com.android.mail.ui.AttachmentTile.AttachmentPreview;
 import com.android.mail.utils.AccountUtils;
 import com.android.mail.utils.AttachmentUtils;
 import com.android.mail.utils.ContentProviderTask;
@@ -116,8 +117,9 @@
 
 public class ComposeActivity extends Activity implements OnClickListener, OnNavigationListener,
         RespondInlineListener, DialogInterface.OnClickListener, TextWatcher,
-        AttachmentAddedOrDeletedListener, OnAccountChangedListener, LoaderManager.LoaderCallbacks<Cursor>,
-        TextView.OnEditorActionListener, FeedbackEnabledActivity {
+        AttachmentAddedOrDeletedListener, OnAccountChangedListener,
+        LoaderManager.LoaderCallbacks<Cursor>, TextView.OnEditorActionListener,
+        FeedbackEnabledActivity {
     // Identifiers for which type of composition this is
     protected static final int COMPOSE = -1;
     protected static final int REPLY = 0;
@@ -205,6 +207,7 @@
     private static final String EXTRA_MESSAGE = "extraMessage";
     private static final int REFERENCE_MESSAGE_LOADER = 0;
     private static final int LOADER_ACCOUNT_CURSOR = 1;
+    private static final int INIT_DRAFT_USING_REFERENCE_MESSAGE = 2;
     private static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
     private static final String TAG_WAIT = "wait-fragment";
     private static final String MIME_TYPE_PHOTO = "image/*";
@@ -262,6 +265,7 @@
     private RecipientTextWatcher mCcListener;
     private RecipientTextWatcher mBccListener;
     private Uri mRefMessageUri;
+    private boolean mShowQuotedText = false;
     private Bundle mSavedInstanceState;
 
 
@@ -280,14 +284,14 @@
      * Can be called from a non-UI thread.
      */
     public static void editDraft(Context launcher, Account account, Message message) {
-        launch(launcher, account, message, EDIT_DRAFT);
+        launch(launcher, account, message, EDIT_DRAFT, null, null);
     }
 
     /**
      * Can be called from a non-UI thread.
      */
     public static void compose(Context launcher, Account account) {
-        launch(launcher, account, null, COMPOSE);
+        launch(launcher, account, null, COMPOSE, null, null);
     }
 
     /**
@@ -322,24 +326,30 @@
      * Can be called from a non-UI thread.
      */
     public static void reply(Context launcher, Account account, Message message) {
-        launch(launcher, account, message, REPLY);
+        launch(launcher, account, message, REPLY, null, null);
     }
 
     /**
      * Can be called from a non-UI thread.
      */
     public static void replyAll(Context launcher, Account account, Message message) {
-        launch(launcher, account, message, REPLY_ALL);
+        launch(launcher, account, message, REPLY_ALL, null, null);
     }
 
     /**
      * Can be called from a non-UI thread.
      */
     public static void forward(Context launcher, Account account, Message message) {
-        launch(launcher, account, message, FORWARD);
+        launch(launcher, account, message, FORWARD, null, null);
     }
 
-    private static void launch(Context launcher, Account account, Message message, int action) {
+    public static void reportRenderingFeedback(Context launcher, Account account, Message message,
+            String body) {
+        launch(launcher, account, message, FORWARD, "android-gmail-readability@google.com", body);
+    }
+
+    private static void launch(Context launcher, Account account, Message message, int action,
+            String toAddress, String body) {
         Intent intent = new Intent(launcher, ComposeActivity.class);
         intent.putExtra(EXTRA_FROM_EMAIL_TASK, true);
         intent.putExtra(EXTRA_ACTION, action);
@@ -349,6 +359,12 @@
         } else {
             intent.putExtra(EXTRA_IN_REFERENCE_TO_MESSAGE, message);
         }
+        if (toAddress != null) {
+            intent.putExtra(EXTRA_TO, toAddress);
+        }
+        if (body != null) {
+            intent.putExtra(EXTRA_BODY, body);
+        }
         launcher.startActivity(intent);
     }
 
@@ -366,7 +382,7 @@
         Intent intent = getIntent();
         Message message;
         ArrayList<AttachmentPreview> previews;
-        boolean showQuotedText = false;
+        mShowQuotedText = false;
         int action;
         // Check for any of the possibly supplied accounts.;
         Account account = null;
@@ -399,7 +415,8 @@
         if (notificationFolder != null) {
             final Intent clearNotifIntent =
                     new Intent(MailIntentService.ACTION_CLEAR_NEW_MAIL_NOTIFICATIONS);
-            clearNotifIntent.putExtra(MailIntentService.ACCOUNT_EXTRA, account.name);
+            clearNotifIntent.setPackage(getPackageName());
+            clearNotifIntent.putExtra(MailIntentService.ACCOUNT_EXTRA, account);
             clearNotifIntent.putExtra(MailIntentService.FOLDER_EXTRA, notificationFolder);
 
             startService(clearNotifIntent);
@@ -417,14 +434,15 @@
         }
 
         if (mRefMessageUri != null) {
-            // We have a referenced message that we must look up.
-            getLoaderManager().initLoader(REFERENCE_MESSAGE_LOADER, null, this);
+            mShowQuotedText = true;
+            mComposeMode = action;
+            getLoaderManager().initLoader(INIT_DRAFT_USING_REFERENCE_MESSAGE, null, this);
             return;
         } else if (message != null && action != EDIT_DRAFT) {
             initFromDraftMessage(message);
             initQuotedTextFromRefMessage(mRefMessage, action);
             showCcBcc(savedInstanceState);
-            showQuotedText = message.appendRefMessageContent;
+            mShowQuotedText = message.appendRefMessageContent;
         } else if (action == EDIT_DRAFT) {
             initFromDraftMessage(message);
             boolean showBcc = !TextUtils.isEmpty(message.getBcc());
@@ -446,17 +464,29 @@
                     action = COMPOSE;
                     break;
             }
-            initQuotedTextFromRefMessage(mRefMessage, action);
-            showQuotedText = message.appendRefMessageContent;
+            LogUtils.d(LOG_TAG, "Previous draft had action type: %d", action);
+
+            mShowQuotedText = message.appendRefMessageContent;
+            if (message.refMessageUri != null) {
+                // If we're editing an existing draft that was in reference to an existing message,
+                // still need to load that original message since we might need to refer to the
+                // original sender and recipients if user switches "reply <-> reply-all".
+                mRefMessageUri = message.refMessageUri;
+                mComposeMode = action;
+                getLoaderManager().initLoader(REFERENCE_MESSAGE_LOADER, null, this);
+                return;
+            }
         } else if ((action == REPLY || action == REPLY_ALL || action == FORWARD)) {
             if (mRefMessage != null) {
                 initFromRefMessage(action);
-                showQuotedText = true;
+                mShowQuotedText = true;
             }
         } else {
             initFromExtras(intent);
         }
-        finishSetup(action, intent, savedInstanceState, showQuotedText);
+
+        mComposeMode = action;
+        finishSetup(action, intent, savedInstanceState);
     }
 
     private void checkValidAccounts() {
@@ -540,8 +570,7 @@
         return account;
     }
 
-    private void finishSetup(int action, Intent intent, Bundle savedInstanceState,
-            boolean showQuotedText) {
+    private void finishSetup(int action, Intent intent, Bundle savedInstanceState) {
         setFocus(action);
         if (action == COMPOSE) {
             mQuotedTextView.setVisibility(View.GONE);
@@ -552,7 +581,7 @@
         if (!hadSavedInstanceStateMessage(savedInstanceState)) {
             initAttachmentsFromIntent(intent);
         }
-        initActionBar(action);
+        initActionBar();
         initFromSpinner(savedInstanceState != null ? savedInstanceState : intent.getExtras(),
                 action);
 
@@ -564,7 +593,7 @@
 
         initChangeListeners();
         updateHideOrShowCcBcc();
-        updateHideOrShowQuotedText(showQuotedText);
+        updateHideOrShowQuotedText(mShowQuotedText);
 
         mRespondedInline = mSavedInstanceState != null ?
                 mSavedInstanceState.getBoolean(EXTRA_RESPONDED_INLINE) : false;
@@ -663,7 +692,7 @@
             addAttachmentAndUpdateView(data);
             mAddingAttachment = false;
         } else if (request == RESULT_CREATE_ACCOUNT) {
-                // We were waiting for the user to create an account
+            // We were waiting for the user to create an account
             if (result != RESULT_OK) {
                 finish();
             } else {
@@ -783,14 +812,12 @@
         message.bodyHtml = fullBody.toString();
         message.bodyText = mBodyView.getText().toString();
         message.embedsExternalResources = false;
-        message.refMessageId = mRefMessage != null ? mRefMessage.uri.toString() : null;
+        message.refMessageUri = mRefMessage != null ? mRefMessage.uri : null;
         message.appendRefMessageContent = mQuotedTextView.getQuotedTextIfIncluded() != null;
         ArrayList<Attachment> attachments = mAttachmentsView.getAttachments();
         message.hasAttachments = attachments != null && attachments.size() > 0;
         message.attachmentListUri = null;
         message.messageFlags = 0;
-        message.saveUri = null;
-        message.sendUri = null;
         message.alwaysShowImages = false;
         message.attachmentsJson = Attachment.toJSONArray(attachments);
         CharSequence quotedText = mQuotedTextView.getQuotedText();
@@ -1055,13 +1082,13 @@
         mAttachmentsView.setAttachmentChangesListener(this);
     }
 
-    private void initActionBar(int action) {
-        mComposeMode = action;
+    private void initActionBar() {
+        LogUtils.d(LOG_TAG, "initializing action bar in ComposeActivity");
         ActionBar actionBar = getActionBar();
         if (actionBar == null) {
             return;
         }
-        if (action == ComposeActivity.COMPOSE) {
+        if (mComposeMode == ComposeActivity.COMPOSE) {
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
             actionBar.setTitle(R.string.compose);
         } else {
@@ -1071,7 +1098,7 @@
             }
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
             actionBar.setListNavigationCallbacks(mComposeModeAdapter, this);
-            switch (action) {
+            switch (mComposeMode) {
                 case ComposeActivity.REPLY:
                     actionBar.setSelectedNavigationItem(0);
                     break;
@@ -1090,6 +1117,23 @@
 
     private void initFromRefMessage(int action) {
         setFieldsFromRefMessage(action);
+
+        // Check if To: address and email body needs to be prefilled based on extras.
+        // This is used for reporting rendering feedback.
+        if (MessageHeaderView.ENABLE_REPORT_RENDERING_PROBLEM) {
+            Intent intent = getIntent();
+            if (intent.getExtras() != null) {
+                String toAddresses = intent.getStringExtra(EXTRA_TO);
+                if (toAddresses != null) {
+                    addToAddresses(Arrays.asList(TextUtils.split(toAddresses, ",")));
+                }
+                String body = intent.getStringExtra(EXTRA_BODY);
+                if (body != null) {
+                    setBody(body, false /* withSignature */);
+                }
+            }
+        }
+
         if (mRefMessage != null) {
             // CC field only gets populated when doing REPLY_ALL.
             // BCC never gets auto-populated, unless the user is editing
@@ -1959,29 +2003,16 @@
                 if (updateExistingMessage) {
                     sendOrSaveMessage.mValues.put(BaseColumns._ID, messageIdToSave);
 
-                    final Bundle result = callAccountSendSaveMethod(resolver,
+                    callAccountSendSaveMethod(resolver,
                             selectedAccount.account, accountMethod, sendOrSaveMessage);
-                    if (result == null) {
-                        // TODO(pwestbro): Once Email supports the call api, remove this block
-                        // If null was returned, then the provider didn't handle the call method
-                        final Uri updateUri = Uri.parse(sendOrSaveMessage.mSave ?
-                                message.saveUri : message.sendUri);
-                        resolver.update(updateUri, sendOrSaveMessage.mValues, null, null);
-                    }
                 } else {
-                    final Uri messageUri;
+                    Uri messageUri = null;
                     final Bundle result = callAccountSendSaveMethod(resolver,
                             selectedAccount.account, accountMethod, sendOrSaveMessage);
                     if (result != null) {
                         // If a non-null value was returned, then the provider handled the call
                         // method
                         messageUri = result.getParcelable(UIProvider.MessageColumns.URI);
-                    } else {
-                        // TODO(pwestbro): Once Email supports the call api, remove this block
-                        messageUri = resolver.insert(
-                                sendOrSaveMessage.mSave ? selectedAccount.account.saveDraftUri
-                                        : selectedAccount.account.sendMessageUri,
-                                sendOrSaveMessage.mValues);
                     }
                     if (sendOrSaveMessage.mSave && messageUri != null) {
                         final Cursor messageCursor = resolver.query(messageUri,
@@ -3092,6 +3123,9 @@
     @Override
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         switch (id) {
+            case INIT_DRAFT_USING_REFERENCE_MESSAGE:
+                return new CursorLoader(this, mRefMessageUri, UIProvider.MESSAGE_PROJECTION, null,
+                        null, null);
             case REFERENCE_MESSAGE_LOADER:
                 return new CursorLoader(this, mRefMessageUri, UIProvider.MESSAGE_PROJECTION, null,
                         null, null);
@@ -3106,14 +3140,13 @@
     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
         int id = loader.getId();
         switch (id) {
-            case REFERENCE_MESSAGE_LOADER:
+            case INIT_DRAFT_USING_REFERENCE_MESSAGE:
                 if (data != null && data.moveToFirst()) {
                     mRefMessage = new Message(data);
                     Intent intent = getIntent();
-                    int action = intent.getIntExtra(EXTRA_ACTION, COMPOSE);
-                    initFromRefMessage(action);
-                    finishSetup(action, intent, null, true);
-                    if (action != FORWARD) {
+                    initFromRefMessage(mComposeMode);
+                    finishSetup(mComposeMode, intent, null);
+                    if (mComposeMode != FORWARD) {
                         String to = intent.getStringExtra(EXTRA_TO);
                         if (!TextUtils.isEmpty(to)) {
                             mRefMessage.setTo(null);
@@ -3127,6 +3160,13 @@
                     finish();
                 }
                 break;
+            case REFERENCE_MESSAGE_LOADER:
+                // Only populate mRefMessage and leave other fields untouched.
+                if (data != null && data.moveToFirst()) {
+                    mRefMessage = new Message(data);
+                }
+                finishSetup(mComposeMode, getIntent(), mSavedInstanceState);
+                break;
             case LOADER_ACCOUNT_CURSOR:
                 if (data != null && data.moveToFirst()) {
                     // there are accounts now!
diff --git a/src/com/android/mail/content/CursorCreator.java b/src/com/android/mail/content/CursorCreator.java
new file mode 100644
index 0000000..f4d1abb
--- /dev/null
+++ b/src/com/android/mail/content/CursorCreator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mail.content;
+
+import android.database.Cursor;
+
+/**
+ * An object that knows how to create its implementing class using a single row of a cursor alone.
+ * @param <T>
+ */
+public interface CursorCreator<T> {
+
+    /**
+     * Creates an object using the current row of the cursor given here. The implementation should
+     * not advance/rewind the cursor, and is only allowed to read the existing row.
+     * @param c
+     * @return a real object of the implementing class.
+     */
+    T createFromCursor(Cursor c);
+}
diff --git a/src/com/android/mail/content/ObjectCursor.java b/src/com/android/mail/content/ObjectCursor.java
new file mode 100644
index 0000000..63c3ea4
--- /dev/null
+++ b/src/com/android/mail/content/ObjectCursor.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mail.content;
+
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.util.SparseArray;
+
+/**
+ * A cursor-backed type that can return an object for each row of the cursor. This class is most
+ * useful when:
+ * 1. The cursor is returned in conjuction with an AsyncTaskLoader and created off the UI thread.
+ * 2. A single row in the cursor specifies everything for an object.
+ */
+public class ObjectCursor <T> extends CursorWrapper {
+    /** The cache for objects in the underlying cursor. */
+    private final SparseArray<T> mCache;
+    /** An object that knows how to construct {@link T} objects using cursors. */
+    private final CursorCreator<T> mFactory;
+
+    /**
+     * Creates a new object cursor.
+     * @param cursor the underlying cursor this wraps.
+     */
+    public ObjectCursor(Cursor cursor, CursorCreator<T> factory) {
+        super(cursor);
+        if (cursor != null) {
+            mCache = new SparseArray<T>(cursor.getCount());
+        } else {
+            mCache = null;
+        }
+        mFactory = factory;
+    }
+
+    /**
+     * Create a concrete object at the current cursor position. There is no guarantee on object
+     * creation: an object might have been previously created, or the cache might be populated
+     * by calling {@link #fillCache()}. In both these cases, the previously created object is
+     * returned.
+     * @return a model
+     */
+    public final T getModel() {
+        final Cursor c = getWrappedCursor();
+        if (c == null ) {
+            return null;
+        }
+        final int currentPosition = c.getPosition();
+        // The cache contains this object, return it.
+        final T prev = mCache.get(currentPosition);
+        if (prev != null) {
+            return prev;
+        }
+        // Get the object at the current position and add it to the cache.
+        final T model = mFactory.createFromCursor(c);
+        mCache.put(currentPosition, model);
+        return model;
+    }
+
+    /**
+     * Reads the entire cursor to populate the objects in the cache. Subsequent calls to {@link
+     * #getModel()} will return the cached objects as far as the underlying cursor does not change.
+     */
+    final void fillCache() {
+        final Cursor c = getWrappedCursor();
+        if (c == null || !c.moveToFirst()) {
+            return;
+        }
+        do {
+            // As a side effect of getModel, the model is cached away.
+            getModel();
+        } while (c.moveToNext());
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        mCache.clear();
+    }
+
+}
diff --git a/src/com/android/mail/content/ObjectCursorLoader.java b/src/com/android/mail/content/ObjectCursorLoader.java
new file mode 100644
index 0000000..a9a930f
--- /dev/null
+++ b/src/com/android/mail/content/ObjectCursorLoader.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mail.content;
+
+import com.android.mail.utils.LogTag;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * A copy of the framework's {@link android.content.CursorLoader} class. Copied because
+ * CursorLoader is not parameterized, and we want to parameterize over the underlying cursor type.
+ * @param <T>
+ */
+public class ObjectCursorLoader<T> extends AsyncTaskLoader<ObjectCursor<T>> {
+    final ForceLoadContentObserver mObserver;
+    protected static final String LOG_TAG = LogTag.getLogTag();
+
+    final Uri mUri;
+    final String[] mProjection;
+    // Copied over from CursorLoader, but none of our uses specify this. So these are hardcoded to
+    // null right here.
+    final String mSelection = null;
+    final String[] mSelectionArgs = null;
+    final String mSortOrder = null;
+
+    /** The underlying cursor that contains the data. */
+    ObjectCursor<T> mCursor;
+
+    /** The factory that knows how to create T objects from cursors: one object per row. */
+    private final CursorCreator<T> mFactory;
+
+    public ObjectCursorLoader(Context context, Uri uri, String[] projection,
+            CursorCreator<T> factory) {
+        super(context);
+
+        /*
+         * If these are null, it's going to crash anyway in loadInBackground(), but this stack trace
+         * is much more useful.
+         */
+        if (uri == null) {
+            throw new NullPointerException("The uri cannot be null");
+        }
+        if (factory == null) {
+            throw new NullPointerException("The factory cannot be null");
+        }
+
+        mObserver = new ForceLoadContentObserver();
+        mUri = uri;
+        mProjection = projection;
+        mFactory = factory;
+    }
+
+    /* Runs on a worker thread */
+    @Override
+    public ObjectCursor<T> loadInBackground() {
+        final Cursor inner = getContext().getContentResolver().query(mUri, mProjection,
+                mSelection, mSelectionArgs, mSortOrder);
+        if (inner != null) {
+            // Ensure the cursor window is filled
+            inner.getCount();
+            inner.registerContentObserver(mObserver);
+        }
+        // Modifications to the ObjectCursor, create an Object Cursor and fill the cache.
+        final ObjectCursor<T> cursor = new ObjectCursor<T>(inner, mFactory);
+        cursor.fillCache();
+        return cursor;
+    }
+
+    /* Runs on the UI thread */
+    @Override
+    public void deliverResult(ObjectCursor<T> cursor) {
+        if (isReset()) {
+            // An async query came in while the loader is stopped
+            if (cursor != null) {
+                cursor.close();
+            }
+            return;
+        }
+        final Cursor oldCursor = mCursor;
+        mCursor = cursor;
+
+        if (isStarted()) {
+            super.deliverResult(cursor);
+        }
+
+        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
+            oldCursor.close();
+        }
+    }
+
+    /**
+     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+     * will be called on the UI thread. If a previous load has been completed and is still valid
+     * the result may be passed to the callbacks immediately.
+     *
+     * Must be called from the UI thread
+     */
+    @Override
+    protected void onStartLoading() {
+        if (mCursor != null) {
+            deliverResult(mCursor);
+        }
+        if (takeContentChanged() || mCursor == null) {
+            forceLoad();
+        }
+    }
+
+    /**
+     * Must be called from the UI thread
+     */
+    @Override
+    protected void onStopLoading() {
+        // Attempt to cancel the current load task if possible.
+        cancelLoad();
+    }
+
+    @Override
+    public void onCanceled(ObjectCursor<T> cursor) {
+        if (cursor != null && !cursor.isClosed()) {
+            cursor.close();
+        }
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+
+        // Ensure the loader is stopped
+        onStopLoading();
+
+        if (mCursor != null && !mCursor.isClosed()) {
+            mCursor.close();
+        }
+        mCursor = null;
+    }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        super.dump(prefix, fd, writer, args);
+        writer.print(prefix); writer.print("mUri="); writer.println(mUri);
+        writer.print(prefix); writer.print("mProjection=");
+        writer.println(Arrays.toString(mProjection));
+        writer.print(prefix); writer.print("mSelection="); writer.println(mSelection);
+        writer.print(prefix); writer.print("mSelectionArgs=");
+        writer.println(Arrays.toString(mSelectionArgs));
+        writer.print(prefix); writer.print("mSortOrder="); writer.println(mSortOrder);
+        writer.print(prefix); writer.print("mCursor="); writer.println(mCursor);
+    }
+}
diff --git a/src/com/android/mail/perf/SimpleTimer.java b/src/com/android/mail/perf/SimpleTimer.java
index a59b17d..8b45b0a 100644
--- a/src/com/android/mail/perf/SimpleTimer.java
+++ b/src/com/android/mail/perf/SimpleTimer.java
@@ -1,25 +1,39 @@
-// Copyright 2011 Google Inc. All Rights Reserved.
-
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.android.mail.perf;
 
 import android.os.SystemClock;
+import android.text.TextUtils;
 
-import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
 
 /**
- * A simple perf timer class that supports lap-time-style measurements. Once a timer is started,
- * any number of laps can be marked, but they are all relative to the original start time.
- *
+ * A simple perf timer class that supports lap-time-style measurements. Once a
+ * timer is started, any number of laps can be marked, but they are all relative
+ * to the original start time.
  */
 public class SimpleTimer {
 
-    private static final boolean ENABLE_SIMPLE_TIMER = true;
-    private static final String LOG_TAG = LogTag.getLogTag();
+    private static final String DEFAULT_LOG_TAG = "SimpleTimer";
+
+    private static final boolean ENABLE_SIMPLE_TIMER = false;
 
     private final boolean mEnabled;
     private long mStartTime;
+    private long mLastMarkTime;
     private String mSessionName;
 
     public SimpleTimer() {
@@ -30,34 +44,32 @@
         mEnabled = enabled;
     }
 
-    public boolean isEnabled() {
-        return ENABLE_SIMPLE_TIMER && LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)
+    public final boolean isEnabled() {
+        return ENABLE_SIMPLE_TIMER && LogUtils.isLoggable(getTag(), LogUtils.DEBUG)
                 && mEnabled;
     }
 
-    public void start() {
-        start(null);
+    public SimpleTimer withSessionName(String sessionName) {
+        mSessionName = sessionName;
+        return this;
     }
 
-    public void start(String sessionName) {
-        mStartTime = SystemClock.uptimeMillis();
-        mSessionName = sessionName;
+    public void start() {
+        mStartTime = mLastMarkTime = SystemClock.uptimeMillis();
+        LogUtils.d(getTag(), "timer START");
     }
 
     public void mark(String msg) {
         if (isEnabled()) {
-            StringBuilder sb = new StringBuilder();
-            if (mSessionName != null) {
-                sb.append("(");
-                sb.append(mSessionName);
-                sb.append(") ");
-            }
-            sb.append(msg);
-            sb.append(": ");
-            sb.append(SystemClock.uptimeMillis() - mStartTime);
-            sb.append("ms elapsed");
-            LogUtils.d(LOG_TAG, sb.toString());
+            long now = SystemClock.uptimeMillis();
+            LogUtils.d(getTag(), "[%s] %sms elapsed (%sms since last mark)", msg, now - mStartTime,
+                    now - mLastMarkTime);
+            mLastMarkTime = now;
         }
     }
 
+    private String getTag() {
+        return TextUtils.isEmpty(mSessionName) ? DEFAULT_LOG_TAG : mSessionName;
+    }
+
 }
diff --git a/src/com/android/mail/perf/Timer.java b/src/com/android/mail/perf/Timer.java
index 57e5a27..668fdfd 100644
--- a/src/com/android/mail/perf/Timer.java
+++ b/src/com/android/mail/perf/Timer.java
@@ -1,5 +1,18 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
-
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.android.mail.perf;
 
 import com.android.mail.utils.LogTag;
diff --git a/src/com/android/mail/photo/MailPhotoViewActivity.java b/src/com/android/mail/photo/MailPhotoViewActivity.java
index 787f660..f3c0ede 100644
--- a/src/com/android/mail/photo/MailPhotoViewActivity.java
+++ b/src/com/android/mail/photo/MailPhotoViewActivity.java
@@ -233,12 +233,11 @@
                 @Override
                 public void onClick(View view) {
                     downloadAttachment();
+                    emptyText.setVisibility(View.GONE);
+                    retryButton.setVisibility(View.GONE);
                 }
             });
             progressBar.setVisibility(View.GONE);
-        } else {
-            emptyText.setVisibility(View.GONE);
-            retryButton.setVisibility(View.GONE);
         }
     }
 
diff --git a/src/com/android/mail/preferences/AccountPreferences.java b/src/com/android/mail/preferences/AccountPreferences.java
new file mode 100644
index 0000000..4471942
--- /dev/null
+++ b/src/com/android/mail/preferences/AccountPreferences.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.mail.preferences;
+
+import com.google.common.collect.ImmutableSet;
+
+import android.content.Context;
+
+import com.android.mail.MailIntentService;
+
+/**
+ * Preferences relevant to one specific account.
+ */
+public class AccountPreferences extends VersionedPrefs {
+
+    private static final String PREFS_NAME_PREFIX = "Account";
+
+    public static final class PreferenceKeys {
+        /**
+         * A temporary preference that can be set during account setup, if we do not know what the
+         * default inbox is yet. This value should be moved into the appropriate
+         * {@link FolderPreferences} once we have the inbox, and removed from here.
+         */
+        private static final String DEFAULT_INBOX_NOTIFICATIONS_ENABLED =
+                "inbox-notifications-enabled";
+
+        /** Boolean value indicating whether notifications are enabled */
+        public static final String NOTIFICATIONS_ENABLED = "notifications-enabled";
+
+        public static final ImmutableSet<String> BACKUP_KEYS =
+                new ImmutableSet.Builder<String>().add(NOTIFICATIONS_ENABLED).build();
+    }
+
+    /**
+     * @param account The account name. This must never change for the account.
+     */
+    public AccountPreferences(final Context context, final String account) {
+        super(context, buildSharedPrefsName(account));
+    }
+
+    private static String buildSharedPrefsName(final String account) {
+        return PREFS_NAME_PREFIX + '-' + account;
+    }
+
+    @Override
+    protected void performUpgrade(final int oldVersion, final int newVersion) {
+        if (oldVersion > newVersion) {
+            throw new IllegalStateException(
+                    "You appear to have downgraded your app. Please clear app data.");
+        }
+    }
+
+    @Override
+    protected boolean canBackup(final String key) {
+        return PreferenceKeys.BACKUP_KEYS.contains(key);
+    }
+
+    public boolean isDefaultInboxNotificationsEnabledSet() {
+        return getSharedPreferences().contains(PreferenceKeys.DEFAULT_INBOX_NOTIFICATIONS_ENABLED);
+    }
+
+    public boolean getDefaultInboxNotificationsEnabled() {
+        return getSharedPreferences()
+                .getBoolean(PreferenceKeys.DEFAULT_INBOX_NOTIFICATIONS_ENABLED, true);
+    }
+
+    public void setDefaultInboxNotificationsEnabled(final boolean enabled) {
+        getEditor().putBoolean(PreferenceKeys.DEFAULT_INBOX_NOTIFICATIONS_ENABLED, enabled).apply();
+    }
+
+    public void clearDefaultInboxNotificationsEnabled() {
+        getEditor().remove(PreferenceKeys.DEFAULT_INBOX_NOTIFICATIONS_ENABLED).apply();
+    }
+
+    public boolean areNotificationsEnabled() {
+        return getSharedPreferences().getBoolean(PreferenceKeys.NOTIFICATIONS_ENABLED, true);
+    }
+
+    public void setNotificationsEnabled(final boolean enabled) {
+        getEditor().putBoolean(PreferenceKeys.NOTIFICATIONS_ENABLED, enabled).apply();
+        MailIntentService.broadcastBackupDataChanged(getContext());
+    }
+}
diff --git a/src/com/android/mail/preferences/BackupSharedPreference.java b/src/com/android/mail/preferences/BackupSharedPreference.java
new file mode 100644
index 0000000..37a27f8
--- /dev/null
+++ b/src/com/android/mail/preferences/BackupSharedPreference.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+package com.android.mail.preferences;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Wraps around various classes used in Gmail's backup/restore mechanism.
+ */
+public interface BackupSharedPreference {
+    String getKey();
+
+    Object getValue();
+
+    JSONObject toJson() throws JSONException;
+}
diff --git a/src/com/android/mail/preferences/BasePreferenceMigrator.java b/src/com/android/mail/preferences/BasePreferenceMigrator.java
new file mode 100644
index 0000000..0e0b766
--- /dev/null
+++ b/src/com/android/mail/preferences/BasePreferenceMigrator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.mail.preferences;
+
+import android.content.Context;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Interface to allow migrating preferences from other projects into the UnifiedEmail code, so apps
+ * can slowly move their preferences into the shared code.
+ */
+public abstract class BasePreferenceMigrator {
+    /** If <code>true</code>, we have not attempted a migration since the app started. */
+    private static final AtomicBoolean sMigrationNecessary = new AtomicBoolean(true);
+
+    public final void performMigration(
+            final Context context, final int oldVersion, final int newVersion) {
+        // Ensure we only run this once
+        if (sMigrationNecessary.getAndSet(false)) {
+            migrate(context, oldVersion, newVersion);
+        }
+    }
+
+    /**
+     * Migrates preferences to UnifiedEmail.
+     *
+     * @param oldVersion The previous version of UnifiedEmail's preferences
+     * @param newVersion The new version of UnifiedEmail's preferences
+     */
+    protected abstract void migrate(final Context context, int oldVersion, int newVersion);
+}
diff --git a/src/com/android/mail/preferences/FolderPreferences.java b/src/com/android/mail/preferences/FolderPreferences.java
new file mode 100644
index 0000000..c62f3c6
--- /dev/null
+++ b/src/com/android/mail/preferences/FolderPreferences.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.mail.preferences;
+
+import com.google.android.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.provider.Settings;
+
+import com.android.mail.MailIntentService;
+import com.android.mail.providers.Folder;
+import com.android.mail.providers.UIProvider.FolderCapabilities;
+import com.android.mail.utils.NotificationActionUtils.NotificationActionType;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Preferences relevant to one specific folder. In Email, this would only be used for an account's
+ * inbox. In Gmail, this is used for every account/label pair.
+ */
+public class FolderPreferences extends VersionedPrefs {
+
+    private static final String PREFS_NAME_PREFIX = "Folder";
+
+    public static final class PreferenceKeys {
+        /** Boolean value indicating whether notifications are enabled */
+        public static final String NOTIFICATIONS_ENABLED = "notifications-enabled";
+        /** String value of the notification ringtone URI */
+        public static final String NOTIFICATION_RINGTONE = "notification-ringtone";
+        /** Boolean value indicating whether we should explicitly vibrate */
+        public static final String NOTIFICATION_VIBRATE = "notification-vibrate";
+        /**
+         * Boolean value indicating whether we notify for every message (<code>true</code>), or just
+         * once for the folder (<code>false</code>)
+         */
+        public static final String NOTIFICATION_NOTIFY_EVERY_MESSAGE =
+                "notification-notify-every-message";
+        /** String set of the notification actions (from {@link NotificationActionType} */
+        public static final String NOTIFICATION_ACTIONS = "notification-actions";
+
+        public static final ImmutableSet<String> BACKUP_KEYS =
+                new ImmutableSet.Builder<String>()
+                        .add(NOTIFICATIONS_ENABLED)
+                        .add(NOTIFICATION_RINGTONE)
+                        .add(NOTIFICATION_VIBRATE)
+                        .add(NOTIFICATION_NOTIFY_EVERY_MESSAGE)
+                        .build();
+    }
+
+    private final Folder mFolder;
+    /** An id that is constant across app installations. */
+    private final String mPersistentId;
+    private final boolean mUseInboxDefaultNotificationSettings;
+
+    /**
+     * @param account The account name. This must never change for the account.
+     * @param folder The folder
+     */
+    public FolderPreferences(final Context context, final String account, final Folder folder,
+            final boolean useInboxDefaultNotificationSettings) {
+        this(context, account, folder, folder.persistentId, useInboxDefaultNotificationSettings);
+    }
+
+    /**
+     * A constructor that can be used when no {@link Folder} object is available (like during a
+     * restore). While this will probably function as expected at other times,
+     * {@link #FolderPreferences(Context, String, Folder, boolean)} should be used if at all
+     * possible.
+     *
+     * @param account The account name. This must never change for the account.
+     * @param persistentId An identifier for the folder that does not change across app
+     *        installations.
+     */
+    public FolderPreferences(final Context context, final String account, final String persistentId,
+            final boolean useInboxDefaultNotificationSettings) {
+        this(context, account, null, persistentId, useInboxDefaultNotificationSettings);
+    }
+
+    private FolderPreferences(final Context context, final String account, final Folder folder,
+            final String persistentId, final boolean useInboxDefaultNotificationSettings) {
+        super(context, buildSharedPrefsName(account, persistentId));
+        mFolder = folder;
+        mPersistentId = persistentId;
+        mUseInboxDefaultNotificationSettings = useInboxDefaultNotificationSettings;
+    }
+
+    private static String buildSharedPrefsName(final String account, final String persistentId) {
+        return PREFS_NAME_PREFIX + '-' + account + '-' + persistentId;
+    }
+
+    @Override
+    protected void performUpgrade(final int oldVersion, final int newVersion) {
+        if (oldVersion > newVersion) {
+            throw new IllegalStateException(
+                    "You appear to have downgraded your app. Please clear app data.");
+        }
+    }
+
+    public String getPersistentId() {
+        return mPersistentId;
+    }
+
+    @Override
+    protected boolean canBackup(final String key) {
+        if (mPersistentId == null) {
+            return false;
+        }
+
+        return PreferenceKeys.BACKUP_KEYS.contains(key);
+    }
+
+    @Override
+    protected Object getBackupValue(final String key, final Object value) {
+        if (PreferenceKeys.NOTIFICATION_RINGTONE.equals(key)) {
+            return getRingtoneTitle((String) value);
+        }
+
+        return super.getBackupValue(key, value);
+    }
+
+    @Override
+    protected Object getRestoreValue(final String key, final Object value) {
+        if (PreferenceKeys.NOTIFICATION_RINGTONE.equals(key)) {
+            return getRingtoneUri((String) value);
+        }
+
+        return super.getBackupValue(key, value);
+    }
+
+    private String getRingtoneTitle(final String ringtoneUriString) {
+        if (ringtoneUriString.length() == 0) {
+            return ringtoneUriString;
+        }
+        final Uri uri = Uri.parse(ringtoneUriString);
+        if (RingtoneManager.isDefault(uri)) {
+            return ringtoneUriString;
+        }
+        final RingtoneManager ringtoneManager = new RingtoneManager(getContext());
+        ringtoneManager.setType(RingtoneManager.TYPE_NOTIFICATION);
+        final Cursor cursor = ringtoneManager.getCursor();
+        try {
+            while (cursor.moveToNext()) {
+                final Uri cursorUri = ContentUris.withAppendedId(
+                        Uri.parse(cursor.getString(RingtoneManager.URI_COLUMN_INDEX)),
+                        cursor.getLong(RingtoneManager.ID_COLUMN_INDEX));
+                if (cursorUri.toString().equals(ringtoneUriString)) {
+                    final String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
+                    if (!Strings.isNullOrEmpty(title)) {
+                        return title;
+                    }
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    private String getRingtoneUri(final String name) {
+        if (name.length() == 0 || RingtoneManager.isDefault(Uri.parse(name))) {
+            return name;
+        }
+
+        final RingtoneManager ringtoneManager = new RingtoneManager(getContext());
+        ringtoneManager.setType(RingtoneManager.TYPE_NOTIFICATION);
+        final Cursor cursor = ringtoneManager.getCursor();
+        try {
+            while (cursor.moveToNext()) {
+                String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
+                if (name.equals(title)) {
+                    Uri uri = ContentUris.withAppendedId(
+                            Uri.parse(cursor.getString(RingtoneManager.URI_COLUMN_INDEX)),
+                            cursor.getLong(RingtoneManager.ID_COLUMN_INDEX));
+                    return uri.toString();
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    /**
+     * If <code>true</code>, we use inbox-defaults for notification settings. If <code>false</code>,
+     * we use standard defaults.
+     */
+    private boolean getUseInboxDefaultNotificationSettings() {
+        return mUseInboxDefaultNotificationSettings;
+    }
+
+    public boolean isNotificationsEnabledSet() {
+        return getSharedPreferences().contains(PreferenceKeys.NOTIFICATIONS_ENABLED);
+    }
+
+    public boolean areNotificationsEnabled() {
+        return getSharedPreferences().getBoolean(
+                PreferenceKeys.NOTIFICATIONS_ENABLED, getUseInboxDefaultNotificationSettings());
+    }
+
+    public void setNotificationsEnabled(final boolean enabled) {
+        getEditor().putBoolean(PreferenceKeys.NOTIFICATIONS_ENABLED, enabled).apply();
+        MailIntentService.broadcastBackupDataChanged(getContext());
+    }
+
+    public String getNotificationRingtoneUri() {
+        return getSharedPreferences().getString(PreferenceKeys.NOTIFICATION_RINGTONE,
+                Settings.System.DEFAULT_NOTIFICATION_URI.toString());
+    }
+
+    public void setNotificationRingtoneUri(final String uri) {
+        getEditor().putString(PreferenceKeys.NOTIFICATION_RINGTONE, uri).apply();
+        MailIntentService.broadcastBackupDataChanged(getContext());
+    }
+
+    public boolean isNotificationVibrateEnabled() {
+        return getSharedPreferences().getBoolean(PreferenceKeys.NOTIFICATION_VIBRATE, false);
+    }
+
+    public void setNotificationVibrateEnabled(final boolean enabled) {
+        getEditor().putBoolean(PreferenceKeys.NOTIFICATION_VIBRATE, enabled).apply();
+        MailIntentService.broadcastBackupDataChanged(getContext());
+    }
+
+    public boolean isEveryMessageNotificationEnabled() {
+        return getSharedPreferences()
+                .getBoolean(PreferenceKeys.NOTIFICATION_NOTIFY_EVERY_MESSAGE, false);
+    }
+
+    public void setEveryMessageNotificationEnabled(final boolean enabled) {
+        getEditor().putBoolean(PreferenceKeys.NOTIFICATION_NOTIFY_EVERY_MESSAGE, enabled).apply();
+        MailIntentService.broadcastBackupDataChanged(getContext());
+    }
+
+    private Set<String> getDefaultNotificationActions(final Context context) {
+        final boolean supportsArchive = mFolder.supportsCapability(FolderCapabilities.ARCHIVE);
+        final boolean supportsRemoveLabel =
+                mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION);
+        // Use the swipe setting, since it is essentially a way to allow the user to specify
+        // whether they prefer archive or delete, without adding another setting
+        final boolean preferDelete =
+                MailPrefs.ConversationListSwipeActions.DELETE.equals(MailPrefs.get(context)
+                        .getConversationListSwipeAction(true /* supportsArchive */));
+        final NotificationActionType destructiveActionType =
+                (supportsArchive || supportsRemoveLabel) && !preferDelete ?
+                        NotificationActionType.ARCHIVE_REMOVE_LABEL : NotificationActionType.DELETE;
+        final String destructiveAction = destructiveActionType.getPersistedValue();
+
+        final String replyAction =
+                MailPrefs.get(context).getDefaultReplyAll() ? NotificationActionType.REPLY_ALL
+                        .getPersistedValue()
+                        : NotificationActionType.REPLY.getPersistedValue();
+
+        final Set<String> notificationActions = new LinkedHashSet<String>(2);
+        notificationActions.add(destructiveAction);
+        notificationActions.add(replyAction);
+
+        return notificationActions;
+    }
+
+    /**
+     * Gets the notification settings configured for this account and label, or the default if none
+     * have been set.
+     */
+    public Set<String> getNotificationActions() {
+        return getSharedPreferences().getStringSet(
+                PreferenceKeys.NOTIFICATION_ACTIONS, getDefaultNotificationActions(getContext()));
+    }
+}
diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java
index 694654d..5a0b0d7 100644
--- a/src/com/android/mail/preferences/MailPrefs.java
+++ b/src/com/android/mail/preferences/MailPrefs.java
@@ -17,37 +17,70 @@
 
 package com.android.mail.preferences;
 
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
+import com.google.common.collect.ImmutableSet;
 
+import android.content.Context;
+
+import com.android.mail.MailIntentService;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Folder;
+import com.android.mail.providers.UIProvider;
+import com.android.mail.widget.BaseWidgetProvider;
+
+import java.util.Set;
 
 /**
  * A high-level API to store and retrieve unified mail preferences.
  * <p>
  * This will serve as an eventual replacement for Gmail's Persistence class.
  */
-public final class MailPrefs {
+public final class MailPrefs extends VersionedPrefs {
 
-    public static final boolean SHOW_EXPERIMENTAL_PREFS = false;
-
-    // TODO: support account-specific prefs. probably just use a different prefs name instead of
-    // prepending every key.
+    public static final boolean SHOW_EXPERIMENTAL_PREFS = true;
 
     private static final String PREFS_NAME = "UnifiedEmail";
 
     private static MailPrefs sInstance;
-    private final SharedPreferences mPrefs;
 
-    private static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
-    private static final String ACCOUNT_FOLDER_PREFERENCE_SEPARATOR = " ";
+    public static final class PreferenceKeys {
+        private static final String MIGRATED_VERSION = "migrated-version";
 
-    private static final String ENABLE_FTS = "enable-fts";
-    private static final String ENABLE_CHIP_DRAG_AND_DROP = "enable-chip-drag-and-drop";
-    public static final String ENABLE_CONVLIST_PHOTOS = "enable-convlist-photos";
-    public static final String ENABLE_WHOOSH_ZOOM = "enable-whoosh-zoom";
+        public static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
+
+        /** Hidden preference to indicate what version a "What's New" dialog was last shown for. */
+        public static final String WHATS_NEW_LAST_SHOWN_VERSION = "whats-new-last-shown-version";
+
+        /**
+         * A boolean that, if <code>true</code>, means we should default all replies to "reply all"
+         */
+        public static final String DEFAULT_REPLY_ALL = "default-reply-all";
+
+        public static final String CONVERSATION_LIST_SWIPE_ACTION =
+                "conversation-list-swipe-action";
+
+        /** Hidden preference used to cache the active notification set */
+        private static final String CACHED_ACTIVE_NOTIFICATION_SET =
+                "cache-active-notification-set";
+
+        public static final ImmutableSet<String> BACKUP_KEYS =
+                new ImmutableSet.Builder<String>()
+                .add(DEFAULT_REPLY_ALL)
+                .add(CONVERSATION_LIST_SWIPE_ACTION)
+                .build();
+
+        private static final String ENABLE_CHIP_DRAG_AND_DROP = "enable-chip-drag-and-drop";
+        public static final String ENABLE_CONVLIST_PHOTOS = "enable-convlist-photos";
+        public static final String ENABLE_WHOOSH_ZOOM = "enable-whoosh-zoom";
+        public static final String ENABLE_MUNGE_TABLES = "enable-munge-tables";
+        public static final String ENABLE_MUNGE_IMAGES = "enable-munge-images";
+        public static final String ENABLE_SECTIONED_INBOX_EXPERIMENT = "enable-sectioned-inbox";
+
+    }
+
+    public static final class ConversationListSwipeActions {
+        public static final String ARCHIVE = "archive";
+        public static final String DELETE = "delete";
+        public static final String DISABLED = "disabled";
+    }
 
     public static MailPrefs get(Context c) {
         if (sInstance == null) {
@@ -57,70 +90,173 @@
     }
 
     private MailPrefs(Context c) {
-        mPrefs = c.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+        super(c, PREFS_NAME);
     }
 
-    public String getSharedPreferencesName() {
-        return PREFS_NAME;
+    @Override
+    protected void performUpgrade(final int oldVersion, final int newVersion) {
+        if (oldVersion > newVersion) {
+            throw new IllegalStateException(
+                    "You appear to have downgraded your app. Please clear app data.");
+        } else if (oldVersion == newVersion) {
+            return;
+        }
     }
 
-    /**
-     * Set the value of a shared preference of type boolean.
-     */
-    public void setSharedBooleanPreference(String pref, boolean value) {
-        mPrefs.edit().putBoolean(pref, value).apply();
+    @Override
+    protected boolean canBackup(final String key) {
+        return PreferenceKeys.BACKUP_KEYS.contains(key);
+    }
+
+    @Override
+    protected boolean hasMigrationCompleted() {
+        return getSharedPreferences().getInt(PreferenceKeys.MIGRATED_VERSION, 0)
+                >= CURRENT_VERSION_NUMBER;
+    }
+
+    @Override
+    protected void setMigrationComplete() {
+        getEditor().putInt(PreferenceKeys.MIGRATED_VERSION, CURRENT_VERSION_NUMBER).apply();
     }
 
     public boolean isWidgetConfigured(int appWidgetId) {
-        return mPrefs.contains(WIDGET_ACCOUNT_PREFIX + appWidgetId);
+        return getSharedPreferences().contains(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId);
     }
 
-    public void configureWidget(int appWidgetId, Account account, Folder folder) {
-        mPrefs.edit()
-            .putString(WIDGET_ACCOUNT_PREFIX + appWidgetId,
-                    createWidgetPreferenceValue(account, folder))
-            .apply();
+    public void configureWidget(int appWidgetId, Account account, final String folderUri) {
+        getEditor().putString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
+                createWidgetPreferenceValue(account, folderUri)).apply();
     }
 
     public String getWidgetConfiguration(int appWidgetId) {
-        return mPrefs.getString(WIDGET_ACCOUNT_PREFIX + appWidgetId, null);
+        return getSharedPreferences().getString(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + appWidgetId,
+                null);
     }
 
-    public boolean fullTextSearchEnabled() {
-        // If experimental preferences are not enabled, return true.
-        return !SHOW_EXPERIMENTAL_PREFS || mPrefs.getBoolean(ENABLE_FTS, true);
-    }
-
+    @SuppressWarnings("unused")
     public boolean chipDragAndDropEnabled() {
         // If experimental preferences are not enabled, return false.
-        return SHOW_EXPERIMENTAL_PREFS && mPrefs.getBoolean(ENABLE_CHIP_DRAG_AND_DROP, false);
+        return SHOW_EXPERIMENTAL_PREFS && getSharedPreferences().getBoolean(
+                PreferenceKeys.ENABLE_CHIP_DRAG_AND_DROP, false);
     }
 
     /**
      * Get whether to show the experimental inline contact photos in the
      * conversation list.
      */
+    @SuppressWarnings("unused")
     public boolean areConvListPhotosEnabled() {
         // If experimental preferences are not enabled, return false.
-        return SHOW_EXPERIMENTAL_PREFS && mPrefs.getBoolean(ENABLE_CONVLIST_PHOTOS, false);
+        return SHOW_EXPERIMENTAL_PREFS && getSharedPreferences().getBoolean(
+                PreferenceKeys.ENABLE_CONVLIST_PHOTOS, false);
     }
 
+    public void setConvListPhotosEnabled(final boolean enabled) {
+        getEditor().putBoolean(PreferenceKeys.ENABLE_CONVLIST_PHOTOS, enabled).apply();
+    }
+
+    @SuppressWarnings("unused")
     public boolean isWhooshZoomEnabled() {
         // If experimental preferences are not enabled, return false.
-        return SHOW_EXPERIMENTAL_PREFS && mPrefs.getBoolean(ENABLE_WHOOSH_ZOOM, false);
+        return SHOW_EXPERIMENTAL_PREFS && getSharedPreferences().getBoolean(
+                PreferenceKeys.ENABLE_WHOOSH_ZOOM, false);
     }
 
-    private static String createWidgetPreferenceValue(Account account, Folder folder) {
-        return account.uri.toString() +
-                ACCOUNT_FOLDER_PREFERENCE_SEPARATOR + folder.uri.toString();
+    @SuppressWarnings("unused")
+    public boolean shouldMungeTables() {
+        // If experimental preferences are not enabled, return false.
+        return SHOW_EXPERIMENTAL_PREFS && getSharedPreferences().getBoolean(
+                PreferenceKeys.ENABLE_MUNGE_TABLES, true);
+    }
+
+    @SuppressWarnings("unused")
+    public boolean shouldMungeImages() {
+        // If experimental preferences are not enabled, return false.
+        return SHOW_EXPERIMENTAL_PREFS && getSharedPreferences().getBoolean(
+                PreferenceKeys.ENABLE_MUNGE_IMAGES, true);
+    }
+
+    private static String createWidgetPreferenceValue(Account account, String folderUri) {
+        return account.uri.toString() + BaseWidgetProvider.ACCOUNT_FOLDER_PREFERENCE_SEPARATOR
+                + folderUri;
 
     }
 
     public void clearWidgets(int[] appWidgetIds) {
-        final Editor e = mPrefs.edit();
         for (int id : appWidgetIds) {
-            e.remove(WIDGET_ACCOUNT_PREFIX + id);
+            getEditor().remove(PreferenceKeys.WIDGET_ACCOUNT_PREFIX + id);
         }
-        e.apply();
+        getEditor().apply();
+    }
+
+    /** If <code>true</code>, we should default all replies to "reply all" rather than "reply" */
+    public boolean getDefaultReplyAll() {
+        return getSharedPreferences().getBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, false);
+    }
+
+    public void setDefaultReplyAll(final boolean replyAll) {
+        getEditor().putBoolean(PreferenceKeys.DEFAULT_REPLY_ALL, replyAll).apply();
+        MailIntentService.broadcastBackupDataChanged(getContext());
+    }
+
+    /**
+     * Gets the action to take (one of the values from {@link ConversationListSwipeActions}) when an
+     * item in the conversation list is swiped.
+     *
+     * @param allowArchive <code>true</code> if Archive is an acceptable action (this will affect
+     *        the default return value)
+     */
+    public String getConversationListSwipeAction(final boolean allowArchive) {
+        return getSharedPreferences().getString(
+                PreferenceKeys.CONVERSATION_LIST_SWIPE_ACTION,
+                allowArchive ? ConversationListSwipeActions.ARCHIVE
+                        : ConversationListSwipeActions.DELETE);
+    }
+
+    /**
+     * Gets the action to take (one of the values from {@link UIProvider.Swipe}) when an item in the
+     * conversation list is swiped.
+     *
+     * @param allowArchive <code>true</code> if Archive is an acceptable action (this will affect
+     *        the default return value)
+     */
+    public int getConversationListSwipeActionInteger(final boolean allowArchive) {
+        final String swipeAction = getConversationListSwipeAction(allowArchive);
+        if (ConversationListSwipeActions.ARCHIVE.equals(swipeAction)) {
+            return UIProvider.Swipe.ARCHIVE;
+        } else if (ConversationListSwipeActions.DELETE.equals(swipeAction)) {
+            return UIProvider.Swipe.DELETE;
+        } else if (ConversationListSwipeActions.DISABLED.equals(swipeAction)) {
+            return UIProvider.Swipe.DISABLED;
+        } else {
+            return UIProvider.Swipe.DEFAULT;
+        }
+    }
+
+    public void setConversationListSwipeAction(final String swipeAction) {
+        getEditor().putString(PreferenceKeys.CONVERSATION_LIST_SWIPE_ACTION, swipeAction).apply();
+        MailIntentService.broadcastBackupDataChanged(getContext());
+    }
+
+    /**
+     * Returns the previously cached notification set
+     */
+    public Set<String> getActiveNotificationSet() {
+        return getSharedPreferences()
+                .getStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, null);
+    }
+
+    /**
+     * Caches the current notification set.
+     */
+    public void cacheActiveNotificationSet(final Set<String> notificationSet) {
+        getEditor().putStringSet(PreferenceKeys.CACHED_ACTIVE_NOTIFICATION_SET, notificationSet)
+                .apply();
+    }
+
+    public boolean isSectionedInboxExperimentEnabled() {
+        // If experimental preferences are not enabled, return false.
+        return SHOW_EXPERIMENTAL_PREFS && getSharedPreferences().getBoolean(
+                PreferenceKeys.ENABLE_SECTIONED_INBOX_EXPERIMENT, false);
     }
 }
diff --git a/src/com/android/mail/preferences/SimpleBackupSharedPreference.java b/src/com/android/mail/preferences/SimpleBackupSharedPreference.java
new file mode 100644
index 0000000..58ea1c7
--- /dev/null
+++ b/src/com/android/mail/preferences/SimpleBackupSharedPreference.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+package com.android.mail.preferences;
+
+import com.google.common.collect.Sets;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Set;
+
+/**
+* A POJO for shared preferences to be used for backing up and restoring.
+*/
+public class SimpleBackupSharedPreference implements BackupSharedPreference {
+    private String mKey;
+    private Object mValue;
+
+    private static final String KEY = "key";
+    private static final String VALUE = "value";
+
+    public SimpleBackupSharedPreference(final String key, final Object value) {
+        mKey = key;
+        mValue = value;
+    }
+
+    @Override
+    public String getKey() {
+        return mKey;
+    }
+
+    @Override
+    public Object getValue() {
+        return mValue;
+    }
+
+    public void setValue(Object value) {
+        mValue = value;
+    }
+
+    @Override
+    public JSONObject toJson() throws JSONException {
+        final JSONObject json = new JSONObject();
+        json.put(KEY, mKey);
+        if (mValue instanceof Set) {
+            final Set<?> set = (Set<?>) mValue;
+            final JSONArray array = new JSONArray();
+            for (final Object o : set) {
+                array.put(o);
+            }
+            json.put(VALUE, array);
+        } else {
+            json.put(VALUE, mValue);
+        }
+        return json;
+    }
+
+    public static BackupSharedPreference fromJson(final JSONObject json) throws JSONException {
+        Object value = json.get(VALUE);
+        if (value instanceof JSONArray) {
+            final Set<Object> set = Sets.newHashSet();
+            final JSONArray array = (JSONArray) value;
+            for (int i = 0, len = array.length(); i < len; i++) {
+                  set.add(array.get(i));
+            }
+            value = set;
+        }
+        return new SimpleBackupSharedPreference(json.getString(KEY), value);
+    }
+
+    @Override
+    public String toString() {
+        return "BackupSharedPreference{" + "mKey='" + mKey + '\'' + ", mValue=" + mValue + '}';
+    }
+}
diff --git a/src/com/android/mail/preferences/VersionedPrefs.java b/src/com/android/mail/preferences/VersionedPrefs.java
new file mode 100644
index 0000000..2a20f91
--- /dev/null
+++ b/src/com/android/mail/preferences/VersionedPrefs.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mail.preferences;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A high-level API to store and retrieve preferences, that can be versioned in a similar manner as
+ * SQLite databases. You must not use the preference key
+ * {@value VersionedPrefs#PREFS_VERSION_NUMBER}
+ */
+public abstract class VersionedPrefs {
+    private final Context mContext;
+    private final String mSharedPreferencesName;
+    private final SharedPreferences mSharedPreferences;
+    private final Editor mEditor;
+
+    /** The key for the version number of the {@link SharedPreferences} file. */
+    private static final String PREFS_VERSION_NUMBER = "prefs-version-number";
+
+    /**
+     * The current version number for {@link SharedPreferences}. This is a constant for all
+     * applications based on UnifiedEmail.
+     */
+    protected static final int CURRENT_VERSION_NUMBER = 1;
+
+    protected static final String LOG_TAG = LogTag.getLogTag();
+
+    /**
+     * @param sharedPrefsName The name of the {@link SharedPreferences} file to use
+     */
+    protected VersionedPrefs(final Context context, final String sharedPrefsName) {
+        mContext = context.getApplicationContext();
+        mSharedPreferencesName = sharedPrefsName;
+        mSharedPreferences = context.getSharedPreferences(sharedPrefsName, Context.MODE_PRIVATE);
+        mEditor = mSharedPreferences.edit();
+
+        final int oldVersion = getCurrentVersion();
+
+        performUpgrade(oldVersion, CURRENT_VERSION_NUMBER);
+        setCurrentVersion(CURRENT_VERSION_NUMBER);
+
+        if (!hasMigrationCompleted()) {
+            new PreferenceMigrator().performMigration(context, oldVersion, CURRENT_VERSION_NUMBER);
+
+            setMigrationComplete();
+        }
+    }
+
+    protected Context getContext() {
+        return mContext;
+    }
+
+    public String getSharedPreferencesName() {
+        return mSharedPreferencesName;
+    }
+
+    protected SharedPreferences getSharedPreferences() {
+        return mSharedPreferences;
+    }
+
+    protected Editor getEditor() {
+        return mEditor;
+    }
+
+    /**
+     * Returns the current version of the {@link SharedPreferences} file.
+     */
+    private int getCurrentVersion() {
+        return mSharedPreferences.getInt(PREFS_VERSION_NUMBER, 0);
+    }
+
+    private void setCurrentVersion(final int versionNumber) {
+        getEditor().putInt(PREFS_VERSION_NUMBER, versionNumber);
+
+        /*
+         * If the only preference we have is the version number, we do not want to commit it.
+         * Instead, we will wait for some other preference to be written. This prevents us from
+         * creating a file with only the version number.
+         */
+        if (shouldBackUp()) {
+            getEditor().apply();
+        }
+    }
+
+    protected boolean hasMigrationCompleted() {
+        return MailPrefs.get(mContext).hasMigrationCompleted();
+    }
+
+    protected void setMigrationComplete() {
+        MailPrefs.get(mContext).setMigrationComplete();
+    }
+
+    /**
+     * Upgrades the {@link SharedPreferences} file.
+     *
+     * @param oldVersion The current version
+     * @param newVersion The new version
+     */
+    protected abstract void performUpgrade(int oldVersion, int newVersion);
+
+    @VisibleForTesting
+    public void clearAllPreferences() {
+        getEditor().clear().commit();
+    }
+
+    protected abstract boolean canBackup(String key);
+
+    /**
+     * Gets the value to backup for a given key-value pair. By default, returns the passed in value.
+     *
+     * @param key The key to backup
+     * @param value The locally stored value for the given key
+     * @return The value to backup
+     */
+    protected Object getBackupValue(final String key, final Object value) {
+        return value;
+    }
+
+    /**
+     * Gets the value to restore for a given key-value pair. By default, returns the passed in
+     * value.
+     *
+     * @param key The key to restore
+     * @param value The backed up value for the given key
+     * @return The value to restore
+     */
+    protected Object getRestoreValue(final String key, final Object value) {
+        return value;
+    }
+
+    /**
+     * Return a list of shared preferences that should be backed up.
+     */
+    public List<BackupSharedPreference> getBackupPreferences() {
+        final List<BackupSharedPreference> backupPreferences = Lists.newArrayList();
+        final SharedPreferences sharedPreferences = getSharedPreferences();
+        final Map<String, ?> preferences = sharedPreferences.getAll();
+
+        for (final Map.Entry<String, ?> entry : preferences.entrySet()) {
+            final String key = entry.getKey();
+
+            if (!canBackup(key)) {
+                continue;
+            }
+
+            final Object value = entry.getValue();
+            final Object backupValue = getBackupValue(key, value);
+
+            if (backupValue != null) {
+                backupPreferences.add(new SimpleBackupSharedPreference(key, backupValue));
+            }
+        }
+
+        return backupPreferences;
+    }
+
+    /**
+     * Restores preferences from a backup.
+     */
+    public void restorePreferences(final List<BackupSharedPreference> preferences) {
+        for (final BackupSharedPreference preference : preferences) {
+            final String key = preference.getKey();
+            final Object value = preference.getValue();
+
+            if (!canBackup(key) || value == null) {
+                continue;
+            }
+
+            final Object restoreValue = getRestoreValue(key, value);
+
+            if (restoreValue instanceof Boolean) {
+                getEditor().putBoolean(key, (Boolean) restoreValue);
+                LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference);
+            } else if (restoreValue instanceof Float) {
+                getEditor().putFloat(key, (Float) restoreValue);
+                LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference);
+            } else if (restoreValue instanceof Integer) {
+                getEditor().putInt(key, (Integer) restoreValue);
+                LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference);
+            } else if (restoreValue instanceof Long) {
+                getEditor().putLong(key, (Long) restoreValue);
+                LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference);
+            } else if (restoreValue instanceof String) {
+                getEditor().putString(key, (String) restoreValue);
+                LogUtils.v(LOG_TAG, "MailPrefs Restore: %s", preference);
+            } else if (restoreValue instanceof Set) {
+                getEditor().putStringSet(key, (Set<String>) restoreValue);
+            } else {
+                LogUtils.e(LOG_TAG, "Unknown MailPrefs preference data type: %s", value.getClass());
+            }
+        }
+
+        getEditor().apply();
+    }
+
+    /**
+     * <p>
+     * Checks if any of the preferences eligible for backup have been modified from their default
+     * values, and therefore should be backed up.
+     * </p>
+     *
+     * @return <code>true</code> if anything has been modified, <code>false</code> otherwise
+     */
+    public boolean shouldBackUp() {
+        final Map<String, ?> allPrefs = getSharedPreferences().getAll();
+
+        for (final String key : allPrefs.keySet()) {
+            if (canBackup(key)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/android/mail/providers/Account.java b/src/com/android/mail/providers/Account.java
index 47403fb..5b8522f 100644
--- a/src/com/android/mail/providers/Account.java
+++ b/src/com/android/mail/providers/Account.java
@@ -18,12 +18,16 @@
 
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.mail.content.CursorCreator;
+import com.android.mail.content.ObjectCursor;
 import com.android.mail.providers.UIProvider.AccountCapabilities;
+import com.android.mail.providers.UIProvider.AccountColumns;
 import com.android.mail.providers.UIProvider.SyncStatus;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
@@ -35,7 +39,9 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class Account extends android.accounts.Account implements Parcelable {
     private static final String SETTINGS_KEY = "settings";
@@ -77,23 +83,6 @@
     public String accountFromAddresses;
 
     /**
-     * The content provider uri that can be used to save (insert) new draft
-     * messages for this account. NOTE: This might be better to be an update
-     * operation on the messageUri.
-     */
-    @Deprecated
-    public final Uri saveDraftUri;
-
-    /**
-     * The content provider uri that can be used to send a message for this
-     * account.
-     * NOTE: This might be better to be an update operation on the
-     * messageUri.
-     */
-    @Deprecated
-    public final Uri sendMessageUri;
-
-    /**
      * The content provider uri that can be used to expunge message from this
      * account. NOTE: This might be better to be an update operation on the
      * messageUri.
@@ -205,8 +194,6 @@
             json.put(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, fullFolderListUri);
             json.put(UIProvider.AccountColumns.SEARCH_URI, searchUri);
             json.put(UIProvider.AccountColumns.ACCOUNT_FROM_ADDRESSES, accountFromAddresses);
-            json.put(UIProvider.AccountColumns.SAVE_DRAFT_URI, saveDraftUri);
-            json.put(UIProvider.AccountColumns.SEND_MAIL_URI, sendMessageUri);
             json.put(UIProvider.AccountColumns.EXPUNGE_MESSAGE_URI, expungeMessageUri);
             json.put(UIProvider.AccountColumns.UNDO_URI, undoUri);
             json.put(UIProvider.AccountColumns.SETTINGS_INTENT_URI, settingsIntentUri);
@@ -287,8 +274,6 @@
         searchUri = Utils.getValidUri(json.optString(UIProvider.AccountColumns.SEARCH_URI));
         accountFromAddresses = json.optString(UIProvider.AccountColumns.ACCOUNT_FROM_ADDRESSES,
                 "");
-        saveDraftUri = Utils.getValidUri(json.optString(UIProvider.AccountColumns.SAVE_DRAFT_URI));
-        sendMessageUri = Utils.getValidUri(json.optString(UIProvider.AccountColumns.SEND_MAIL_URI));
         expungeMessageUri = Utils.getValidUri(json
                 .optString(UIProvider.AccountColumns.EXPUNGE_MESSAGE_URI));
         undoUri = Utils.getValidUri(json.optString(UIProvider.AccountColumns.UNDO_URI));
@@ -336,8 +321,6 @@
         fullFolderListUri = in.readParcelable(null);
         searchUri = in.readParcelable(null);
         accountFromAddresses = in.readString();
-        saveDraftUri = in.readParcelable(null);
-        sendMessageUri = in.readParcelable(null);
         expungeMessageUri = in.readParcelable(null);
         undoUri = in.readParcelable(null);
         settingsIntentUri = in.readParcelable(null);
@@ -365,47 +348,56 @@
     }
 
     public Account(Cursor cursor) {
-        super(cursor.getString(UIProvider.ACCOUNT_NAME_COLUMN), "unknown");
-        accountFromAddresses = cursor.getString(UIProvider.ACCOUNT_FROM_ADDRESSES_COLUMN);
-        capabilities = cursor.getInt(UIProvider.ACCOUNT_CAPABILITIES_COLUMN);
-        providerVersion = cursor.getInt(UIProvider.ACCOUNT_PROVIDER_VERISON_COLUMN);
-        uri = Uri.parse(cursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
-        folderListUri = Uri.parse(cursor.getString(UIProvider.ACCOUNT_FOLDER_LIST_URI_COLUMN));
-        fullFolderListUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_FULL_FOLDER_LIST_URI_COLUMN));
-        searchUri = Utils.getValidUri(cursor.getString(UIProvider.ACCOUNT_SEARCH_URI_COLUMN));
-        saveDraftUri = Utils
-                .getValidUri(cursor.getString(UIProvider.ACCOUNT_SAVE_DRAFT_URI_COLUMN));
-        sendMessageUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_SEND_MESSAGE_URI_COLUMN));
-        expungeMessageUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_EXPUNGE_MESSAGE_URI_COLUMN));
-        undoUri = Utils.getValidUri(cursor.getString(UIProvider.ACCOUNT_UNDO_URI_COLUMN));
-        settingsIntentUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_SETTINGS_INTENT_URI_COLUMN));
-        helpIntentUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_HELP_INTENT_URI_COLUMN));
-        sendFeedbackIntentUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_SEND_FEEDBACK_INTENT_URI_COLUMN));
-        reauthenticationIntentUri = Utils.getValidUri(
-                cursor.getString(UIProvider.ACCOUNT_REAUTHENTICATION_INTENT_URI_COLUMN));
-        syncStatus = cursor.getInt(UIProvider.ACCOUNT_SYNC_STATUS_COLUMN);
-        composeIntentUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_COMPOSE_INTENT_URI_COLUMN));
-        mimeType = cursor.getString(UIProvider.ACCOUNT_MIME_TYPE_COLUMN);
-        recentFolderListUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_RECENT_FOLDER_LIST_URI_COLUMN));
-        color = cursor.getInt(UIProvider.ACCOUNT_COLOR_COLUMN);
-        defaultRecentFolderListUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_DEFAULT_RECENT_FOLDER_LIST_URI_COLUMN));
-        manualSyncUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_MANUAL_SYNC_URI_COLUMN));
-        viewIntentProxyUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_VIEW_INTENT_PROXY_URI_COLUMN));
-        accoutCookieQueryUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_COOKIE_QUERY_URI_COLUMN));
-        updateSettingsUri = Utils.getValidUri(cursor
-                .getString(UIProvider.ACCOUNT_UPDATE_SETTINGS_URI_COLUMN));
+        super(cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.NAME)), "unknown");
+        accountFromAddresses = cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.ACCOUNT_FROM_ADDRESSES));
+
+        final int capabilitiesColumnIndex =
+                cursor.getColumnIndex(UIProvider.AccountColumns.CAPABILITIES);
+        if (capabilitiesColumnIndex != -1) {
+            capabilities = cursor.getInt(capabilitiesColumnIndex);
+        } else {
+            capabilities = 0;
+        }
+
+        providerVersion =
+                cursor.getInt(cursor.getColumnIndex(UIProvider.AccountColumns.PROVIDER_VERSION));
+        uri = Uri.parse(cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.URI)));
+        folderListUri = Uri.parse(
+                cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.FOLDER_LIST_URI)));
+        fullFolderListUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI)));
+        searchUri = Utils.getValidUri(
+                cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.SEARCH_URI)));
+        expungeMessageUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.EXPUNGE_MESSAGE_URI)));
+        undoUri = Utils.getValidUri(
+                cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.UNDO_URI)));
+        settingsIntentUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SETTINGS_INTENT_URI)));
+        helpIntentUri = Utils.getValidUri(
+                cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.HELP_INTENT_URI)));
+        sendFeedbackIntentUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SEND_FEEDBACK_INTENT_URI)));
+        reauthenticationIntentUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.REAUTHENTICATION_INTENT_URI)));
+        syncStatus = cursor.getInt(cursor.getColumnIndex(UIProvider.AccountColumns.SYNC_STATUS));
+        composeIntentUri = Utils.getValidUri(
+                cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.COMPOSE_URI)));
+        mimeType = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.MIME_TYPE));
+        recentFolderListUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI)));
+        color = cursor.getInt(cursor.getColumnIndex(UIProvider.AccountColumns.COLOR));
+        defaultRecentFolderListUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI)));
+        manualSyncUri = Utils.getValidUri(
+                cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.MANUAL_SYNC_URI)));
+        viewIntentProxyUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.VIEW_INTENT_PROXY_URI)));
+        accoutCookieQueryUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.ACCOUNT_COOKIE_QUERY_URI)));
+        updateSettingsUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.UPDATE_SETTINGS_URI)));
         settings = new Settings(cursor);
     }
 
@@ -415,7 +407,7 @@
      * @param cursor cursor pointing to the list of accounts
      * @return the array of all accounts stored at this cursor.
      */
-    public static Account[] getAllAccounts(Cursor cursor) {
+    public static Account[] getAllAccounts(ObjectCursor<Account> cursor) {
         final int initialLength = cursor.getCount();
         if (initialLength <= 0 || !cursor.moveToFirst()) {
             // Return zero length account array rather than null
@@ -425,7 +417,7 @@
         final Account[] allAccounts = new Account[initialLength];
         int i = 0;
         do {
-            allAccounts[i++] = new Account(cursor);
+            allAccounts[i++] = cursor.getModel();
         } while (cursor.moveToNext());
         // Ensure that the length of the array is accurate
         assert (i == initialLength);
@@ -463,8 +455,6 @@
         dest.writeParcelable(fullFolderListUri, 0);
         dest.writeParcelable(searchUri, 0);
         dest.writeString(accountFromAddresses);
-        dest.writeParcelable(saveDraftUri, 0);
-        dest.writeParcelable(sendMessageUri, 0);
         dest.writeParcelable(expungeMessageUri, 0);
         dest.writeParcelable(undoUri, 0);
         dest.writeParcelable(settingsIntentUri, 0);
@@ -508,9 +498,6 @@
         sb.append(",searchUri=");
         sb.append(searchUri);
         sb.append(",saveDraftUri=");
-        sb.append(saveDraftUri);
-        sb.append(",sendMessageUri=");
-        sb.append(sendMessageUri);
         sb.append(",expungeMessageUri=");
         sb.append(expungeMessageUri);
         sb.append(",undoUri=");
@@ -559,8 +546,6 @@
                 Objects.equal(fullFolderListUri, other.fullFolderListUri) &&
                 Objects.equal(searchUri, other.searchUri) &&
                 Objects.equal(accountFromAddresses, other.accountFromAddresses) &&
-                Objects.equal(saveDraftUri, other.saveDraftUri) &&
-                Objects.equal(sendMessageUri, other.sendMessageUri) &&
                 Objects.equal(expungeMessageUri, other.expungeMessageUri) &&
                 Objects.equal(undoUri, other.undoUri) &&
                 Objects.equal(settingsIntentUri, other.settingsIntentUri) &&
@@ -601,12 +586,11 @@
     public int hashCode() {
         return super.hashCode()
                 ^ Objects.hashCode(name, type, capabilities, providerVersion, uri, folderListUri,
-                        fullFolderListUri, searchUri, accountFromAddresses, saveDraftUri,
-                        sendMessageUri, expungeMessageUri, undoUri, settingsIntentUri,
-                        helpIntentUri, sendFeedbackIntentUri, reauthenticationIntentUri, syncStatus,
-                        composeIntentUri, mimeType, recentFolderListUri, color,
-                        defaultRecentFolderListUri, viewIntentProxyUri, accoutCookieQueryUri,
-                        updateSettingsUri);
+                        fullFolderListUri, searchUri, accountFromAddresses, expungeMessageUri,
+                        undoUri, settingsIntentUri, helpIntentUri, sendFeedbackIntentUri,
+                        reauthenticationIntentUri, syncStatus, composeIntentUri, mimeType,
+                        recentFolderListUri, color, defaultRecentFolderListUri, viewIntentProxyUri,
+                        accoutCookieQueryUri, updateSettingsUri);
     }
 
     /**
@@ -699,4 +683,83 @@
         }
         return -1;
     }
+
+    /**
+     * Creates a {@link Map} where the column name is the key and the value is the value, which can
+     * be used for populating a {@link MatrixCursor}.
+     */
+    public Map<String, Object> getMatrixCursorValueMap() {
+        // ImmutableMap.Builder does not allow null values
+        final Map<String, Object> map = new HashMap<String, Object>();
+
+        map.put(UIProvider.AccountColumns._ID, 0);
+        map.put(UIProvider.AccountColumns.NAME, name);
+        map.put(UIProvider.AccountColumns.TYPE, type);
+        map.put(UIProvider.AccountColumns.PROVIDER_VERSION, providerVersion);
+        map.put(UIProvider.AccountColumns.URI, uri);
+        map.put(UIProvider.AccountColumns.CAPABILITIES, capabilities);
+        map.put(UIProvider.AccountColumns.FOLDER_LIST_URI, folderListUri);
+        map.put(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, fullFolderListUri);
+        map.put(UIProvider.AccountColumns.SEARCH_URI, searchUri);
+        map.put(UIProvider.AccountColumns.ACCOUNT_FROM_ADDRESSES, accountFromAddresses);
+        map.put(UIProvider.AccountColumns.EXPUNGE_MESSAGE_URI, expungeMessageUri);
+        map.put(UIProvider.AccountColumns.UNDO_URI, undoUri);
+        map.put(UIProvider.AccountColumns.SETTINGS_INTENT_URI, settingsIntentUri);
+        map.put(UIProvider.AccountColumns.HELP_INTENT_URI, helpIntentUri);
+        map.put(UIProvider.AccountColumns.SEND_FEEDBACK_INTENT_URI, sendFeedbackIntentUri);
+        map.put(
+                UIProvider.AccountColumns.REAUTHENTICATION_INTENT_URI, reauthenticationIntentUri);
+        map.put(UIProvider.AccountColumns.SYNC_STATUS, syncStatus);
+        map.put(UIProvider.AccountColumns.COMPOSE_URI, composeIntentUri);
+        map.put(UIProvider.AccountColumns.MIME_TYPE, mimeType);
+        map.put(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI, recentFolderListUri);
+        map.put(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
+                defaultRecentFolderListUri);
+        map.put(UIProvider.AccountColumns.MANUAL_SYNC_URI, manualSyncUri);
+        map.put(UIProvider.AccountColumns.VIEW_INTENT_PROXY_URI, viewIntentProxyUri);
+        map.put(UIProvider.AccountColumns.ACCOUNT_COOKIE_QUERY_URI, accoutCookieQueryUri);
+        map.put(UIProvider.AccountColumns.COLOR, color);
+        map.put(UIProvider.AccountColumns.UPDATE_SETTINGS_URI, updateSettingsUri);
+        map.put(AccountColumns.SettingsColumns.SIGNATURE, settings.signature);
+        map.put(AccountColumns.SettingsColumns.AUTO_ADVANCE, settings.getAutoAdvanceSetting());
+        map.put(AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, settings.messageTextSize);
+        map.put(AccountColumns.SettingsColumns.SNAP_HEADERS, settings.snapHeaders);
+        map.put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR, settings.replyBehavior);
+        map.put(
+                AccountColumns.SettingsColumns.HIDE_CHECKBOXES, settings.hideCheckboxes ? 1 : 0);
+        map.put(AccountColumns.SettingsColumns.CONFIRM_DELETE, settings.confirmDelete ? 1 : 0);
+        map.put(
+                AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, settings.confirmArchive ? 1 : 0);
+        map.put(AccountColumns.SettingsColumns.CONFIRM_SEND, settings.confirmSend ? 1 : 0);
+        map.put(AccountColumns.SettingsColumns.DEFAULT_INBOX, settings.defaultInbox);
+        map.put(AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME, settings.defaultInboxName);
+        map.put(AccountColumns.SettingsColumns.FORCE_REPLY_FROM_DEFAULT,
+                settings.forceReplyFromDefault ? 1 : 0);
+        map.put(AccountColumns.SettingsColumns.MAX_ATTACHMENT_SIZE, settings.maxAttachmentSize);
+        map.put(AccountColumns.SettingsColumns.SWIPE, settings.swipe);
+        map.put(AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED,
+                settings.priorityArrowsEnabled ? 1 : 0);
+        map.put(AccountColumns.SettingsColumns.SETUP_INTENT_URI, settings.setupIntentUri);
+        map.put(AccountColumns.SettingsColumns.CONVERSATION_VIEW_MODE,
+                settings.conversationViewMode);
+        map.put(AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN,
+                settings.veiledAddressPattern);
+
+        return map;
+    }
+
+    /**
+     * Public object that knows how to construct Accounts given Cursors.
+     */
+    public final static CursorCreator<Account> FACTORY = new CursorCreator<Account>() {
+        @Override
+        public Account createFromCursor(Cursor c) {
+            return new Account(c);
+        }
+
+        @Override
+        public String toString() {
+            return "Account CursorCreator";
+        }
+    };
 }
diff --git a/src/com/android/mail/providers/AllAccountObserver.java b/src/com/android/mail/providers/AllAccountObserver.java
new file mode 100644
index 0000000..50ce259
--- /dev/null
+++ b/src/com/android/mail/providers/AllAccountObserver.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.mail.providers;
+
+import com.android.mail.ui.AccountController;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+
+import android.database.DataSetObserver;
+
+/**
+ * A simple extension of {@link android.database.DataSetObserver} to provide all Accounts in
+ * {@link #onChanged(Account[])} when the list of Accounts changes. Initializing the object
+ * registers with the observer with the {@link com.android.mail.ui.AccountController} provided.
+ * The object will then begin to receive {@link #onChanged(Account[])} till {@link
+ * #unregisterAndDestroy()} is called. <p> To implement an {@link com.android.mail.providers
+ * .AllAccountObserver}, you need to implement the {@link #onChanged(Account[])} method.
+ */
+public abstract class AllAccountObserver extends DataSetObserver {
+    /**
+     * The AccountController that the observer is registered with.
+     */
+    private AccountController mController;
+
+    private static final String LOG_TAG = LogTag.getLogTag();
+
+    /**
+     * The no-argument constructor leaves the object unusable till
+     * {@link #initialize(com.android.mail.ui.AccountController)} is called.
+     */
+    public AllAccountObserver() {
+    }
+
+    /**
+     * Initializes an {@link com.android.mail.providers.AllAccountObserver} object that receives
+     * a call to {@link #onChanged(Account[])} when the controller changes the list of accounts.
+     *
+     * @param controller
+     */
+    public Account[] initialize(AccountController controller) {
+        if (controller == null) {
+            LogUtils.wtf(LOG_TAG, "AllAccountObserver initialized with null controller!");
+        }
+        mController = controller;
+        mController.registerAllAccountObserver(this);
+        return mController.getAllAccounts();
+    }
+
+    @Override
+    public final void onChanged() {
+        if (mController == null) {
+            return;
+        }
+        onChanged(mController.getAllAccounts());
+    }
+
+    /**
+     * Callback invoked when the list of Accounts changes.
+     * The updated list is passed as the argument.
+     * @param allAccounts the array of all accounts in the current application.
+     */
+    public abstract void onChanged(Account[] allAccounts);
+
+    /**
+     * Return the array of existing accounts.
+     * @return the array of existing accounts.
+     */
+    public final Account[] getAllAccounts() {
+        if (mController == null) {
+            return null;
+        }
+        return mController.getAllAccounts();
+    }
+
+    /**
+     * Unregisters for list of Account changes and makes the object unusable.
+     */
+    public void unregisterAndDestroy() {
+        if (mController == null) {
+            return;
+        }
+        mController.unregisterAllAccountObserver(this);
+    }
+}
diff --git a/src/com/android/mail/providers/Attachment.java b/src/com/android/mail/providers/Attachment.java
index 8a2e81d..0542998 100644
--- a/src/com/android/mail/providers/Attachment.java
+++ b/src/com/android/mail/providers/Attachment.java
@@ -43,6 +43,11 @@
 
 public class Attachment implements Parcelable {
     public static final String LOG_TAG = LogTag.getLogTag();
+    /**
+     * Workaround for b/8070022 so that appending a null partId to the end of a
+     * uri wouldn't remove the trailing backslash
+     */
+    public static final String EMPTY_PART_ID = "empty";
 
     /**
      * Part id of the attachment.
@@ -201,16 +206,16 @@
     public JSONObject toJSON() throws JSONException {
         final JSONObject result = new JSONObject();
 
-        result.putOpt(AttachmentColumns.NAME, name);
-        result.putOpt(AttachmentColumns.SIZE, size);
-        result.putOpt(AttachmentColumns.URI, stringify(uri));
-        result.putOpt(AttachmentColumns.CONTENT_TYPE, contentType);
-        result.putOpt(AttachmentColumns.STATE, state);
-        result.putOpt(AttachmentColumns.DESTINATION, destination);
-        result.putOpt(AttachmentColumns.DOWNLOADED_SIZE, downloadedSize);
-        result.putOpt(AttachmentColumns.CONTENT_URI, stringify(contentUri));
-        result.putOpt(AttachmentColumns.THUMBNAIL_URI, stringify(thumbnailUri));
-        result.putOpt(AttachmentColumns.PREVIEW_INTENT_URI, stringify(previewIntentUri));
+        result.put(AttachmentColumns.NAME, name);
+        result.put(AttachmentColumns.SIZE, size);
+        result.put(AttachmentColumns.URI, stringify(uri));
+        result.put(AttachmentColumns.CONTENT_TYPE, contentType);
+        result.put(AttachmentColumns.STATE, state);
+        result.put(AttachmentColumns.DESTINATION, destination);
+        result.put(AttachmentColumns.DOWNLOADED_SIZE, downloadedSize);
+        result.put(AttachmentColumns.CONTENT_URI, stringify(contentUri));
+        result.put(AttachmentColumns.THUMBNAIL_URI, stringify(thumbnailUri));
+        result.put(AttachmentColumns.PREVIEW_INTENT_URI, stringify(previewIntentUri));
         result.put(AttachmentColumns.PROVIDER_DATA, providerData);
 
         return result;
@@ -222,7 +227,12 @@
             final JSONObject jsonObject = toJSON();
             // Add some additional fields that are helpful when debugging issues
             jsonObject.put("partId", partId);
-            return jsonObject.toString();
+            try {
+                // pretty print the provider data
+                jsonObject.put(AttachmentColumns.PROVIDER_DATA, new JSONObject(providerData));
+            } catch (JSONException e) {
+            }
+            return jsonObject.toString(4);
         } catch (JSONException e) {
             LogUtils.e(LOG_TAG, e, "JSONException in toString");
             return super.toString();
diff --git a/src/com/android/mail/providers/Conversation.java b/src/com/android/mail/providers/Conversation.java
index 4646085..f978d00 100644
--- a/src/com/android/mail/providers/Conversation.java
+++ b/src/com/android/mail/providers/Conversation.java
@@ -16,6 +16,7 @@
 
 package com.android.mail.providers;
 
+import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
@@ -97,6 +98,10 @@
      */
     public boolean read;
     /**
+     * @see UIProvider.ConversationColumns#SEEN
+     */
+    public boolean seen;
+    /**
      * @see UIProvider.ConversationColumns#STARRED
      */
     public boolean starred;
@@ -190,6 +195,7 @@
         dest.writeInt(sendingState);
         dest.writeInt(priority);
         dest.writeInt(read ? 1 : 0);
+        dest.writeInt(seen ? 1 : 0);
         dest.writeInt(starred ? 1 : 0);
         dest.writeParcelable(rawFolders, 0);
         dest.writeInt(convFlags);
@@ -218,6 +224,7 @@
         sendingState = in.readInt();
         priority = in.readInt();
         read = (in.readInt() != 0);
+        seen = (in.readInt() != 0);
         starred = (in.readInt() != 0);
         rawFolders = in.readParcelable(loader);
         convFlags = in.readInt();
@@ -290,6 +297,7 @@
             sendingState = cursor.getInt(UIProvider.CONVERSATION_SENDING_STATE_COLUMN);
             priority = cursor.getInt(UIProvider.CONVERSATION_PRIORITY_COLUMN);
             read = cursor.getInt(UIProvider.CONVERSATION_READ_COLUMN) != 0;
+            seen = cursor.getInt(UIProvider.CONVERSATION_SEEN_COLUMN) != 0;
             starred = cursor.getInt(UIProvider.CONVERSATION_STARRED_COLUMN) != 0;
             rawFolders = FolderList.fromBlob(
                     cursor.getBlob(UIProvider.CONVERSATION_RAW_FOLDERS_COLUMN));
@@ -319,15 +327,52 @@
         }
     }
 
+    public Conversation(Conversation other) {
+        if (other == null) {
+            return;
+        }
+
+        id = other.id;
+        uri = other.uri;
+        dateMs = other.dateMs;
+        subject = other.subject;
+        hasAttachments = other.hasAttachments;
+        messageListUri = other.messageListUri;
+        sendingState = other.sendingState;
+        priority = other.priority;
+        read = other.read;
+        seen = other.seen;
+        starred = other.starred;
+        rawFolders = other.rawFolders; // FolderList is immutable, shallow copy is OK
+        convFlags = other.convFlags;
+        personalLevel = other.personalLevel;
+        spam = other.spam;
+        phishing = other.phishing;
+        muted = other.muted;
+        color = other.color;
+        accountUri = other.accountUri;
+        position = other.position;
+        localDeleteOnUpdate = other.localDeleteOnUpdate;
+        // although ConversationInfo is mutable (see ConversationInfo.markRead), applyCachedValues
+        // will overwrite this if cached changes exist anyway, so a shallow copy is OK
+        conversationInfo = other.conversationInfo;
+        conversationBaseUri = other.conversationBaseUri;
+        snippet = other.snippet;
+        senders = other.senders;
+        numMessages = other.numMessages;
+        numDrafts = other.numDrafts;
+        isRemote = other.isRemote;
+    }
+
     public Conversation() {
     }
 
     public static Conversation create(long id, Uri uri, String subject, long dateMs,
             String snippet, boolean hasAttachment, Uri messageListUri, String senders,
             int numMessages, int numDrafts, int sendingState, int priority, boolean read,
-            boolean starred, FolderList rawFolders, int convFlags, int personalLevel, boolean spam,
-            boolean phishing, boolean muted, Uri accountUri, ConversationInfo conversationInfo,
-            Uri conversationBase, boolean isRemote) {
+            boolean seen, boolean starred, FolderList rawFolders, int convFlags, int personalLevel,
+            boolean spam, boolean phishing, boolean muted, Uri accountUri,
+            ConversationInfo conversationInfo, Uri conversationBase, boolean isRemote) {
 
         final Conversation conversation = new Conversation();
 
@@ -344,6 +389,7 @@
         conversation.sendingState = sendingState;
         conversation.priority = priority;
         conversation.read = read;
+        conversation.seen = seen;
         conversation.starred = starred;
         conversation.rawFolders = rawFolders;
         conversation.convFlags = convFlags;
@@ -360,6 +406,40 @@
     }
 
     /**
+     * Apply any column values from the given {@link ContentValues} (where column names are the
+     * keys) to this conversation.
+     *
+     */
+    public void applyCachedValues(ContentValues values) {
+        if (values == null) {
+            return;
+        }
+        for (String key : values.keySet()) {
+            final Object val = values.get(key);
+            LogUtils.i(LOG_TAG, "Conversation: applying cached value to col=%s val=%s", key,
+                    val);
+            if (ConversationColumns.READ.equals(key)) {
+                read = (Integer) val != 0;
+            } else if (ConversationColumns.CONVERSATION_INFO.equals(key)) {
+                conversationInfo = ConversationInfo.fromBlob((byte[]) val);
+            } else if (ConversationColumns.FLAGS.equals(key)) {
+                convFlags = (Integer) val;
+            } else if (ConversationColumns.STARRED.equals(key)) {
+                starred = (Integer) val != 0;
+            } else if (ConversationColumns.SEEN.equals(key)) {
+                seen = (Integer) val != 0;
+            } else if (ConversationColumns.RAW_FOLDERS.equals(key)) {
+                rawFolders = FolderList.fromBlob((byte[]) val);
+            } else if (ConversationColumns.VIEWED.equals(key)) {
+                // ignore. this is not read from the cursor, either.
+            } else {
+                LogUtils.e(LOG_TAG, new UnsupportedOperationException(),
+                        "unsupported cached conv value in col=%s", key);
+            }
+        }
+    }
+
+    /**
      * Get the <strong>immutable</strong> list of {@link Folder}s for this conversation. To modify
      * this list, make a new {@link FolderList} and use {@link #setRawFolders(FolderList)}.
      *
@@ -378,12 +458,12 @@
         cachedDisplayableFolders = null;
     }
 
-    public ArrayList<Folder> getRawFoldersForDisplay(Folder ignoreFolder) {
+    public ArrayList<Folder> getRawFoldersForDisplay(final Uri ignoreFolderUri) {
         if (cachedDisplayableFolders == null) {
             cachedDisplayableFolders = new ArrayList<Folder>();
             for (Folder folder : rawFolders.folders) {
                 // skip the ignoreFolder
-                if (ignoreFolder != null && ignoreFolder.equals(folder)) {
+                if (ignoreFolderUri != null && ignoreFolderUri.equals(folder.uri)) {
                     continue;
                 }
                 cachedDisplayableFolders.add(folder);
@@ -480,7 +560,7 @@
         }
     }
 
-    private String getSendersDelimeter(Context context) {
+    private static String getSendersDelimeter(Context context) {
         if (sSendersDelimeter == null) {
             sSendersDelimeter = context.getResources().getString(R.string.senders_split_token);
         }
@@ -550,8 +630,14 @@
         if (sSubjectAndSnippet == null) {
             sSubjectAndSnippet = context.getString(R.string.subject_and_snippet);
         }
-        return (!TextUtils.isEmpty(snippet)) ?
-                String.format(sSubjectAndSnippet, filteredSubject, snippet)
-                : filteredSubject;
+        if (TextUtils.isEmpty(filteredSubject) && TextUtils.isEmpty(snippet)) {
+            return "";
+        } else if (TextUtils.isEmpty(filteredSubject)) {
+            return snippet;
+        } else if (TextUtils.isEmpty(snippet)) {
+            return filteredSubject;
+        }
+
+        return String.format(sSubjectAndSnippet, filteredSubject, snippet);
     }
 }
diff --git a/src/com/android/mail/providers/Folder.java b/src/com/android/mail/providers/Folder.java
index 57bb5bd..928d6bb 100644
--- a/src/com/android/mail/providers/Folder.java
+++ b/src/com/android/mail/providers/Folder.java
@@ -18,7 +18,6 @@
 package com.android.mail.providers;
 
 import android.content.Context;
-import android.content.CursorLoader;
 import android.database.Cursor;
 import android.graphics.drawable.PaintDrawable;
 import android.net.Uri;
@@ -29,6 +28,9 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.mail.content.CursorCreator;
+import com.android.mail.content.ObjectCursorLoader;
+import com.android.mail.providers.UIProvider.FolderType;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
@@ -39,7 +41,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
-import java.util.regex.Pattern;
 
 /**
  * A folder is a collection of conversations, and perhaps other folders.
@@ -64,6 +65,11 @@
     public int id;
 
     /**
+     * Persistent (across installations) id of this folder.
+     */
+    public String persistentId;
+
+    /**
      * The content provider URI that returns this folder for this account.
      */
     public Uri uri;
@@ -101,6 +107,11 @@
     public Uri childFoldersListUri;
 
     /**
+     * The number of messages that are unseen in this folder.
+     */
+    public int unseenCount;
+
+    /**
      * The number of messages that are unread in this folder.
      */
     public int unreadCount;
@@ -136,8 +147,12 @@
     /**
      * Icon for this folder; 0 implies no icon.
      */
-    // FIXME: resource IDs are ints, not longs.
-    public long iconResId;
+    public int iconResId;
+
+    /**
+     * Notification icon for this folder; 0 implies no icon.
+     */
+    public int notificationIconResId;
 
     public String bgColor;
     public String fgColor;
@@ -160,21 +175,23 @@
      */
     public Folder parent;
 
+    /**
+     * The time at which the last message was received.
+     */
+    public long lastMessageTimestamp;
+
     /** An immutable, empty conversation list */
     public static final Collection<Folder> EMPTY = Collections.emptyList();
 
-    @Deprecated
-    public static final String SPLITTER = "^*^";
-    @Deprecated
-    private static final Pattern SPLITTER_REGEX = Pattern.compile("\\^\\*\\^");
-
     // TODO: we desperately need a Builder here
-    public Folder(int id, Uri uri, String name, int capabilities, boolean hasChildren,
-            int syncWindow, Uri conversationListUri, Uri childFoldersListUri, int unreadCount,
-            int totalCount, Uri refreshUri, int syncStatus, int lastSyncResult, int type,
-            long iconResId, String bgColor, String fgColor, Uri loadMoreUri,
-            String hierarchicalDesc, Folder parent) {
+    public Folder(int id, String persistentId, Uri uri, String name, int capabilities,
+            boolean hasChildren, int syncWindow, Uri conversationListUri, Uri childFoldersListUri,
+            int unseenCount, int unreadCount, int totalCount, Uri refreshUri, int syncStatus,
+            int lastSyncResult, int type, int iconResId, int notificationIconResId, String bgColor,
+            String fgColor, Uri loadMoreUri, String hierarchicalDesc, Folder parent,
+            final long lastMessageTimestamp) {
         this.id = id;
+        this.persistentId = persistentId;
         this.uri = uri;
         this.name = name;
         this.capabilities = capabilities;
@@ -182,6 +199,7 @@
         this.syncWindow = syncWindow;
         this.conversationListUri = conversationListUri;
         this.childFoldersListUri = childFoldersListUri;
+        this.unseenCount = unseenCount;
         this.unreadCount = unreadCount;
         this.totalCount = totalCount;
         this.refreshUri = refreshUri;
@@ -189,39 +207,18 @@
         this.lastSyncResult = lastSyncResult;
         this.type = type;
         this.iconResId = iconResId;
+        this.notificationIconResId = notificationIconResId;
         this.bgColor = bgColor;
         this.fgColor = fgColor;
         this.loadMoreUri = loadMoreUri;
         this.hierarchicalDesc = hierarchicalDesc;
         this.parent = parent;
+        this.lastMessageTimestamp = lastMessageTimestamp;
     }
 
-    public Folder(Parcel in, ClassLoader loader) {
-        id = in.readInt();
-        uri = in.readParcelable(loader);
-        name = in.readString();
-        capabilities = in.readInt();
-        // 1 for true, 0 for false.
-        hasChildren = in.readInt() == 1;
-        syncWindow = in.readInt();
-        conversationListUri = in.readParcelable(loader);
-        childFoldersListUri = in.readParcelable(loader);
-        unreadCount = in.readInt();
-        totalCount = in.readInt();
-        refreshUri = in.readParcelable(loader);
-        syncStatus = in.readInt();
-        lastSyncResult = in.readInt();
-        type = in.readInt();
-        iconResId = in.readLong();
-        bgColor = in.readString();
-        fgColor = in.readString();
-        loadMoreUri = in.readParcelable(loader);
-        hierarchicalDesc = in.readString();
-        parent = in.readParcelable(loader);
-     }
-
     public Folder(Cursor cursor) {
         id = cursor.getInt(UIProvider.FOLDER_ID_COLUMN);
+        persistentId = cursor.getString(UIProvider.FOLDER_PERSISTENT_ID_COLUMN);
         uri = Uri.parse(cursor.getString(UIProvider.FOLDER_URI_COLUMN));
         name = cursor.getString(UIProvider.FOLDER_NAME_COLUMN);
         capabilities = cursor.getInt(UIProvider.FOLDER_CAPABILITIES_COLUMN);
@@ -233,6 +230,7 @@
         String childList = cursor.getString(UIProvider.FOLDER_CHILD_FOLDERS_LIST_COLUMN);
         childFoldersListUri = (hasChildren && !TextUtils.isEmpty(childList)) ? Uri.parse(childList)
                 : null;
+        unseenCount = cursor.getInt(UIProvider.FOLDER_UNSEEN_COUNT_COLUMN);
         unreadCount = cursor.getInt(UIProvider.FOLDER_UNREAD_COUNT_COLUMN);
         totalCount = cursor.getInt(UIProvider.FOLDER_TOTAL_COUNT_COLUMN);
         String refresh = cursor.getString(UIProvider.FOLDER_REFRESH_URI_COLUMN);
@@ -240,18 +238,64 @@
         syncStatus = cursor.getInt(UIProvider.FOLDER_SYNC_STATUS_COLUMN);
         lastSyncResult = cursor.getInt(UIProvider.FOLDER_LAST_SYNC_RESULT_COLUMN);
         type = cursor.getInt(UIProvider.FOLDER_TYPE_COLUMN);
-        iconResId = cursor.getLong(UIProvider.FOLDER_ICON_RES_ID_COLUMN);
+        iconResId = cursor.getInt(UIProvider.FOLDER_ICON_RES_ID_COLUMN);
+        notificationIconResId = cursor.getInt(UIProvider.FOLDER_NOTIFICATION_ICON_RES_ID_COLUMN);
         bgColor = cursor.getString(UIProvider.FOLDER_BG_COLOR_COLUMN);
         fgColor = cursor.getString(UIProvider.FOLDER_FG_COLOR_COLUMN);
         String loadMore = cursor.getString(UIProvider.FOLDER_LOAD_MORE_URI_COLUMN);
         loadMoreUri = !TextUtils.isEmpty(loadMore) ? Uri.parse(loadMore) : null;
         hierarchicalDesc = cursor.getString(UIProvider.FOLDER_HIERARCHICAL_DESC_COLUMN);
         parent = null;
+        lastMessageTimestamp = cursor.getLong(UIProvider.FOLDER_LAST_MESSAGE_TIMESTAMP_COLUMN);
     }
 
+    /**
+     * Public object that knows how to construct Folders given Cursors.
+     */
+    public static final CursorCreator<Folder> FACTORY = new CursorCreator<Folder>() {
+        @Override
+        public Folder createFromCursor(Cursor c) {
+            return new Folder(c);
+        }
+
+        @Override
+        public String toString() {
+            return "Folder CursorCreator";
+        }
+    };
+
+    public Folder(Parcel in, ClassLoader loader) {
+        id = in.readInt();
+        persistentId = in.readString();
+        uri = in.readParcelable(loader);
+        name = in.readString();
+        capabilities = in.readInt();
+        // 1 for true, 0 for false.
+        hasChildren = in.readInt() == 1;
+        syncWindow = in.readInt();
+        conversationListUri = in.readParcelable(loader);
+        childFoldersListUri = in.readParcelable(loader);
+        unseenCount = in.readInt();
+        unreadCount = in.readInt();
+        totalCount = in.readInt();
+        refreshUri = in.readParcelable(loader);
+        syncStatus = in.readInt();
+        lastSyncResult = in.readInt();
+        type = in.readInt();
+        iconResId = in.readInt();
+        notificationIconResId = in.readInt();
+        bgColor = in.readString();
+        fgColor = in.readString();
+        loadMoreUri = in.readParcelable(loader);
+        hierarchicalDesc = in.readString();
+        parent = in.readParcelable(loader);
+        lastMessageTimestamp = in.readLong();
+     }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(id);
+        dest.writeString(persistentId);
         dest.writeParcelable(uri, 0);
         dest.writeString(name);
         dest.writeInt(capabilities);
@@ -260,31 +304,35 @@
         dest.writeInt(syncWindow);
         dest.writeParcelable(conversationListUri, 0);
         dest.writeParcelable(childFoldersListUri, 0);
+        dest.writeInt(unseenCount);
         dest.writeInt(unreadCount);
         dest.writeInt(totalCount);
         dest.writeParcelable(refreshUri, 0);
         dest.writeInt(syncStatus);
         dest.writeInt(lastSyncResult);
         dest.writeInt(type);
-        dest.writeLong(iconResId);
+        dest.writeInt(iconResId);
+        dest.writeInt(notificationIconResId);
         dest.writeString(bgColor);
         dest.writeString(fgColor);
         dest.writeParcelable(loadMoreUri, 0);
         dest.writeString(hierarchicalDesc);
         dest.writeParcelable(parent, 0);
+        dest.writeLong(lastMessageTimestamp);
     }
 
     /**
      * Construct a folder that queries for search results. Do not call on the UI
      * thread.
      */
-    public static CursorLoader forSearchResults(Account account, String query, Context context) {
+    public static ObjectCursorLoader<Folder> forSearchResults(Account account, String query,
+            Context context) {
         if (account.searchUri != null) {
-            Builder searchBuilder = account.searchUri.buildUpon();
+            final Builder searchBuilder = account.searchUri.buildUpon();
             searchBuilder.appendQueryParameter(UIProvider.SearchQueryParameters.QUERY, query);
-            Uri searchUri = searchBuilder.build();
-            return new CursorLoader(context, searchUri, UIProvider.FOLDERS_PROJECTION, null, null,
-                    null);
+            final Uri searchUri = searchBuilder.build();
+            return new ObjectCursorLoader<Folder>(context, searchUri, UIProvider.FOLDERS_PROJECTION,
+                    FACTORY);
         }
         return null;
     }
@@ -297,13 +345,6 @@
         return folders;
     }
 
-    private static Uri getValidUri(String uri) {
-        if (TextUtils.isEmpty(uri)) {
-            return null;
-        }
-        return Uri.parse(uri);
-    }
-
     /**
      * Constructor that leaves everything uninitialized.
      */
@@ -321,7 +362,6 @@
         return new Folder();
     }
 
-    @SuppressWarnings("hiding")
     public static final ClassLoaderCreator<Folder> CREATOR = new ClassLoaderCreator<Folder>() {
         @Override
         public Folder createFromParcel(Parcel source) {
@@ -395,7 +435,8 @@
         if (colorBlock == null) {
             return;
         }
-        boolean showBg = !TextUtils.isEmpty(folder.bgColor);
+        boolean showBg =
+                !TextUtils.isEmpty(folder.bgColor) && folder.type != FolderType.INBOX_SECTION;
         final int backgroundColor = showBg ? Integer.parseInt(folder.bgColor) : 0;
         if (backgroundColor == Utils.getDefaultFolderBackgroundColor(colorBlock.getContext())) {
             showBg = false;
@@ -415,12 +456,12 @@
         if (iconView == null) {
             return;
         }
-        final long icon = folder.iconResId;
+        final int icon = folder.iconResId;
         if (icon > 0) {
-            iconView.setImageResource((int)icon);
+            iconView.setImageResource(icon);
             iconView.setVisibility(View.VISIBLE);
         } else {
-            iconView.setVisibility(View.INVISIBLE);
+            iconView.setVisibility(View.GONE);
         }
     }
 
@@ -439,115 +480,6 @@
         return TextUtils.isEmpty(fgColor) ? defaultColor : Integer.parseInt(fgColor);
     }
 
-    @Deprecated
-    public static Folder fromString(String inString) {
-        if (TextUtils.isEmpty(inString)) {
-            return null;
-        }
-        final Folder f = new Folder();
-        int indexOf = inString.indexOf(SPLITTER);
-        int id = -1;
-        if (indexOf != -1) {
-            id = Integer.valueOf(inString.substring(0, indexOf));
-        } else {
-            // If no separator was found, we can't parse this folder and the
-            // TextUtils.split call would also fail. Return null.
-            LogUtils.w(LOG_TAG, "Problem parsing folderId: original string: %s", inString);
-            return null;
-        }
-        final String[] split = TextUtils.split(inString, SPLITTER_REGEX);
-        if (split.length < 20) {
-            return null;
-        }
-        f.id = id;
-        f.uri = Folder.getValidUri(split[1]);
-        f.name = split[2];
-        f.hasChildren = Integer.parseInt(split[3]) != 0;
-        f.capabilities = Integer.parseInt(split[4]);
-        f.syncWindow = Integer.parseInt(split[5]);
-        f.conversationListUri = getValidUri(split[6]);
-        f.childFoldersListUri = getValidUri(split[7]);
-        f.unreadCount = Integer.parseInt(split[8]);
-        f.totalCount = Integer.parseInt(split[9]);
-        f.refreshUri = getValidUri(split[10]);
-        f.syncStatus = Integer.parseInt(split[11]);
-        f.lastSyncResult = Integer.parseInt(split[12]);
-        f.type = Integer.parseInt(split[13]);
-        f.iconResId = Integer.parseInt(split[14]);
-        f.bgColor = split[15];
-        f.fgColor = split[16];
-        f.loadMoreUri = getValidUri(split[17]);
-        f.hierarchicalDesc = split[18];
-        f.parent = Folder.fromString(split[19]);
-        return f;
-    }
-
-    /**
-     * Create a string representation of a folder.
-     */
-    @Deprecated
-    public static String createAsString(int id, Uri uri, String name, boolean hasChildren,
-            int capabilities, int syncWindow, Uri convListUri, Uri childFoldersListUri,
-            int unreadCount, int totalCount, Uri refreshUri, int syncStatus, int lastSyncResult,
-            int type, long iconResId, String bgColor, String fgColor, Uri loadMore,
-            String hierarchicalDesc, Folder parent) {
-        StringBuilder builder = new StringBuilder();
-        builder.append(id);
-        builder.append(SPLITTER);
-        builder.append(uri != null ? uri : "");
-        builder.append(SPLITTER);
-        builder.append(name != null ? name : "");
-        builder.append(SPLITTER);
-        builder.append(hasChildren ? 1 : 0);
-        builder.append(SPLITTER);
-        builder.append(capabilities);
-        builder.append(SPLITTER);
-        builder.append(syncWindow);
-        builder.append(SPLITTER);
-        builder.append(convListUri != null ? convListUri : "");
-        builder.append(SPLITTER);
-        builder.append(childFoldersListUri != null ? childFoldersListUri : "");
-        builder.append(SPLITTER);
-        builder.append(unreadCount);
-        builder.append(SPLITTER);
-        builder.append(totalCount);
-        builder.append(SPLITTER);
-        builder.append(refreshUri != null ? refreshUri : "");
-        builder.append(SPLITTER);
-        builder.append(syncStatus);
-        builder.append(SPLITTER);
-        builder.append(lastSyncResult);
-        builder.append(SPLITTER);
-        builder.append(type);
-        builder.append(SPLITTER);
-        builder.append(iconResId);
-        builder.append(SPLITTER);
-        builder.append(bgColor != null ? bgColor : "");
-        builder.append(SPLITTER);
-        builder.append(fgColor != null ? fgColor : "");
-        builder.append(SPLITTER);
-        builder.append(loadMore != null ? loadMore : "");
-        builder.append(SPLITTER);
-        builder.append(hierarchicalDesc != null ? hierarchicalDesc : "");
-        builder.append(SPLITTER);
-        if (parent != null) {
-            builder.append(Folder.toString(parent));
-        } else {
-            builder.append("");
-        }
-        return builder.toString();
-    }
-
-    @Deprecated
-    public static String toString(Folder folder) {
-        return createAsString(folder.id, folder.uri, folder.name, folder.hasChildren,
-                folder.capabilities, folder.syncWindow, folder.conversationListUri,
-                folder.childFoldersListUri, folder.unreadCount, folder.totalCount,
-                folder.refreshUri, folder.syncStatus, folder.lastSyncResult, folder.type,
-                folder.iconResId, folder.bgColor, folder.fgColor, folder.loadMoreUri,
-                folder.hierarchicalDesc, folder.parent);
-    }
-
     /**
      * Returns a comma separated list of folder URIs for all the folders in the collection.
      * @param folders
@@ -692,6 +624,7 @@
         f.id = cursor.getInt(UIProvider.FOLDER_ID_COLUMN);
         f.uri = Utils.getValidUri(cursor.getString(UIProvider.FOLDER_URI_COLUMN));
         f.totalCount = cursor.getInt(UIProvider.FOLDER_TOTAL_COUNT_COLUMN);
+        f.unseenCount = cursor.getInt(UIProvider.FOLDER_UNSEEN_COUNT_COLUMN);
         f.unreadCount = cursor.getInt(UIProvider.FOLDER_UNREAD_COUNT_COLUMN);
         f.conversationListUri = Utils.getValidUri(cursor
                 .getString(UIProvider.FOLDER_CONVERSATION_LIST_URI_COLUMN));
@@ -699,6 +632,9 @@
         f.capabilities = cursor.getInt(UIProvider.FOLDER_CAPABILITIES_COLUMN);
         f.bgColor = cursor.getString(UIProvider.FOLDER_BG_COLOR_COLUMN);
         f.name = cursor.getString(UIProvider.FOLDER_NAME_COLUMN);
+        f.iconResId = cursor.getInt(UIProvider.FOLDER_ICON_RES_ID_COLUMN);
+        f.notificationIconResId = cursor.getInt(UIProvider.FOLDER_NOTIFICATION_ICON_RES_ID_COLUMN);
+        f.lastMessageTimestamp = cursor.getLong(UIProvider.FOLDER_LAST_MESSAGE_TIMESTAMP_COLUMN);
         return f;
     }
 }
diff --git a/src/com/android/mail/providers/FolderObserver.java b/src/com/android/mail/providers/FolderObserver.java
new file mode 100644
index 0000000..03c1ac8
--- /dev/null
+++ b/src/com/android/mail/providers/FolderObserver.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.mail.providers;
+
+import com.android.mail.ui.FolderController;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+
+import android.database.DataSetObserver;
+
+/**
+ * A simple extension of {@link android.database.DataSetObserver} to provide the updated Folder in
+ * {@link #onChanged(Folder)} when the Folder changes. Initializing the object registers with
+ * the observer with the {@link com.android.mail.ui.FolderController} provided. The object will then begin to
+ * receive {@link #onChanged(Folder)} till {@link #unregisterAndDestroy()} is called.
+ * <p>
+ * To implement an {@link FolderObserver}, you need to implement the {@link #onChanged(Folder)}
+ * method.
+ */
+public abstract class FolderObserver extends DataSetObserver {
+    /**
+     * The FolderController that the observer is registered with.
+     */
+    private FolderController mController;
+
+    private static final String LOG_TAG = LogTag.getLogTag();
+
+    /**
+     * The no-argument constructor leaves the object unusable till
+     * {@link #initialize(FolderController)} is called.
+     */
+    public FolderObserver () {
+    }
+
+    /**
+     * Initializes an {@link FolderObserver} object that receives a call to
+     * {@link #onChanged(Folder)} when the controller changes the Folder.
+     *
+     * @param controller
+     */
+    public Folder initialize(FolderController controller) {
+        if (controller == null) {
+            LogUtils.wtf(LOG_TAG, "FolderObserver initialized with null controller!");
+        }
+        mController = controller;
+        mController.registerFolderObserver(this);
+        return mController.getFolder();
+    }
+
+    @Override
+    public final void onChanged() {
+        if (mController == null) {
+            return;
+        }
+        onChanged(mController.getFolder());
+    }
+
+    /**
+     * Callback invoked when the Folder object is changed.  Since {@link Folder} objects are
+     * immutable, updates can be received on changes to individual settings (sync on/off)
+     * in addition to changes of Folders: alice@example.com -> bob@example.com.
+     * The updated Folder is passed as the argument.
+     * @param newFolder
+     */
+    public abstract void onChanged(Folder newFolder);
+
+    /**
+     * Return the current folder.
+     * @return
+     */
+    public final Folder getFolder() {
+        if (mController == null) {
+            return null;
+        }
+        return mController.getFolder();
+    }
+
+    /**
+     * Unregisters for Folder changes and makes the object unusable.
+     */
+    public void unregisterAndDestroy() {
+        if (mController == null) {
+            return;
+        }
+        mController.unregisterFolderObserver(this);
+    }
+}
diff --git a/src/com/android/mail/providers/MailAppProvider.java b/src/com/android/mail/providers/MailAppProvider.java
index 587170d..23c8965 100644
--- a/src/com/android/mail/providers/MailAppProvider.java
+++ b/src/com/android/mail/providers/MailAppProvider.java
@@ -27,18 +27,18 @@
 import android.content.Loader;
 import android.content.Loader.OnLoadCompleteListener;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.Bundle;
 
+import com.android.mail.R;
 import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
-import com.android.mail.providers.protos.boot.AccountReceiver;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.MatrixCursorWithExtra;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
@@ -86,7 +86,6 @@
     private ContentResolver mResolver;
     private static String sAuthority;
     private static MailAppProvider sInstance;
-    private final static Set<Uri> PENDING_ACCOUNT_URIS = Sets.newHashSet();
 
     private volatile boolean mAccountsFullyLoaded = false;
 
@@ -141,24 +140,19 @@
     @Override
     public boolean onCreate() {
         sAuthority = getAuthority();
+        sInstance = this;
         mResolver = getContext().getContentResolver();
 
-        final Intent intent = new Intent(AccountReceiver.ACTION_PROVIDER_CREATED);
-        getContext().sendBroadcast(intent);
-
         // Load the previously saved account list
         loadCachedAccountList();
 
-        synchronized (PENDING_ACCOUNT_URIS) {
-            sInstance = this;
+        final Resources res = getContext().getResources();
+        // Load the uris for the account list
+        final String[] accountQueryUris = res.getStringArray(R.array.account_providers);
 
-            // Handle the case where addAccountsForUriAsync was called before
-            // this Provider instance was created
-            final Set<Uri> urisToQery = ImmutableSet.copyOf(PENDING_ACCOUNT_URIS);
-            PENDING_ACCOUNT_URIS.clear();
-            for (Uri accountQueryUri : urisToQery) {
-                addAccountsForUriAsync(accountQueryUri);
-            }
+        for (String accountQueryUri : accountQueryUris) {
+            final Uri uri = Uri.parse(accountQueryUri);
+            addAccountsForUriAsync(uri);
         }
 
         return true;
@@ -166,9 +160,7 @@
 
     @Override
     public void shutdown() {
-        synchronized (PENDING_ACCOUNT_URIS) {
-            sInstance = null;
-        }
+        sInstance = null;
 
         for (CursorLoader loader : mCursorLoaderMap.values()) {
             loader.stopLoading();
@@ -199,149 +191,13 @@
         for (AccountCacheEntry accountEntry : accountList) {
             final Account account = accountEntry.mAccount;
             final MatrixCursor.RowBuilder builder = cursor.newRow();
+            final Map<String, Object> accountValues = account.getMatrixCursorValueMap();
 
             for (final String columnName : resultProjection) {
-                final int column = UIProvider.getAccountColumn(columnName);
-                switch (column) {
-                    case UIProvider.ACCOUNT_ID_COLUMN:
-                        builder.add(Integer.valueOf(0));
-                        break;
-                    case UIProvider.ACCOUNT_NAME_COLUMN:
-                        builder.add(account.name);
-                        break;
-                    case UIProvider.ACCOUNT_PROVIDER_VERISON_COLUMN:
-                        // TODO fix this
-                        builder.add(Integer.valueOf(account.providerVersion));
-                        break;
-                    case UIProvider.ACCOUNT_URI_COLUMN:
-                        builder.add(account.uri);
-                        break;
-                    case UIProvider.ACCOUNT_CAPABILITIES_COLUMN:
-                        builder.add(Integer.valueOf(account.capabilities));
-                        break;
-                    case UIProvider.ACCOUNT_FOLDER_LIST_URI_COLUMN:
-                        builder.add(account.folderListUri);
-                        break;
-                    case UIProvider.ACCOUNT_FULL_FOLDER_LIST_URI_COLUMN:
-                        builder.add(account.fullFolderListUri);
-                        break;
-                    case UIProvider.ACCOUNT_SEARCH_URI_COLUMN:
-                        builder.add(account.searchUri);
-                        break;
-                    case UIProvider.ACCOUNT_FROM_ADDRESSES_COLUMN:
-                        builder.add(account.accountFromAddresses);
-                        break;
-                    case UIProvider.ACCOUNT_SAVE_DRAFT_URI_COLUMN:
-                        builder.add(account.saveDraftUri);
-                        break;
-                    case UIProvider.ACCOUNT_SEND_MESSAGE_URI_COLUMN:
-                        builder.add(account.sendMessageUri);
-                        break;
-                    case UIProvider.ACCOUNT_EXPUNGE_MESSAGE_URI_COLUMN:
-                        builder.add(account.expungeMessageUri);
-                        break;
-                    case UIProvider.ACCOUNT_UNDO_URI_COLUMN:
-                        builder.add(account.undoUri);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_INTENT_URI_COLUMN:
-                        builder.add(account.settingsIntentUri);
-                        break;
-                    case UIProvider.ACCOUNT_HELP_INTENT_URI_COLUMN:
-                        builder.add(account.helpIntentUri);
-                        break;
-                    case UIProvider.ACCOUNT_SEND_FEEDBACK_INTENT_URI_COLUMN:
-                        builder.add(account.sendFeedbackIntentUri);
-                        break;
-                    case UIProvider.ACCOUNT_REAUTHENTICATION_INTENT_URI_COLUMN:
-                        builder.add(account.reauthenticationIntentUri);
-                        break;
-                    case UIProvider.ACCOUNT_SYNC_STATUS_COLUMN:
-                        builder.add(Integer.valueOf(account.syncStatus));
-                        break;
-                    case UIProvider.ACCOUNT_COMPOSE_INTENT_URI_COLUMN:
-                        builder.add(account.composeIntentUri);
-                        break;
-                    case UIProvider.ACCOUNT_MIME_TYPE_COLUMN:
-                        builder.add(account.mimeType);
-                        break;
-                    case UIProvider.ACCOUNT_RECENT_FOLDER_LIST_URI_COLUMN:
-                        builder.add(account.recentFolderListUri);
-                        break;
-                    case UIProvider.ACCOUNT_DEFAULT_RECENT_FOLDER_LIST_URI_COLUMN:
-                        builder.add(account.defaultRecentFolderListUri);
-                        break;
-                    case UIProvider.ACCOUNT_MANUAL_SYNC_URI_COLUMN:
-                        builder.add(account.manualSyncUri);
-                        break;
-                    case UIProvider.ACCOUNT_VIEW_INTENT_PROXY_URI_COLUMN:
-                        builder.add(account.viewIntentProxyUri);
-                        break;
-                    case UIProvider.ACCOUNT_COOKIE_QUERY_URI_COLUMN:
-                        builder.add(account.accoutCookieQueryUri);
-                        break;
-                    case UIProvider.ACCOUNT_COLOR_COLUMN:
-                        builder.add(account.color);
-                        break;
-
-                    case UIProvider.ACCOUNT_SETTINGS_SIGNATURE_COLUMN:
-                        builder.add(account.settings.signature);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_AUTO_ADVANCE_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.getAutoAdvanceSetting()));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_MESSAGE_TEXT_SIZE_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.messageTextSize));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.replyBehavior));
-                        break;
-                    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));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_CONFIRM_ARCHIVE_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.confirmArchive ? 1 : 0));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_CONFIRM_SEND_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.confirmSend ? 1 : 0));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_DEFAULT_INBOX_COLUMN:
-                        builder.add(account.settings.defaultInbox);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_DEFAULT_INBOX_NAME_COLUMN:
-                        builder.add(account.settings.defaultInboxName);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.snapHeaders));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_FORCE_REPLY_FROM_DEFAULT_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.forceReplyFromDefault ? 1 : 0));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_MAX_ATTACHMENT_SIZE_COLUMN:
-                        builder.add(account.settings.maxAttachmentSize);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_SWIPE_COLUMN:
-                        builder.add(account.settings.swipe);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_PRIORITY_ARROWS_ENABLED_COLUMN:
-                        builder.add(Integer.valueOf(account.settings.priorityArrowsEnabled ? 1 : 0));
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_SETUP_INTENT_URI:
-                        builder.add(account.settings.setupIntentUri);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_CONVERSATION_MODE_COLUMN:
-                        builder.add(account.settings.conversationViewMode);
-                        break;
-                    case UIProvider.ACCOUNT_SETTINGS_VEILED_ADDRESS_PATTERN_COLUMN:
-                        builder.add(account.settings.veiledAddressPattern);
-                        break;
-                    case UIProvider.ACCOUNT_UPDATE_SETTINGS_URI_COLUMN:
-                        builder.add(account.updateSettingsUri);
-                        break;
-                    default:
-                        throw new IllegalStateException("Column not found: " + columnName);
+                if (accountValues.containsKey(columnName)) {
+                    builder.add(accountValues.get(columnName));
+                } else {
+                    throw new IllegalStateException("Unexpected column: " + columnName);
                 }
             }
         }
@@ -379,15 +235,8 @@
      * @param resolver
      * @param accountsQueryUri
      */
-    public static void addAccountsForUriAsync(Uri accountsQueryUri) {
-        synchronized (PENDING_ACCOUNT_URIS) {
-            final MailAppProvider instance = getInstance();
-            if (instance != null) {
-                instance.startAccountsLoader(accountsQueryUri);
-            } else {
-                PENDING_ACCOUNT_URIS.add(accountsQueryUri);
-            }
-        }
+    private void addAccountsForUriAsync(Uri accountsQueryUri) {
+        startAccountsLoader(accountsQueryUri);
     }
 
     /**
diff --git a/src/com/android/mail/providers/Message.java b/src/com/android/mail/providers/Message.java
index 37e60f2..ff007eb 100644
--- a/src/com/android/mail/providers/Message.java
+++ b/src/com/android/mail/providers/Message.java
@@ -110,7 +110,7 @@
     /**
      * @see UIProvider.MessageColumns#REF_MESSAGE_ID
      */
-    public String refMessageId;
+    public Uri refMessageUri;
     /**
      * @see UIProvider.MessageColumns#DRAFT_TYPE
      */
@@ -132,16 +132,6 @@
      */
     public long messageFlags;
     /**
-     * @see UIProvider.MessageColumns#SAVE_MESSAGE_URI
-     */
-    @Deprecated
-    public String saveUri;
-    /**
-     * @see UIProvider.MessageColumns#SEND_MESSAGE_URI
-     */
-    @Deprecated
-    public String sendUri;
-    /**
      * @see UIProvider.MessageColumns#ALWAYS_SHOW_IMAGES
      */
     public boolean alwaysShowImages;
@@ -150,6 +140,10 @@
      */
     public boolean read;
     /**
+     * @see UIProvider.MessageColumns#SEEN
+     */
+    public boolean seen;
+    /**
      * @see UIProvider.MessageColumns#STARRED
      */
     public boolean starred;
@@ -233,14 +227,12 @@
         dest.writeString(bodyHtml);
         dest.writeString(bodyText);
         dest.writeInt(embedsExternalResources ? 1 : 0);
-        dest.writeString(refMessageId);
+        dest.writeParcelable(refMessageUri, 0);
         dest.writeInt(draftType);
         dest.writeInt(appendRefMessageContent ? 1 : 0);
         dest.writeInt(hasAttachments ? 1 : 0);
         dest.writeParcelable(attachmentListUri, 0);
         dest.writeLong(messageFlags);
-        dest.writeString(saveUri);
-        dest.writeString(sendUri);
         dest.writeInt(alwaysShowImages ? 1 : 0);
         dest.writeInt(quotedTextOffset);
         dest.writeString(attachmentsJson);
@@ -269,14 +261,12 @@
         bodyHtml = in.readString();
         bodyText = in.readString();
         embedsExternalResources = in.readInt() != 0;
-        refMessageId = in.readString();
+        refMessageUri = in.readParcelable(null);
         draftType = in.readInt();
         appendRefMessageContent = in.readInt() != 0;
         hasAttachments = in.readInt() != 0;
         attachmentListUri = in.readParcelable(null);
         messageFlags = in.readLong();
-        saveUri = in.readString();
-        sendUri = in.readString();
         alwaysShowImages = in.readInt() != 0;
         quotedTextOffset = in.readInt();
         attachmentsJson = in.readString();
@@ -332,7 +322,10 @@
             bodyText = cursor.getString(UIProvider.MESSAGE_BODY_TEXT_COLUMN);
             embedsExternalResources = cursor
                     .getInt(UIProvider.MESSAGE_EMBEDS_EXTERNAL_RESOURCES_COLUMN) != 0;
-            refMessageId = cursor.getString(UIProvider.MESSAGE_REF_MESSAGE_ID_COLUMN);
+            final String refMessageUriStr =
+                    cursor.getString(UIProvider.MESSAGE_REF_MESSAGE_URI_COLUMN);
+            refMessageUri = !TextUtils.isEmpty(refMessageUriStr) ?
+                    Uri.parse(refMessageUriStr) : null;
             draftType = cursor.getInt(UIProvider.MESSAGE_DRAFT_TYPE_COLUMN);
             appendRefMessageContent = cursor
                     .getInt(UIProvider.MESSAGE_APPEND_REF_MESSAGE_CONTENT_COLUMN) != 0;
@@ -342,12 +335,9 @@
             attachmentListUri = hasAttachments && !TextUtils.isEmpty(attachmentsUri) ? Uri
                     .parse(attachmentsUri) : null;
             messageFlags = cursor.getLong(UIProvider.MESSAGE_FLAGS_COLUMN);
-            saveUri = cursor
-                    .getString(UIProvider.MESSAGE_SAVE_URI_COLUMN);
-            sendUri = cursor
-                    .getString(UIProvider.MESSAGE_SEND_URI_COLUMN);
             alwaysShowImages = cursor.getInt(UIProvider.MESSAGE_ALWAYS_SHOW_IMAGES_COLUMN) != 0;
             read = cursor.getInt(UIProvider.MESSAGE_READ_COLUMN) != 0;
+            seen = cursor.getInt(UIProvider.MESSAGE_SEEN_COLUMN) != 0;
             starred = cursor.getInt(UIProvider.MESSAGE_STARRED_COLUMN) != 0;
             quotedTextOffset = cursor.getInt(UIProvider.QUOTED_TEXT_OFFSET_COLUMN);
             attachmentsJson = cursor.getString(UIProvider.MESSAGE_ATTACHMENTS_COLUMN);
diff --git a/src/com/android/mail/providers/MessageModification.java b/src/com/android/mail/providers/MessageModification.java
index 9c0f7a8..c94de02 100644
--- a/src/com/android/mail/providers/MessageModification.java
+++ b/src/com/android/mail/providers/MessageModification.java
@@ -156,8 +156,6 @@
     }
 
     public static void putAttachments(ContentValues values, List<Attachment> attachments) {
-        values.put(
-                MessageColumns.JOINED_ATTACHMENT_INFOS, Attachment.toJSONArray(attachments));
         values.put(MessageColumns.ATTACHMENTS,  Attachment.toJSONArray(attachments));
     }
 }
diff --git a/src/com/android/mail/providers/Settings.java b/src/com/android/mail/providers/Settings.java
index b77a19d..6cb7c3a 100644
--- a/src/com/android/mail/providers/Settings.java
+++ b/src/com/android/mail/providers/Settings.java
@@ -138,29 +138,42 @@
     }
 
     public Settings(Cursor cursor) {
-        signature = cursor.getString(UIProvider.ACCOUNT_SETTINGS_SIGNATURE_COLUMN);
-        mAutoAdvance = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_AUTO_ADVANCE_COLUMN);
-        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);
-        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;
-        defaultInbox = Utils.getValidUri(
-                cursor.getString(UIProvider.ACCOUNT_SETTINGS_DEFAULT_INBOX_COLUMN));
-        defaultInboxName = cursor.getString(UIProvider.ACCOUNT_SETTINGS_DEFAULT_INBOX_NAME_COLUMN);
-        forceReplyFromDefault = cursor.getInt(
-                UIProvider.ACCOUNT_SETTINGS_FORCE_REPLY_FROM_DEFAULT_COLUMN) != 0;
-        maxAttachmentSize = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_MAX_ATTACHMENT_SIZE_COLUMN);
-        swipe = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_SWIPE_COLUMN);
-        priorityArrowsEnabled =
-                cursor.getInt(UIProvider.ACCOUNT_SETTINGS_PRIORITY_ARROWS_ENABLED_COLUMN) != 0;
-        setupIntentUri = Utils.getValidUri(
-                cursor.getString(UIProvider.ACCOUNT_SETTINGS_SETUP_INTENT_URI));
-        conversationViewMode = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_CONVERSATION_MODE_COLUMN);
-        veiledAddressPattern =
-                cursor.getString(UIProvider.ACCOUNT_SETTINGS_VEILED_ADDRESS_PATTERN_COLUMN);
+        signature = cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.SIGNATURE));
+        mAutoAdvance = cursor.getInt(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE));
+        messageTextSize = cursor.getInt(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE));
+        snapHeaders = cursor.getInt(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS));
+        replyBehavior = cursor.getInt(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR));
+        hideCheckboxes = cursor.getInt(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.HIDE_CHECKBOXES)) != 0;
+        confirmDelete = cursor.getInt(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE)) != 0;
+        confirmArchive = cursor.getInt(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE)) != 0;
+        confirmSend = cursor.getInt(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND)) != 0;
+        defaultInbox = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX)));
+        defaultInboxName = cursor.getString(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME));
+        forceReplyFromDefault = cursor.getInt(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.FORCE_REPLY_FROM_DEFAULT)) != 0;
+        maxAttachmentSize = cursor.getInt(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.MAX_ATTACHMENT_SIZE));
+        swipe = cursor.getInt(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.SWIPE));
+        priorityArrowsEnabled = cursor.getInt(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED)) != 0;
+        setupIntentUri = Utils.getValidUri(cursor.getString(
+                cursor.getColumnIndex(UIProvider.AccountColumns.SettingsColumns.SETUP_INTENT_URI)));
+        conversationViewMode = cursor.getInt(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.CONVERSATION_VIEW_MODE));
+        veiledAddressPattern = cursor.getString(cursor.getColumnIndex(
+                UIProvider.AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN));
     }
 
     private Settings(JSONObject json) {
@@ -347,6 +360,19 @@
     }
 
     /**
+     * @return true if {@link UIProvider.ConversationViewMode.OVERVIEW} mode is set. In the event
+     * that the setting is not yet set, fall back to
+     * {@link UIProvider.ConversationViewMode.DEFAULT}.
+     */
+    public boolean isOverviewMode() {
+        final boolean isDefined = (conversationViewMode
+                != UIProvider.ConversationViewMode.UNDEFINED);
+        final int val = (conversationViewMode != UIProvider.ConversationViewMode.UNDEFINED) ?
+                conversationViewMode : UIProvider.ConversationViewMode.DEFAULT;
+        return (val == UIProvider.ConversationViewMode.OVERVIEW);
+    }
+
+    /**
      * Return the swipe setting for the settings provided. It is safe to pass this method
      * a null object. It always returns a valid {@link Swipe} setting.
      * @return the auto advance setting, a constant from {@link Swipe}
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index c60de8b..86ab8aa 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -116,21 +116,19 @@
      * required to respect this query parameter
      */
     public static final String LIST_PARAMS_QUERY_PARAMETER = "listParams";
+    public static final String LABEL_QUERY_PARAMETER = "label";
+    public static final String SEEN_QUERY_PARAMETER = "seen";
 
-    public static final Map<String, Class<?>> ACCOUNTS_COLUMNS =
+    public static final Map<String, Class<?>> ACCOUNTS_COLUMNS_NO_CAPABILITIES =
             new ImmutableMap.Builder<String, Class<?>>()
-            // order matters! (ImmutableMap.Builder preserves insertion order)
-            .put(BaseColumns._ID, Integer.class)
+            .put(AccountColumns._ID, Integer.class)
             .put(AccountColumns.NAME, String.class)
             .put(AccountColumns.PROVIDER_VERSION, Integer.class)
             .put(AccountColumns.URI, String.class)
-            .put(AccountColumns.CAPABILITIES, Integer.class)
             .put(AccountColumns.FOLDER_LIST_URI, String.class)
             .put(AccountColumns.FULL_FOLDER_LIST_URI, String.class)
             .put(AccountColumns.SEARCH_URI, String.class)
             .put(AccountColumns.ACCOUNT_FROM_ADDRESSES, String.class)
-            .put(AccountColumns.SAVE_DRAFT_URI, String.class)
-            .put(AccountColumns.SEND_MAIL_URI, String.class)
             .put(AccountColumns.EXPUNGE_MESSAGE_URI, String.class)
             .put(AccountColumns.UNDO_URI, String.class)
             .put(AccountColumns.SETTINGS_INTENT_URI, String.class)
@@ -167,57 +165,19 @@
             .put(AccountColumns.UPDATE_SETTINGS_URI, String.class)
             .build();
 
-    // pull out the (ordered!) keyset from above to form the projection
-    public static final String[] ACCOUNTS_PROJECTION = ACCOUNTS_COLUMNS.keySet()
-            .toArray(new String[ACCOUNTS_COLUMNS.size()]);
+    public static final Map<String, Class<?>> ACCOUNTS_COLUMNS =
+            new ImmutableMap.Builder<String, Class<?>>()
+            .putAll(ACCOUNTS_COLUMNS_NO_CAPABILITIES)
+            .put(AccountColumns.CAPABILITIES, Integer.class)
+            .build();
 
-    public static final int ACCOUNT_ID_COLUMN = 0;
-    public static final int ACCOUNT_NAME_COLUMN = 1;
-    public static final int ACCOUNT_PROVIDER_VERISON_COLUMN = 2;
-    public static final int ACCOUNT_URI_COLUMN = 3;
-    public static final int ACCOUNT_CAPABILITIES_COLUMN = 4;
-    public static final int ACCOUNT_FOLDER_LIST_URI_COLUMN = 5;
-    public static final int ACCOUNT_FULL_FOLDER_LIST_URI_COLUMN = 6;
-    public static final int ACCOUNT_SEARCH_URI_COLUMN = 7;
-    public static final int ACCOUNT_FROM_ADDRESSES_COLUMN = 8;
-    public static final int ACCOUNT_SAVE_DRAFT_URI_COLUMN = 9;
-    public static final int ACCOUNT_SEND_MESSAGE_URI_COLUMN = 10;
-    public static final int ACCOUNT_EXPUNGE_MESSAGE_URI_COLUMN = 11;
-    public static final int ACCOUNT_UNDO_URI_COLUMN = 12;
-    public static final int ACCOUNT_SETTINGS_INTENT_URI_COLUMN = 13;
-    public static final int ACCOUNT_SYNC_STATUS_COLUMN = 14;
-    public static final int ACCOUNT_HELP_INTENT_URI_COLUMN = 15;
-    public static final int ACCOUNT_SEND_FEEDBACK_INTENT_URI_COLUMN = 16;
-    public static final int ACCOUNT_REAUTHENTICATION_INTENT_URI_COLUMN = 17;
-    public static final int ACCOUNT_COMPOSE_INTENT_URI_COLUMN = 18;
-    public static final int ACCOUNT_MIME_TYPE_COLUMN = 19;
-    public static final int ACCOUNT_RECENT_FOLDER_LIST_URI_COLUMN = 20;
-    public static final int ACCOUNT_COLOR_COLUMN = 21;
-    public static final int ACCOUNT_DEFAULT_RECENT_FOLDER_LIST_URI_COLUMN = 22;
-    public static final int ACCOUNT_MANUAL_SYNC_URI_COLUMN = 23;
-    public static final int ACCOUNT_VIEW_INTENT_PROXY_URI_COLUMN = 24;
-    public static final int ACCOUNT_COOKIE_QUERY_URI_COLUMN = 25;
+    // pull out the keyset from above to form the projection
+    public static final String[] ACCOUNTS_PROJECTION =
+            ACCOUNTS_COLUMNS.keySet().toArray(new String[ACCOUNTS_COLUMNS.size()]);
 
-    public static final int ACCOUNT_SETTINGS_SIGNATURE_COLUMN = 26;
-    public static final int ACCOUNT_SETTINGS_AUTO_ADVANCE_COLUMN = 27;
-    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_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;
-    public static final int ACCOUNT_SETTINGS_DEFAULT_INBOX_COLUMN = 35;
-    public static final int ACCOUNT_SETTINGS_DEFAULT_INBOX_NAME_COLUMN = 36;
-    public static final int ACCOUNT_SETTINGS_FORCE_REPLY_FROM_DEFAULT_COLUMN = 37;
-    public static final int ACCOUNT_SETTINGS_MAX_ATTACHMENT_SIZE_COLUMN = 38;
-    public static final int ACCOUNT_SETTINGS_SWIPE_COLUMN = 39;
-    public static final int ACCOUNT_SETTINGS_PRIORITY_ARROWS_ENABLED_COLUMN = 40;
-    public static final int ACCOUNT_SETTINGS_SETUP_INTENT_URI = 41;
-    public static final int ACCOUNT_SETTINGS_CONVERSATION_MODE_COLUMN = 42;
-    public static final int ACCOUNT_SETTINGS_VEILED_ADDRESS_PATTERN_COLUMN = 43;
-
-    public static final int ACCOUNT_UPDATE_SETTINGS_URI_COLUMN = 44;
+    public static final
+            String[] ACCOUNTS_PROJECTION_NO_CAPABILITIES = ACCOUNTS_COLUMNS_NO_CAPABILITIES.keySet()
+                    .toArray(new String[ACCOUNTS_COLUMNS_NO_CAPABILITIES.size()]);
 
     public static final class AccountCapabilities {
         /**
@@ -323,7 +283,7 @@
         public static final int DISCARD_CONVERSATION_DRAFTS = 0x100000;
     }
 
-    public static final class AccountColumns {
+    public static final class AccountColumns implements BaseColumns {
         /**
          * This string column contains the human visible name for the account.
          */
@@ -381,22 +341,6 @@
         public static final String ACCOUNT_FROM_ADDRESSES = "accountFromAddresses";
 
         /**
-         * This string column contains the content provider uri that can be used to save (insert)
-         * new draft messages for this account. NOTE: This might be better to
-         * be an update operation on the messageUri.
-         */
-        @Deprecated
-        public static final String SAVE_DRAFT_URI = "saveDraftUri";
-
-        /**
-         * This string column contains the content provider uri that can be used to send
-         * a message for this account.
-         * NOTE: This might be better to be an update operation on the messageUri.
-         */
-        @Deprecated
-        public static final String SEND_MAIL_URI = "sendMailUri";
-
-        /**
          * This string column contains the content provider uri that can be used
          * to expunge a message from this account. NOTE: This might be better to
          * be an update operation on the messageUri.
@@ -596,91 +540,6 @@
         }
     }
 
-    /**
-     * Map to go from account column name to account column. Can only be used through
-     * {@link #getAccountColumn(String)}.
-     */
-    private static final ImmutableMap<String, Integer> ACCOUNT_TO_ID_MAP =
-            new ImmutableMap.Builder<String, Integer>()
-            .put(BaseColumns._ID, ACCOUNT_ID_COLUMN)
-            .put(AccountColumns.NAME, ACCOUNT_NAME_COLUMN)
-            .put(AccountColumns.TYPE, -1)
-            .put(AccountColumns.PROVIDER_VERSION, ACCOUNT_PROVIDER_VERISON_COLUMN)
-            .put(AccountColumns.URI, ACCOUNT_URI_COLUMN)
-            .put(AccountColumns.CAPABILITIES, ACCOUNT_CAPABILITIES_COLUMN)
-            .put(AccountColumns.FOLDER_LIST_URI, ACCOUNT_FOLDER_LIST_URI_COLUMN)
-            .put(AccountColumns.FULL_FOLDER_LIST_URI, ACCOUNT_FULL_FOLDER_LIST_URI_COLUMN)
-            .put(AccountColumns.SEARCH_URI, ACCOUNT_SEARCH_URI_COLUMN)
-            .put(AccountColumns.ACCOUNT_FROM_ADDRESSES, ACCOUNT_FROM_ADDRESSES_COLUMN)
-            .put(AccountColumns.SAVE_DRAFT_URI, ACCOUNT_SAVE_DRAFT_URI_COLUMN)
-            .put(AccountColumns.SEND_MAIL_URI, ACCOUNT_SEND_MESSAGE_URI_COLUMN)
-            .put(AccountColumns.EXPUNGE_MESSAGE_URI, ACCOUNT_EXPUNGE_MESSAGE_URI_COLUMN)
-            .put(AccountColumns.UNDO_URI, ACCOUNT_UNDO_URI_COLUMN)
-            .put(AccountColumns.SETTINGS_INTENT_URI, ACCOUNT_SETTINGS_INTENT_URI_COLUMN)
-            .put(AccountColumns.HELP_INTENT_URI, ACCOUNT_HELP_INTENT_URI_COLUMN)
-            .put(AccountColumns.SEND_FEEDBACK_INTENT_URI, ACCOUNT_SEND_FEEDBACK_INTENT_URI_COLUMN)
-            .put(AccountColumns.REAUTHENTICATION_INTENT_URI,
-                    ACCOUNT_REAUTHENTICATION_INTENT_URI_COLUMN)
-            .put(AccountColumns.SYNC_STATUS, ACCOUNT_SYNC_STATUS_COLUMN)
-            .put(AccountColumns.COMPOSE_URI, ACCOUNT_COMPOSE_INTENT_URI_COLUMN)
-            .put(AccountColumns.MIME_TYPE, ACCOUNT_MIME_TYPE_COLUMN)
-            .put(AccountColumns.RECENT_FOLDER_LIST_URI, ACCOUNT_RECENT_FOLDER_LIST_URI_COLUMN)
-            .put(AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
-                    ACCOUNT_DEFAULT_RECENT_FOLDER_LIST_URI_COLUMN)
-            .put(AccountColumns.COLOR, ACCOUNT_COLOR_COLUMN)
-            .put(AccountColumns.MANUAL_SYNC_URI, ACCOUNT_MANUAL_SYNC_URI_COLUMN)
-            .put(AccountColumns.VIEW_INTENT_PROXY_URI, ACCOUNT_VIEW_INTENT_PROXY_URI_COLUMN)
-            .put(AccountColumns.ACCOUNT_COOKIE_QUERY_URI, ACCOUNT_COOKIE_QUERY_URI_COLUMN)
-            .put(AccountColumns.SettingsColumns.SIGNATURE, ACCOUNT_SETTINGS_SIGNATURE_COLUMN)
-            .put(AccountColumns.SettingsColumns.AUTO_ADVANCE, ACCOUNT_SETTINGS_AUTO_ADVANCE_COLUMN)
-            .put(AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE,
-                    ACCOUNT_SETTINGS_MESSAGE_TEXT_SIZE_COLUMN)
-            .put(AccountColumns.SettingsColumns.SNAP_HEADERS,ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN)
-            .put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
-                    ACCOUNT_SETTINGS_REPLY_BEHAVIOR_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,
-                    ACCOUNT_SETTINGS_CONFIRM_ARCHIVE_COLUMN)
-            .put(AccountColumns.SettingsColumns.CONFIRM_SEND,
-                    ACCOUNT_SETTINGS_CONFIRM_SEND_COLUMN)
-            .put(AccountColumns.SettingsColumns.DEFAULT_INBOX,
-                    ACCOUNT_SETTINGS_DEFAULT_INBOX_COLUMN)
-            .put(AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME,
-                    ACCOUNT_SETTINGS_DEFAULT_INBOX_NAME_COLUMN)
-            .put(AccountColumns.SettingsColumns.FORCE_REPLY_FROM_DEFAULT,
-                    ACCOUNT_SETTINGS_FORCE_REPLY_FROM_DEFAULT_COLUMN)
-            .put(AccountColumns.SettingsColumns.MAX_ATTACHMENT_SIZE,
-                    ACCOUNT_SETTINGS_MAX_ATTACHMENT_SIZE_COLUMN)
-            .put(AccountColumns.SettingsColumns.SWIPE, ACCOUNT_SETTINGS_SWIPE_COLUMN)
-            .put(AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED,
-                    ACCOUNT_SETTINGS_PRIORITY_ARROWS_ENABLED_COLUMN)
-            .put(AccountColumns.SettingsColumns.SETUP_INTENT_URI,
-                    ACCOUNT_SETTINGS_SETUP_INTENT_URI)
-            .put(AccountColumns.SettingsColumns.CONVERSATION_VIEW_MODE,
-                    ACCOUNT_SETTINGS_CONVERSATION_MODE_COLUMN)
-            .put(AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN,
-                    ACCOUNT_SETTINGS_VEILED_ADDRESS_PATTERN_COLUMN)
-            .put(AccountColumns.UPDATE_SETTINGS_URI, ACCOUNT_UPDATE_SETTINGS_URI_COLUMN)
-            .build();
-
-    /**
-     * Returns the column number for a given column name. The column numbers are guaranteed to be
-     * unique for distinct column names. Column names are values from {@link AccountColumns} while
-     * columns are integers.
-     * @param columnName
-     * @return
-     */
-    public static final int getAccountColumn(String columnName) {
-        final Integer id = ACCOUNT_TO_ID_MAP.get(columnName);
-        if (id == null) {
-            return -1;
-        }
-        return id.intValue();
-    }
-
     public static final String[] ACCOUNT_COOKIE_PROJECTION = {
         AccountCookieColumns.COOKIE
     };
@@ -735,6 +594,7 @@
 
     public static final String[] FOLDERS_PROJECTION = {
         BaseColumns._ID,
+        FolderColumns.PERSISTENT_ID,
         FolderColumns.URI,
         FolderColumns.NAME,
         FolderColumns.HAS_CHILDREN,
@@ -742,6 +602,7 @@
         FolderColumns.SYNC_WINDOW,
         FolderColumns.CONVERSATION_LIST_URI,
         FolderColumns.CHILD_FOLDERS_LIST_URI,
+        FolderColumns.UNSEEN_COUNT,
         FolderColumns.UNREAD_COUNT,
         FolderColumns.TOTAL_COUNT,
         FolderColumns.REFRESH_URI,
@@ -749,31 +610,37 @@
         FolderColumns.LAST_SYNC_RESULT,
         FolderColumns.TYPE,
         FolderColumns.ICON_RES_ID,
+        FolderColumns.NOTIFICATION_ICON_RES_ID,
         FolderColumns.BG_COLOR,
         FolderColumns.FG_COLOR,
         FolderColumns.LOAD_MORE_URI,
-        FolderColumns.HIERARCHICAL_DESC
+        FolderColumns.HIERARCHICAL_DESC,
+        FolderColumns.LAST_MESSAGE_TIMESTAMP
     };
 
     public static final int FOLDER_ID_COLUMN = 0;
-    public static final int FOLDER_URI_COLUMN = 1;
-    public static final int FOLDER_NAME_COLUMN = 2;
-    public static final int FOLDER_HAS_CHILDREN_COLUMN = 3;
-    public static final int FOLDER_CAPABILITIES_COLUMN = 4;
-    public static final int FOLDER_SYNC_WINDOW_COLUMN = 5;
-    public static final int FOLDER_CONVERSATION_LIST_URI_COLUMN = 6;
-    public static final int FOLDER_CHILD_FOLDERS_LIST_COLUMN = 7;
-    public static final int FOLDER_UNREAD_COUNT_COLUMN = 8;
-    public static final int FOLDER_TOTAL_COUNT_COLUMN = 9;
-    public static final int FOLDER_REFRESH_URI_COLUMN = 10;
-    public static final int FOLDER_SYNC_STATUS_COLUMN = 11;
-    public static final int FOLDER_LAST_SYNC_RESULT_COLUMN = 12;
-    public static final int FOLDER_TYPE_COLUMN = 13;
-    public static final int FOLDER_ICON_RES_ID_COLUMN = 14;
-    public static final int FOLDER_BG_COLOR_COLUMN = 15;
-    public static final int FOLDER_FG_COLOR_COLUMN = 16;
-    public static final int FOLDER_LOAD_MORE_URI_COLUMN = 17;
-    public static final int FOLDER_HIERARCHICAL_DESC_COLUMN = 18;
+    public static final int FOLDER_PERSISTENT_ID_COLUMN = 1;
+    public static final int FOLDER_URI_COLUMN = 2;
+    public static final int FOLDER_NAME_COLUMN = 3;
+    public static final int FOLDER_HAS_CHILDREN_COLUMN = 4;
+    public static final int FOLDER_CAPABILITIES_COLUMN = 5;
+    public static final int FOLDER_SYNC_WINDOW_COLUMN = 6;
+    public static final int FOLDER_CONVERSATION_LIST_URI_COLUMN = 7;
+    public static final int FOLDER_CHILD_FOLDERS_LIST_COLUMN = 8;
+    public static final int FOLDER_UNSEEN_COUNT_COLUMN = 9;
+    public static final int FOLDER_UNREAD_COUNT_COLUMN = 10;
+    public static final int FOLDER_TOTAL_COUNT_COLUMN = 11;
+    public static final int FOLDER_REFRESH_URI_COLUMN = 12;
+    public static final int FOLDER_SYNC_STATUS_COLUMN = 13;
+    public static final int FOLDER_LAST_SYNC_RESULT_COLUMN = 14;
+    public static final int FOLDER_TYPE_COLUMN = 15;
+    public static final int FOLDER_ICON_RES_ID_COLUMN = 16;
+    public static final int FOLDER_NOTIFICATION_ICON_RES_ID_COLUMN = 17;
+    public static final int FOLDER_BG_COLOR_COLUMN = 18;
+    public static final int FOLDER_FG_COLOR_COLUMN = 19;
+    public static final int FOLDER_LOAD_MORE_URI_COLUMN = 20;
+    public static final int FOLDER_HIERARCHICAL_DESC_COLUMN = 21;
+    public static final int FOLDER_LAST_MESSAGE_TIMESTAMP_COLUMN = 22;
 
     public static final class FolderType {
         /** A user defined label. */
@@ -796,6 +663,8 @@
         public static final int OTHER_PROVIDER_FOLDER = 8;
         /** All mail folder **/
         public static final int ALL_MAIL = 9;
+        /** Gmail's inbox sections **/
+        public static final int INBOX_SECTION = 10;
     }
 
     public static final class FolderCapabilities {
@@ -856,10 +725,21 @@
          * the report phishing functionality.
          */
         public static final int REPORT_PHISHING = 0x2000;
+
+        /**
+         * The flag indicates that the user has the ability to move conversations
+         * from this folder.
+         */
+        public static final int ALLOWS_REMOVE_CONVERSATION = 0x4000;
     }
 
     public static final class FolderColumns {
         /**
+         * This string column contains an id for the folder that is constant across devices, or
+         * null if there is no constant id.
+         */
+        public static final String PERSISTENT_ID = "persistentId";
+        /**
          * This string column contains the uri of the folder.
          */
         public static final String URI = "folderUri";
@@ -891,7 +771,13 @@
          * list of child folders of this folder.
          */
         public static final String CHILD_FOLDERS_LIST_URI = "childFoldersListUri";
-
+        /**
+         * This int column contains the current unseen count for the folder, if known.
+         */
+        public static final String UNSEEN_COUNT = "unseenCount";
+        /**
+         * This int column contains the current unread count for the folder.
+         */
         public static final String UNREAD_COUNT = "unreadCount";
 
         public static final String TOTAL_COUNT = "totalCount";
@@ -911,10 +797,15 @@
          */
         public static final String LAST_SYNC_RESULT  = "lastSyncResult";
         /**
-         * This long column contains the icon res id for this folder, or 0 if there is none.
+         * This int column contains the icon res id for this folder, or 0 if there is none.
          */
         public static final String ICON_RES_ID = "iconResId";
         /**
+         * This int column contains the notification icon res id for this folder, or 0 if there is
+         * none.
+         */
+        public static final String NOTIFICATION_ICON_RES_ID = "notificationIconResId";
+        /**
          * This int column contains the type of the folder. Zero is default.
          */
         public static final String TYPE = "type";
@@ -939,6 +830,11 @@
          */
         public static final String HIERARCHICAL_DESC = "hierarchicalDesc";
 
+        /**
+         * The timestamp of the last message received in this folder.
+         */
+        public static final String LAST_MESSAGE_TIMESTAMP = "lastMessageTimestamp";
+
         public FolderColumns() {}
     }
 
@@ -963,6 +859,7 @@
         ConversationColumns.SENDING_STATE,
         ConversationColumns.PRIORITY,
         ConversationColumns.READ,
+        ConversationColumns.SEEN,
         ConversationColumns.STARRED,
         ConversationColumns.RAW_FOLDERS,
         ConversationColumns.FLAGS,
@@ -992,18 +889,19 @@
     public static final int CONVERSATION_SENDING_STATE_COLUMN = 10;
     public static final int CONVERSATION_PRIORITY_COLUMN = 11;
     public static final int CONVERSATION_READ_COLUMN = 12;
-    public static final int CONVERSATION_STARRED_COLUMN = 13;
-    public static final int CONVERSATION_RAW_FOLDERS_COLUMN = 14;
-    public static final int CONVERSATION_FLAGS_COLUMN = 15;
-    public static final int CONVERSATION_PERSONAL_LEVEL_COLUMN = 16;
-    public static final int CONVERSATION_IS_SPAM_COLUMN = 17;
-    public static final int CONVERSATION_IS_PHISHING_COLUMN = 18;
-    public static final int CONVERSATION_MUTED_COLUMN = 19;
-    public static final int CONVERSATION_COLOR_COLUMN = 20;
-    public static final int CONVERSATION_ACCOUNT_URI_COLUMN = 21;
-    public static final int CONVERSATION_SENDER_INFO_COLUMN = 22;
-    public static final int CONVERSATION_BASE_URI_COLUMN = 23;
-    public static final int CONVERSATION_REMOTE_COLUMN = 24;
+    public static final int CONVERSATION_SEEN_COLUMN = 13;
+    public static final int CONVERSATION_STARRED_COLUMN = 14;
+    public static final int CONVERSATION_RAW_FOLDERS_COLUMN = 15;
+    public static final int CONVERSATION_FLAGS_COLUMN = 16;
+    public static final int CONVERSATION_PERSONAL_LEVEL_COLUMN = 17;
+    public static final int CONVERSATION_IS_SPAM_COLUMN = 18;
+    public static final int CONVERSATION_IS_PHISHING_COLUMN = 19;
+    public static final int CONVERSATION_MUTED_COLUMN = 20;
+    public static final int CONVERSATION_COLOR_COLUMN = 21;
+    public static final int CONVERSATION_ACCOUNT_URI_COLUMN = 22;
+    public static final int CONVERSATION_SENDER_INFO_COLUMN = 23;
+    public static final int CONVERSATION_BASE_URI_COLUMN = 24;
+    public static final int CONVERSATION_REMOTE_COLUMN = 25;
 
     public static final class ConversationSendingState {
         public static final int OTHER = 0;
@@ -1109,6 +1007,12 @@
          * This int column indicates whether the conversation has been read
          */
         public static String READ = "read";
+
+        /**
+         * This int column indicates whether the conversation has been seen
+         */
+        public static String SEEN = "seen";
+
         /**
          * This int column indicates whether the conversation has been starred
          */
@@ -1399,11 +1303,9 @@
         MessageColumns.HAS_ATTACHMENTS,
         MessageColumns.ATTACHMENT_LIST_URI,
         MessageColumns.MESSAGE_FLAGS,
-        MessageColumns.JOINED_ATTACHMENT_INFOS,
-        MessageColumns.SAVE_MESSAGE_URI,
-        MessageColumns.SEND_MESSAGE_URI,
         MessageColumns.ALWAYS_SHOW_IMAGES,
         MessageColumns.READ,
+        MessageColumns.SEEN,
         MessageColumns.STARRED,
         MessageColumns.QUOTE_START_POS,
         MessageColumns.ATTACHMENTS,
@@ -1440,28 +1342,26 @@
     public static final int MESSAGE_BODY_HTML_COLUMN = 12;
     public static final int MESSAGE_BODY_TEXT_COLUMN = 13;
     public static final int MESSAGE_EMBEDS_EXTERNAL_RESOURCES_COLUMN = 14;
-    public static final int MESSAGE_REF_MESSAGE_ID_COLUMN = 15;
+    public static final int MESSAGE_REF_MESSAGE_URI_COLUMN = 15;
     public static final int MESSAGE_DRAFT_TYPE_COLUMN = 16;
     public static final int MESSAGE_APPEND_REF_MESSAGE_CONTENT_COLUMN = 17;
     public static final int MESSAGE_HAS_ATTACHMENTS_COLUMN = 18;
     public static final int MESSAGE_ATTACHMENT_LIST_URI_COLUMN = 19;
     public static final int MESSAGE_FLAGS_COLUMN = 20;
-    public static final int MESSAGE_JOINED_ATTACHMENT_INFOS_COLUMN = 21;
-    public static final int MESSAGE_SAVE_URI_COLUMN = 22;
-    public static final int MESSAGE_SEND_URI_COLUMN = 23;
-    public static final int MESSAGE_ALWAYS_SHOW_IMAGES_COLUMN = 24;
-    public static final int MESSAGE_READ_COLUMN = 25;
-    public static final int MESSAGE_STARRED_COLUMN = 26;
-    public static final int QUOTED_TEXT_OFFSET_COLUMN = 27;
-    public static final int MESSAGE_ATTACHMENTS_COLUMN = 28;
-    public static final int MESSAGE_CUSTOM_FROM_ADDRESS_COLUMN = 29;
-    public static final int MESSAGE_ACCOUNT_URI_COLUMN = 30;
-    public static final int MESSAGE_EVENT_INTENT_COLUMN = 31;
-    public static final int MESSAGE_SPAM_WARNING_STRING_ID_COLUMN = 32;
-    public static final int MESSAGE_SPAM_WARNING_LEVEL_COLUMN = 33;
-    public static final int MESSAGE_SPAM_WARNING_LINK_TYPE_COLUMN = 34;
-    public static final int MESSAGE_VIA_DOMAIN_COLUMN = 35;
-    public static final int MESSAGE_IS_SENDING_COLUMN = 36;
+    public static final int MESSAGE_ALWAYS_SHOW_IMAGES_COLUMN = 21;
+    public static final int MESSAGE_READ_COLUMN = 22;
+    public static final int MESSAGE_SEEN_COLUMN = 23;
+    public static final int MESSAGE_STARRED_COLUMN = 24;
+    public static final int QUOTED_TEXT_OFFSET_COLUMN = 25;
+    public static final int MESSAGE_ATTACHMENTS_COLUMN = 26;
+    public static final int MESSAGE_CUSTOM_FROM_ADDRESS_COLUMN = 27;
+    public static final int MESSAGE_ACCOUNT_URI_COLUMN = 28;
+    public static final int MESSAGE_EVENT_INTENT_COLUMN = 29;
+    public static final int MESSAGE_SPAM_WARNING_STRING_ID_COLUMN = 30;
+    public static final int MESSAGE_SPAM_WARNING_LEVEL_COLUMN = 31;
+    public static final int MESSAGE_SPAM_WARNING_LINK_TYPE_COLUMN = 32;
+    public static final int MESSAGE_VIA_DOMAIN_COLUMN = 33;
+    public static final int MESSAGE_IS_SENDING_COLUMN = 34;
 
     public static final class CursorStatus {
         // The cursor is actively loading more data
@@ -1495,6 +1395,11 @@
          */
         public static final String EXTRA_ERROR = "cursor_error";
 
+
+        /**
+         * This integer column contains the total message count for this folder.
+         */
+        public static final String EXTRA_TOTAL_COUNT = "cursor_total_count";
     }
 
     public static final class AccountCursorExtraKeys {
@@ -1598,27 +1503,6 @@
          */
         public static final String MESSAGE_FLAGS = "messageFlags";
         /**
-         * This string column contains a specially formatted string representing all
-         * attachments that we added to a message that is being sent or saved.
-         *
-         * TODO: remove this and use {@link #ATTACHMENTS} instead
-         */
-        @Deprecated
-        public static final String JOINED_ATTACHMENT_INFOS = "joinedAttachmentInfos";
-        /**
-         * This string column contains the content provider URI for saving this
-         * message.
-         */
-        @Deprecated
-        public static final String SAVE_MESSAGE_URI = "saveMessageUri";
-        /**
-         * This string column contains content provider URI for sending this
-         * message.
-         */
-        @Deprecated
-        public static final String SEND_MESSAGE_URI = "sendMessageUri";
-
-        /**
          * This integer column represents whether the user has specified that images should always
          * be shown.  The value of "1" indicates that the user has specified that images should be
          * shown, while the value of "0" indicates that the user should be prompted before loading
@@ -1632,6 +1516,11 @@
         public static String READ = "read";
 
         /**
+         * This boolean column indicates whether the message has been seen
+         */
+        public static String SEEN = "seen";
+
+        /**
          * This boolean column indicates whether the message has been starred
          */
         public static String STARRED = "starred";
@@ -1929,12 +1818,15 @@
          */
         public static final int BEST = 1;
 
+        private static final String SIMPLE_STRING = "SIMPLE";
+        private static final String BEST_STRING = "BEST";
+
         public static int parseRendition(String rendition) {
-            return rendition.equalsIgnoreCase("BEST") ? BEST : SIMPLE;
+            return TextUtils.equals(rendition, SIMPLE_STRING) ? SIMPLE : BEST;
         }
 
         public static String toString(int rendition) {
-            return rendition == BEST ? "BEST" : "SIMPLE";
+            return rendition == BEST ? BEST_STRING : SIMPLE_STRING;
         }
     }
 
@@ -2042,6 +1934,7 @@
          * require panning
          */
         public static final int READING = 1;
+        public static final int DEFAULT = OVERVIEW;
     }
 
     public static final class SnapHeaderValue {
diff --git a/src/com/android/mail/providers/protos/mock/MockUiProvider.java b/src/com/android/mail/providers/protos/mock/MockUiProvider.java
index 97bad3c..1055b42 100644
--- a/src/com/android/mail/providers/protos/mock/MockUiProvider.java
+++ b/src/com/android/mail/providers/protos/mock/MockUiProvider.java
@@ -28,6 +28,7 @@
 import com.android.mail.providers.Account;
 import com.android.mail.providers.ConversationInfo;
 import com.android.mail.providers.Folder;
+import com.android.mail.providers.FolderList;
 import com.android.mail.providers.MailAppProvider;
 import com.android.mail.providers.MessageInfo;
 import com.android.mail.providers.ReplyFromAccount;
@@ -212,37 +213,34 @@
         conversationMap.put(ConversationColumns.NUM_DRAFTS, 1);
         conversationMap.put(ConversationColumns.SENDING_STATE, 1);
         conversationMap.put(ConversationColumns.READ, 0);
+        conversationMap.put(ConversationColumns.SEEN, 0);
         conversationMap.put(ConversationColumns.STARRED, 0);
         conversationMap.put(ConversationColumns.CONVERSATION_INFO,
                 generateConversationInfo(messageCount, draftCount));
-        Folder[] folders = new Folder[3];
-        StringBuilder foldersString = new StringBuilder();
-        for (int i = 0; i < folders.length; i++) {
-            folders[i] = Folder.newUnsafeInstance();
-            folders[i].name = "folder" + i;
+        final List<Folder> folders = new ArrayList<Folder>(3);
+        for (int i = 0; i < 3; i++) {
+            final Folder folder = Folder.newUnsafeInstance();
+            folder.name = "folder" + i;
             switch (i) {
                 case 0:
-                    folders[i].bgColor = "#fff000";
+                    folder.bgColor = "#fff000";
                     break;
                 case 1:
-                    folders[i].bgColor = "#0000FF";
+                    folder.bgColor = "#0000FF";
                     break;
                 case 2:
-                    folders[i].bgColor = "#FFFF00";
+                    folder.bgColor = "#FFFF00";
                     break;
                 default:
-                    folders[i].bgColor = null;
+                    folder.bgColor = null;
                     break;
             }
 
+            folders.add(folder);
+
         }
-        for (int i = 0; i < folders.length; i++) {
-            foldersString.append(Folder.toString(folders[i]));
-            if (i < folders.length - 1) {
-                foldersString.append(",");
-            }
-        }
-        conversationMap.put(ConversationColumns.RAW_FOLDERS, foldersString.toString());
+        final FolderList folderList = FolderList.copyOf(folders);
+        conversationMap.put(ConversationColumns.RAW_FOLDERS, folderList);
         return conversationMap;
     }
 
@@ -356,8 +354,6 @@
         accountMap.put(AccountColumns.ACCOUNT_FROM_ADDRESSES, replyFroms.toString());
         accountMap.put(AccountColumns.FOLDER_LIST_URI, Uri.parse(accountUri + "/folders"));
         accountMap.put(AccountColumns.SEARCH_URI, Uri.parse(accountUri + "/search"));
-        accountMap.put(AccountColumns.SAVE_DRAFT_URI, Uri.parse(accountUri + "/saveDraft"));
-        accountMap.put(AccountColumns.SEND_MAIL_URI, Uri.parse(accountUri + "/sendMail"));
         accountMap.put(AccountColumns.EXPUNGE_MESSAGE_URI,
                 Uri.parse(accountUri + "/expungeMessage"));
         accountMap.put(AccountColumns.UNDO_URI, Uri.parse(accountUri + "/undo"));
@@ -391,6 +387,7 @@
 
     @Override
     public boolean onCreate() {
+        MockUiProvider.initializeMockProvider();
         return true;
     }
 
@@ -467,8 +464,6 @@
         dest.writeParcelable((Uri) accountInfo.get(AccountColumns.FULL_FOLDER_LIST_URI), 0);
         dest.writeParcelable((Uri) accountInfo.get(AccountColumns.SEARCH_URI), 0);
         dest.writeString((String) accountInfo.get(AccountColumns.ACCOUNT_FROM_ADDRESSES));
-        dest.writeParcelable((Uri) accountInfo.get(AccountColumns.SAVE_DRAFT_URI), 0);
-        dest.writeParcelable((Uri) accountInfo.get(AccountColumns.SEND_MAIL_URI), 0);
         dest.writeParcelable((Uri) accountInfo.get(AccountColumns.EXPUNGE_MESSAGE_URI), 0);
         dest.writeParcelable((Uri) accountInfo.get(AccountColumns.UNDO_URI), 0);
         dest.writeParcelable((Uri) accountInfo.get(AccountColumns.SETTINGS_INTENT_URI), 0);
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index f035089..0f07ef0 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -31,22 +31,18 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.CursorLoader;
 import android.content.DialogInterface;
 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;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.BadParcelableException;
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.SearchRecentSuggestions;
-import android.text.TextUtils;
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -58,6 +54,7 @@
 import android.widget.Toast;
 
 import com.android.mail.ConversationListContext;
+import com.android.mail.MailLogService;
 import com.android.mail.R;
 import com.android.mail.browse.ConfirmDialogFragment;
 import com.android.mail.browse.ConversationCursor;
@@ -68,6 +65,9 @@
 import com.android.mail.browse.SelectedConversationsActionMenu;
 import com.android.mail.browse.SyncErrorDialogFragment;
 import com.android.mail.compose.ComposeActivity;
+import com.android.mail.content.CursorCreator;
+import com.android.mail.content.ObjectCursor;
+import com.android.mail.content.ObjectCursorLoader;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.ConversationInfo;
@@ -89,6 +89,7 @@
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.NotificationActionUtils;
+import com.android.mail.utils.Observable;
 import com.android.mail.utils.Utils;
 import com.android.mail.utils.VeiledAddressMatcher;
 
@@ -173,6 +174,7 @@
     /** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
     private SuppressNotificationReceiver mNewEmailReceiver = null;
 
+    /** Handler for all our local runnables. */
     protected Handler mHandler = new Handler();
 
     /**
@@ -202,22 +204,12 @@
 
     private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
     protected ConversationCursor mConversationListCursor;
-    private final DataSetObservable mConversationListObservable = new DataSetObservable() {
-        @Override
-        public void registerObserver(DataSetObserver observer) {
-            final int count = mObservers.size();
-            super.registerObserver(observer);
-            LogUtils.d(LOG_TAG, "IN AAC.register(List)Observer: %s before=%d after=%d", observer,
-                    count, mObservers.size());
-        }
-        @Override
-        public void unregisterObserver(DataSetObserver observer) {
-            final int count = mObservers.size();
-            super.unregisterObserver(observer);
-            LogUtils.d(LOG_TAG, "IN AAC.unregister(List)Observer: %s before=%d after=%d", observer,
-                    count, mObservers.size());
-        }
-    };
+    private final DataSetObservable mConversationListObservable = new Observable("List");
+
+    /** Runnable that checks the logging level to enable/disable the logging service. */
+    private Runnable mLogServiceChecker = null;
+    /** List of all accounts currently known to the controller. */
+    private Account[] mAllAccounts;
 
     /**
      * Interface for actions that are deferred until after a load completes. This is for handling
@@ -235,40 +227,13 @@
     private RefreshTimerTask mConversationListRefreshTask;
 
     /** Listeners that are interested in changes to the current account. */
-    private final DataSetObservable mAccountObservers = new DataSetObservable() {
-        @Override
-        public void registerObserver(DataSetObserver observer) {
-            final int count = mObservers.size();
-            super.registerObserver(observer);
-            LogUtils.d(LOG_TAG, "IN AAC.register(Account)Observer: %s before=%d after=%d",
-                    observer, count, mObservers.size());
-        }
-        @Override
-        public void unregisterObserver(DataSetObserver observer) {
-            final int count = mObservers.size();
-            super.unregisterObserver(observer);
-            LogUtils.d(LOG_TAG, "IN AAC.unregister(Account)Observer: %s before=%d after=%d",
-                    observer, count, mObservers.size());
-        }
-    };
-
+    private final DataSetObservable mAccountObservers = new Observable("Account");
     /** Listeners that are interested in changes to the recent folders. */
-    private final DataSetObservable mRecentFolderObservers = new DataSetObservable() {
-        @Override
-        public void registerObserver(DataSetObserver observer) {
-            final int count = mObservers.size();
-            super.registerObserver(observer);
-            LogUtils.d(LOG_TAG, "IN AAC.register(RecentFolder)Observer: %s before=%d after=%d",
-                    observer, count, mObservers.size());
-        }
-        @Override
-        public void unregisterObserver(DataSetObserver observer) {
-            final int count = mObservers.size();
-            super.unregisterObserver(observer);
-            LogUtils.d(LOG_TAG, "IN AAC.unregister(RecentFolder)Observer: %s before=%d after=%d",
-                    observer, count, mObservers.size());
-        }
-    };
+    private final DataSetObservable mRecentFolderObservers = new Observable("RecentFolder");
+    /** Listeners that are interested in changes to the list of all accounts. */
+    private final DataSetObservable mAllAccountObservers = new Observable("AllAccounts");
+    /** Listeners that are interested in changes to the current folder. */
+    private final DataSetObservable mFolderObservable = new Observable("CurrentFolder");
 
     /**
      * Selected conversations, if any.
@@ -292,7 +257,10 @@
     private final ConversationListLoaderCallbacks mListCursorCallbacks =
             new ConversationListLoaderCallbacks();
 
-    private final DataSetObservable mFolderObservable = new DataSetObservable();
+    /** Object that listens to all LoaderCallbacks that result in {@link Folder} creation. */
+    private final FolderLoads mFolderCallbacks = new FolderLoads();
+    /** Object that listens to all LoaderCallbacks that result in {@link Account} creation. */
+    private final AccountLoads mAccountCallbacks = new AccountLoads();
 
     /**
      * Matched addresses that must be shielded from users because they are temporary. Even though
@@ -310,6 +278,9 @@
     private static final int LOADER_ACCOUNT_INBOX = 5;
     private static final int LOADER_SEARCH = 6;
     private static final int LOADER_ACCOUNT_UPDATE_CURSOR = 7;
+    /** Loader for showing the initial folder/conversation at app start. */
+    public static final int LOADER_FIRST_FOLDER = 8;
+
     /**
      * Guaranteed to be the last loader ID used by the activity. Loaders are owned by Activity or
      * fragments, and within an activity, loader IDs need to be unique. A hack to ensure that the
@@ -320,6 +291,13 @@
      * perhaps.
      */
     public static final int LAST_LOADER_ID = 100;
+    /**
+     * Guaranteed to be the last loader ID used by the Fragment. Loaders are owned by Activity or
+     * fragments, and within an activity, loader IDs need to be unique. Currently,
+     * {@link SectionedInboxTeaserView} is the only class that uses the
+     * {@link ConversationListFragment}'s LoaderManager.
+     */
+    public static final int LAST_FRAGMENT_LOADER_ID = 1000;
 
     private static final int ADD_ACCOUNT_REQUEST_CODE = 1;
     private static final int REAUTHENTICATE_REQUEST_CODE = 2;
@@ -327,10 +305,6 @@
     /** The pending destructive action to be carried out before swapping the conversation cursor.*/
     private DestructiveAction mPendingDestruction;
     protected AsyncRefreshTask mFolderSyncTask;
-    // Task for setting any share intents for the account to enabled.
-    // This gets cancelled if the user kills the app before it finishes, and
-    // will just run the next time the user opens the app.
-    private AsyncTask<String, Void, Void> mEnableShareIntents;
     private Folder mFolderListFolder;
     private boolean mIsDragHappening;
     private int mShowUndoBarDelay;
@@ -352,6 +326,9 @@
      */
     private boolean mDialogFromSelectedSet;
 
+    /** Which conversation to show, if started from widget/notification. */
+    private Conversation mConversationToShow = null;
+
     private final Deque<UpOrBackHandler> mUpOrBackHandlers = Lists.newLinkedList();
 
     public static final String SYNC_ERROR_DIALOG_FRAGMENT_TAG = "SyncErrorDialogFragment";
@@ -416,14 +393,11 @@
 
     /**
      * Check if the fragment is attached to an activity and has a root view.
-     * @param in
+     * @param in fragment to be checked
      * @return true if the fragment is valid, false otherwise
      */
-    private static final boolean isValidFragment(Fragment in) {
-        if (in == null || in.getActivity() == null || in.getView() == null) {
-            return false;
-        }
-        return true;
+    private static boolean isValidFragment(Fragment in) {
+        return !(in == null || in.getActivity() == null || in.getView() == null);
     }
 
     /**
@@ -480,7 +454,7 @@
                 && Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction());
         mActionBarView = (MailActionBarView) inflater.inflate(
                 isSearch ? R.layout.search_actionbar_view : R.layout.actionbar_view, null);
-        mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
+        mActionBarView.initialize(mActivity, this, actionBar);
     }
 
     /**
@@ -491,11 +465,10 @@
         if (actionBar != null && mActionBarView != null) {
             actionBar.setCustomView(mActionBarView, new ActionBar.LayoutParams(
                     LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-            // Show a custom view and home icon, but remove the title
+            // Show a custom view and home icon, keep the title and subttitle
             final int mask = ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_TITLE
                     | ActionBar.DISPLAY_SHOW_HOME;
-            final int enabled = ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME;
-            actionBar.setDisplayOptions(enabled, mask);
+            actionBar.setDisplayOptions(mask, mask);
             mActionBarView.attach();
         }
         mViewMode.addListener(mActionBarView);
@@ -537,7 +510,7 @@
     }
 
     @Override
-    public void onAccountChanged(Account account) {
+    public void changeAccount(Account account) {
         // Is the account or account settings different from the existing account?
         final boolean firstLoad = mAccount == null;
         final boolean accountChanged = firstLoad || !account.uri.equals(mAccount.uri);
@@ -547,7 +520,7 @@
         }
         // We also don't want to do anything if the new account is null
         if (account == null) {
-            LogUtils.e(LOG_TAG, "AAC.onAccountChanged(null) called.");
+            LogUtils.e(LOG_TAG, "AAC.changeAccount(null) called.");
             return;
         }
         final String accountName = account.name;
@@ -596,6 +569,21 @@
     }
 
     @Override
+    public void registerAllAccountObserver(DataSetObserver observer) {
+        mAllAccountObservers.registerObserver(observer);
+    }
+
+    @Override
+    public void unregisterAllAccountObserver(DataSetObserver observer) {
+        mAllAccountObservers.unregisterObserver(observer);
+    }
+
+    @Override
+    public Account[] getAllAccounts() {
+        return mAllAccounts;
+    }
+
+    @Override
     public Account getAccount() {
         return mAccount;
     }
@@ -604,7 +592,7 @@
         final Bundle args = new Bundle();
         args.putString(ConversationListContext.EXTRA_SEARCH_QUERY, intent
                 .getStringExtra(ConversationListContext.EXTRA_SEARCH_QUERY));
-        mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, this);
+        mActivity.getLoaderManager().restartLoader(LOADER_SEARCH, args, mFolderCallbacks);
     }
 
     @Override
@@ -615,7 +603,8 @@
     /**
      * Sets the folder state without changing view mode and without creating a list fragment, if
      * possible.
-     * @param folder
+     * @param folder the folder whose list of conversations are to be shown
+     * @param query the query string for a list of conversations matching a search
      */
     private void setListContext(Folder folder, String query) {
         updateFolder(folder);
@@ -632,7 +621,7 @@
      * @param folder the folder to change to
      * @param query if non-null, this represents the search string that the folder represents.
      */
-    private final void changeFolder(Folder folder, String query) {
+    private void changeFolder(Folder folder, String query) {
         if (!Objects.equal(mFolder, folder)) {
             commitDestructiveActions(false);
         }
@@ -686,16 +675,16 @@
     // field in the account.
     @Override
     public void loadAccountInbox() {
-        restartOptionalLoader(LOADER_ACCOUNT_INBOX);
+        restartOptionalLoader(LOADER_ACCOUNT_INBOX, mFolderCallbacks, Bundle.EMPTY);
     }
 
     /**
      * Marks the {@link #mFolderChanged} value if the newFolder is different from the existing
      * {@link #mFolder}. This should be called immediately <b>before</b> assigning newFolder to
      * mFolder.
-     * @param newFolder
+     * @param newFolder the new folder we are switching to.
      */
-    private final void setHasFolderChanged(final Folder newFolder) {
+    private void setHasFolderChanged(final Folder newFolder) {
         // We should never try to assign a null folder. But in the rare event that we do, we should
         // only set the bit when we have a valid folder, and null is not valid.
         if (newFolder == null) {
@@ -744,21 +733,21 @@
         // previous loader's instance and data upon configuration change (e.g. rotation).
         // If there was not already an instance of the loader, init it.
         if (lm.getLoader(LOADER_FOLDER_CURSOR) == null) {
-            lm.initLoader(LOADER_FOLDER_CURSOR, null, this);
+            lm.initLoader(LOADER_FOLDER_CURSOR, Bundle.EMPTY, mFolderCallbacks);
         } else {
-            lm.restartLoader(LOADER_FOLDER_CURSOR, null, this);
+            lm.restartLoader(LOADER_FOLDER_CURSOR, Bundle.EMPTY, mFolderCallbacks);
         }
         // In this case, we are starting from no folder, which would occur
         // the first time the app was launched or on orientation changes.
         // We want to attach to an existing loader, if available.
         if (wasNull || lm.getLoader(LOADER_CONVERSATION_LIST) == null) {
-            lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
+            lm.initLoader(LOADER_CONVERSATION_LIST, Bundle.EMPTY, mListCursorCallbacks);
         } else {
             // However, if there was an existing folder AND we have changed
             // folders, we want to restart the loader to get the information
             // for the newly selected folder
             lm.destroyLoader(LOADER_CONVERSATION_LIST);
-            lm.initLoader(LOADER_CONVERSATION_LIST, null, mListCursorCallbacks);
+            lm.initLoader(LOADER_CONVERSATION_LIST, Bundle.EMPTY, mListCursorCallbacks);
         }
     }
 
@@ -784,8 +773,8 @@
                 // We were waiting for the user to create an account
                 if (resultCode == Activity.RESULT_OK) {
                     // restart the loader to get the updated list of accounts
-                    mActivity.getLoaderManager().initLoader(
-                            LOADER_ACCOUNT_CURSOR, null, this);
+                    mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, Bundle.EMPTY,
+                            mAccountCallbacks);
                 } else {
                     // The user failed to create an account, just exit the app
                     mActivity.finish();
@@ -805,7 +794,7 @@
 
     /**
      * Inform the conversation cursor that there has been a visibility change.
-     * @param visible
+     * @param visible true if the conversation list is visible, false otherwise.
      */
     protected synchronized void informCursorVisiblity(boolean visible) {
         if (mConversationListCursor != null) {
@@ -829,9 +818,57 @@
     public void onConversationVisibilityChanged(boolean visible) {
     }
 
+    /**
+     * Initialize development time logging. This can potentially log a lot of PII, and we don't want
+     * to turn it on for shipped versions.
+     */
+    private void initializeDevLoggingService() {
+        if (!MailLogService.DEBUG_ENABLED) {
+            return;
+        }
+        // Check every 5 minutes.
+        final int WAIT_TIME = 5 * 60 * 1000;
+        // Start a runnable that periodically checks the log level and starts/stops the service.
+        mLogServiceChecker = new Runnable() {
+            /** True if currently logging. */
+            private boolean mCurrentlyLogging = false;
+
+            /**
+             * If the logging level has been changed since the previous run, start or stop the
+             * service.
+             */
+            private void startOrStopService() {
+                // If the log level is already high, start the service.
+                final Intent i = new Intent(mContext, MailLogService.class);
+                final boolean loggingEnabled = MailLogService.isLoggingLevelHighEnough();
+                if (mCurrentlyLogging == loggingEnabled) {
+                    // No change since previous run, just return;
+                    return;
+                }
+                if (loggingEnabled) {
+                    LogUtils.e(LOG_TAG, "Starting MailLogService");
+                    mContext.startService(i);
+                } else {
+                    LogUtils.e(LOG_TAG, "Stopping MailLogService");
+                    mContext.stopService(i);
+                }
+                mCurrentlyLogging = loggingEnabled;
+            }
+
+            @Override
+            public void run() {
+                startOrStopService();
+                mHandler.postDelayed(this, WAIT_TIME);
+            }
+        };
+        // Start the runnable right away.
+        mHandler.post(mLogServiceChecker);
+    }
+
     @Override
     public boolean onCreate(Bundle savedState) {
         initializeActionBar();
+        initializeDevLoggingService();
         // Allow shortcut keys to function for the ActionBar and menus.
         mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
         mResolver = mActivity.getContentResolver();
@@ -871,7 +908,8 @@
             handleIntent(intent);
         }
         // Create the accounts loader; this loads the account switch spinner.
-        mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
+        mActivity.getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, Bundle.EMPTY,
+                mAccountCallbacks);
         return true;
     }
 
@@ -884,7 +922,7 @@
 
     @Override
     public void onRestart() {
-        DialogFragment fragment = (DialogFragment)
+        final DialogFragment fragment = (DialogFragment)
                 mFragmentManager.findFragmentByTag(SYNC_ERROR_DIALOG_FRAGMENT_TAG);
         if (fragment != null) {
             fragment.dismiss();
@@ -987,7 +1025,7 @@
                 ComposeActivity.compose(mActivity.getActivityContext(), mAccount);
                 break;
             case R.id.show_all_folders:
-                showFolderList();
+                toggleFolderListState();
                 break;
             case R.id.refresh:
                 requestFolderRefresh();
@@ -1007,10 +1045,13 @@
             case R.id.manage_folders_item:
                 Utils.showManageFolder(mActivity.getActivityContext(), mAccount);
                 break;
+            case R.id.move_to:
+                /* fall through */
             case R.id.change_folder:
                 final FolderSelectionDialog dialog = FolderSelectionDialog.getInstance(
                         mActivity.getActivityContext(), mAccount, this,
-                        Conversation.listOf(mCurrentConversation), false, mFolder);
+                        Conversation.listOf(mCurrentConversation), false, mFolder,
+                        id == R.id.move_to);
                 if (dialog != null) {
                     dialog.show();
                 }
@@ -1022,6 +1063,14 @@
         return handled;
     }
 
+    /**
+     * Stand-in method overriden in OnePaneController for toggling the state
+     * of the drawer.
+     */
+    protected void toggleFolderListState() {
+        //Implemented in OnePaneController.java
+    }
+
     @Override
     public final boolean onUpPressed() {
         for (UpOrBackHandler h : mUpOrBackHandlers) {
@@ -1212,6 +1261,11 @@
             final ContentValues value = new ContentValues();
             value.put(ConversationColumns.READ, read);
 
+            // We never want to mark unseen here, but we do want to mark it seen
+            if (read || markViewed) {
+                value.put(ConversationColumns.SEEN, Boolean.TRUE);
+            }
+
             // The mark read/unread/viewed operations do not show an undo bar
             value.put(ConversationOperations.Parameters.SUPPRESS_UNDO, true);
             if (markViewed) {
@@ -1438,11 +1492,9 @@
 
     /**
      * Requests that the action be performed and the UI state is updated to reflect the new change.
-     * @param target
-     * @param action
+     * @param action the action to be performed, specified as a menu id: R.id.archive, ...
      */
-    private void requestUpdate(final Collection<Conversation> target,
-            final DestructiveAction action) {
+    private void requestUpdate(final DestructiveAction action) {
         action.performAction();
         refreshConversationList();
     }
@@ -1509,8 +1561,7 @@
             outState.putParcelable(SAVED_DETACHED_CONV_URI, mDetachedConvUri);
         }
         mSafeToModifyFragments = false;
-        outState.putString(SAVED_HIERARCHICAL_FOLDER,
-                (mFolderListFolder != null) ? Folder.toString(mFolderListFolder) : null);
+        outState.putParcelable(SAVED_HIERARCHICAL_FOLDER, mFolderListFolder);
     }
 
     /**
@@ -1533,10 +1584,6 @@
 
     @Override
     public void onStop() {
-        if (mEnableShareIntents != null) {
-            mEnableShareIntents.cancel(true);
-        }
-
         NotificationActionUtils.unregisterUndoNotificationObserver(mUndoNotificationObserver);
     }
 
@@ -1551,6 +1598,8 @@
         mActionBarView.onDestroy();
         mRecentFolderList.destroy();
         mDestroyed = true;
+        mHandler.removeCallbacks(mLogServiceChecker);
+        mLogServiceChecker = null;
     }
 
     /**
@@ -1606,7 +1655,7 @@
 
     /**
      * Set the account, and carry out all the account-related changes that rely on this.
-     * @param account
+     * @param account new account to set to.
      */
     private void setAccount(Account account) {
         if (account == null) {
@@ -1618,10 +1667,10 @@
         mAccount = account;
         // Only change AAC state here. Do *not* modify any other object's state. The object
         // should listen on account changes.
-        restartOptionalLoader(LOADER_RECENT_FOLDERS);
+        restartOptionalLoader(LOADER_RECENT_FOLDERS, mFolderCallbacks, Bundle.EMPTY);
         mActivity.invalidateOptionsMenu();
         disableNotificationsOnAccountChange(mAccount);
-        restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
+        restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR, mAccountCallbacks, Bundle.EMPTY);
         // The Mail instance can be null during test runs.
         final MailAppProvider instance = MailAppProvider.getInstance();
         if (instance != null) {
@@ -1640,7 +1689,7 @@
      * method from the parent class, since it performs important UI
      * initialization.
      *
-     * @param savedState
+     * @param savedState previous state
      */
     @Override
     public void onRestoreInstanceState(Bundle savedState) {
@@ -1666,10 +1715,7 @@
                 }
             }
         }
-        final String folderString = savedState.getString(SAVED_HIERARCHICAL_FOLDER, null);
-        if (!TextUtils.isEmpty(folderString)) {
-            mFolderListFolder = Folder.fromString(folderString);
-        }
+        mFolderListFolder = savedState.getParcelable(SAVED_HIERARCHICAL_FOLDER);
         final ConversationListFragment convListFragment = getConversationListFragment();
         if (convListFragment != null) {
             convListFragment.getAnimatedAdapter().onRestoreInstanceState(savedState);
@@ -1694,10 +1740,9 @@
      * Handle an intent to open the app. This method is called only when there is no saved state,
      * so we need to set state that wasn't set before. It is correct to change the viewmode here
      * since it has not been previously set.
-     * @param intent
+     * @param intent intent passed to the activity.
      */
     private void handleIntent(Intent intent) {
-        boolean handled = false;
         if (Intent.ACTION_VIEW.equals(intent.getAction())) {
             if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
                 setAccount(Account.newinstance(intent.getStringExtra(Utils.EXTRA_ACCOUNT)));
@@ -1705,48 +1750,21 @@
             if (mAccount == null) {
                 return;
             }
-            boolean isConversationMode;
-            try {
-                isConversationMode = intent.hasExtra(Utils.EXTRA_CONVERSATION);
-            } catch (BadParcelableException e) {
-                // If a previous version of Gmail had a PendingIntent for a conversation when the
-                // app upgrades, the system may not actually update the PendingIntent to the new
-                // format.  If we see one of these old intents, just treat it as a conversation list
-                // intent.
-                LogUtils.e(LOG_TAG, e, "Error parsing conversation");
-                isConversationMode = false;
-            }
+            final boolean isConversationMode = intent.hasExtra(Utils.EXTRA_CONVERSATION);
             if (isConversationMode && mViewMode.getMode() == ViewMode.UNKNOWN) {
                 mViewMode.enterConversationMode();
             } else {
                 mViewMode.enterConversationListMode();
             }
-            final Folder folder = intent.hasExtra(Utils.EXTRA_FOLDER) ?
-                    Folder.fromString(intent.getStringExtra(Utils.EXTRA_FOLDER)) : null;
-            if (folder != null) {
-                onFolderChanged(folder);
-                handled = true;
-            }
-
-            if (isConversationMode) {
-                // Open the conversation.
-                LogUtils.d(LOG_TAG, "SHOW THE CONVERSATION at %s",
-                        intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
-                final Conversation conversation =
-                        intent.getParcelableExtra(Utils.EXTRA_CONVERSATION);
-                if (conversation != null && conversation.position < 0) {
-                    // Set the position to 0 on this conversation, as we don't know where it is
-                    // in the list
-                    conversation.position = 0;
-                }
-                showConversation(conversation);
-                handled = true;
-            }
-
-            if (!handled) {
-                // We have an account, but nothing else: load the default inbox.
-                loadAccountInbox();
-            }
+            // Put the folder and conversation, and ask the loader to create this folder.
+            final Bundle args = new Bundle();
+            final Uri folderUri = intent.hasExtra(Utils.EXTRA_FOLDER_URI)
+                    ? (Uri) intent.getParcelableExtra(Utils.EXTRA_FOLDER_URI)
+                    : mAccount.settings.defaultInbox;
+            args.putParcelable(Utils.EXTRA_FOLDER_URI, folderUri);
+            args.putParcelable(Utils.EXTRA_CONVERSATION,
+                    intent.getParcelableExtra(Utils.EXTRA_CONVERSATION));
+            restartOptionalLoader(LOADER_FIRST_FOLDER, mFolderCallbacks, args);
         } else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
             if (intent.hasExtra(Utils.EXTRA_ACCOUNT)) {
                 mHaveSearchResults = false;
@@ -1769,7 +1787,7 @@
             }
         }
         if (mAccount != null) {
-            restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
+            restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR, mAccountCallbacks, Bundle.EMPTY);
         }
     }
 
@@ -1785,7 +1803,7 @@
      * triggering {@link ConversationSetObserver} callbacks as our selection set changes.
      *
      */
-    private final void restoreSelectedConversations(Bundle savedState) {
+    private void restoreSelectedConversations(Bundle savedState) {
         if (savedState == null) {
             mSelectedSet.clear();
             return;
@@ -1805,7 +1823,7 @@
         return mActionBarView;
     }
 
-    private final void showConversation(Conversation conversation) {
+    private void showConversation(Conversation conversation) {
         showConversation(conversation, false /* inLoaderCallbacks */);
     }
 
@@ -1813,11 +1831,17 @@
      * Show the conversation provided in the arguments. It is safe to pass a null conversation
      * object, which is a signal to back out of conversation view mode.
      * Child classes must call super.showConversation() <b>before</b> their own implementations.
-     * @param conversation
+     * @param conversation the conversation to be shown, or null if we want to back out to list
+     *                     mode.
      * @param inLoaderCallbacks true if the method is called as a result of
-     * {@link #onLoadFinished(Loader, Cursor)}
+     * onLoadFinished(Loader, Cursor) on any callback.
      */
     protected void showConversation(Conversation conversation, boolean inLoaderCallbacks) {
+        if (conversation != null) {
+            Utils.sConvLoadTimer.start();
+        }
+
+        MailLogService.log("AbstractActivityController", "showConversation(%s)", conversation);
         // Set the current conversation just in case it wasn't already set.
         setCurrentConversation(conversation);
         // Add the folder that we were viewing to the recent folders list.
@@ -1858,7 +1882,7 @@
      * Use the instance variable and the wait fragment's tag to get the wait fragment.  This is
      * far superior to using the value of mWaitFragment, which might be invalid or might refer
      * to a fragment after it has been destroyed.
-     * @return
+     * @return a wait fragment that is already attached to the activity, if one exists
      */
     protected final WaitFragment getWaitFragment() {
         final FragmentManager manager = mActivity.getFragmentManager();
@@ -1909,7 +1933,8 @@
      * Set the current conversation. This is the conversation on which all actions are performed.
      * Do not modify mCurrentConversation except through this method, which makes it easy to
      * perform common actions associated with changing the current conversation.
-     * @param conversation
+     * @param conversation new conversation to view. Passing null indicates that we are backing
+     *                     out to conversation list mode.
      */
     @Override
     public void setCurrentConversation(Conversation conversation) {
@@ -1933,54 +1958,6 @@
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-        switch (id) {
-            case LOADER_ACCOUNT_CURSOR:
-                return new CursorLoader(mContext, MailAppProvider.getAccountsUri(),
-                        UIProvider.ACCOUNTS_PROJECTION, null, null, null);
-            case LOADER_FOLDER_CURSOR:
-                final CursorLoader loader = new CursorLoader(mContext, mFolder.uri,
-                        UIProvider.FOLDERS_PROJECTION, null, null, null);
-                loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
-                return loader;
-            case LOADER_RECENT_FOLDERS:
-                if (mAccount != null && mAccount.recentFolderListUri != null) {
-                    return new CursorLoader(mContext, mAccount.recentFolderListUri,
-                            UIProvider.FOLDERS_PROJECTION, null, null, null);
-                }
-                break;
-            case LOADER_ACCOUNT_INBOX:
-                final Uri defaultInbox = Settings.getDefaultInboxUri(mAccount.settings);
-                final Uri inboxUri = defaultInbox.equals(Uri.EMPTY) ?
-                    mAccount.folderListUri : defaultInbox;
-                LogUtils.d(LOG_TAG, "Loading the default inbox: %s", inboxUri);
-                if (inboxUri != null) {
-                    return new CursorLoader(mContext, inboxUri, UIProvider.FOLDERS_PROJECTION, null,
-                            null, null);
-                }
-                break;
-            case LOADER_SEARCH:
-                return Folder.forSearchResults(mAccount,
-                        args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
-                        mActivity.getActivityContext());
-            case LOADER_ACCOUNT_UPDATE_CURSOR:
-                return new CursorLoader(mContext, mAccount.uri, UIProvider.ACCOUNTS_PROJECTION,
-                        null, null, null);
-            default:
-                LogUtils.wtf(LOG_TAG, "Loader returned unexpected id: %d", id);
-        }
-        return null;
-    }
-
-    @Override
-    public void onLoaderReset(Loader<Cursor> loader) {
-
-    }
-
-    /**
      * {@link LoaderManager} currently has a bug in
      * {@link LoaderManager#restartLoader(int, Bundle, android.app.LoaderManager.LoaderCallbacks)}
      * where, if a previous onCreateLoader returned a null loader, this method will NPE. Work around
@@ -1991,11 +1968,14 @@
      * give the controller a chance to invalidate UI corresponding the prior loader result.
      *
      * @param id loader ID to safely restart
+     * @param handler the LoaderCallback which will handle this loader ID.
+     * @param args arguments, if any, to be passed to the loader. Use {@link Bundle#EMPTY} if no
+     *             arguments need to be specified.
      */
-    private void restartOptionalLoader(int id) {
+    private void restartOptionalLoader(int id, LoaderManager.LoaderCallbacks handler, Bundle args) {
         final LoaderManager lm = mActivity.getLoaderManager();
         lm.destroyLoader(id);
-        lm.restartLoader(id, Bundle.EMPTY, this);
+        lm.restartLoader(id, args, handler);
     }
 
     @Override
@@ -2049,10 +2029,10 @@
     /**
      * Returns true if the number of accounts is different, or if the current account has been
      * removed from the device
-     * @param accountCursor
-     * @return
+     * @param accountCursor the cursor which points to all the accounts.
+     * @return true if the number of accounts is changed or current account missing from the list.
      */
-    private boolean accountsUpdated(Cursor accountCursor) {
+    private boolean accountsUpdated(ObjectCursor<Account> accountCursor) {
         // Check to see if the current account hasn't been set, or the account cursor is empty
         if (mAccount == null || !accountCursor.moveToFirst()) {
             return true;
@@ -2068,8 +2048,8 @@
         // the cursor.
         boolean foundCurrentAccount = false;
         do {
-            final Uri accountUri =
-                    Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
+            final Uri accountUri = Uri.parse(accountCursor.getString(
+                    accountCursor.getColumnIndex(UIProvider.AccountColumns.URI)));
             if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
                 foundCurrentAccount = true;
             }
@@ -2090,7 +2070,7 @@
      * @param accounts cursor into the AccountCache
      * @return true if the update was successful, false otherwise
      */
-    private boolean updateAccounts(Cursor accounts) {
+    private boolean updateAccounts(ObjectCursor<Account> accounts) {
         if (accounts == null || !accounts.moveToFirst()) {
             return false;
         }
@@ -2143,11 +2123,13 @@
             }
         }
         if (accountChanged) {
-            onAccountChanged(newAccount);
+            changeAccount(newAccount);
         }
+
         // Whether we have updated the current account or not, we need to update the list of
         // accounts in the ActionBar.
-        mActionBarView.setAccounts(allAccounts);
+        mAllAccounts = allAccounts;
+        mAllAccountObservers.notifyChanged();
         return (allAccounts.length > 0);
     }
 
@@ -2171,148 +2153,6 @@
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-        // We want to reinitialize only if we haven't ever been initialized, or
-        // if the current account has vanished.
-        if (data == null) {
-            LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
-        }
-        switch (loader.getId()) {
-            case LOADER_ACCOUNT_CURSOR:
-                if (data == null) {
-                    // Nothing useful to do if we have no valid data.
-                    break;
-                }
-                if (data.getCount() == 0) {
-                    // If an empty cursor is returned, the MailAppProvider is indicating that
-                    // no accounts have been specified.  We want to navigate to the "add account"
-                    // activity that will handle the intent returned by the MailAppProvider
-
-                    // If the MailAppProvider believes that all accounts have been loaded, and the
-                    // account list is still empty, we want to prompt the user to add an account
-                    final Bundle extras = data.getExtras();
-                    final boolean accountsLoaded =
-                            extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
-
-                    if (accountsLoaded) {
-                        final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(mContext);
-                        if (noAccountIntent != null) {
-                            mActivity.startActivityForResult(noAccountIntent,
-                                    ADD_ACCOUNT_REQUEST_CODE);
-                        }
-                    }
-                } else {
-                    final boolean accountListUpdated = accountsUpdated(data);
-                    if (!isLoaderInitialized || accountListUpdated) {
-                        isLoaderInitialized = updateAccounts(data);
-                    }
-                }
-                break;
-            case LOADER_ACCOUNT_UPDATE_CURSOR:
-                // We have gotten an update for current account.
-
-                // Make sure that this is an update for the current account
-                if (data != null && data.moveToFirst()) {
-                    final Account updatedAccount = new Account(data);
-
-                    if (updatedAccount.uri.equals(mAccount.uri)) {
-                        // Keep a reference to the previous settings object
-                        final Settings previousSettings = mAccount.settings;
-
-                        // Update the controller's reference to the current account
-                        mAccount = updatedAccount;
-                        LogUtils.d(LOG_TAG, "AbstractActivityController.onLoadFinished(): "
-                                + "mAccount = %s", mAccount.uri);
-
-                        // Only notify about a settings change if something differs
-                        if (!Objects.equal(mAccount.settings, previousSettings)) {
-                            mAccountObservers.notifyChanged();
-                        }
-                        perhapsEnterWaitMode();
-                    } else {
-                        LogUtils.e(LOG_TAG, "Got update for account: %s with current account: %s",
-                                updatedAccount.uri, mAccount.uri);
-                        // We need to restart the loader, so the correct account information will
-                        // be returned
-                        restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR);
-                    }
-                }
-                break;
-            case LOADER_FOLDER_CURSOR:
-                // Check status of the cursor.
-                if (data != null && data.moveToFirst()) {
-                    final Folder folder = new Folder(data);
-                    LogUtils.d(LOG_TAG, "FOLDER STATUS = %d", folder.syncStatus);
-                    setHasFolderChanged(folder);
-                    mFolder = folder;
-                    mFolderObservable.notifyChanged();
-                } else {
-                    LogUtils.d(LOG_TAG, "Unable to get the folder %s",
-                            mFolder != null ? mAccount.name : "");
-                }
-                break;
-            case LOADER_RECENT_FOLDERS:
-                // Few recent folders and we are running on a phone? Populate the default recents.
-                // The number of default recent folders is at least 2: every provider has at
-                // least two folders, and the recent folder count never decreases. Having a single
-                // recent folder is an erroneous case, and we can gracefully recover by populating
-                // 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 && !mIsTablet) {
-                    final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
-                        @Override
-                        protected Void doInBackground(Uri... uri) {
-                            // Asking for an update on the URI and ignore the result.
-                            final ContentResolver resolver = mContext.getContentResolver();
-                            resolver.update(uri[0], null, null, null);
-                            return null;
-                        }
-                    }
-                    final Uri uri = mAccount.defaultRecentFolderListUri;
-                    LogUtils.v(LOG_TAG, "Default recents at %s", uri);
-                    new PopulateDefault().execute(uri);
-                    break;
-                }
-                LogUtils.v(LOG_TAG, "Reading recent folders from the cursor.");
-                loadRecentFolders(data);
-                break;
-            case LOADER_ACCOUNT_INBOX:
-                if (data != null && !data.isClosed() && data.moveToFirst()) {
-                    Folder inbox = new Folder(data);
-                    onFolderChanged(inbox);
-                    // Just want to get the inbox, don't care about updates to it
-                    // as this will be tracked by the folder change listener.
-                    mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
-                } else {
-                    LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
-                            mAccount != null ? mAccount.name : "");
-                }
-                break;
-            case LOADER_SEARCH:
-                if (data != null && data.getCount() > 0) {
-                    data.moveToFirst();
-                    final Folder search = new Folder(data);
-                    updateFolder(search);
-                    mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
-                            mActivity.getIntent()
-                                    .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
-                    showConversationList(mConvListContext);
-                    mActivity.invalidateOptionsMenu();
-                    mHaveSearchResults = search.totalCount > 0;
-                    mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
-                } else {
-                    LogUtils.e(LOG_TAG, "Null or empty cursor returned by LOADER_SEARCH loader");
-                }
-                break;
-        }
-    }
-
-
-    /**
      * Destructive actions on Conversations. This class should only be created by controllers, and
      * clients should only require {@link DestructiveAction}s, not specific implementations of the.
      * Only the controllers should know what kind of destructive actions are being created.
@@ -2331,9 +2171,9 @@
         private final boolean mIsSelectedSet;
 
         /**
-         * Create a listener object. action is one of four constants: R.id.y_button (archive),
+         * Create a listener object.
+         * @param action action is one of four constants: R.id.y_button (archive),
          * R.id.delete , R.id.mute, and R.id.report_spam.
-         * @param action
          * @param target Conversation that we want to apply the action to.
          * @param isBatch whether the conversations are in the currently selected batch set.
          */
@@ -2437,7 +2277,7 @@
                     @Override
                     public void run() {
                         onUndoAvailable(new ToastBarOperation(mTarget.size(), mAction,
-                                ToastBarOperation.UNDO, mIsSelectedSet));
+                                ToastBarOperation.UNDO, mIsSelectedSet, mFolder));
                     }
                 }, mShowUndoBarDelay);
             }
@@ -2464,7 +2304,8 @@
     // conversations to.
     @Override
     public final void assignFolder(Collection<FolderOperation> folderOps,
-            Collection<Conversation> target, boolean batch, boolean showUndo) {
+            Collection<Conversation> target, boolean batch, boolean showUndo,
+            final boolean isMoveTo) {
         // Actions are destructive only when the current folder can be assigned
         // to (which is the same as being able to un-assign a conversation from the folder) and
         // when the list of folders contains the current folder.
@@ -2481,13 +2322,41 @@
         // Update the UI elements depending no their visibility and availability
         // TODO(viki): Consolidate this into a single method requestDelete.
         if (isDestructive) {
+            /*
+             * If this is a MOVE operation, we want the action folder to be the destination folder.
+             * Otherwise, we want it to be the current folder.
+             *
+             * A set of folder operations is a move if there are exactly two operations: an add and
+             * a remove.
+             */
+            final Folder actionFolder;
+            if (folderOps.size() != 2) {
+                actionFolder = mFolder;
+            } else {
+                Folder addedFolder = null;
+                boolean hasRemove = false;
+                for (final FolderOperation folderOperation : folderOps) {
+                    if (folderOperation.mAdd) {
+                        addedFolder = folderOperation.mFolder;
+                    } else {
+                        hasRemove = true;
+                    }
+                }
+
+                if (hasRemove && addedFolder != null) {
+                    actionFolder = addedFolder;
+                } else {
+                    actionFolder = mFolder;
+                }
+            }
+
             folderChange = getDeferredFolderChange(target, folderOps, isDestructive,
-                    batch, showUndo);
+                    batch, showUndo, isMoveTo, actionFolder);
             delete(0, target, folderChange);
         } else {
             folderChange = getFolderChange(target, folderOps, isDestructive,
-                    batch, showUndo);
-            requestUpdate(target, folderChange);
+                    batch, showUndo, false /* isMoveTo */, mFolder);
+            requestUpdate(folderChange);
         }
     }
 
@@ -2572,7 +2441,7 @@
     /**
      * If the Conversation List Fragment is visible, updates the fragment.
      */
-    private final void updateConversationListFragment() {
+    private void updateConversationListFragment() {
         final ConversationListFragment convList = getConversationListFragment();
         if (convList != null) {
             refreshConversationList();
@@ -2615,15 +2484,6 @@
         }
     }
 
-    private void loadRecentFolders(Cursor data) {
-        mRecentFolderList.loadFromUiProvider(data);
-        if (isAnimating()) {
-            mRecentsDataUpdated = true;
-        } else {
-            mRecentFolderObservers.notifyChanged();
-        }
-    }
-
     @Override
     public void onAnimationEnd(AnimatedAdapter animatedAdapter) {
         if (mConversationListCursor == null) {
@@ -2771,8 +2631,9 @@
         }
         // Drag and drop is destructive: we remove conversations from the
         // current folder.
-        final DestructiveAction action = getFolderChange(conversations, dragDropOperations,
-                isDestructive, true, true);
+        final DestructiveAction action =
+                getFolderChange(conversations, dragDropOperations, isDestructive,
+                        true /* isBatch */, true /* showUndo */, true /* isMoveTo */, folder);
         if (isDestructive) {
             delete(0, conversations, action);
         } else {
@@ -2807,7 +2668,6 @@
             }
             refreshConversationList();
             mSelectedSet.clear();
-            return;
         }
     }
 
@@ -2821,7 +2681,6 @@
             LogUtils.d(LOG_TAG, "AAC.requestDelete: ListFragment is handling delete.");
             convListFragment.requestDelete(R.id.change_folder, conversations,
                     new DroppedInStarredAction(conversations, mFolder, folder));
-            return;
         }
     }
 
@@ -2842,7 +2701,7 @@
         @Override
         public void performAction() {
             ToastBarOperation undoOp = new ToastBarOperation(mConversations.size(),
-                    R.id.change_folder, ToastBarOperation.UNDO, true);
+                    R.id.change_folder, ToastBarOperation.UNDO, true /* batch */, mInitialFolder);
             onUndoAvailable(undoOp);
             ArrayList<ConversationOperation> ops = new ArrayList<ConversationOperation>();
             ContentValues values = new ContentValues();
@@ -2898,7 +2757,7 @@
      * Check if the fragment given here is visible. Checking {@link Fragment#isVisible()} is
      * insufficient because that doesn't check if the window is currently in focus or not.
      */
-    private final boolean isFragmentVisible(Fragment in) {
+    private boolean isFragmentVisible(Fragment in) {
         return in != null && in.isVisible() && mActivity.hasWindowFocus();
     }
 
@@ -2907,9 +2766,8 @@
 
         @Override
         public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
-            Loader<ConversationCursor> result = new ConversationCursorLoader((Activity) mActivity,
+            return new ConversationCursorLoader((Activity) mActivity,
                     mAccount, mFolder.conversationListUri, mFolder.name);
-            return result;
         }
 
         @Override
@@ -2956,9 +2814,274 @@
     }
 
     /**
+     * Class to perform {@link LoaderManager.LoaderCallbacks} for creating {@link Folder} objects.
+     */
+    private class FolderLoads implements LoaderManager.LoaderCallbacks<ObjectCursor<Folder>> {
+        @Override
+        public Loader<ObjectCursor<Folder>> onCreateLoader(int id, Bundle args) {
+            final String[] everything = UIProvider.FOLDERS_PROJECTION;
+            switch (id) {
+                case LOADER_FOLDER_CURSOR:
+                    LogUtils.d(LOG_TAG, "LOADER_FOLDER_CURSOR created");
+                    final ObjectCursorLoader<Folder> loader = new
+                            ObjectCursorLoader<Folder>(
+                            mContext, mFolder.uri, everything, Folder.FACTORY);
+                    loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
+                    return loader;
+                case LOADER_RECENT_FOLDERS:
+                    LogUtils.d(LOG_TAG, "LOADER_RECENT_FOLDERS created");
+                    if (mAccount != null && mAccount.recentFolderListUri != null
+                            && !mAccount.recentFolderListUri.equals(Uri.EMPTY)) {
+                        return new ObjectCursorLoader<Folder>(mContext,
+                                mAccount.recentFolderListUri, everything, Folder.FACTORY);
+                    }
+                    break;
+                case LOADER_ACCOUNT_INBOX:
+                    LogUtils.d(LOG_TAG, "LOADER_ACCOUNT_INBOX created");
+                    final Uri defaultInbox = Settings.getDefaultInboxUri(mAccount.settings);
+                    final Uri inboxUri = defaultInbox.equals(Uri.EMPTY) ?
+                            mAccount.folderListUri : defaultInbox;
+                    LogUtils.d(LOG_TAG, "Loading the default inbox: %s", inboxUri);
+                    if (inboxUri != null) {
+                        return new ObjectCursorLoader<Folder>(mContext, inboxUri,
+                                everything, Folder.FACTORY);
+                    }
+                    break;
+                case LOADER_SEARCH:
+                    LogUtils.d(LOG_TAG, "LOADER_SEARCH created");
+                    return Folder.forSearchResults(mAccount,
+                            args.getString(ConversationListContext.EXTRA_SEARCH_QUERY),
+                            mActivity.getActivityContext());
+                case LOADER_FIRST_FOLDER:
+                    LogUtils.d(LOG_TAG, "LOADER_FIRST_FOLDER created");
+                    final Uri folderUri = args.getParcelable(Utils.EXTRA_FOLDER_URI);
+                    mConversationToShow = args.getParcelable(Utils.EXTRA_CONVERSATION);
+                    if (mConversationToShow != null && mConversationToShow.position < 0){
+                        mConversationToShow.position = 0;
+                    }
+                    return new ObjectCursorLoader<Folder>(mContext, folderUri,
+                            everything, Folder.FACTORY);
+                default:
+                    LogUtils.wtf(LOG_TAG, "FolderLoads.onCreateLoader(%d) for invalid id", id);
+                    return null;
+            }
+            return null;
+        }
+
+        @Override
+        public void onLoadFinished(Loader<ObjectCursor<Folder>> loader, ObjectCursor<Folder> data) {
+            if (data == null) {
+                LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
+            }
+            switch (loader.getId()) {
+                case LOADER_FOLDER_CURSOR:
+                    if (data != null && data.moveToFirst()) {
+                        final Folder folder = data.getModel();
+                        setHasFolderChanged(folder);
+                        mFolder = folder;
+                        mFolderObservable.notifyChanged();
+                    } else {
+                        LogUtils.d(LOG_TAG, "Unable to get the folder %s",
+                                mFolder != null ? mAccount.name : "");
+                    }
+                    break;
+                case LOADER_RECENT_FOLDERS:
+                    // Few recent folders and we are running on a phone? Populate the default
+                    // recents. The number of default recent folders is at least 2: every provider
+                    // has at least two folders, and the recent folder count never decreases.
+                    // Having a single recent folder is an erroneous case, and we can gracefully
+                    // recover by populating 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 && !mIsTablet) {
+                        final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
+                            @Override
+                            protected Void doInBackground(Uri... uri) {
+                                // Asking for an update on the URI and ignore the result.
+                                final ContentResolver resolver = mContext.getContentResolver();
+                                resolver.update(uri[0], null, null, null);
+                                return null;
+                            }
+                        }
+                        final Uri uri = mAccount.defaultRecentFolderListUri;
+                        LogUtils.v(LOG_TAG, "Default recents at %s", uri);
+                        new PopulateDefault().execute(uri);
+                        break;
+                    }
+                    LogUtils.v(LOG_TAG, "Reading recent folders from the cursor.");
+                    mRecentFolderList.loadFromUiProvider(data);
+                    if (isAnimating()) {
+                        mRecentsDataUpdated = true;
+                    } else {
+                        mRecentFolderObservers.notifyChanged();
+                    }
+                    break;
+                case LOADER_ACCOUNT_INBOX:
+                    if (data != null && !data.isClosed() && data.moveToFirst()) {
+                        final Folder inbox = data.getModel();
+                        onFolderChanged(inbox);
+                        // Just want to get the inbox, don't care about updates to it
+                        // as this will be tracked by the folder change listener.
+                        mActivity.getLoaderManager().destroyLoader(LOADER_ACCOUNT_INBOX);
+                    } else {
+                        LogUtils.d(LOG_TAG, "Unable to get the account inbox for account %s",
+                                mAccount != null ? mAccount.name : "");
+                    }
+                    break;
+                case LOADER_SEARCH:
+                    if (data != null && data.getCount() > 0) {
+                        data.moveToFirst();
+                        final Folder search = data.getModel();
+                        updateFolder(search);
+                        mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder,
+                                mActivity.getIntent()
+                                        .getStringExtra(UIProvider.SearchQueryParameters.QUERY));
+                        showConversationList(mConvListContext);
+                        mActivity.invalidateOptionsMenu();
+                        mHaveSearchResults = search.totalCount > 0;
+                        mActivity.getLoaderManager().destroyLoader(LOADER_SEARCH);
+                    } else {
+                        LogUtils.e(LOG_TAG, "Null/empty cursor returned by LOADER_SEARCH loader");
+                    }
+                    break;
+                case LOADER_FIRST_FOLDER:
+                    if (data == null || data.getCount() <=0 || !data.moveToFirst()) {
+                        return;
+                    }
+                    final Folder folder = data.getModel();
+                    boolean handled = false;
+                    if (folder != null) {
+                        onFolderChanged(folder);
+                        handled = true;
+                    }
+                    if (mConversationToShow != null) {
+                        // Open the conversation.
+                        showConversation(mConversationToShow);
+                        handled = true;
+                    }
+                    if (!handled) {
+                        // We have an account, but nothing else: load the default inbox.
+                        loadAccountInbox();
+                    }
+                    mConversationToShow = null;
+                    // And don't run this anymore.
+                    mActivity.getLoaderManager().destroyLoader(LOADER_FIRST_FOLDER);
+                    break;
+            }
+        }
+
+        @Override
+        public void onLoaderReset(Loader<ObjectCursor<Folder>> loader) {
+        }
+    }
+
+    /**
+     * Class to perform {@link LoaderManager.LoaderCallbacks} for creating {@link Account} objects.
+     */
+    private class AccountLoads implements LoaderManager.LoaderCallbacks<ObjectCursor<Account>> {
+        final String[] mProjection = UIProvider.ACCOUNTS_PROJECTION;
+        final CursorCreator<Account> mFactory = Account.FACTORY;
+
+        @Override
+        public Loader<ObjectCursor<Account>> onCreateLoader(int id, Bundle args) {
+            switch (id) {
+                case LOADER_ACCOUNT_CURSOR:
+                    LogUtils.d(LOG_TAG,  "LOADER_ACCOUNT_CURSOR created");
+                    return new ObjectCursorLoader<Account>(mContext,
+                            MailAppProvider.getAccountsUri(), mProjection, mFactory);
+                case LOADER_ACCOUNT_UPDATE_CURSOR:
+                    LogUtils.d(LOG_TAG,  "LOADER_ACCOUNT_UPDATE_CURSOR created");
+                    return new ObjectCursorLoader<Account>(mContext, mAccount.uri, mProjection,
+                            mFactory);
+                default:
+                    LogUtils.wtf(LOG_TAG, "Got an id  (%d) that I cannot create!", id);
+                    break;
+            }
+            return null;
+        }
+
+        @Override
+        public void onLoadFinished(Loader<ObjectCursor<Account>> loader,
+                ObjectCursor<Account> data) {
+            if (data == null) {
+                LogUtils.e(LOG_TAG, "Received null cursor from loader id: %d", loader.getId());
+            }
+            switch (loader.getId()) {
+                case LOADER_ACCOUNT_CURSOR:
+                    if (data == null) {
+                        // Nothing useful to do if we have no valid data.
+                        break;
+                    }
+                    if (data.getCount() == 0) {
+                        // If an empty cursor is returned, the MailAppProvider is indicating that
+                        // no accounts have been specified.  We want to navigate to the
+                        // "add account" activity that will handle the intent returned by the
+                        // MailAppProvider
+
+                        // If the MailAppProvider believes that all accounts have been loaded,
+                        // and the account list is still empty, we want to prompt the user to add
+                        // an account.
+                        final Bundle extras = data.getExtras();
+                        final boolean accountsLoaded =
+                                extras.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
+
+                        if (accountsLoaded) {
+                            final Intent noAccountIntent = MailAppProvider.getNoAccountIntent
+                                    (mContext);
+                            if (noAccountIntent != null) {
+                                mActivity.startActivityForResult(noAccountIntent,
+                                        ADD_ACCOUNT_REQUEST_CODE);
+                            }
+                        }
+                    } else {
+                        final boolean accountListUpdated = accountsUpdated(data);
+                        if (!isLoaderInitialized || accountListUpdated) {
+                            isLoaderInitialized = updateAccounts(data);
+                        }
+                    }
+                    break;
+                case LOADER_ACCOUNT_UPDATE_CURSOR:
+                    // We have received an update for current account.
+
+                    // Make sure that this is an update for the current account
+                    if (data != null && data.moveToFirst()) {
+                        final Account updatedAccount = data.getModel();
+
+                        if (updatedAccount.uri.equals(mAccount.uri)) {
+                            // Keep a reference to the previous settings object
+                            final Settings previousSettings = mAccount.settings;
+
+                            // Update the controller's reference to the current account
+                            mAccount = updatedAccount;
+                            LogUtils.d(LOG_TAG, "AbstractActivityController.onLoadFinished(): "
+                                    + "mAccount = %s", mAccount.uri);
+
+                            // Only notify about a settings change if something differs
+                            if (!Objects.equal(mAccount.settings, previousSettings)) {
+                                mAccountObservers.notifyChanged();
+                            }
+                            perhapsEnterWaitMode();
+                        } else {
+                            LogUtils.e(LOG_TAG, "Got update for account: %s with current account:"
+                                    + " %s", updatedAccount.uri, mAccount.uri);
+                            // We need to restart the loader, so the correct account information
+                            // will be returned.
+                            restartOptionalLoader(LOADER_ACCOUNT_UPDATE_CURSOR, this, Bundle.EMPTY);
+                        }
+                    }
+                    break;
+            }
+        }
+
+        @Override
+        public void onLoaderReset(Loader<ObjectCursor<Account>> loader) {
+        }
+    }
+
+    /**
      * Updates controller state based on search results and shows first conversation if required.
      */
-    private final void perhapsShowFirstSearchResult() {
+    private void perhapsShowFirstSearchResult() {
         if (mCurrentConversation == null) {
             // Shown for search results in two-pane mode only.
             mHaveSearchResults = Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())
@@ -2978,7 +3101,7 @@
      * next destructive action..
      * @param nextAction the next destructive action to be performed. This can be null.
      */
-    private final void destroyPending(DestructiveAction nextAction) {
+    private void destroyPending(DestructiveAction nextAction) {
         // If there is a pending action, perform that first.
         if (mPendingDestruction != null) {
             mPendingDestruction.performAction();
@@ -2990,14 +3113,13 @@
      * Register a destructive action with the controller. This performs the previous destructive
      * action as a side effect. This method is final because we don't want the child classes to
      * embellish this method any more.
-     * @param action
+     * @param action the action to register.
      */
-    private final void registerDestructiveAction(DestructiveAction action) {
+    private void registerDestructiveAction(DestructiveAction action) {
         // TODO(viki): This is not a good idea. The best solution is for clients to request a
         // destructive action from the controller and for the controller to own the action. This is
         // a half-way solution while refactoring DestructiveAction.
         destroyPending(action);
-        return;
     }
 
     @Override
@@ -3021,7 +3143,7 @@
      * @param target the conversations to act upon.
      * @return a {@link DestructiveAction} that performs the specified action.
      */
-    private final DestructiveAction getDeferredAction(int action, Collection<Conversation> target,
+    private DestructiveAction getDeferredAction(int action, Collection<Conversation> target,
             boolean batch) {
         return new ConversationAction(action, target, batch);
     }
@@ -3040,20 +3162,23 @@
         private boolean mIsSelectedSet;
         private boolean mShowUndo;
         private int mAction;
+        private final Folder mActionFolder;
 
         /**
          * Create a new folder destruction object to act on the given conversations.
-         * @param target
+         * @param target conversations to act upon.
+         * @param actionFolder the {@link Folder} being acted upon, used for displaying the undo bar
          */
         private FolderDestruction(final Collection<Conversation> target,
                 final Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
-                boolean showUndo, int action) {
+                boolean showUndo, int action, final Folder actionFolder) {
             mTarget = ImmutableList.copyOf(target);
             mFolderOps.addAll(folders);
             mIsDestructive = isDestructive;
             mIsSelectedSet = isBatch;
             mShowUndo = showUndo;
             mAction = action;
+            mActionFolder = actionFolder;
         }
 
         @Override
@@ -3063,7 +3188,7 @@
             }
             if (mIsDestructive && mShowUndo) {
                 ToastBarOperation undoOp = new ToastBarOperation(mTarget.size(), mAction,
-                        ToastBarOperation.UNDO, mIsSelectedSet);
+                        ToastBarOperation.UNDO, mIsSelectedSet, mActionFolder);
                 onUndoAvailable(undoOp);
             }
             // For each conversation, for each operation, add/ remove the
@@ -3115,19 +3240,18 @@
 
     public final DestructiveAction getFolderChange(Collection<Conversation> target,
             Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
-            boolean showUndo) {
+            boolean showUndo, final boolean isMoveTo, final Folder actionFolder) {
         final DestructiveAction da = getDeferredFolderChange(target, folders, isDestructive,
-                isBatch, showUndo);
+                isBatch, showUndo, isMoveTo, actionFolder);
         registerDestructiveAction(da);
         return da;
     }
 
     public final DestructiveAction getDeferredFolderChange(Collection<Conversation> target,
             Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
-            boolean showUndo) {
-        final DestructiveAction da = new FolderDestruction(target, folders, isDestructive, isBatch,
-                showUndo, R.id.change_folder);
-        return da;
+            boolean showUndo, final boolean isMoveTo, final Folder actionFolder) {
+        return new FolderDestruction(target, folders, isDestructive, isBatch, showUndo,
+                isMoveTo ? R.id.move_folder : R.id.change_folder, actionFolder);
     }
 
     @Override
@@ -3137,7 +3261,7 @@
         Collection<FolderOperation> folderOps = new ArrayList<FolderOperation>();
         folderOps.add(new FolderOperation(toRemove, false));
         return new FolderDestruction(target, folderOps, isDestructive, isBatch,
-                showUndo, R.id.remove_folder);
+                showUndo, R.id.remove_folder, mFolder);
     }
 
     @Override
@@ -3224,7 +3348,7 @@
                 false, /* showActionIcon */
                 actionTextResourceId,
                 replaceVisibleToast,
-                new ToastBarOperation(1, 0, ToastBarOperation.ERROR, false));
+                new ToastBarOperation(1, 0, ToastBarOperation.ERROR, false, folder));
     }
 
     private ActionClickedListener getRetryClickedListener(final Folder folder) {
@@ -3373,8 +3497,8 @@
      * Sets the listener for the positive action on a confirmation dialog.  Since only a single
      * confirmation dialog can be shown, this overwrites the previous listener.  It is safe to
      * unset the listener; in which case action should be set to -1.
-     * @param listener
-     * @param action
+     * @param listener the listener that will perform the task for this dialog's positive action.
+     * @param action the action that created this dialog.
      */
     private void setListener(AlertDialog.OnClickListener listener, final int action){
         mDialogListener = listener;
@@ -3410,5 +3534,4 @@
         }
         mDetachedConvUri = null;
     }
-
 }
diff --git a/src/com/android/mail/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index 0bb2e57..ba63588 100644
--- a/src/com/android/mail/ui/AbstractConversationViewFragment.java
+++ b/src/com/android/mail/ui/AbstractConversationViewFragment.java
@@ -98,7 +98,6 @@
     protected String mBaseUri;
     protected Account mAccount;
     protected final Map<String, Address> mAddressCache = Maps.newHashMap();
-    protected boolean mEnableContentReadySignal;
     private MessageCursor mCursor;
     private Context mContext;
     /**
@@ -203,17 +202,6 @@
         // base uri that us guaranteed to be unique for this conversation.
         mBaseUri = "x-thread://" + mAccount.name + "/" + mConversation.id;
 
-        // On JB or newer, we use the 'webkitAnimationStart' DOM event to signal load complete
-        // Below JB, try to speed up initial render by having the webview do supplemental draws to
-        // custom a software canvas.
-        // TODO(mindyp):
-        //PAGE READINESS SIGNAL FOR JELLYBEAN AND NEWER
-        // Notify the app on 'webkitAnimationStart' of a simple dummy element with a simple no-op
-        // animation that immediately runs on page load. The app uses this as a signal that the
-        // content is loaded and ready to draw, since WebView delays firing this event until the
-        // layers are composited and everything is ready to draw.
-        // This signal does not seem to be reliable, so just use the old method for now.
-        mEnableContentReadySignal = false; //Utils.isRunningJellybeanOrLater();
         LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
         // Not really, we just want to get a crack to store a reference to the change_folder item
         setHasOptionsMenu(true);
@@ -473,6 +461,12 @@
         return mUserVisible;
     }
 
+    protected void timerMark(String msg) {
+        if (isUserVisible()) {
+            Utils.sConvLoadTimer.mark(msg);
+        }
+    }
+
     private class MessageLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
 
         @Override
diff --git a/src/com/android/mail/ui/AbstractMailActivity.java b/src/com/android/mail/ui/AbstractMailActivity.java
index 33a7da2..43f3b23 100644
--- a/src/com/android/mail/ui/AbstractMailActivity.java
+++ b/src/com/android/mail/ui/AbstractMailActivity.java
@@ -39,7 +39,7 @@
 
     private final UiHandler mUiHandler = new UiHandler();
 
-    private static final boolean STRICT_MODE = false;
+    private static final boolean STRICT_MODE = true;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
diff --git a/src/com/android/mail/ui/AccountChangeListener.java b/src/com/android/mail/ui/AccountChangeListener.java
deleted file mode 100644
index 7690799..0000000
--- a/src/com/android/mail/ui/AccountChangeListener.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*******************************************************************************
- *      Copyright (C) 2012 Google Inc.
- *      Licensed to The Android Open Source Project.
- *
- *      Licensed under the Apache License, Version 2.0 (the "License");
- *      you may not use this file except in compliance with the License.
- *      You may obtain a copy of the License at
- *
- *           http://www.apache.org/licenses/LICENSE-2.0
- *
- *      Unless required by applicable law or agreed to in writing, software
- *      distributed under the License is distributed on an "AS IS" BASIS,
- *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *      See the License for the specific language governing permissions and
- *      limitations under the License.
- *******************************************************************************/
-package com.android.mail.ui;
-
-import com.android.mail.providers.Account;
-
-/**
- * The callback interface for when a list item has been selected.
- */
-public interface AccountChangeListener {
-    /**
-     * Handles selecting a folder from within the {@link FolderListFragment}.
-     *
-     * @param folder the selected folder
-     */
-    void onAccountChanged(Account account);
-}
diff --git a/src/com/android/mail/ui/AccountController.java b/src/com/android/mail/ui/AccountController.java
index e13b2eb..1d7745c 100644
--- a/src/com/android/mail/ui/AccountController.java
+++ b/src/com/android/mail/ui/AccountController.java
@@ -45,9 +45,31 @@
      */
     Account getAccount();
 
+
+    /**
+     * Registers to receive changes to the list of accounts, and obtain the current list.
+     */
+    void registerAllAccountObserver(DataSetObserver observer);
+
+    /**
+     * Removes a listener from receiving account list changes.
+     */
+    void unregisterAllAccountObserver(DataSetObserver observer);
+
+    /** Returns a list of all accounts currently known. */
+    Account[] getAllAccounts();
+
     /**
      * Returns an object that can check veiled addresses.
      * @return
      */
     VeiledAddressMatcher getVeiledAddressMatcher();
+
+    /**
+     * Handles selecting an account from within the {@link FolderListFragment}.
+     *
+     * @param account the account to change to.
+     */
+    void changeAccount(Account account);
+
 }
diff --git a/src/com/android/mail/ui/ActionableToastBar.java b/src/com/android/mail/ui/ActionableToastBar.java
index 12279ec..a7e4719 100644
--- a/src/com/android/mail/ui/ActionableToastBar.java
+++ b/src/com/android/mail/ui/ActionableToastBar.java
@@ -18,6 +18,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.content.Context;
+import android.os.Handler;
 import android.text.Spanned;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -38,9 +39,14 @@
     private boolean mHidden = false;
     private Animator mShowAnimation;
     private Animator mHideAnimation;
+    private final Runnable mRunnable;
+    private final Handler mFadeOutHandler;
     private final int mBottomMarginSizeInConversation;
     private final int mBottomMarginSize;
 
+    /** How long toast will last in ms */
+    private static final long TOAST_LIFETIME = 15*1000L;
+
     /** Icon for the description. */
     private ImageView mActionDescriptionIcon;
     /** The clickable view */
@@ -63,6 +69,15 @@
 
     public ActionableToastBar(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mFadeOutHandler = new Handler();
+        mRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if(!mHidden) {
+                    hide(true);
+                }
+            }
+        };
         mBottomMarginSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.toast_bar_bottom_margin);
         mBottomMarginSizeInConversation = context.getResources().getDimensionPixelSize(
@@ -113,6 +128,8 @@
         if (!mHidden && !replaceVisibleToast) {
             return;
         }
+        // Remove any running delayed animations first
+        mFadeOutHandler.removeCallbacks(mRunnable);
 
         mOperation = op;
 
@@ -138,6 +155,9 @@
 
         mHidden = false;
         getShowAnimation().start();
+
+        // Set up runnable to execute hide toast once delay is completed
+        mFadeOutHandler.postDelayed(mRunnable, TOAST_LIFETIME);
     }
 
     public ToastBarOperation getOperation() {
@@ -149,6 +169,7 @@
      */
     public void hide(boolean animate) {
         mHidden = true;
+        mFadeOutHandler.removeCallbacks(mRunnable);
         if (getVisibility() == View.VISIBLE) {
             mActionDescriptionView.setText("");
             mActionButton.setOnClickListener(null);
@@ -224,6 +245,13 @@
     public boolean isAnimating() {
         return mShowAnimation != null && mShowAnimation.isStarted();
     }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mFadeOutHandler.removeCallbacks(mRunnable);
+        super.onDetachedFromWindow();
+    }
+
     /**
      * Classes that wish to perform some action when the action button is clicked
      * should implement this interface.
diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java
index 239185b..d1a1740 100644
--- a/src/com/android/mail/ui/ActivityController.java
+++ b/src/com/android/mail/ui/ActivityController.java
@@ -42,8 +42,7 @@
  */
 public interface ActivityController extends LayoutListener,
         ModeChangeListener, ConversationListCallbacks,
-        FolderChangeListener, AccountChangeListener, LoaderManager.LoaderCallbacks<Cursor>,
-        ConversationSetObserver, ConversationListener,
+        FolderChangeListener, ConversationSetObserver, ConversationListener,
         FolderListFragment.FolderListSelectionListener, HelpCallback, UndoListener,
         ConversationUpdater, ErrorListener, FolderController, AccountController,
         ConversationPositionTracker.Callbacks, ConversationListFooterView.FooterViewClickListener,
@@ -217,9 +216,10 @@
     public void showWaitForInitialization();
 
     /**
-     * Show the folder list associated with the currently selected account.
+     * Load the folder list into the drawer fragment. Only handled by
+     * OnePaneController on account change.
      */
-    void showFolderList();
+    void loadFolderList();
 
     /**
      * Handle a touch event.
diff --git a/src/com/android/mail/ui/AddableFolderSelectorAdapter.java b/src/com/android/mail/ui/AddableFolderSelectorAdapter.java
index 73ff38e..0888369 100644
--- a/src/com/android/mail/ui/AddableFolderSelectorAdapter.java
+++ b/src/com/android/mail/ui/AddableFolderSelectorAdapter.java
@@ -43,6 +43,8 @@
                 if (type == UIProvider.FolderType.INBOX || type == UIProvider.FolderType.DEFAULT) {
                     folder[UIProvider.FOLDER_ID_COLUMN] = folderCursor
                             .getLong(UIProvider.FOLDER_ID_COLUMN);
+                    folder[UIProvider.FOLDER_PERSISTENT_ID_COLUMN] = folderCursor
+                            .getString(UIProvider.FOLDER_PERSISTENT_ID_COLUMN);
                     folder[UIProvider.FOLDER_URI_COLUMN] = folderCursor
                             .getString(UIProvider.FOLDER_URI_COLUMN);
                     folder[UIProvider.FOLDER_NAME_COLUMN] = folderCursor
@@ -57,6 +59,8 @@
                             .getString(UIProvider.FOLDER_CONVERSATION_LIST_URI_COLUMN);
                     folder[UIProvider.FOLDER_CHILD_FOLDERS_LIST_COLUMN] = folderCursor
                             .getString(UIProvider.FOLDER_CHILD_FOLDERS_LIST_COLUMN);
+                    folder[UIProvider.FOLDER_UNSEEN_COUNT_COLUMN] = folderCursor
+                            .getInt(UIProvider.FOLDER_UNSEEN_COUNT_COLUMN);
                     folder[UIProvider.FOLDER_UNREAD_COUNT_COLUMN] = folderCursor
                             .getInt(UIProvider.FOLDER_UNREAD_COUNT_COLUMN);
                     folder[UIProvider.FOLDER_TOTAL_COUNT_COLUMN] = folderCursor
@@ -69,7 +73,9 @@
                             .getInt(UIProvider.FOLDER_LAST_SYNC_RESULT_COLUMN);
                     folder[UIProvider.FOLDER_TYPE_COLUMN] = type;
                     folder[UIProvider.FOLDER_ICON_RES_ID_COLUMN] = folderCursor
-                            .getLong(UIProvider.FOLDER_ICON_RES_ID_COLUMN);
+                            .getInt(UIProvider.FOLDER_ICON_RES_ID_COLUMN);
+                    folder[UIProvider.FOLDER_NOTIFICATION_ICON_RES_ID_COLUMN] = folderCursor
+                            .getInt(UIProvider.FOLDER_NOTIFICATION_ICON_RES_ID_COLUMN);
                     folder[UIProvider.FOLDER_BG_COLOR_COLUMN] = folderCursor
                             .getString(UIProvider.FOLDER_BG_COLOR_COLUMN);
                     folder[UIProvider.FOLDER_FG_COLOR_COLUMN] = folderCursor
@@ -78,6 +84,8 @@
                             .getString(UIProvider.FOLDER_LOAD_MORE_URI_COLUMN);
                     folder[UIProvider.FOLDER_HIERARCHICAL_DESC_COLUMN] = folderCursor
                             .getString(UIProvider.FOLDER_HIERARCHICAL_DESC_COLUMN);
+                    folder[UIProvider.FOLDER_LAST_MESSAGE_TIMESTAMP_COLUMN] = folderCursor
+                            .getLong(UIProvider.FOLDER_LAST_MESSAGE_TIMESTAMP_COLUMN);
                     cursor.addRow(folder);
                 }
             } while (folderCursor.moveToNext());
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index abb1dd6..0a75711 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -24,6 +24,7 @@
 import android.database.Cursor;
 import android.os.Bundle;
 import android.os.Handler;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -49,6 +50,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map.Entry;
 
 public class AnimatedAdapter extends SimpleCursorAdapter implements
@@ -115,6 +117,9 @@
         }
     };
 
+    private final List<ConversationSpecialItemView> mSpecialViews;
+    private final SparseArray<ConversationSpecialItemView> mSpecialViewPositions;
+
     private final void setAccount(Account newAccount) {
         mAccount = newAccount;
         mPriorityMarkersEnabled = mAccount.settings.priorityArrowsEnabled;
@@ -130,6 +135,12 @@
     public AnimatedAdapter(Context context, ConversationCursor cursor,
             ConversationSelectionSet batch, ControllableActivity activity,
             SwipeableListView listView) {
+        this(context, cursor, batch, activity, listView, null);
+    }
+
+    public AnimatedAdapter(Context context, ConversationCursor cursor,
+            ConversationSelectionSet batch, ControllableActivity activity,
+            SwipeableListView listView, final List<ConversationSpecialItemView> specialViews) {
         super(context, -1, cursor, UIProvider.CONVERSATION_PROJECTION, null, 0);
         mContext = context;
         mBatchConversations = batch;
@@ -146,6 +157,16 @@
                     context.getResources()
                         .getInteger(R.integer.dismiss_all_leavebehinds_long_delay);
         }
+        mSpecialViews =
+                specialViews == null ? new ArrayList<ConversationSpecialItemView>(0)
+                        : new ArrayList<ConversationSpecialItemView>(specialViews);
+        mSpecialViewPositions = new SparseArray<ConversationSpecialItemView>(mSpecialViews.size());
+
+        for (final ConversationSpecialItemView view : mSpecialViews) {
+            view.setAdapter(this);
+        }
+
+        updateSpecialViews();
     }
 
     public void cancelDismissCounter() {
@@ -169,7 +190,10 @@
 
     @Override
     public int getCount() {
-        final int count = super.getCount();
+        // mSpecialViewPositions only contains the views that are currently being displayed
+        final int specialViewCount = mSpecialViewPositions.size();
+
+        final int count = super.getCount() + specialViewCount;
         return mShowFooter ? count + 1 : count;
     }
 
@@ -231,7 +255,7 @@
     @Override
     public int getItemViewType(int position) {
         // Try to recycle views.
-        if (mShowFooter && position == super.getCount()) {
+        if (mShowFooter && position == getCount() - 1) {
             return TYPE_VIEW_FOOTER;
         } else if (hasLeaveBehinds() || isAnimating()) {
             // Setting as type -1 means the recycler won't take this view and
@@ -241,6 +265,9 @@
             // types. In a future release, use position/id map to try to make
             // this cleaner / faster to determine if the view is animating.
             return TYPE_VIEW_DONT_RECYCLE;
+        } else if (mSpecialViewPositions.get(position) != null) {
+            // Don't recycle the special views
+            return TYPE_VIEW_DONT_RECYCLE;
         }
         return TYPE_VIEW_CONVERSATION;
     }
@@ -306,11 +333,18 @@
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
-        if (mShowFooter && position == super.getCount()) {
+        if (mShowFooter && position == getCount() - 1) {
             return mFooter;
         }
+
+        // Check if this is a special view
+        final View specialView = (View) mSpecialViewPositions.get(position);
+        if (specialView != null) {
+            return specialView;
+        }
+
         ConversationCursor cursor = (ConversationCursor) getItem(position);
-        Conversation conv = new Conversation(cursor);
+        final Conversation conv = cursor.getConversation();
         if (isPositionUndoing(conv.id)) {
             return getUndoingView(position, conv, parent, false /* don't show swipe background */);
         } if (isPositionUndoingSwipe(conv.id)) {
@@ -347,6 +381,7 @@
                 return fadeIn;
             }
         }
+
         if (convertView != null && !(convertView instanceof SwipeableConversationItemView)) {
             LogUtils.w(LOG_TAG, "Incorrect convert view received; nulling it out");
             convertView = newView(mContext, cursor, parent);
@@ -497,10 +532,11 @@
 
     @Override
     public long getItemId(int position) {
-        if (mShowFooter && position == super.getCount()) {
+        if (mShowFooter && position == getCount() - 1
+                || mSpecialViewPositions.get(position) != null) {
             return -1;
         }
-        return super.getItemId(position);
+        return super.getItemId(position - getPositionOffset(position));
     }
 
     private View getDeletingView(int position, Conversation conversation, ViewGroup parent,
@@ -559,10 +595,12 @@
 
     @Override
     public Object getItem(int position) {
-        if (mShowFooter && position == super.getCount()) {
+        if (mShowFooter && position == getCount() - 1) {
             return mFooter;
+        } else if (mSpecialViewPositions.get(position) != null) {
+            return mSpecialViewPositions.get(position);
         }
-        return super.getItem(position);
+        return super.getItem(position - getPositionOffset(position));
     }
 
     private boolean isPositionDeleting(long id) {
@@ -797,4 +835,67 @@
             item.cancelFadeOutText();
         }
     }
+
+    private void updateSpecialViews() {
+        mSpecialViewPositions.clear();
+
+        for (int i = 0; i < mSpecialViews.size(); i++) {
+            final ConversationSpecialItemView specialView = mSpecialViews.get(i);
+            specialView.onUpdate(mAccount.name, mFolder, getConversationCursor());
+
+            if (specialView.getShouldDisplayInList()) {
+                mSpecialViewPositions.put(specialView.getPosition(), specialView);
+            }
+        }
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        updateSpecialViews();
+        super.notifyDataSetChanged();
+    }
+
+    @Override
+    public void changeCursor(final Cursor cursor) {
+        super.changeCursor(cursor);
+        updateSpecialViews();
+    }
+
+    @Override
+    public void changeCursorAndColumns(final Cursor c, final String[] from, final int[] to) {
+        super.changeCursorAndColumns(c, from, to);
+        updateSpecialViews();
+    }
+
+    @Override
+    public Cursor swapCursor(final Cursor c) {
+        final Cursor oldCursor = super.swapCursor(c);
+        updateSpecialViews();
+
+        return oldCursor;
+    }
+
+    /**
+     * Gets the offset for the given position in the underlying cursor, based on any special views
+     * that may be above it.
+     */
+    public int getPositionOffset(final int position) {
+        int offset = 0;
+
+        for (int i = 0; i < mSpecialViewPositions.size(); i++) {
+            final int key = mSpecialViewPositions.keyAt(i);
+            final ConversationSpecialItemView specialView = mSpecialViewPositions.get(key);
+            if (specialView.getPosition() <= position) {
+                offset++;
+            }
+        }
+
+        return offset;
+    }
+
+    public void cleanup() {
+        for (final ConversationSpecialItemView view : mSpecialViews) {
+            view.cleanup();
+        }
+    }
 }
diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java
index 37e70f5..f4a1d87 100644
--- a/src/com/android/mail/ui/ControllableActivity.java
+++ b/src/com/android/mail/ui/ControllableActivity.java
@@ -116,4 +116,9 @@
     void stopDragMode();
 
     boolean isAccessibilityEnabled();
+
+    /**
+     * Gets a helper to provide addition features in the conversation list. This may be null.
+     */
+    ConversationListHelper getConversationListHelper();
 }
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index ec57e4a..12d2087 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -43,6 +43,7 @@
 import com.android.mail.providers.AccountObserver;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
+import com.android.mail.providers.FolderObserver;
 import com.android.mail.providers.Settings;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.providers.UIProvider.AccountCapabilities;
@@ -57,6 +58,7 @@
 import com.android.mail.utils.Utils;
 
 import java.util.Collection;
+import java.util.List;
 
 /**
  * The conversation list UI component.
@@ -117,7 +119,7 @@
     private ConversationListFooterView mFooterView;
     private View mEmptyView;
     private ErrorListener mErrorListener;
-    private DataSetObserver mFolderObserver;
+    private FolderObserver mFolderObserver;
     private DataSetObserver mConversationCursorObserver;
 
     private ConversationSelectionSet mSelectedSet;
@@ -140,21 +142,6 @@
         super();
     }
 
-    // update the pager title strip as the Folder's conversation count changes
-    private class FolderObserver extends DataSetObserver {
-        @Override
-        public void onChanged() {
-            if (mActivity == null) {
-                return;
-            }
-            final FolderController controller = mActivity.getFolderController();
-            if (controller == null) {
-                return;
-            }
-            onFolderUpdated(controller.getFolder());
-        }
-    }
-
     private class ConversationCursorObserver extends DataSetObserver {
         @Override
         public void onChanged() {
@@ -264,15 +251,32 @@
                 null);
         mFooterView.setClickListener(mActivity);
         final ConversationCursor conversationCursor = getConversationListCursor();
-        mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(),
-                conversationCursor, mActivity.getSelectedSet(), mActivity, mListView);
+
+        final ConversationListHelper helper = mActivity.getConversationListHelper();
+        final List<ConversationSpecialItemView> specialItemViews =
+                helper != null ? helper.makeConversationListSpecialViews(getActivity(), mAccount,
+                        mActivity.getFolderListSelectionListener()) : null;
+        if (specialItemViews != null) {
+            // Attach to the LoaderManager
+            for (final ConversationSpecialItemView view : specialItemViews) {
+                view.bindLoaderManager(getLoaderManager());
+            }
+        }
+
+        mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(), conversationCursor,
+                        mActivity.getSelectedSet(), mActivity, mListView, specialItemViews);
         mListAdapter.addFooter(mFooterView);
         mListView.setAdapter(mListAdapter);
         mSelectedSet = mActivity.getSelectedSet();
         mListView.setSelectionSet(mSelectedSet);
         mListAdapter.hideFooter();
-        mFolderObserver = new FolderObserver();
-        mActivity.getFolderController().registerFolderObserver(mFolderObserver);
+        mFolderObserver = new FolderObserver(){
+            @Override
+            public void onChanged(Folder newFolder) {
+                onFolderUpdated(newFolder);
+            }
+        };
+        mFolderObserver.initialize(mActivity.getFolderController());
         mConversationCursorObserver = new ConversationCursorObserver();
         mUpdater = mActivity.getConversationUpdater();
         mUpdater.registerConversationListObserver(mConversationCursorObserver);
@@ -438,7 +442,7 @@
 
         mActivity.unsetViewModeListener(this);
         if (mFolderObserver != null) {
-            mActivity.getFolderController().unregisterFolderObserver(mFolderObserver);
+            mFolderObserver.unregisterAndDestroy();
             mFolderObserver = null;
         }
         if (mConversationCursorObserver != null) {
@@ -446,6 +450,7 @@
             mConversationCursorObserver = null;
         }
         mAccountObserver.unregisterAndDestroy();
+        getAnimatedAdapter().cleanup();
         super.onDestroyView();
     }
 
@@ -475,7 +480,7 @@
     public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
         // Ignore anything that is not a conversation item. Could be a footer.
         if (!(view instanceof ConversationItemView)) {
-            return true;
+            return false;
         }
         ((ConversationItemView) view).toggleCheckMarkOrBeginDrag();
         return true;
@@ -584,30 +589,40 @@
 
     /**
      * View the message at the given position.
-     * @param position
+     *
+     * @param position The position of the conversation in the list (as opposed to its position in
+     *        the cursor)
      */
-    protected void viewConversation(int position) {
+    protected void viewConversation(final int position) {
         LogUtils.d(LOG_TAG, "ConversationListFragment.viewConversation(%d)", position);
-        setSelected(position, true);
-        final ConversationCursor cursor = getConversationListCursor();
-        if (cursor != null && cursor.moveToPosition(position)) {
-            final Conversation conv = new Conversation(cursor);
-            conv.position = position;
-            mCallbacks.onConversationSelected(conv, false /* inLoaderCallbacks */);
-        }
+
+        final ConversationCursor cursor =
+                (ConversationCursor) getAnimatedAdapter().getItem(position);
+        final Conversation conv = cursor.getConversation();
+        /*
+         * The cursor position may be different than the position method parameter because of
+         * special views in the list.
+         */
+        conv.position = cursor.getPosition();
+        setSelected(conv.position, true);
+        mCallbacks.onConversationSelected(conv, false /* inLoaderCallbacks */);
     }
 
     /**
      * Sets the selected conversation to the position given here.
-     * @param position
+     * @param position The position of the conversation in the cursor (as opposed to in the list)
      * @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) {
+    public void setSelected(final int cursorPosition, boolean different) {
         if (mListView.getChoiceMode() == ListView.CHOICE_MODE_NONE) {
             return;
         }
+
+        final int position =
+                cursorPosition + getAnimatedAdapter().getPositionOffset(cursorPosition);
+
         if (different) {
             mListView.smoothScrollToPosition(position);
         }
@@ -775,6 +790,11 @@
             mListAdapter.notifyDataSetChanged();
         }
         mConversationCursorHash = newCursorHash;
+
+        if (newCursor != null) {
+            newCursor.markContentsSeen();
+        }
+
         // If a current conversation is available, and none is selected in the list, then ask
         // the list to select the current conversation.
         final Conversation conv = mCallbacks.getCurrentConversation();
diff --git a/src/com/android/mail/ui/ConversationListHelper.java b/src/com/android/mail/ui/ConversationListHelper.java
new file mode 100644
index 0000000..8470be1
--- /dev/null
+++ b/src/com/android/mail/ui/ConversationListHelper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+package com.android.mail.ui;
+
+import android.content.Context;
+
+import com.android.mail.providers.Account;
+import com.android.mail.ui.FolderListFragment.FolderListSelectionListener;
+
+import java.util.List;
+
+public interface ConversationListHelper {
+    /**
+     * Creates a list of newly created special views.
+     */
+    List<ConversationSpecialItemView> makeConversationListSpecialViews(Context context,
+            Account account, FolderListSelectionListener listener);
+}
diff --git a/src/com/android/mail/ui/ConversationSpecialItemView.java b/src/com/android/mail/ui/ConversationSpecialItemView.java
new file mode 100644
index 0000000..8488162
--- /dev/null
+++ b/src/com/android/mail/ui/ConversationSpecialItemView.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+package com.android.mail.ui;
+
+import android.app.LoaderManager;
+import android.widget.BaseAdapter;
+
+import com.android.mail.browse.ConversationCursor;
+import com.android.mail.providers.Folder;
+
+/**
+ * An interface for a view that can be inserted into an {@link AnimatedAdapter} at an arbitrary
+ * point. The methods described here control whether the view gets displayed, and what it displays.
+ */
+public interface ConversationSpecialItemView {
+    /**
+     * Called when there as an update to the information being displayed.
+     *
+     * @param cursor The {@link ConversationCursor}. May be <code>null</code>
+     */
+    void onUpdate(String account, Folder folder, ConversationCursor cursor);
+
+    boolean getShouldDisplayInList();
+
+    int getPosition();
+
+    void setAdapter(BaseAdapter adapter);
+
+    void bindLoaderManager(LoaderManager loaderManager);
+
+    /**
+     * Called when the view is being destroyed.
+     */
+    void cleanup();
+}
diff --git a/src/com/android/mail/ui/ConversationUpdater.java b/src/com/android/mail/ui/ConversationUpdater.java
index fe7f621..55511ac 100644
--- a/src/com/android/mail/ui/ConversationUpdater.java
+++ b/src/com/android/mail/ui/ConversationUpdater.java
@@ -23,7 +23,6 @@
 
 import com.android.mail.browse.ConfirmDialogFragment;
 import com.android.mail.browse.ConversationCursor;
-import com.android.mail.browse.ConversationItemView;
 import com.android.mail.browse.MessageCursor.ConversationMessage;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.ConversationInfo;
@@ -142,15 +141,17 @@
             boolean showUndo);
 
     /**
-     * Assign the target conversations to the given folders, and remove them from all other
-     * folders that they might be assigned to.
+     * Assign the target conversations to the given folders, and remove them from all other folders
+     * that they might be assigned to.
      * @param folders the folders to assign the conversations to.
      * @param target the conversations to act upon.
      * @param batch whether this is a batch operation
      * @param showUndo whether to show the undo bar
+     * @param isMoveTo <code>true</code> if this is a move operation, <code>false</code> if it is
+     *        some other type of folder change operation
      */
     public void assignFolder(Collection<FolderOperation> folders, Collection<Conversation> target,
-            boolean batch, boolean showUndo);
+            boolean batch, boolean showUndo, boolean isMoveTo);
 
     /**
      * Refreshes the conversation list, if one exists.
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 714b6b6..8aae3f3 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -83,10 +83,12 @@
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 
@@ -193,6 +195,8 @@
 
     private long mWebViewLoadStartMs;
 
+    private final Map<String, String> mMessageTransforms = Maps.newHashMap();
+
     private final DataSetObserver mLoadedObserver = new DataSetObserver() {
         @Override
         public void onChanged() {
@@ -386,8 +390,9 @@
         final WebChromeClient wcc = new WebChromeClient() {
             @Override
             public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
-                LogUtils.i(LOG_TAG, "JS: %s (%s:%d)", consoleMessage.message(),
-                        consoleMessage.sourceId(), consoleMessage.lineNumber());
+                LogUtils.i(LOG_TAG, "JS: %s (%s:%d) f=%s", consoleMessage.message(),
+                        consoleMessage.sourceId(), consoleMessage.lineNumber(),
+                        ConversationViewFragment.this);
                 return true;
             }
         };
@@ -589,6 +594,7 @@
             LogUtils.i(LOG_TAG,
                     "SHOWCONV: CVF is user-visible, immediately loading conversation (%s)", this);
             reason = LOAD_NOW;
+            timerMark("CVF.showConversation");
         } else {
             final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING
                     || (mConversation.isRemote
@@ -625,12 +631,6 @@
     private void startConversationLoad() {
         mWebView.setVisibility(View.VISIBLE);
         getLoaderManager().initLoader(MESSAGE_LOADER, Bundle.EMPTY, getMessageLoaderCallbacks());
-        if (isUserVisible()) {
-            final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
-            if (sdc != null) {
-                sdc.setSubject(mConversation.subject);
-            }
-        }
         // TODO(mindyp): don't show loading status for a previously rendered
         // conversation. Ielieve this is better done by making sure don't show loading status
         // until XX ms have passed without loading completed.
@@ -638,6 +638,7 @@
     }
 
     private void revealConversation() {
+        timerMark("revealing conversation");
         dismissLoadingStatus(mOnProgressDismiss);
     }
 
@@ -647,6 +648,7 @@
 
     private void renderConversation(MessageCursor messageCursor) {
         final String convHtml = renderMessageBodies(messageCursor, mEnableContentReadySignal);
+        timerMark("rendered conversation");
 
         if (DEBUG_DUMP_CONVERSATION_HTML) {
             java.io.FileWriter fw = null;
@@ -787,9 +789,11 @@
 
         mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages);
 
+        final MailPrefs prefs = MailPrefs.get(getContext());
         // If the conversation has specified a base uri, use it here, otherwise use mBaseUri
         return mTemplates.endConversation(mBaseUri, mConversation.getBaseUri(mBaseUri), 320,
-                mWebView.getViewportWidth(), enableContentReadySignal, isOverviewMode(mAccount));
+                mWebView.getViewportWidth(), enableContentReadySignal, isOverviewMode(mAccount),
+                prefs.shouldMungeTables(), prefs.shouldMungeImages());
     }
 
     private void renderSuperCollapsedBlock(int start, int end) {
@@ -814,6 +818,7 @@
 
         mTemplates.appendMessageHtml(msg, expanded, safeForImages,
                 mWebView.screenPxToWebPx(headerPx), mWebView.screenPxToWebPx(footerPx));
+        timerMark("rendered message");
     }
 
     private String renderCollapsedHeaders(MessageCursor cursor,
@@ -960,6 +965,18 @@
                 "javascript:unblockImages(['%s']);", TextUtils.join("','", messageDomIds));
         mWebView.loadUrl(url);
     }
+
+    @Override
+    public boolean supportsMessageTransforms() {
+        return true;
+    }
+
+    @Override
+    public String getMessageTransforms(final Message msg) {
+        final String domId = mTemplates.getMessageDomId(msg);
+        return (domId == null) ? null : mMessageTransforms.get(domId);
+    }
+
     // END message header callbacks
 
     @Override
@@ -1025,7 +1042,7 @@
     }
 
     private static boolean isOverviewMode(Account acct) {
-        return acct.settings.conversationViewMode == UIProvider.ConversationViewMode.OVERVIEW;
+        return acct.settings.isOverviewMode();
     }
 
     private void setupOverviewMode() {
@@ -1216,6 +1233,13 @@
                 return 0f;
             }
         }
+
+        @SuppressWarnings("unused")
+        @JavascriptInterface
+        public void onMessageTransform(String messageDomId, String transformText) {
+            LogUtils.i(LOG_TAG, "TRANSFORM: (%s) %s", messageDomId, transformText);
+            mMessageTransforms.put(messageDomId, transformText);
+        }
     }
 
     /**
@@ -1341,6 +1365,7 @@
             }
         } else {
             LogUtils.i(LOG_TAG, "CONV RENDER: initial render. (%s)", this);
+            timerMark("message cursor load finished");
         }
 
         // if layout hasn't happened, delay render
@@ -1647,7 +1672,7 @@
             mTemplates.appendMessageHtml(msgItem.getMessage(), true /* expanded */,
                     safeForImages, 0, 0);
             final String html = mTemplates.endConversation(mBaseUri,
-                    mConversation.getBaseUri(mBaseUri), 0, 0, false, false);
+                    mConversation.getBaseUri(mBaseUri), 0, 0, false, false, false, false);
 
             mMessageView.loadDataWithBaseURL(mBaseUri, html, "text/html", "utf-8", null);
             mMessageViewLoadStartMs = SystemClock.uptimeMillis();
diff --git a/src/com/android/mail/ui/FolderDisplayer.java b/src/com/android/mail/ui/FolderDisplayer.java
index 6f64511..05cfebe 100644
--- a/src/com/android/mail/ui/FolderDisplayer.java
+++ b/src/com/android/mail/ui/FolderDisplayer.java
@@ -21,6 +21,7 @@
 import com.google.common.collect.Sets;
 
 import android.content.Context;
+import android.net.Uri;
 
 import com.android.mail.R;
 import com.android.mail.providers.Conversation;
@@ -54,9 +55,9 @@
      * @param foldersString string containing serialized folders to display.
      * @param ignoreFolder (optional) folder to omit from the displayed set
      */
-    public void loadConversationFolders(Conversation conv, Folder ignoreFolder) {
+    public void loadConversationFolders(Conversation conv, final Uri ignoreFolderUri) {
         mFoldersSortedSet.clear();
-        mFoldersSortedSet.addAll(conv.getRawFoldersForDisplay(ignoreFolder));
+        mFoldersSortedSet.addAll(conv.getRawFoldersForDisplay(ignoreFolderUri));
     }
 
     /**
diff --git a/src/com/android/mail/ui/FolderItemView.java b/src/com/android/mail/ui/FolderItemView.java
index 3e9d233..8235602 100644
--- a/src/com/android/mail/ui/FolderItemView.java
+++ b/src/com/android/mail/ui/FolderItemView.java
@@ -20,7 +20,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.mail.providers.Account;
 import com.android.mail.providers.Folder;
+import com.android.mail.providers.UIProvider.FolderType;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
@@ -30,6 +32,7 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.DragEvent;
@@ -58,6 +61,7 @@
     private Folder mFolder;
     private TextView mFolderTextView;
     private TextView mUnreadCountTextView;
+    private TextView mUnseenCountTextView;
     private DropHandler mDropHandler;
     private ImageView mFolderParentIcon;
 
@@ -107,6 +111,7 @@
         }
         mFolderTextView = (TextView)findViewById(R.id.name);
         mUnreadCountTextView = (TextView)findViewById(R.id.unread);
+        mUnseenCountTextView = (TextView)findViewById(R.id.unseen);
         mBackground = getBackground();
         mInitialFolderTextColor = mFolderTextView.getTextColors();
         mInitialUnreadCountTextColor = mUnreadCountTextView.getTextColors();
@@ -118,14 +123,29 @@
         mDropHandler = dropHandler;
         mFolderTextView.setText(folder.name);
         mFolderParentIcon.setVisibility(mFolder.hasChildren ? View.VISIBLE : View.GONE);
-        setUnreadCount(Utils.getFolderUnreadDisplayCount(mFolder));
+        if (folder.type == FolderType.INBOX_SECTION && mFolder.unseenCount > 0) {
+            mUnreadCountTextView.setVisibility(View.GONE);
+            setUnseenCount(mFolder.getBackgroundColor(Color.BLACK), mFolder.unseenCount);
+        } else {
+            mUnseenCountTextView.setVisibility(View.GONE);
+            setUnreadCount(Utils.getFolderUnreadDisplayCount(mFolder));
+        }
+    }
+
+    public void bind(Account account, DropHandler dropHandler, int count) {
+        mFolder = null;
+        mDropHandler = dropHandler;
+        mFolderTextView.setText(account.name);
+        mFolderParentIcon.setVisibility(View.GONE);
+        mUnreadCountTextView.setVisibility(View.GONE);
+        setUnseenCount(Color.BLACK, 0);
+        setUnreadCount(count);
     }
 
     /**
      * Sets the unread count, taking care to hide/show the textview if the count is zero/non-zero.
-     * @param count
      */
-    private final void setUnreadCount(int count) {
+    private void setUnreadCount(int count) {
         mUnreadCountTextView.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
         if (count > 0) {
             mUnreadCountTextView.setText(Utils.getUnreadCountString(getContext(), count));
@@ -133,6 +153,18 @@
     }
 
     /**
+     * Sets the unseen count, taking care to hide/show the textview if the count is zero/non-zero.
+     */
+    private void setUnseenCount(final int color, final int count) {
+        mUnseenCountTextView.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
+        if (count > 0) {
+            mUnseenCountTextView.setBackgroundColor(color);
+            mUnseenCountTextView.setText(
+                    getContext().getString(R.string.inbox_unseen_banner, count));
+        }
+    }
+
+    /**
      * Used if we detect a problem with the unread count and want to force an override.
      * @param count
      */
diff --git a/src/com/android/mail/ui/FolderListFragment.java b/src/com/android/mail/ui/FolderListFragment.java
index 8dd4665..fed9da4 100644
--- a/src/com/android/mail/ui/FolderListFragment.java
+++ b/src/com/android/mail/ui/FolderListFragment.java
@@ -34,12 +34,10 @@
 import android.widget.ImageView;
 import android.widget.ListAdapter;
 import android.widget.ListView;
-import android.widget.TextView;
 
 import com.android.mail.R;
-import com.android.mail.providers.Folder;
-import com.android.mail.providers.RecentFolderObserver;
-import com.android.mail.providers.UIProvider;
+import com.android.mail.adapter.DrawerItem;
+import com.android.mail.providers.*;
 import com.android.mail.providers.UIProvider.FolderType;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
@@ -61,10 +59,14 @@
     private Uri mFolderListUri;
     /** True if you want a sectioned FolderList, false otherwise. */
     private boolean mIsSectioned;
+    /** Is the current device using tablet UI (true if 2-pane, false if 1-pane) */
+    private boolean mIsTabletUI;
     /** An {@link ArrayList} of {@link FolderType}s to exclude from displaying. */
     private ArrayList<Integer> mExcludedFolderTypes;
-    /** Callback into the parent */
-    private FolderListSelectionListener mListener;
+    /** Object that changes folders on our behalf. */
+    private FolderListSelectionListener mFolderChanger;
+    /** Object that changes accounts on our behalf */
+    private AccountController mAccountChanger;
 
     /** The currently selected folder (the folder being viewed).  This is never null. */
     private Uri mSelectedFolderUri = Uri.EMPTY;
@@ -77,16 +79,18 @@
     private Folder mParentFolder;
 
     private static final int FOLDER_LOADER_ID = 0;
-    public static final int MODE_DEFAULT = 0;
-    public static final int MODE_PICK = 1;
     /** Key to store {@link #mParentFolder}. */
     private static final String ARG_PARENT_FOLDER = "arg-parent-folder";
     /** Key to store {@link #mFolderListUri}. */
     private static final String ARG_FOLDER_URI = "arg-folder-list-uri";
     /** Key to store {@link #mIsSectioned} */
     private static final String ARG_IS_SECTIONED = "arg-is-sectioned";
+    /** Key to store {@link #mIsTabletUI} */
+    private static final String ARG_IS_TABLET_UI = "arg-is-tablet-ui";
     /** Key to store {@link #mExcludedFolderTypes} */
     private static final String ARG_EXCLUDED_FOLDER_TYPES = "arg-excluded-folder-types";
+    /** Should the {@link FolderListFragment} show less labels to begin with? */
+    private static final boolean ARE_ITEMS_COLLAPSED = true;
 
     private static final String BUNDLE_LIST_STATE = "flf-list-state";
     private static final String BUNDLE_SELECTED_FOLDER = "flf-selected-folder";
@@ -97,31 +101,24 @@
     private View mEmptyView;
     /** Observer to wait for changes to the current folder so we can change the selected folder */
     private FolderObserver mFolderObserver = null;
+    /** Listen for account changes. */
+    private AccountObserver mAccountObserver = null;
+
+    /** Listen to changes to list of all accounts */
+    private AllAccountObserver mAllAccountObserver = null;
     /**
-     * Type of currently selected folder: {@link FolderListAdapter.Item#FOLDER_SYSTEM},
-     * {@link FolderListAdapter.Item#FOLDER_RECENT} or {@link FolderListAdapter.Item#FOLDER_USER}.
+     * Type of currently selected folder: {@link DrawerItem#FOLDER_SYSTEM},
+     * {@link DrawerItem#FOLDER_RECENT} or {@link DrawerItem#FOLDER_USER}.
      */
-    // Setting to NOT_A_FOLDER = leaving uninitialized.
-    private int mSelectedFolderType = FolderListAdapter.Item.NOT_A_FOLDER;
+    // Setting to INERT_HEADER = leaving uninitialized.
+    private int mSelectedFolderType = DrawerItem.INERT_HEADER;
     private Cursor mFutureData;
     private ConversationListCallbacks mConversationListCallback;
+    /** The current account according to the controller */
+    private Account mCurrentAccount;
 
-    /**
-     * Listens to folder changes from the controller and updates state accordingly.
-     */
-    private final class FolderObserver extends DataSetObserver {
-        @Override
-        public void onChanged() {
-            if (mActivity == null) {
-                return;
-            }
-            final FolderController controller = mActivity.getFolderController();
-            if (controller == null) {
-                return;
-            }
-            setSelectedFolder(controller.getFolder());
-        }
-    }
+    /** List of all accounts currently known */
+    private Account[] mAllAccounts;
 
     /**
      * Constructor needs to be public to handle orientation changes and activity lifecycle events.
@@ -133,21 +130,24 @@
     /**
      * Creates a new instance of {@link ConversationListFragment}, initialized
      * to display conversation list context.
-     * @param isSectioned TODO(viki):
+     * @param isSectioned True if sections should be shown for folder list
+     * @param isTabletUI True if two-pane layout, false if not
      */
     public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri,
-            boolean isSectioned) {
-        return newInstance(parentFolder, folderUri, isSectioned, null);
+            boolean isSectioned, boolean isTabletUI) {
+        return newInstance(parentFolder, folderUri, isSectioned, null, isTabletUI);
     }
 
     /**
      * Creates a new instance of {@link ConversationListFragment}, initialized
      * to display conversation list context.
-     * @param isSectioned TODO(viki):
+     * @param isSectioned True if sections should be shown for folder list
      * @param excludedFolderTypes A list of {@link FolderType}s to exclude from displaying
+     * @param isTabletUI True if two-pane layout, false if not
      */
     public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri,
-            boolean isSectioned, final ArrayList<Integer> excludedFolderTypes) {
+            boolean isSectioned, final ArrayList<Integer> excludedFolderTypes,
+            boolean isTabletUI) {
         final FolderListFragment fragment = new FolderListFragment();
         final Bundle args = new Bundle();
         if (parentFolder != null) {
@@ -155,6 +155,7 @@
         }
         args.putString(ARG_FOLDER_URI, folderUri.toString());
         args.putBoolean(ARG_IS_SECTIONED, isSectioned);
+        args.putBoolean(ARG_IS_TABLET_UI, isTabletUI);
         if (excludedFolderTypes != null) {
             args.putIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES, excludedFolderTypes);
         }
@@ -179,14 +180,38 @@
         mConversationListCallback = mActivity.getListHandler();
         final FolderController controller = mActivity.getFolderController();
         // Listen to folder changes in the future
-        mFolderObserver = new FolderObserver();
+        mFolderObserver = new FolderObserver() {
+            @Override
+            public void onChanged(Folder newFolder) {
+                setSelectedFolder(newFolder);
+            }
+        };
         if (controller != null) {
             // Only register for selected folder updates if we have a controller.
-            controller.registerFolderObserver(mFolderObserver);
-            mCurrentFolderForUnreadCheck = controller.getFolder();
+            mCurrentFolderForUnreadCheck = mFolderObserver.initialize(controller);
+        }
+        final AccountController accountController = mActivity.getAccountController();
+        mAccountObserver = new AccountObserver() {
+            @Override
+            public void onChanged(Account newAccount) {
+                setSelectedAccount(newAccount);
+            }
+        };
+        if (accountController != null) {
+            // Current account and its observer.
+            mCurrentAccount = mAccountObserver.initialize(accountController);
+            // List of all accounts and its observer.
+            mAllAccountObserver = new AllAccountObserver(){
+                @Override
+                public void onChanged(Account[] allAccounts) {
+                    mAllAccounts = allAccounts;
+                }
+            };
+            mAllAccounts = mAllAccountObserver.initialize(accountController);
+            mAccountChanger = accountController;
         }
 
-        mListener = mActivity.getFolderListSelectionListener();
+        mFolderChanger = mActivity.getFolderListSelectionListener();
         if (mActivity.isFinishing()) {
             // Activity is finishing, just bail.
             return;
@@ -197,7 +222,12 @@
             mCursorAdapter = new HierarchicalFolderListAdapter(null, mParentFolder);
             selectedFolder = mActivity.getHierarchyFolder();
         } else {
-            mCursorAdapter = new FolderListAdapter(R.layout.folder_item, mIsSectioned);
+            // Initiate FLA with accounts and folders collapsed in the list
+            // The second param is for whether folders should be collapsed
+            // The third param is for whether accounts should be collapsed
+            mCursorAdapter = new FolderListAdapter(mIsSectioned,
+                    !mIsTabletUI && ARE_ITEMS_COLLAPSED,
+                    !mIsTabletUI && ARE_ITEMS_COLLAPSED);
             selectedFolder = controller == null ? null : controller.getFolder();
         }
         // Is the selected folder fresher than the one we have restored from a bundle?
@@ -216,6 +246,7 @@
         mFolderListUri = Uri.parse(args.getString(ARG_FOLDER_URI));
         mParentFolder = (Folder) args.getParcelable(ARG_PARENT_FOLDER);
         mIsSectioned = args.getBoolean(ARG_IS_SECTIONED);
+        mIsTabletUI = args.getBoolean(ARG_IS_TABLET_UI);
         mExcludedFolderTypes = args.getIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES);
         final View rootView = inflater.inflate(R.layout.folder_list, null);
         mListView = (ListView) rootView.findViewById(android.R.id.list);
@@ -272,31 +303,54 @@
         // Clear the adapter.
         setListAdapter(null);
         if (mFolderObserver != null) {
-            FolderController controller = mActivity.getFolderController();
-            if (controller != null) {
-                controller.unregisterFolderObserver(mFolderObserver);
-                mFolderObserver = null;
-            }
+            mFolderObserver.unregisterAndDestroy();
+            mFolderObserver = null;
+        }
+        if (mAccountObserver != null) {
+            mAccountObserver.unregisterAndDestroy();
+            mAccountObserver = null;
+        }
+        if (mAllAccountObserver != null) {
+            mAllAccountObserver.unregisterAndDestroy();
+            mAllAccountObserver = null;
         }
         super.onDestroyView();
     }
 
     @Override
     public void onListItemClick(ListView l, View v, int position, long id) {
-        viewFolder(position);
+        viewFolderOrChangeAccount(position);
     }
 
     /**
      * Display the conversation list from the folder at the position given.
-     * @param position
+     * @param position a zero indexed position into the list.
      */
-    private void viewFolder(int position) {
+    private void viewFolderOrChangeAccount(int position) {
         final Object item = getListAdapter().getItem(position);
         final Folder folder;
-        if (item instanceof FolderListAdapter.Item) {
-            final FolderListAdapter.Item folderItem = (FolderListAdapter.Item) item;
-            folder = mCursorAdapter.getFullFolder(folderItem);
-            mSelectedFolderType = folderItem.mFolderType;
+        if (item instanceof DrawerItem) {
+            final DrawerItem folderItem = (DrawerItem) item;
+            // Could be a folder, account, or expand block.
+            final int itemType = mCursorAdapter.getItemType(folderItem);
+            if (itemType == DrawerItem.VIEW_ACCOUNT) {
+                // Account, so switch.
+                folder = null;
+                final Account account = mCursorAdapter.getFullAccount(folderItem);
+                mAccountChanger.changeAccount(account);
+            } else if (itemType == DrawerItem.VIEW_FOLDER) {
+                // Folder type, so change folders only.
+                folder = mCursorAdapter.getFullFolder(folderItem);
+                mSelectedFolderType = folderItem.mFolderType;
+            } else {
+                // Block for expanding/contracting labels/accounts
+                folder = null;
+                if(!folderItem.mIsExpandForAccount) {
+                    mCursorAdapter.toggleShowLessFolders();
+                } else {
+                    mCursorAdapter.toggleShowLessAccounts();
+                }
+            }
         } else if (item instanceof Folder) {
             folder = (Folder) item;
         } else {
@@ -309,10 +363,7 @@
             // update its parent!
             folder.parent = folder.equals(mParentFolder) ? null : mParentFolder;
             // Go to the conversation list for this folder.
-            mListener.onFolderSelected(folder);
-        } else {
-            LogUtils.d(LOG_TAG, "FolderListFragment unable to get a full fledged folder" +
-                    " to hand to the listener for position %d", position);
+            mFolderChanger.onFolderSelected(folder);
         }
     }
 
@@ -360,8 +411,21 @@
     private interface FolderListFragmentCursorAdapter extends ListAdapter {
         /** Update the folder list cursor with the cursor given here. */
         void setCursor(Cursor cursor);
-        /** Get the cursor associated with this adapter **/
-        Folder getFullFolder(FolderListAdapter.Item item);
+        /** Toggles showing more accounts or less accounts. */
+        boolean toggleShowLessAccounts();
+        /** Toggles showing more folders or less. */
+        boolean toggleShowLessFolders();
+        /**
+         * Given an item, find the type of the item, which is {@link
+         * DrawerItem#VIEW_FOLDER}, {@link DrawerItem#VIEW_ACCOUNT} or
+         * {@link DrawerItem#VIEW_MORE}
+         * @return the type of the item.
+         */
+        int getItemType(DrawerItem item);
+        /** Get the folder associated with this item. **/
+        Folder getFullFolder(DrawerItem item);
+        /** Get the account associated with this item. **/
+        Account getFullAccount(DrawerItem item);
         /** Remove all observers and destroy the object. */
         void destroy();
         /** Notifies the adapter that the data has changed. */
@@ -380,131 +444,30 @@
             }
         };
 
+        /** After given number of accounts, show "more" until expanded. */
+        private static final int MAX_ACCOUNTS = 2;
+        /** After the given number of labels, show "more" until expanded. */
+        private static final int MAX_FOLDERS = 7;
+
         private final RecentFolderList mRecentFolders;
         /** True if the list is sectioned, false otherwise */
         private final boolean mIsSectioned;
-        private final LayoutInflater mInflater;
         /** All the items */
-        private final List<Item> mItemList = new ArrayList<Item>();
+        private final List<DrawerItem> mItemList = new ArrayList<DrawerItem>();
         /** Cursor into the folder list. This might be null. */
         private Cursor mCursor = null;
-
-        /** A union of either a folder or a resource string */
-        private class Item {
-            public int mPosition;
-            public final Folder mFolder;
-            public final int mResource;
-            /** Either {@link #VIEW_FOLDER} or {@link #VIEW_HEADER} */
-            public final int mType;
-            /** A normal folder, also a child, if a parent is specified. */
-            private static final int VIEW_FOLDER = 0;
-            /** A text-label which serves as a header in sectioned lists. */
-            private static final int VIEW_HEADER = 1;
-
-            /**
-             * Either {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or {@link #FOLDER_USER} when
-             * {@link #mType} is {@link #VIEW_FOLDER}, and {@link #NOT_A_FOLDER} otherwise.
-             */
-            public final int mFolderType;
-            private static final int NOT_A_FOLDER = 0;
-            private static final int FOLDER_SYSTEM = 1;
-            private static final int FOLDER_RECENT = 2;
-            private static final int FOLDER_USER = 3;
-
-            /**
-             * Create a folder item with the given type.
-             * @param folder
-             * @param folderType one of {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or
-             * {@link #FOLDER_USER}
-             */
-            private Item(Folder folder, int folderType, int cursorPosition) {
-                mFolder = folder;
-                mResource = -1;
-                mType = VIEW_FOLDER;
-                mFolderType = folderType;
-                mPosition = cursorPosition;
-            }
-            /**
-             * Create a header item with a string resource.
-             * @param resource the string resource: R.string.all_folders_heading
-             */
-            private Item(int resource) {
-                mFolder = null;
-                mResource = resource;
-                mType = VIEW_HEADER;
-                mFolderType = NOT_A_FOLDER;
-            }
-
-            private final View getView(int position, View convertView, ViewGroup parent) {
-                if (mType == VIEW_FOLDER) {
-                    return getFolderView(position, convertView, parent);
-                } else {
-                    return getHeaderView(position, convertView, parent);
-                }
-            }
-
-            /**
-             * Returns a text divider between sections.
-             * @param convertView
-             * @param parent
-             * @return a text header at the given position.
-             */
-            private final View getHeaderView(int position, View convertView, ViewGroup parent) {
-                final TextView headerView;
-                if (convertView != null) {
-                    headerView = (TextView) convertView;
-                } else {
-                    headerView = (TextView) mInflater.inflate(
-                            R.layout.folder_list_header, parent, false);
-                }
-                headerView.setText(mResource);
-                return headerView;
-            }
-
-            /**
-             * Return a folder: either a parent folder or a normal (child or flat)
-             * folder.
-             * @param position
-             * @param convertView
-             * @param parent
-             * @return a view showing a folder at the given position.
-             */
-            private final View getFolderView(int position, View convertView, ViewGroup parent) {
-                final FolderItemView folderItemView;
-                if (convertView != null) {
-                    folderItemView = (FolderItemView) convertView;
-                } else {
-                    folderItemView =
-                            (FolderItemView) mInflater.inflate(R.layout.folder_item, null, false);
-                }
-                folderItemView.bind(mFolder, mActivity);
-                if (mListView != null) {
-                    final boolean isSelected = (mFolderType == mSelectedFolderType)
-                            && mFolder.uri.equals(mSelectedFolderUri);
-                    mListView.setItemChecked(position, isSelected);
-                    // If this is the current folder, also check to verify that the unread count
-                    // matches what the action bar shows.
-                    if (isSelected && (mCurrentFolderForUnreadCheck != null)
-                            && mFolder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount) {
-                        folderItemView.overrideUnreadCount(
-                                mCurrentFolderForUnreadCheck.unreadCount);
-                    }
-                }
-                Folder.setFolderBlockColor(mFolder, folderItemView.findViewById(R.id.color_block));
-                Folder.setIcon(mFolder, (ImageView) folderItemView.findViewById(R.id.folder_box));
-                return folderItemView;
-            }
-        }
+        /** Watcher for tracking and receiving unread counts for mail */
+        private FolderWatcher mFolderWatcher = null;
+        private boolean mShowLessFolders;
+        private boolean mShowLessAccounts;
 
         /**
          * Creates a {@link FolderListAdapter}.This is a flat folder list of all the folders for the
          * given account.
-         * @param layout
          * @param isSectioned TODO(viki):
          */
-        public FolderListAdapter(int layout, boolean isSectioned) {
+        public FolderListAdapter(boolean isSectioned, boolean showLess, boolean showLessAccounts) {
             super();
-            mInflater = LayoutInflater.from(mActivity.getActivityContext());
             mIsSectioned = isSectioned;
             final RecentFolderController controller = mActivity.getRecentFolderController();
             if (controller != null && mIsSectioned) {
@@ -512,22 +475,48 @@
             } else {
                 mRecentFolders = null;
             }
+            mFolderWatcher = new FolderWatcher(mActivity, this);
+            mShowLessFolders = showLess;
+            mShowLessAccounts = showLessAccounts;
+            for (int i=0; i < mAllAccounts.length; i++) {
+                final Uri uri = mAllAccounts[i].settings.defaultInbox;
+                mFolderWatcher.startWatching(uri);
+            }
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            return ((Item) getItem(position)).getView(position, convertView, parent);
+            final DrawerItem item = (DrawerItem) getItem(position);
+            final View view = item.getView(position, convertView, parent);
+            final int type = item.mType;
+            if (mListView!= null) {
+                final boolean isSelected =
+                        item.isHighlighted(mCurrentFolderForUnreadCheck, mSelectedFolderType);
+                if (type == DrawerItem.VIEW_FOLDER) {
+                    mListView.setItemChecked(position, isSelected);
+                }
+                // If this is the current folder, also check to verify that the unread count
+                // matches what the action bar shows.
+                if (type == DrawerItem.VIEW_FOLDER
+                        && isSelected
+                        && (mCurrentFolderForUnreadCheck != null)
+                        && item.mFolder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount) {
+                    ((FolderItemView) view).overrideUnreadCount(
+                            mCurrentFolderForUnreadCheck.unreadCount);
+                }
+            }
+            return view;
         }
 
         @Override
         public int getViewTypeCount() {
-            // Headers and folders
-            return 2;
+            // Accounts, headers and folders
+            return DrawerItem.getViewTypes();
         }
 
         @Override
         public int getItemViewType(int position) {
-            return ((Item) getItem(position)).mType;
+            return ((DrawerItem) getItem(position)).mType;
         }
 
         @Override
@@ -537,22 +526,27 @@
 
         @Override
         public boolean isEnabled(int position) {
-            // We disallow taps on headers
-            return ((Item) getItem(position)).mType != Item.VIEW_HEADER;
+            final DrawerItem item = (DrawerItem) getItem(position);
+            return item.isItemEnabled(getCurrentAccountUri());
+
+        }
+
+        private Uri getCurrentAccountUri() {
+            return mCurrentAccount == null ? Uri.EMPTY : mCurrentAccount.uri;
         }
 
         @Override
         public boolean areAllItemsEnabled() {
-            // The headers are not enabled.
+            // The headers and current accounts are not enabled.
             return false;
         }
 
         /**
          * Returns all the recent folders from the list given here. Safe to call with a null list.
-         * @param recentList
+         * @param recentList a list of all recently accessed folders.
          * @return a valid list of folders, which are all recent folders.
          */
-        private final List<Folder> getRecentFolders(RecentFolderList recentList) {
+        private List<Folder> getRecentFolders(RecentFolderList recentList) {
             final List<Folder> folderList = new ArrayList<Folder>();
             if (recentList == null) {
                 return folderList;
@@ -567,21 +561,84 @@
         }
 
         /**
-         * Recalculates the system, recent and user label lists. Notifies that the data has changed.
-         * This method modifies all the three lists on every single invocation.
+         * Toggle boolean for what folders are shown and which ones are
+         * hidden. Redraws list after toggling to show changes.
+         * @return true if folders are hidden, false if all are shown
+         */
+        @Override
+        public boolean toggleShowLessFolders() {
+            mShowLessFolders = !mShowLessFolders;
+            recalculateList();
+            return mShowLessFolders;
+        }
+
+        /**
+         * Toggle boolean for what accounts are shown and which ones are
+         * hidden. Redraws list after toggling to show changes.
+         * @return true if accounts are hidden, false if all are shown
+         */
+        @Override
+        public boolean toggleShowLessAccounts() {
+            mShowLessAccounts = !mShowLessAccounts;
+            recalculateList();
+            return mShowLessAccounts;
+        }
+
+        /**
+         * Responsible for verifying mCursor, adding collapsed view items
+         * when necessary, and notifying the data set has changed.
          */
         private void recalculateList() {
             if (mCursor == null || mCursor.isClosed() || mCursor.getCount() <= 0
                     || !mCursor.moveToFirst()) {
                 return;
             }
+            recalculateListFolders();
+            if(mShowLessFolders) {
+                mItemList.add(new DrawerItem(mActivity, R.string.folder_list_more, false));
+            }
+            // Ask the list to invalidate its views.
+            notifyDataSetChanged();
+        }
+
+        /**
+         * Recalculates the system, recent and user label lists.
+         * This method modifies all the three lists on every single invocation.
+         */
+        private void recalculateListFolders() {
             mItemList.clear();
+            if (mAllAccounts != null) {
+                // Add the accounts at the top.
+                // TODO(shahrk): The logic here is messy and will be changed
+                //               to properly add/reflect on LRU/MRU account
+                //               changes similar to RecentFoldersList
+                if (mShowLessAccounts && mAllAccounts.length > MAX_ACCOUNTS) {
+                    mItemList.add(new DrawerItem(
+                            mActivity, R.string.folder_list_show_all_accounts, true));
+                    final int unreadCount =
+                            getFolderUnreadCount(mCurrentAccount.settings.defaultInbox);
+                    mItemList.add(new DrawerItem(mActivity, mCurrentAccount, unreadCount));
+                } else {
+                    Uri currentAccountUri = getCurrentAccountUri();
+                    for (final Account c : mAllAccounts) {
+                        if (!currentAccountUri.equals(c.uri)) {
+                            final int otherAccountUnreadCount =
+                                    getFolderUnreadCount(c.settings.defaultInbox);
+                            mItemList.add(new DrawerItem(mActivity, c, otherAccountUnreadCount));
+                        }
+                    }
+                    final int accountUnreadCount =
+                            getFolderUnreadCount(mCurrentAccount.settings.defaultInbox);
+                    mItemList.add(new DrawerItem(mActivity, mCurrentAccount, accountUnreadCount));
+                }
+            }
             if (!mIsSectioned) {
                 // Adapter for a flat list. Everything is a FOLDER_USER, and there are no headers.
                 do {
                     final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor);
                     if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) {
-                        mItemList.add(new Item(f, Item.FOLDER_USER, mCursor.getPosition()));
+                        mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_USER,
+                                mCursor.getPosition()));
                     }
                 } while (mCursor.moveToNext());
                 // Ask the list to invalidate its views.
@@ -589,16 +646,25 @@
                 return;
             }
 
+            // Tracks how many folders have been added through the rest of the function
+            int folderCount = 0;
             // Otherwise, this is an adapter for a sectioned list.
             // First add all the system folders.
-            final List<Item> userFolderList = new ArrayList<Item>();
+            final List<DrawerItem> userFolderList = new ArrayList<DrawerItem>();
             do {
                 final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor);
                 if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) {
                     if (f.isProviderFolder()) {
-                        mItemList.add(new Item(f, Item.FOLDER_SYSTEM, mCursor.getPosition()));
+                        mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_SYSTEM,
+                                mCursor.getPosition()));
+                        // Check if show less is enabled and we've passed max folders
+                        folderCount++;
+                        if(mShowLessFolders && folderCount >= MAX_FOLDERS) {
+                            return;
+                        }
                     } else {
-                        userFolderList.add(new Item(f, Item.FOLDER_USER, mCursor.getPosition()));
+                        userFolderList.add(new DrawerItem(
+                                mActivity, f, DrawerItem.FOLDER_USER, mCursor.getPosition()));
                     }
                 }
             } while (mCursor.moveToNext());
@@ -616,20 +682,33 @@
             }
 
             if (recentFolderList.size() > 0) {
-                mItemList.add(new Item(R.string.recent_folders_heading));
+                mItemList.add(new DrawerItem(mActivity, R.string.recent_folders_heading));
                 for (Folder f : recentFolderList) {
-                    mItemList.add(new Item(f, Item.FOLDER_RECENT, -1));
+                    mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_RECENT, -1));
+                    // Check if show less is enabled and we've passed max folders
+                    folderCount++;
+                    if(mShowLessFolders && folderCount >= MAX_FOLDERS) {
+                        return;
+                    }
                 }
             }
             // If there are user folders, add them and a header.
             if (userFolderList.size() > 0) {
-                mItemList.add(new Item(R.string.all_folders_heading));
-                for (final Item i : userFolderList) {
+                mItemList.add(new DrawerItem(mActivity, R.string.all_folders_heading));
+                for (final DrawerItem i : userFolderList) {
                     mItemList.add(i);
+                    // Check if show less is enabled and we've passed max folders
+                    folderCount++;
+                    if(mShowLessFolders && folderCount >= MAX_FOLDERS) {
+                        return;
+                    }
                 }
             }
-            // Ask the list to invalidate its views.
-            notifyDataSetChanged();
+        }
+
+        private int getFolderUnreadCount(Uri folderUri) {
+            final Folder folder = mFolderWatcher.get(folderUri);
+            return folder != null ? folder.unreadCount : 0;
         }
 
         @Override
@@ -654,8 +733,13 @@
         }
 
         @Override
-        public Folder getFullFolder(Item folderItem) {
-            if (folderItem.mFolderType == Item.FOLDER_RECENT) {
+        public int getItemType(DrawerItem item) {
+            return item.mType;
+        }
+
+        @Override
+        public Folder getFullFolder(DrawerItem folderItem) {
+            if (folderItem.mFolderType == DrawerItem.FOLDER_RECENT) {
                 return folderItem.mFolder;
             } else {
                 int pos = folderItem.mPosition;
@@ -671,6 +755,11 @@
                 }
             }
         }
+
+        @Override
+        public Account getFullAccount(DrawerItem item) {
+            return item.mAccount;
+        }
     }
 
     private class HierarchicalFolderListAdapter extends ArrayAdapter<Folder>
@@ -699,14 +788,14 @@
 
         @Override
         public int getItemViewType(int position) {
-            Folder f = getItem(position);
+            final Folder f = getItem(position);
             return f.uri.equals(mParentUri) ? PARENT : CHILD;
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            FolderItemView folderItemView;
-            Folder folder = getItem(position);
+            final FolderItemView folderItemView;
+            final Folder folder = getItem(position);
             boolean isParent = folder.uri.equals(mParentUri);
             if (convertView != null) {
                 folderItemView = (FolderItemView) convertView;
@@ -726,7 +815,8 @@
                     folderItemView.overrideUnreadCount(mCurrentFolderForUnreadCheck.unreadCount);
                 }
             }
-            Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.folder_box));
+            Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.color_block));
+            Folder.setIcon(folder, (ImageView) folderItemView.findViewById(R.id.folder_icon));
             return folderItemView;
         }
 
@@ -753,7 +843,13 @@
         }
 
         @Override
-        public Folder getFullFolder(FolderListAdapter.Item folderItem) {
+        public int getItemType(DrawerItem item) {
+            // Always returns folders for now.
+            return DrawerItem.VIEW_FOLDER;
+        }
+
+        @Override
+        public Folder getFullFolder(DrawerItem folderItem) {
             int pos = folderItem.mPosition;
             if (mCursor == null || mCursor.isClosed()) {
                 // See if we have a cursor hanging out we can use
@@ -767,6 +863,21 @@
                 return null;
             }
         }
+
+        @Override
+        public Account getFullAccount(DrawerItem item) {
+            return null;
+        }
+
+        @Override
+        public boolean toggleShowLessFolders() {
+            return false;
+        }
+
+        @Override
+        public boolean toggleShowLessAccounts() {
+            return false;
+        }
     }
 
     /**
@@ -781,7 +892,7 @@
         }
         // If the current folder changed, we don't have a selected folder type anymore.
         if (!folder.uri.equals(mSelectedFolderUri)) {
-            mSelectedFolderType = FolderListAdapter.Item.NOT_A_FOLDER;
+            mSelectedFolderType = DrawerItem.INERT_HEADER;
         }
         mCurrentFolderForUnreadCheck = folder;
         mSelectedFolderUri = folder.uri;
@@ -793,15 +904,23 @@
 
     /**
      * Sets the selected folder type safely.
-     * @param folder
+     * @param folder folder to set to.
      */
     private void setSelectedFolderType(Folder folder) {
         // If it is set already, assume it is correct.
-        if (mSelectedFolderType != FolderListAdapter.Item.NOT_A_FOLDER) {
+        if (mSelectedFolderType != DrawerItem.INERT_HEADER) {
             return;
         }
-        mSelectedFolderType = folder.isProviderFolder() ? FolderListAdapter.Item.FOLDER_SYSTEM
-                : FolderListAdapter.Item.FOLDER_USER;
+        mSelectedFolderType = folder.isProviderFolder() ? DrawerItem.FOLDER_SYSTEM
+                : DrawerItem.FOLDER_USER;
+    }
+
+    /**
+     * Sets the current account to the one provided here.
+     * @param account the current account to set to.
+     */
+    private void setSelectedAccount(Account account){
+        mCurrentAccount = account;
     }
 
     public interface FolderListSelectionListener {
diff --git a/src/com/android/mail/ui/FolderSelectionActivity.java b/src/com/android/mail/ui/FolderSelectionActivity.java
index c98b0b1..3bee9b0 100644
--- a/src/com/android/mail/ui/FolderSelectionActivity.java
+++ b/src/com/android/mail/ui/FolderSelectionActivity.java
@@ -110,7 +110,7 @@
     private void createFolderListFragment(Folder parent, Uri uri) {
         final FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
         final Fragment fragment = FolderListFragment.newInstance(parent, uri, false,
-                getExcludedFolderTypes());
+                getExcludedFolderTypes(), true);
         fragmentTransaction.replace(R.id.content_pane, fragment);
         fragmentTransaction.commitAllowingStateLoss();
     }
@@ -155,7 +155,8 @@
      * Create a widget for the specified account and folder
      */
     protected void createWidget(int id, Account account, Folder selectedFolder) {
-        WidgetProvider.updateWidget(this, id, account, selectedFolder);
+        WidgetProvider.updateWidget(this, id, account, selectedFolder.uri,
+                selectedFolder.conversationListUri, selectedFolder.name);
         final Intent result = new Intent();
         result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id);
         setResult(RESULT_OK, result);
@@ -185,7 +186,7 @@
                  * account, calculate the human readable name of the folder and
                  * use it as the shortcut name, etc...
                  */
-                final Intent clickIntent = Utils.createViewFolderIntent(mSelectedFolder,
+                final Intent clickIntent = Utils.createViewFolderIntent(this, mSelectedFolder.uri,
                         mAccount);
                 resultIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, clickIntent);
                 resultIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
@@ -370,4 +371,10 @@
         // Unsupported
         return true;
     }
+
+    @Override
+    public ConversationListHelper getConversationListHelper() {
+        // Unsupported
+        return null;
+    }
 }
diff --git a/src/com/android/mail/ui/FolderSelectionDialog.java b/src/com/android/mail/ui/FolderSelectionDialog.java
index 412cfa4..dc2a185 100644
--- a/src/com/android/mail/ui/FolderSelectionDialog.java
+++ b/src/com/android/mail/ui/FolderSelectionDialog.java
@@ -52,16 +52,17 @@
 
     public static FolderSelectionDialog getInstance(final Context context, Account account,
             final ConversationUpdater updater, Collection<Conversation> target, boolean isBatch,
-            Folder currentFolder) {
+            Folder currentFolder, boolean isMoveTo) {
         if (sDialogShown) {
             return null;
         }
 
-        if (account.supportsCapability(UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV)) {
-            return new MultiFoldersSelectionDialog(
+        if (isMoveTo || !account.supportsCapability(
+                UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV)) {
+            return new SingleFolderSelectionDialog(
                     context, account, updater, target, isBatch, currentFolder);
         } else {
-            return new SingleFolderSelectionDialog(
+            return new MultiFoldersSelectionDialog(
                     context, account, updater, target, isBatch, currentFolder);
         }
     }
diff --git a/src/com/android/mail/ui/FolderSelectorAdapter.java b/src/com/android/mail/ui/FolderSelectorAdapter.java
index e5faee0..d5d85af 100644
--- a/src/com/android/mail/ui/FolderSelectorAdapter.java
+++ b/src/com/android/mail/ui/FolderSelectorAdapter.java
@@ -211,9 +211,9 @@
         if (view == null) {
             view = mInflater.inflate(mLayout, parent, false);
         }
-        FolderRow row = (FolderRow) getItem(position);
-        Folder folder = row.getFolder();
-        String folderDisplay = !TextUtils.isEmpty(folder.hierarchicalDesc) ?
+        final FolderRow row = (FolderRow) getItem(position);
+        final Folder folder = row.getFolder();
+        final String folderDisplay = !TextUtils.isEmpty(folder.hierarchicalDesc) ?
                 folder.hierarchicalDesc : folder.name;
         checkBox = (CompoundButton) view.findViewById(R.id.checkbox);
         display = (TextView) view.findViewById(R.id.folder_name);
@@ -228,7 +228,7 @@
             display.setText(folderDisplay);
         }
         colorBlock = view.findViewById(R.id.color_block);
-        iconView = (ImageView) view.findViewById(R.id.folder_box);
+        iconView = (ImageView) view.findViewById(R.id.folder_icon);
         Folder.setFolderBlockColor(folder, colorBlock);
         Folder.setIcon(folder, iconView);
         return view;
diff --git a/src/com/android/mail/ui/HtmlConversationTemplates.java b/src/com/android/mail/ui/HtmlConversationTemplates.java
index 3420d53..92340b3 100644
--- a/src/com/android/mail/ui/HtmlConversationTemplates.java
+++ b/src/com/android/mail/ui/HtmlConversationTemplates.java
@@ -181,7 +181,8 @@
     }
 
     public String endConversation(String docBaseUri, String conversationBaseUri, int viewWidth,
-            int viewportWidth, boolean enableContentReadySignal, boolean normalizeMessageWidths) {
+            int viewportWidth, boolean enableContentReadySignal, boolean normalizeMessageWidths,
+            boolean enableMungeTables, boolean enableMungeImages) {
         if (!mInProgress) {
             throw new IllegalStateException("must call startConversation first");
         }
@@ -190,7 +191,8 @@
 
         append(sConversationLower, contentReadyClass, mContext.getString(R.string.hide_elided),
                 mContext.getString(R.string.show_elided), docBaseUri, conversationBaseUri,
-                viewWidth, viewportWidth, enableContentReadySignal, normalizeMessageWidths);
+                viewWidth, viewportWidth, enableContentReadySignal, normalizeMessageWidths,
+                enableMungeTables, enableMungeImages);
 
         mInProgress = false;
 
diff --git a/src/com/android/mail/ui/MailActionBarView.java b/src/com/android/mail/ui/MailActionBarView.java
index 943d4a1..9e94d33 100644
--- a/src/com/android/mail/ui/MailActionBarView.java
+++ b/src/com/android/mail/ui/MailActionBarView.java
@@ -17,6 +17,23 @@
 
 package com.android.mail.ui;
 
+import com.android.mail.ConversationListContext;
+import com.android.mail.R;
+import com.android.mail.browse.SnippetTextView;
+import com.android.mail.providers.Account;
+import com.android.mail.providers.AccountObserver;
+import com.android.mail.providers.AllAccountObserver;
+import com.android.mail.providers.Conversation;
+import com.android.mail.providers.Folder;
+import com.android.mail.providers.FolderObserver;
+import com.android.mail.providers.SearchRecentSuggestionsProvider;
+import com.android.mail.providers.UIProvider;
+import com.android.mail.providers.UIProvider.AccountCapabilities;
+import com.android.mail.providers.UIProvider.FolderCapabilities;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
+
 import android.app.ActionBar;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
@@ -24,7 +41,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.database.DataSetObserver;
 import android.os.Bundle;
 import android.os.Handler;
 import android.text.SpannableString;
@@ -40,22 +56,6 @@
 import android.widget.SearchView.OnQueryTextListener;
 import android.widget.SearchView.OnSuggestionListener;
 
-import com.android.mail.AccountSpinnerAdapter;
-import com.android.mail.ConversationListContext;
-import com.android.mail.R;
-import com.android.mail.browse.SnippetTextView;
-import com.android.mail.providers.Account;
-import com.android.mail.providers.AccountObserver;
-import com.android.mail.providers.Conversation;
-import com.android.mail.providers.Folder;
-import com.android.mail.providers.SearchRecentSuggestionsProvider;
-import com.android.mail.providers.UIProvider;
-import com.android.mail.providers.UIProvider.AccountCapabilities;
-import com.android.mail.providers.UIProvider.FolderCapabilities;
-import com.android.mail.utils.LogTag;
-import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
-
 /**
  * View to manage the various states of the Mail Action Bar.
  * <p>
@@ -78,8 +78,6 @@
     private int mMode = ViewMode.UNKNOWN;
 
     private MenuItem mSearch;
-    private AccountSpinnerAdapter mSpinnerAdapter;
-    private MailSpinner mSpinner;
     /**
      * The account currently being shown
      */
@@ -96,12 +94,12 @@
     private MenuItem mRefreshItem;
     private MenuItem mFolderSettingsItem;
     private View mRefreshActionView;
+    /** True if the current device is a tablet, false otherwise. */
+    private boolean mIsOnTablet;
     private boolean mRefreshInProgress;
     private Conversation mCurrentConversation;
-    /**
-     * True if we are running on tablet.
-     */
-    private final boolean mIsOnTablet;
+    private AllAccountObserver mAllAccountObserver;
+    private boolean mHaveMultipleAccounts = false;
 
     public static final String LOG_TAG = LogTag.getLogTag();
 
@@ -113,13 +111,12 @@
         }
     };
     private final boolean mShowConversationSubject;
-    private DataSetObserver mFolderObserver;
+    private FolderObserver mFolderObserver;
 
     private final AccountObserver mAccountObserver = new AccountObserver() {
         @Override
         public void onChanged(Account newAccount) {
             updateAccount(newAccount);
-            mSpinner.setAccount(mAccount);
         }
     };
 
@@ -165,9 +162,8 @@
         return reports;
     }
 
-    /** True if the application has more than one account. */
-    private boolean mHasManyAccounts;
-
+    // Created via view inflation.
+    @SuppressWarnings("unused")
     public MailActionBarView(Context context) {
         this(context, null);
     }
@@ -183,18 +179,9 @@
         mIsOnTablet = Utils.useTabletUI(r);
     }
 
-    // update the pager title strip as the Folder's conversation count changes
-    private class FolderObserver extends DataSetObserver {
-        @Override
-        public void onChanged() {
-            onFolderUpdated(mController.getFolder());
-        }
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-
         mSubjectView = (SnippetTextView) findViewById(R.id.conversation_subject);
     }
 
@@ -274,37 +261,37 @@
         return modeMenu[mMode];
     }
 
-    public void handleRestore(Bundle savedInstanceState) {
-    }
-
-    public void handleSaveInstanceState(Bundle outState) {
-    }
-
     public void initialize(ControllableActivity activity, ActivityController callback,
-            ViewMode viewMode, ActionBar actionBar, RecentFolderList recentFolders) {
+            ActionBar actionBar) {
         mActionBar = actionBar;
         mController = callback;
         mActivity = activity;
-        mFolderObserver = new FolderObserver();
-        mController.registerFolderObserver(mFolderObserver);
-        // We don't want to include the "Show all folders" menu item on tablet devices
-        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);
+        mFolderObserver = new FolderObserver() {
+            @Override
+            public void onChanged(Folder newFolder) {
+                onFolderUpdated(newFolder);
+            }
+        };
+        mFolderObserver.initialize(mController);
+        mAllAccountObserver = new AllAccountObserver() {
+            @Override
+            public void onChanged(Account[] allAccounts) {
+                mHaveMultipleAccounts = (allAccounts.length > 1);
+            }
+        };
+        mAllAccountObserver.initialize(mController);
         updateAccount(mAccountObserver.initialize(activity.getAccountController()));
     }
 
     private void updateAccount(Account account) {
         mAccount = account;
         if (mAccount != null) {
-            ContentResolver resolver = mActivity.getActivityContext().getContentResolver();
-            Bundle bundle = new Bundle(1);
+            final ContentResolver resolver = mActivity.getActivityContext().getContentResolver();
+            final Bundle bundle = new Bundle(1);
             bundle.putParcelable(UIProvider.SetCurrentAccountColumns.ACCOUNT, account);
             resolver.call(mAccount.uri, UIProvider.AccountCallMethods.SET_CURRENT_ACCOUNT,
                     mAccount.uri.toString(), bundle);
+            setFolderAndAccount();
         }
     }
 
@@ -316,72 +303,34 @@
     }
 
     /**
-     * Sets the array of accounts to the value provided here.
-     * @param accounts
-     */
-    public void setAccounts(Account[] accounts) {
-        mSpinnerAdapter.setAccountArray(accounts);
-        mHasManyAccounts = accounts.length > 1;
-        enableDisableSpinnner();
-    }
-
-    /**
-     * Changes the spinner state according to the following logic. On phone we always show recent
-     * labels: pre-populating if necessary. So on phone we always want to enable the spinner.
-     * On tablet, we enable the spinner when the Folder list is NOT visible: In conversation view,
-     * and search conversation view.
-     */
-    private final void enableDisableSpinnner() {
-        // Spinner is always shown on phone, and it is enabled by default, so don't mess with it.
-        // By default the drawable is set in the XML layout, and the view is enabled.
-        if (!mIsOnTablet) {
-            return;
-        }
-        // We do not populate default recent folders on tablet, so we need to check that in the
-        // conversation mode we have some recent folders. If we don't have any, then we should
-        // disable the spinner.
-        final boolean hasRecentsInConvView = ViewMode.isConversationMode(mMode)
-                && mSpinnerAdapter.hasRecentFolders();
-        // More than one account, OR has recent folders in conversation view.
-        final boolean enabled = mHasManyAccounts || hasRecentsInConvView;
-        mSpinner.changeEnabledState(enabled);
-    }
-
-    /**
      * Called by the owner of the ActionBar to set the
      * folder that is currently being displayed.
      */
     public void setFolder(Folder folder) {
         setRefreshInProgress(false);
         mFolder = folder;
-        mSpinner.setFolder(folder);
+        setFolderAndAccount();
         mActivity.invalidateOptionsMenu();
     }
 
     public void onDestroy() {
         if (mFolderObserver != null) {
-            mController.unregisterFolderObserver(mFolderObserver);
+            mFolderObserver.unregisterAndDestroy();
             mFolderObserver = null;
         }
-        mSpinnerAdapter.destroy();
+        if (mAllAccountObserver != null) {
+            mAllAccountObserver.unregisterAndDestroy();
+            mAllAccountObserver = null;
+        }
         mAccountObserver.unregisterAndDestroy();
     }
 
     @Override
     public void onViewModeChanged(int newMode) {
         mMode = newMode;
-        // Always update the options menu and redraw. This will read the new mode and redraw
-        // the options menu.
-        enableDisableSpinnner();
         mActivity.invalidateOptionsMenu();
         // Check if we are either on a phone, or in Conversation mode on tablet. For these, the
         // recent folders is enabled.
-        if (!mIsOnTablet || mMode == ViewMode.CONVERSATION) {
-            mSpinnerAdapter.enableRecentFolders();
-        } else {
-            mSpinnerAdapter.disableRecentFolders();
-        }
-
         switch (mMode) {
             case ViewMode.UNKNOWN:
                 closeSearchField();
@@ -416,7 +365,7 @@
      * Close the search query entry field to avoid keyboard events, and to restore the actionbar
      * to non-search mode.
      */
-    private final void closeSearchField() {
+    private void closeSearchField() {
         if (mSearch == null) {
             return;
         }
@@ -484,9 +433,9 @@
      * 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);
+        setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE);
         mSubjectView.setVisibility(View.GONE);
+        setFolderAndAccount();
     }
 
     /**
@@ -496,24 +445,23 @@
      */
     protected void setSnippetMode() {
         setTitleModeFlags(ActionBar.DISPLAY_SHOW_CUSTOM);
-        mSpinner.setVisibility(View.GONE);
         mSubjectView.setVisibility(View.VISIBLE);
-
         mSubjectView.addOnLayoutChangeListener(mSnippetLayoutListener);
     }
 
     private void setFoldersMode() {
         setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE);
         mActionBar.setTitle(R.string.folders);
-        mActionBar.setSubtitle(mAccount.name);
+        if (mHaveMultipleAccounts) {
+            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);
+        setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE);
         mSubjectView.setVisibility(View.GONE);
     }
 
@@ -571,14 +519,6 @@
         setRefreshInProgress(false);
     }
 
-    /**
-     * Get the query text the user entered in the search widget, or empty string
-     * if there is none.
-     */
-    public String getQuery() {
-        return mSearchWidget != null ? mSearchWidget.getQuery().toString() : "";
-    }
-
     // Next two methods are called when search suggestions are clicked.
     @Override
     public boolean onSuggestionSelect(int position) {
@@ -630,10 +570,30 @@
     }
 
     /**
+     * Uses the current state to update the current folder {@link #mFolder} and the current
+     * account {@link #mAccount} shown in the actionbar.
+     */
+    private void setFolderAndAccount() {
+        // Check if we should be changing the actionbar at all, and back off if not.
+        final boolean isShowingFolderAndAccount =
+                (mActionBar != null && (mIsOnTablet || ViewMode.isListMode(mMode)));
+        if (!isShowingFolderAndAccount) {
+            return;
+        }
+        if (mFolder != null) {
+            mActionBar.setTitle(mFolder.name);
+        }
+        if (mAccount != null && mHaveMultipleAccounts) {
+            mActionBar.setSubtitle(mAccount.name);
+        }
+        // TODO(viki): Show unread count.
+    }
+
+    /**
      * Notify that the folder has changed.
      */
     public void onFolderUpdated(Folder folder) {
-        mSpinner.onFolderUpdated(folder);
+        setFolderAndAccount();
         if (folder.isSyncInProgress()) {
             onRefreshStarted();
         } else {
@@ -672,7 +632,6 @@
     private void setTitleModeFlags(int enabledFlags) {
         final int mask = ActionBar.DISPLAY_SHOW_TITLE
                 | ActionBar.DISPLAY_SHOW_CUSTOM | DISPLAY_TITLE_MULTIPLE_LINES;
-
         mActionBar.setDisplayOptions(enabledFlags, mask);
     }
 
@@ -745,8 +704,10 @@
         Utils.setMenuItemVisibility(menu, R.id.remove_folder, !archiveVisible && mFolder != null
                 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
                 && !mFolder.isProviderFolder());
+        Utils.setMenuItemVisibility(menu, R.id.move_to, mFolder != null
+                && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION));
         final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
-        if (removeFolder != null) {
+        if (mFolder != null && removeFolder != null) {
             removeFolder.setTitle(mActivity.getApplicationContext().getString(
                     R.string.remove_folder, mFolder.name));
         }
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index 73a4db3..0bc75da 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -67,7 +67,7 @@
      * to be static since the {@link ComposeActivity} needs to statically change the account name
      * and have the NFC message changed accordingly.
      */
-    private static String sAccountName = null;
+    protected static String sAccountName = null;
 
     /**
      * Create an NFC message (in the NDEF: Nfc Data Exchange Format) to instruct the recepient to
@@ -404,4 +404,10 @@
         mAccessibilityEnabled = enabled;
         mController.onAccessibilityStateChanged();
     }
+
+    @Override
+    public ConversationListHelper getConversationListHelper() {
+        // Unsupported
+        return null;
+    }
 }
diff --git a/src/com/android/mail/ui/MailSpinner.java b/src/com/android/mail/ui/MailSpinner.java
deleted file mode 100644
index d10606f..0000000
--- a/src/com/android/mail/ui/MailSpinner.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/**
- * Copyright (c) 2012, Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.mail.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.ListPopupWindow;
-import android.widget.TextView;
-
-import com.android.mail.AccountSpinnerAdapter;
-import com.android.mail.R;
-import com.android.mail.providers.Account;
-import com.android.mail.providers.Folder;
-import com.android.mail.utils.LogTag;
-import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
-
-public class MailSpinner extends FrameLayout implements OnItemClickListener, OnClickListener {
-    private static final String LOG_TAG = LogTag.getLogTag();
-    private final ListPopupWindow mListPopupWindow;
-    private AccountSpinnerAdapter mSpinnerAdapter;
-    private Account mAccount;
-    private Folder mFolder;
-    private ActivityController mController;
-    private final TextView mAccountName;
-    private final TextView mFolderName;
-    private final TextView mFolderCount;
-    private final LinearLayout mContainer;
-
-    public MailSpinner(Context context) {
-        this(context, null);
-    }
-
-    public MailSpinner(Context context, AttributeSet attrs) {
-        this(context, attrs, -1);
-    }
-
-    public MailSpinner(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        mListPopupWindow = new ListPopupWindow(context);
-        mListPopupWindow.setOnItemClickListener(this);
-        mListPopupWindow.setAnchorView(this);
-        int dropDownWidth = context.getResources().getDimensionPixelSize(
-                R.dimen.account_dropdown_dropdownwidth);
-        mListPopupWindow.setWidth(dropDownWidth);
-        mListPopupWindow.setModal(true);
-        addView(LayoutInflater.from(getContext()).inflate(R.layout.account_switch_spinner_item,
-                null));
-        mAccountName = (TextView) findViewById(R.id.account_second);
-        mFolderName = (TextView) findViewById(R.id.account_first);
-        mFolderCount = (TextView) findViewById(R.id.account_unread);
-        mContainer = (LinearLayout) findViewById(R.id.account_spinner_container);
-        mContainer.setOnClickListener(this);
-    }
-
-    public void setAdapter(AccountSpinnerAdapter adapter) {
-        mSpinnerAdapter = adapter;
-        mListPopupWindow.setAdapter(mSpinnerAdapter);
-    }
-
-    /**
-     * Changes the enabled state of the spinner. Not called {@link #setEnabled(boolean)} because
-     * that is an existing method on views.
-     *
-     * @param enabled
-     */
-    public final void changeEnabledState(boolean enabled) {
-        setEnabled(enabled);
-        if (enabled) {
-            mContainer.setBackgroundResource(R.drawable.spinner_ab_holo_light);
-        } else {
-            mContainer.setBackgroundDrawable(null);
-        }
-    }
-
-    public void setAccount(Account account) {
-        mAccount = account;
-        if (mAccount != null) {
-            mAccountName.setText(mAccount.name);
-        }
-    }
-
-    public void setFolder(Folder f) {
-        mFolder = f;
-        if (mFolder != null) {
-            mFolderName.setText(mFolder.name);
-            int unreadCount = Utils.getFolderUnreadDisplayCount(mFolder);
-            mFolderCount.setText(Utils.getUnreadCountString(getContext(), unreadCount));
-            mFolderCount.setContentDescription(Utils.formatPlural(getContext(),
-                    R.plurals.unread_mail_count, unreadCount));
-
-            if (mSpinnerAdapter != null) {
-                // Update the spinner with this current folder, as it could change the recent items
-                // that are shown.
-                mSpinnerAdapter.setCurrentFolder(mFolder);
-            }
-        }
-    }
-
-    @Override
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        LogUtils.d(LOG_TAG, "onNavigationItemSelected(%d, %d) called", position, id);
-        final int type = mSpinnerAdapter.getItemViewType(position);
-        boolean dismiss = false;
-        switch (type) {
-            case AccountSpinnerAdapter.TYPE_ACCOUNT:
-                // Get the capabilities associated with this account.
-                final Account account = (Account) mSpinnerAdapter.getItem(position);
-                LogUtils.d(LOG_TAG, "onNavigationItemSelected: Selecting account: %s",
-                        account.name);
-                if (mAccount.uri.equals(account.uri)) {
-                    // The selected account is the same, let's load the default inbox.
-                    mController.loadAccountInbox();
-                } else {
-                    // Switching accounts.
-                    mController.onAccountChanged(account);
-                }
-                dismiss = true;
-                break;
-            case AccountSpinnerAdapter.TYPE_FOLDER:
-                final Object folder = mSpinnerAdapter.getItem(position);
-                assert (folder instanceof Folder);
-                LogUtils.d(LOG_TAG, "onNavigationItemSelected: Selecting folder: %s",
-                        ((Folder)folder).name);
-                mController.onFolderChanged((Folder) folder);
-                dismiss = true;
-                break;
-            case AccountSpinnerAdapter.TYPE_ALL_FOLDERS:
-                mController.showFolderList();
-                dismiss = true;
-                break;
-            case AccountSpinnerAdapter.TYPE_HEADER:
-                LogUtils.e(LOG_TAG, "MailSpinner.onItemClick(): Got unexpected click on header.");
-                break;
-            default:
-                LogUtils.e(LOG_TAG, "MailSpinner.onItemClick(%d): Strange click ignored: type %d.",
-                        position, type);
-                break;
-        }
-        if (dismiss) {
-            mListPopupWindow.dismiss();
-        }
-    }
-
-    public void setController(ActivityController controller) {
-        mController = controller;
-    }
-
-    @Override
-    public void onClick(View arg0) {
-        if (isEnabled() && !mListPopupWindow.isShowing()) {
-            mListPopupWindow.show();
-            // Commit any leave behind items.
-            mController.commitDestructiveActions(false);
-        }
-    }
-
-    public void dismiss() {
-        mListPopupWindow.dismiss();
-    }
-
-    public void onFolderUpdated(Folder folder) {
-        mSpinnerAdapter.onFolderUpdated(folder);
-        setFolder(folder);
-    }
-}
diff --git a/src/com/android/mail/ui/MultiFoldersSelectionDialog.java b/src/com/android/mail/ui/MultiFoldersSelectionDialog.java
index 41b0778..cc95a48 100644
--- a/src/com/android/mail/ui/MultiFoldersSelectionDialog.java
+++ b/src/com/android/mail/ui/MultiFoldersSelectionDialog.java
@@ -30,7 +30,6 @@
 import com.android.mail.ui.FolderSelectorAdapter.FolderRow;
 import com.android.mail.utils.Utils;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
@@ -152,7 +151,8 @@
         switch (which) {
             case DialogInterface.BUTTON_POSITIVE:
                 if (mUpdater != null) {
-                    mUpdater.assignFolder(mOperations.values(), mTarget, mBatch, true);
+                    mUpdater.assignFolder(mOperations.values(), mTarget, mBatch,
+                            true /* showUndo */, false /* isMoveTo */);
                 }
                 break;
             case DialogInterface.BUTTON_NEGATIVE:
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index e084c5d..fc33b8d 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -23,12 +23,15 @@
 import android.app.LoaderManager.LoaderCallbacks;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.v4.widget.DrawerLayout;
+import android.view.View;
+import android.view.ViewGroup;
+
 import com.android.mail.ConversationListContext;
 import com.android.mail.R;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
-import com.android.mail.providers.Settings;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
@@ -55,6 +58,8 @@
     private static final String CONVERSATION_LIST_NEVER_SHOWN_KEY = "conversation-list-never-shown";
     /** Key to store {@link #mInbox}. */
     private final static String SAVED_INBOX_KEY = "m-inbox";
+    /** Set to true to show sections/recent inbox in drawer, false otherwise*/
+    private final static boolean SECTIONS_AND_RECENT_FOLDERS_ENABLED = true;
 
     private static final int INVALID_ID = -1;
     private boolean mConversationListVisible = false;
@@ -65,11 +70,9 @@
     private Folder mInbox;
     /** Whether a conversation list for this account has ever been shown.*/
     private boolean mConversationListNeverShown = true;
+    private DrawerLayout mDrawerContainer;
+    private ViewGroup mDrawerPullout;
 
-    /**
-     * @param activity
-     * @param viewMode
-     */
     public OnePaneController(MailActivity activity, ViewMode viewMode) {
         super(activity, viewMode);
     }
@@ -108,7 +111,8 @@
     public void resetActionBarIcon() {
         final int mode = mViewMode.getMode();
         if (mode == ViewMode.CONVERSATION_LIST
-                && inInbox(mAccount, mConvListContext)) {
+                && inInbox(mAccount, mConvListContext)
+                || mViewMode.isWaitingForSync()) {
             mActionBarView.removeBackButton();
         } else {
             mActionBarView.setBackButton();
@@ -117,42 +121,43 @@
 
     /**
      * Returns true if the candidate URI is the URI for the default inbox for the given account.
-     * @param candidate
-     * @param account
-     * @return
+     * @param candidate the URI to check
+     * @param account the account whose default Inbox the candidate might be
+     * @return true if the candidate is indeed the default inbox for the given account.
      */
-    private static final boolean isDefaultInbox(Uri candidate, Account account) {
-        if (candidate == null || account == null) {
-            return false;
-        }
-        final Uri inboxUri = Settings.getDefaultInboxUri(account.settings);
-        return candidate.equals(account.settings.defaultInbox);
+    private static boolean isDefaultInbox(Uri candidate, Account account) {
+        return (candidate != null && account != null)
+                && candidate.equals(account.settings.defaultInbox);
     }
 
     /**
      * Returns true if the user is currently in the conversation list view, viewing the default
      * inbox.
-     * @return
+     * @return true if user is in conversation list mode, viewing the default inbox.
      */
     private static boolean inInbox(final Account account, final ConversationListContext context) {
         // If we don't have valid state, then we are not in the inbox.
-        if (account == null || context == null || context.folder == null
-                || account.settings == null) {
-            return false;
-        }
-        return !ConversationListContext.isSearchResult(context)
+        return !(account == null || context == null || context.folder == null
+                || account.settings == null) && !ConversationListContext.isSearchResult(context)
                 && isDefaultInbox(context.folder.uri, account);
     }
 
+    /**
+     * On account change, carry out super implementation, load FolderListFragment
+     * into drawer (to avoid repetitive calls to replaceFragment).
+     */
     @Override
-    public void onAccountChanged(Account account) {
-        super.onAccountChanged(account);
+    public void changeAccount(Account account) {
+        super.changeAccount(account);
         mConversationListNeverShown = true;
+        resetAndLoadDrawer();
     }
 
     @Override
     public boolean onCreate(Bundle savedInstanceState) {
         mActivity.setContentView(R.layout.one_pane_activity);
+        mDrawerContainer = (DrawerLayout) mActivity.findViewById(R.id.drawer_container);
+        mDrawerPullout = (ViewGroup) mDrawerContainer.findViewById(R.id.drawer_pullout);
         // The parent class sets the correct viewmode and starts the application off.
         return super.onCreate(savedInstanceState);
     }
@@ -162,10 +167,34 @@
         return mConversationListVisible;
     }
 
+    /**
+     * If drawer is open/visible (even partially), close it and replace the
+     * folder fragment upon closing the drawer. Otherwise, the drawer is closed
+     * and we can replace the folder list fragment without concern.
+     */
+    private void resetAndLoadDrawer() {
+        if(mDrawerContainer.isDrawerVisible(mDrawerPullout)) {
+            mDrawerContainer.setDrawerListener(new DrawerLayout.SimpleDrawerListener() {
+                @Override
+                public void onDrawerClosed(View drawerView) {
+                    loadFolderList();
+                    mDrawerContainer.setDrawerListener(null);
+                }
+            });
+            mDrawerContainer.closeDrawers();
+        } else {
+            loadFolderList();
+        }
+    }
+
     @Override
     public void onViewModeChanged(int newMode) {
         super.onViewModeChanged(newMode);
 
+        // When view mode changes, we should wait for drawer to close and
+        // repopulate folders.
+        resetAndLoadDrawer();
+
         // When entering conversation list mode, hide and clean up any currently visible
         // conversation.
         if (ViewMode.isListMode(newMode)) {
@@ -196,12 +225,12 @@
             // Maintain fragment transaction history so we can get back to the
             // fragment used to launch this list.
             mLastConversationListTransactionId = replaceFragment(conversationListFragment,
-                    transition, TAG_CONVERSATION_LIST);
+                    transition, TAG_CONVERSATION_LIST, R.id.content_pane);
         } else {
             // If going to the inbox, clear the folder list transaction history.
             mInbox = listContext.folder;
             mLastInboxConversationListTransactionId = replaceFragment(conversationListFragment,
-                    transition, TAG_CONVERSATION_LIST);
+                    transition, TAG_CONVERSATION_LIST, R.id.content_pane);
             mLastFolderListTransactionId = INVALID_ID;
 
             // If we ever to to the inbox, we want to unset the transation id for any other
@@ -254,7 +283,8 @@
     @Override
     public void showWaitForInitialization() {
         super.showWaitForInitialization();
-        replaceFragment(getWaitFragment(), FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_WAIT);
+        replaceFragment(getWaitFragment(), FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_WAIT,
+                R.id.content_pane);
     }
 
     @Override
@@ -284,58 +314,105 @@
         }
     }
 
+    /**
+     * Loads the FolderListFragment into the drawer pullout FrameLayout.
+     * TODO(shahrk): Clean up and move out drawer calls if necessary
+     */
     @Override
-    public void showFolderList() {
+    public void loadFolderList() {
         if (mAccount == null) {
             LogUtils.e(LOG_TAG, "Null account in showFolderList");
             return;
         }
+
         // Null out the currently selected folder; we have nothing selected the
         // first time the user enters the folder list
+        // TODO(shahrk): Tweak the function call for nested folders?
         setHierarchyFolder(null);
-        mViewMode.enterFolderListMode();
-        enableCabMode();
-        mLastFolderListTransactionId = replaceFragment(
-                FolderListFragment.newInstance(null, mAccount.folderListUri, false),
-                FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST);
-        mConversationListVisible = false;
-        onConversationVisibilityChanged(false);
-        onConversationListVisibilityChanged(false);
+
+        /*
+         * TODO(shahrk): Drawer addition/support
+         * Take out view mode changes: mViewMode.enterFolderListMode(); enableCabMode();
+         * Adding this will enable back stack to labels: mLastFolderListTransactionId =
+         */
+        replaceFragment(
+                FolderListFragment.newInstance(null, mAccount.folderListUri,
+                        SECTIONS_AND_RECENT_FOLDERS_ENABLED, false),
+                FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST,
+                R.id.drawer_pullout);
+
+        /*
+         * TODO(shahrk): Move or remove this
+         * Don't make the list invisible as of right now:
+         * mConversationListVisible = false;
+         * onConversationVisibilityChanged(false);
+         * onConversationListVisibilityChanged(false);
+         */
+    }
+
+    /**
+     * Toggles the drawer pullout. If it was open (Fully extended), the
+     * drawer will be closed. Otherwise, the drawer will be opened. This should
+     * only be called when used with a toggle item. Other cases should be handled
+     * explicitly with just closeDrawers() or openDrawer(View drawerView);
+     */
+    @Override
+    protected void toggleFolderListState() {
+        if(mDrawerContainer.isDrawerOpen(mDrawerPullout)) {
+            mDrawerContainer.closeDrawers();
+        } else {
+            mDrawerContainer.openDrawer(mDrawerPullout);
+        }
+        return;
+    }
+
+    public void onFolderChanged(Folder folder) {
+        mDrawerContainer.closeDrawers();
+        super.onFolderChanged(folder);
     }
 
     /**
      * Replace the content_pane with the fragment specified here. The tag is specified so that
      * the {@link ActivityController} can look up the fragments through the
      * {@link android.app.FragmentManager}.
-     * @param fragment
-     * @param transition
-     * @param tag
+     * @param fragment the new fragment to put
+     * @param transition the transition to show
+     * @param tag a tag for the fragment manager.
+     * @param anchor ID of view to replace fragment in
      * @return transaction ID returned when the transition is committed.
      */
-    private int replaceFragment(Fragment fragment, int transition, String tag) {
+    private int replaceFragment(Fragment fragment, int transition, String tag, int anchor) {
         FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
         fragmentTransaction.addToBackStack(null);
         fragmentTransaction.setTransition(transition);
-        fragmentTransaction.replace(R.id.content_pane, fragment, tag);
+        fragmentTransaction.replace(anchor, fragment, tag);
         return fragmentTransaction.commitAllowingStateLoss();
     }
 
     /**
      * Back works as follows:
-     * 1) If the user is in the folder list view, go back
+     * 1) If the drawer is pulled out (Or mid-drag), close it - handled.
+     * 2) If the user is in the folder list view, go back
      * to the account default inbox.
-     * 2) If the user is in a conversation list
+     * 3) If the user is in a conversation list
      * that is not the inbox AND:
      *  a) they got there by going through the folder
      *  list view, go back to the folder list view.
      *  b) they got there by using some other means (account dropdown), go back to the inbox.
-     * 3) If the user is in a conversation, go back to the conversation list they were last in.
-     * 4) If the user is in the conversation list for the default account inbox,
+     * 4) If the user is in a conversation, go back to the conversation list they were last in.
+     * 5) If the user is in the conversation list for the default account inbox,
      * back exits the app.
      */
     @Override
     public boolean handleBackPress() {
         final int mode = mViewMode.getMode();
+
+        if (mDrawerContainer.isDrawerVisible(mDrawerPullout)) {
+            mDrawerContainer.closeDrawers();
+            return true;
+        }
+
+        //TODO(shahrk): Remove the folder list standalone view
         if (mode == ViewMode.FOLDER_LIST) {
             final Folder hierarchyFolder = getHierarchyFolder();
             final FolderListFragment folderListFragment = getFolderListFragment();
@@ -378,14 +455,15 @@
             // showing this folder's children if we are not already
             // looking at the child view for this folder.
             mLastFolderListTransactionId = replaceFragment(FolderListFragment.newInstance(
-                    top, top.childFoldersListUri, false),
-                    FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST);
+                    top, top.childFoldersListUri, SECTIONS_AND_RECENT_FOLDERS_ENABLED, false),
+                    FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST,
+                    R.id.content_pane);
             // Show the up affordance when digging into child folders.
             mActionBarView.setBackButton();
         } else {
             // Otherwise, clear the selected folder and go back to whatever the
             // last folder list displayed was.
-            showFolderList();
+            loadFolderList();
         }
     }
 
@@ -418,8 +496,9 @@
             // showing this folder's children if we are not already
             // looking at the child view for this folder.
             mLastFolderListTransactionId = replaceFragment(
-                    FolderListFragment.newInstance(folder, folder.childFoldersListUri, false),
-                    FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST);
+                    FolderListFragment.newInstance(folder, folder.childFoldersListUri,
+                            SECTIONS_AND_RECENT_FOLDERS_ENABLED, false),
+                    FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST, R.id.content_pane);
             // Show the up affordance when digging into child folders.
             mActionBarView.setBackButton();
         } else {
@@ -529,7 +608,7 @@
                                     convList != null ? convList.getAnimatedAdapter() : null),
                             0,
                             Utils.convertHtmlToPlainText
-                                (op.getDescription(mActivity.getActivityContext(), mFolder)),
+                                (op.getDescription(mActivity.getActivityContext())),
                             true, /* showActionIcon */
                             R.string.undo,
                             true,  /* replaceVisibleToast */
@@ -543,7 +622,7 @@
                                 getUndoClickedListener(convList.getAnimatedAdapter()),
                                 0,
                                 Utils.convertHtmlToPlainText
-                                    (op.getDescription(mActivity.getActivityContext(), mFolder)),
+                                    (op.getDescription(mActivity.getActivityContext())),
                                 true, /* showActionIcon */
                                 R.string.undo,
                                 true,  /* replaceVisibleToast */
diff --git a/src/com/android/mail/ui/RecentFolderList.java b/src/com/android/mail/ui/RecentFolderList.java
index 244924f..d05530e 100644
--- a/src/com/android/mail/ui/RecentFolderList.java
+++ b/src/com/android/mail/ui/RecentFolderList.java
@@ -18,10 +18,10 @@
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.AsyncTask;
 
+import com.android.mail.content.ObjectCursor;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.AccountObserver;
 import com.android.mail.providers.Folder;
@@ -96,8 +96,8 @@
         /**
          * Create a new asynchronous task to store the recent folder list. Both the account
          * and the folder should be non-null.
-         * @param account
-         * @param folder
+         * @param account the current account for this folder.
+         * @param folder the folder which is to be stored.
          */
         public StoreRecent(Account account, Folder folder) {
             assert (account != null && folder != null);
@@ -123,7 +123,7 @@
     /**
      * Create a Recent Folder List from the given account. This will query the UIProvider to
      * retrieve the RecentFolderList from persistent storage (if any).
-     * @param context
+     * @param context the context for the activity
      */
     public RecentFolderList(Context context) {
         mFolderCache = new LruCache<String, RecentFolderListEntry>(
@@ -133,7 +133,7 @@
 
     /**
      * Initialize the {@link RecentFolderList} with a controllable activity.
-     * @param activity
+     * @param activity the underlying activity
      */
     public void initialize(ControllableActivity activity){
         setCurrentAccount(mAccountObserver.initialize(activity.getAccountController()));
@@ -141,7 +141,8 @@
 
     /**
      * Change the current account. When a cursor over the recent folders for this account is
-     * available, the client <b>must</b> call {@link #loadFromUiProvider(Cursor)} with the updated
+     * available, the client <b>must</b> call {@link
+     * #loadFromUiProvider(com.android.mail.content.ObjectCursor)} with the updated
      * cursor. Till then, the recent account list will be empty.
      * @param account the new current account
      */
@@ -158,7 +159,7 @@
      * Load the account information from the UI provider given the cursor over the recent folders.
      * @param c a cursor over the recent folders.
      */
-    public void loadFromUiProvider(Cursor c) {
+    public void loadFromUiProvider(ObjectCursor<Folder> c) {
         if (mAccount == null || c == null) {
             LogUtils.e(TAG, "RecentFolderList.loadFromUiProvider: bad input. mAccount=%s,cursor=%s",
                     mAccount, c);
@@ -173,7 +174,7 @@
         // This enables older values to fall off the LRU cache. Also, read all values, just in case
         // there are duplicates in the cursor.
         do {
-            final Folder folder = new Folder(c);
+            final Folder folder = c.getModel();
             final RecentFolderListEntry entry = new RecentFolderListEntry(folder);
             mFolderCache.putElement(folder.uri.toString(), entry);
             LogUtils.v(TAG, "Account %s, Recent: %s", mAccount.name, folder.name);
diff --git a/src/com/android/mail/ui/SecureConversationViewFragment.java b/src/com/android/mail/ui/SecureConversationViewFragment.java
index 61d3579..eebec55 100644
--- a/src/com/android/mail/ui/SecureConversationViewFragment.java
+++ b/src/com/android/mail/ui/SecureConversationViewFragment.java
@@ -21,9 +21,6 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.text.Html;
-import android.text.SpannedString;
-import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,17 +32,16 @@
 
 import com.android.mail.R;
 import com.android.mail.browse.ConversationViewAdapter;
+import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
 import com.android.mail.browse.ConversationViewHeader;
 import com.android.mail.browse.MessageCursor;
+import com.android.mail.browse.MessageCursor.ConversationMessage;
 import com.android.mail.browse.MessageFooterView;
 import com.android.mail.browse.MessageHeaderView;
-import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
-import com.android.mail.browse.MessageCursor.ConversationMessage;
 import com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Message;
-import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 
@@ -61,7 +57,7 @@
     private ConversationMessage mMessage;
     private ScrollView mScrollView;
 
-    private WebViewClient mWebViewClient = new AbstractConversationWebViewClient() {
+    private final WebViewClient mWebViewClient = new AbstractConversationWebViewClient() {
         @Override
         public void onPageFinished(WebView view, String url) {
             if (isUserVisible()) {
@@ -99,17 +95,16 @@
         super.onActivityCreated(savedInstanceState);
         mConversationHeaderView.setCallbacks(this, this);
         mConversationHeaderView.setFoldersVisible(false);
-        final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
-        if (sdc != null) {
-            sdc.setSubject(mConversation.subject);
-        }
         mConversationHeaderView.setSubject(mConversation.subject);
+        mMessageHeaderView.initialize(mDateBuilder, this, mAddressCache);
+        mMessageHeaderView.setExpandMode(MessageHeaderView.POPUP_MODE);
         mMessageHeaderView.setContactInfoSource(getContactInfoSource());
         mMessageHeaderView.setCallbacks(this);
         mMessageHeaderView.setExpandable(false);
         mMessageHeaderView.setVeiledMatcher(
                 ((ControllableActivity) getActivity()).getAccountController()
                         .getVeiledAddressMatcher());
+        mMessageFooterView.initialize(getLoaderManager(), getFragmentManager());
         getLoaderManager().initLoader(MESSAGE_LOADER, null, getMessageLoaderCallbacks());
         showLoadingStatus();
     }
@@ -165,16 +160,6 @@
     }
 
     @Override
-    public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight) {
-        // Do nothing.
-    }
-
-    @Override
-    public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeight) {
-        // Do nothing.
-    }
-
-    @Override
     public void onConversationViewHeaderHeightChange(int newHeight) {
         // Do nothing.
     }
@@ -185,12 +170,26 @@
             return;
         }
         if (isUserVisible()) {
-            mScrollView.scrollTo(0, 0);
             onConversationSeen();
         }
     }
 
     @Override
+    public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight) {
+        // Do nothing.
+    }
+
+    @Override
+    public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeight) {
+        // Do nothing.
+    }
+
+    @Override
+    public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightbefore) {
+        // Do nothing.
+    }
+
+    @Override
     public void showExternalResources(final Message msg) {
         mWebView.getSettings().setBlockNetworkImage(false);
     }
@@ -201,6 +200,16 @@
     }
 
     @Override
+    public boolean supportsMessageTransforms() {
+        return false;
+    }
+
+    @Override
+    public String getMessageTransforms(final Message msg) {
+        return null;
+    }
+
+    @Override
     protected void onMessageCursorLoadFinished(Loader<Cursor> loader, MessageCursor newCursor,
             MessageCursor oldCursor) {
         // ignore cursors that are still loading results
@@ -212,7 +221,7 @@
             // Activity is finishing, just bail.
             return;
         }
-        renderMessageBodies(newCursor, mEnableContentReadySignal);
+        renderMessageBodies(newCursor);
     }
 
     /**
@@ -220,35 +229,23 @@
      * blocks, a conversation header), and return an HTML document with spacer
      * divs inserted for all overlays.
      */
-    private void renderMessageBodies(MessageCursor messageCursor,
-            boolean enableContentReadySignal) {
-        final StringBuilder convHtml = new StringBuilder();
-        String content;
-        if (messageCursor.moveToFirst()) {
-            content = messageCursor.getString(UIProvider.MESSAGE_BODY_HTML_COLUMN);
-            if (TextUtils.isEmpty(content)) {
-                content = messageCursor.getString(UIProvider.MESSAGE_BODY_TEXT_COLUMN);
-                if (content != null) {
-                    content = Html.toHtml(new SpannedString(content));
-                }
-            }
-            convHtml.append(content);
-            mMessage = messageCursor.getMessage();
-            mWebView.getSettings().setBlockNetworkImage(!mMessage.alwaysShowImages);
-            mWebView.loadDataWithBaseURL(mBaseUri, convHtml.toString(), "text/html", "utf-8", null);
-            ConversationViewAdapter mAdapter = new ConversationViewAdapter(mActivity, null, null,
-                    null, null, null, null, null, null);
-            MessageHeaderItem item = mAdapter.newMessageHeaderItem(mMessage, true,
-                    mMessage.alwaysShowImages);
-            mMessageHeaderView.initialize(mDateBuilder, this, mAddressCache);
-            mMessageHeaderView.setExpandMode(MessageHeaderView.POPUP_MODE);
-            mMessageHeaderView.bind(item, false);
-            mMessageHeaderView.setMessageDetailsVisibility(View.VISIBLE);
-            if (mMessage.hasAttachments) {
-                mMessageFooterView.setVisibility(View.VISIBLE);
-                mMessageFooterView.initialize(getLoaderManager(), getFragmentManager());
-                mMessageFooterView.bind(item, false);
-            }
+    private void renderMessageBodies(MessageCursor messageCursor) {
+        if (!messageCursor.moveToFirst()) {
+            LogUtils.e(LOG_TAG, "unable to open message cursor");
+            return;
+        }
+        final ConversationMessage m = messageCursor.getMessage();
+        mMessage = messageCursor.getMessage();
+        mWebView.getSettings().setBlockNetworkImage(!mMessage.alwaysShowImages);
+        mWebView.loadDataWithBaseURL(mBaseUri, m.getBodyAsHtml(), "text/html", "utf-8", null);
+        final ConversationViewAdapter adapter = new ConversationViewAdapter(mActivity, null, null,
+                null, null, null, null, null, null);
+        final MessageHeaderItem item = adapter.newMessageHeaderItem(mMessage, true,
+                mMessage.alwaysShowImages);
+        mMessageHeaderView.bind(item, false);
+        if (mMessage.hasAttachments) {
+            mMessageFooterView.setVisibility(View.VISIBLE);
+            mMessageFooterView.bind(item, false);
         }
     }
 
@@ -261,8 +258,4 @@
         }
     }
 
-    @Override
-    public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightbefore) {
-        // Do nothing.
-    }
 }
diff --git a/src/com/android/mail/ui/SingleFolderSelectionDialog.java b/src/com/android/mail/ui/SingleFolderSelectionDialog.java
index 7676732..f7c5771 100644
--- a/src/com/android/mail/ui/SingleFolderSelectionDialog.java
+++ b/src/com/android/mail/ui/SingleFolderSelectionDialog.java
@@ -71,11 +71,12 @@
             // Currently, the number of adapters are assumed to match the
             // number of headers in the string array.
             mAdapter.addSection(new SystemFolderSelectorAdapter(context, foldersCursor,
-                    R.layout.single_folders_view, null, mCurrentFolder));
+                    R.layout.single_folders_view, headers[0], mCurrentFolder));
 
             // TODO(mindyp): we currently do not support frequently moved to
             // folders, at headers[1]; need to define what that means.*/
-            mAdapter.addSection(new HierarchicalFolderSelectorAdapter(context,
+            // TODO(pwestbro): determine if we need to call filterFolders
+            mAdapter.addSection(new UserFolderHierarchicalFolderSelectorAdapter(context,
                     AddableFolderSelectorAdapter.filterFolders(foldersCursor),
                     R.layout.single_folders_view, headers[2], mCurrentFolder));
             mBuilder.setAdapter(mAdapter, SingleFolderSelectionDialog.this);
@@ -95,7 +96,7 @@
             // Remove the current folder and add the new folder.
             ops.add(new FolderOperation(mCurrentFolder, false));
             ops.add(new FolderOperation(folder, true));
-            mUpdater.assignFolder(ops, mTarget, mBatch, true);
+            mUpdater.assignFolder(ops, mTarget, mBatch, true /* showUndo */, true /* isMoveTo */);
             mDialog.dismiss();
         }
     }
diff --git a/src/com/android/mail/ui/SuppressNotificationReceiver.java b/src/com/android/mail/ui/SuppressNotificationReceiver.java
index 7dba99b..a2d94e7 100644
--- a/src/com/android/mail/ui/SuppressNotificationReceiver.java
+++ b/src/com/android/mail/ui/SuppressNotificationReceiver.java
@@ -35,7 +35,7 @@
 
 
 /**
- * A simple {@code BroadcastReceiver} which supresses new e-mail notifications for a given folder.
+ * A simple {@code BroadcastReceiver} which suppresses new e-mail notifications for a given folder.
  */
 public class SuppressNotificationReceiver extends BroadcastReceiver {
     private static final String LOG_TAG = LogTag.getLogTag();
@@ -45,7 +45,7 @@
     private String mMimeType;
 
     /**
-     * Registers this receiver to supress the new mail notifications for a given folder so
+     * Registers this receiver to suppress the new mail notifications for a given folder so
      * that other {@code BroadcastReceiver}s don't receive them.
      */
     public boolean activate(Context context, AbstractActivityController controller) {
diff --git a/src/com/android/mail/ui/SwipeableListView.java b/src/com/android/mail/ui/SwipeableListView.java
index 4048119..82d4d33 100644
--- a/src/com/android/mail/ui/SwipeableListView.java
+++ b/src/com/android/mail/ui/SwipeableListView.java
@@ -196,7 +196,8 @@
         final Context context = getContext();
         final ToastBarOperation undoOp;
 
-        undoOp = new ToastBarOperation(1, mSwipeAction, ToastBarOperation.UNDO, false);
+        undoOp = new ToastBarOperation(1, mSwipeAction, ToastBarOperation.UNDO, false /* batch */,
+                mFolder);
         Conversation conv = target.getConversation();
         target.getConversation().position = findConversation(target, conv);
         final AnimatedAdapter adapter = getAnimatedAdapter();
diff --git a/src/com/android/mail/ui/ToastBarOperation.java b/src/com/android/mail/ui/ToastBarOperation.java
index b56a94d..b47879f 100644
--- a/src/com/android/mail/ui/ToastBarOperation.java
+++ b/src/com/android/mail/ui/ToastBarOperation.java
@@ -33,19 +33,25 @@
     private final int mCount;
     private final boolean mBatch;
     private final int mType;
+    private final Folder mFolder;
 
     /**
      * Create a ToastBarOperation
      *
      * @param count Number of conversations this action would be applied to.
-     * @param menuId res id identifying the menu item tapped; used to determine
-     *            what action was performed
+     * @param menuId res id identifying the menu item tapped; used to determine what action was
+     *        performed
+     * @param operationFolder The {@link Folder} upon which the operation was run. This may be
+     *        <code>null</code>, but is required in {@link #getDescription(Context)} for certain
+     *        actions.
      */
-    public ToastBarOperation(int count, int menuId, int type, boolean batch) {
+    public ToastBarOperation(int count, int menuId, int type, boolean batch,
+            final Folder operationFolder) {
         mCount = count;
         mAction = menuId;
         mBatch = batch;
         mType = type;
+        mFolder = operationFolder;
     }
 
     public int getType() {
@@ -56,11 +62,12 @@
         return mBatch;
     }
 
-    public ToastBarOperation(Parcel in) {
+    public ToastBarOperation(final Parcel in, final ClassLoader loader) {
         mCount = in.readInt();
         mAction = in.readInt();
         mBatch = in.readInt() != 0;
         mType = in.readInt();
+        mFolder = in.readParcelable(loader);
     }
 
     @Override
@@ -69,35 +76,44 @@
         dest.writeInt(mAction);
         dest.writeInt(mBatch ? 1 : 0);
         dest.writeInt(mType);
+        dest.writeParcelable(mFolder, 0);
     }
 
-    public static final Creator<ToastBarOperation> CREATOR = new Creator<ToastBarOperation>() {
+    public static final ClassLoaderCreator<ToastBarOperation> CREATOR =
+            new ClassLoaderCreator<ToastBarOperation>() {
         @Override
-        public ToastBarOperation createFromParcel(Parcel source) {
-            return new ToastBarOperation(source);
+        public ToastBarOperation createFromParcel(final Parcel source) {
+            return createFromParcel(source, null);
         }
 
         @Override
-        public ToastBarOperation[] newArray(int size) {
+        public ToastBarOperation[] newArray(final int size) {
             return new ToastBarOperation[size];
         }
+
+        @Override
+        public ToastBarOperation createFromParcel(final Parcel source, final ClassLoader loader) {
+            return new ToastBarOperation(source, loader);
+        }
     };
 
     /**
      * Get a string description of the operation that will be performed
      * when the user taps the undo bar.
      */
-    public String getDescription(Context context, Folder folder) {
+    public String getDescription(Context context) {
         int resId = -1;
         switch (mAction) {
             case R.id.delete:
                 resId = R.plurals.conversation_deleted;
                 break;
             case R.id.remove_folder:
-                return context.getString(R.string.folder_removed, folder.name);
+                return context.getString(R.string.folder_removed, mFolder.name);
             case R.id.change_folder:
                 resId = R.plurals.conversation_folder_changed;
                 break;
+            case R.id.move_folder:
+                return context.getString(R.string.conversation_folder_moved, mFolder.name);
             case R.id.archive:
                 resId = R.plurals.conversation_archived;
                 break;
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 8e8be99..d022352 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -41,29 +41,22 @@
     private TwoPaneLayout mLayout;
     private Conversation mConversationToShow;
 
-    /**
-     * @param activity
-     * @param viewMode
-     */
     public TwoPaneController(MailActivity activity, ViewMode viewMode) {
         super(activity, viewMode);
     }
 
     /**
      * Display the conversation list fragment.
-     * @param show
      */
-    private void initializeConversationListFragment(boolean show) {
-        if (show) {
-            if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) {
-                if (shouldEnterSearchConvMode()) {
-                    mViewMode.enterSearchResultsConversationMode();
-                } else {
-                    mViewMode.enterSearchResultsListMode();
-                }
+    private void initializeConversationListFragment() {
+        if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) {
+            if (shouldEnterSearchConvMode()) {
+                mViewMode.enterSearchResultsConversationMode();
             } else {
-                mViewMode.enterConversationListMode();
+                mViewMode.enterSearchResultsListMode();
             }
+        } else {
+            mViewMode.enterConversationListMode();
         }
         renderConversationList();
     }
@@ -112,7 +105,8 @@
     private void createFolderListFragment(Folder parent, Uri uri) {
         setHierarchyFolder(parent);
         // Create a sectioned FolderListFragment.
-        FolderListFragment folderListFragment = FolderListFragment.newInstance(parent, uri, true);
+        FolderListFragment folderListFragment = FolderListFragment.newInstance(parent, uri, true,
+                true);
         FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
         if (Utils.useFolderListFragmentTransition(mActivity.getActivityContext())) {
             fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
@@ -134,11 +128,11 @@
     @Override
     public void showConversationList(ConversationListContext listContext) {
         super.showConversationList(listContext);
-        initializeConversationListFragment(true);
+        initializeConversationListFragment();
     }
 
     @Override
-    public void showFolderList() {
+    public void loadFolderList() {
         // On two-pane layouts, showing the folder list takes you to the top level of the
         // application, which is the same as pressing the Up button
         handleUpPress();
@@ -158,8 +152,7 @@
         // notifications upon animation completion:
         // (onConversationVisibilityChanged, onConversationListVisibilityChanged)
         mViewMode.addListener(mLayout);
-        final boolean isParentInitialized = super.onCreate(savedState);
-        return isParentInitialized;
+        return super.onCreate(savedState);
     }
 
     @Override
@@ -171,8 +164,8 @@
     }
 
     @Override
-    public void onAccountChanged(Account account) {
-        super.onAccountChanged(account);
+    public void changeAccount(Account account) {
+        super.changeAccount(account);
         renderFolderList();
     }
 
@@ -196,11 +189,10 @@
             createFolderListFragment(folder, folder.childFoldersListUri);
             // Show the up affordance when digging into child folders.
             mActionBarView.setBackButton();
-            super.onFolderSelected(folder);
         } else {
             setHierarchyFolder(folder);
-            super.onFolderSelected(folder);
         }
+        super.onFolderSelected(folder);
     }
 
     private void goUpFolderHierarchy(Folder current) {
@@ -253,7 +245,7 @@
     public void resetActionBarIcon() {
         // On two-pane, the back button is only removed in the conversation list mode, and shown
         // for every other condition.
-        if (mViewMode.isListMode()) {
+        if (mViewMode.isListMode() || mViewMode.isWaitingForSync()) {
             mActionBarView.removeBackButton();
         } else {
             mActionBarView.setBackButton();
@@ -263,7 +255,7 @@
     /**
      * Enable or disable the CAB mode based on the visibility of the conversation list fragment.
      */
-    private final void enableOrDisableCab() {
+    private void enableOrDisableCab() {
         if (mLayout.isConversationListCollapsed()) {
             disableCabMode();
         } else {
@@ -296,7 +288,7 @@
         mConversationToShow = conversation;
 
         final int mode = mViewMode.getMode();
-        boolean changedMode = false;
+        final boolean changedMode;
         if (mode == ViewMode.SEARCH_RESULTS_LIST || mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
             changedMode = mViewMode.enterSearchResultsConversationMode();
         } else {
@@ -482,7 +474,7 @@
                             getUndoClickedListener(convList.getAnimatedAdapter()),
                             0,
                             Utils.convertHtmlToPlainText
-                                (op.getDescription(mActivity.getActivityContext(), mFolder)),
+                                (op.getDescription(mActivity.getActivityContext())),
                             true, /* showActionIcon */
                             R.string.undo,
                             true,  /* replaceVisibleToast */
@@ -494,7 +486,7 @@
                 if (convList != null) {
                     mToastBar.show(getUndoClickedListener(convList.getAnimatedAdapter()), 0,
                             Utils.convertHtmlToPlainText
-                                (op.getDescription(mActivity.getActivityContext(), mFolder)),
+                                (op.getDescription(mActivity.getActivityContext())),
                             true, /* showActionIcon */
                             R.string.undo, true, /* replaceVisibleToast */
                             op);
diff --git a/src/com/android/mail/ui/UserFolderHierarchicalFolderSelectorAdapter.java b/src/com/android/mail/ui/UserFolderHierarchicalFolderSelectorAdapter.java
new file mode 100644
index 0000000..9a601cf
--- /dev/null
+++ b/src/com/android/mail/ui/UserFolderHierarchicalFolderSelectorAdapter.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ *      Copyright (C) 2013 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.
+ *******************************************************************************/
+package com.android.mail.ui;
+
+import android.content.Context;
+import android.database.Cursor;
+import com.android.mail.providers.Folder;
+
+import java.util.Set;
+
+public class UserFolderHierarchicalFolderSelectorAdapter extends HierarchicalFolderSelectorAdapter {
+    public UserFolderHierarchicalFolderSelectorAdapter(Context context, Cursor folders, int layout,
+                                                       String header, Folder excludedFolder) {
+        super(context, folders, layout, header, excludedFolder);
+    }
+
+    /**
+     * Return whether the supplied folder meets the requirements to be displayed
+     * in the folder list.
+     */
+    @Override
+    protected boolean meetsRequirements(Folder folder) {
+        if (folder.isProviderFolder()) {
+            return false;
+        }
+        return super.meetsRequirements(folder);
+    }
+}
diff --git a/src/com/android/mail/ui/ViewMode.java b/src/com/android/mail/ui/ViewMode.java
index 220a35b..9644fdc 100644
--- a/src/com/android/mail/ui/ViewMode.java
+++ b/src/com/android/mail/ui/ViewMode.java
@@ -182,6 +182,10 @@
         return mode == CONVERSATION || mode == SEARCH_RESULTS_CONVERSATION;
     }
 
+    public boolean isWaitingForSync() {
+        return mMode == WAITING_FOR_ACCOUNT_INITIALIZATION;
+    }
+
     /**
      * Restoring from a saved state restores only the mode. It does not restore the listeners of
      * this object.
diff --git a/src/com/android/mail/utils/AttachmentUtils.java b/src/com/android/mail/utils/AttachmentUtils.java
index 1f776df..3735d63 100644
--- a/src/com/android/mail/utils/AttachmentUtils.java
+++ b/src/com/android/mail/utils/AttachmentUtils.java
@@ -22,18 +22,38 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
 import android.text.TextUtils;
 
 import com.android.mail.R;
 import com.android.mail.providers.Attachment;
 
+import com.google.common.collect.ImmutableMap;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.File;
 import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.Map;
 
 public class AttachmentUtils {
+    private static final String LOG_TAG = LogTag.getLogTag();
+
     private static final int KILO = 1024;
     private static final int MEGA = KILO * KILO;
 
+    /** Any IO reads should be limited to this timeout */
+    private static final long READ_TIMEOUT = 3600 * 1000;
+
+    private static final float MIN_CACHE_THRESHOLD = 0.25f;
+    private static final int MIN_CACHE_AVAILABLE_SPACE_BYTES = 100 * 1024 * 1024;
+
     /**
      * Singleton map of MIME->friendly description
      * @see #getMimeTypeDisplayName(Context, String)
@@ -135,6 +155,105 @@
     }
 
     /**
+     * Cache the file specified by the given attachment.  This will attempt to use any
+     * {@link ParcelFileDescriptor} in the Bundle parameter
+     * @param context
+     * @param attachment  Attachment to be cached
+     * @param attachmentFds optional {@link Bundle} containing {@link ParcelFileDescriptor} if the
+     *        caller has opened the files
+     * @return String file path for the cached attachment
+     */
+    // TODO(pwestbro): Once the attachment has a field for the cached path, this method should be
+    // changed to update the attachment, and return a boolean indicating that the attachment has
+    // been cached.
+    public static String cacheAttachmentUri(Context context, Attachment attachment,
+            Bundle attachmentFds) {
+        final File cacheDir = context.getCacheDir();
+
+        final long totalSpace = cacheDir.getTotalSpace();
+        if (attachment.size > 0) {
+            final long usableSpace = cacheDir.getUsableSpace() - attachment.size;
+            if (isLowSpace(totalSpace, usableSpace)) {
+                LogUtils.w(LOG_TAG, "Low memory (%d/%d). Can't cache attachment %s",
+                        usableSpace, totalSpace, attachment);
+                return null;
+            }
+        }
+        InputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        File file = null;
+        try {
+            final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-kk:mm:ss");
+            file = File.createTempFile(dateFormat.format(new Date()), ".attachment", cacheDir);
+            final ParcelFileDescriptor fileDescriptor = attachmentFds != null
+                    && attachment.contentUri != null ? (ParcelFileDescriptor) attachmentFds
+                    .getParcelable(attachment.contentUri.toString())
+                    : null;
+            if (fileDescriptor != null) {
+                // Get the input stream from the file descriptor
+                inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
+            } else {
+                // Attempt to open the file
+                inputStream = context.getContentResolver().openInputStream(attachment.contentUri);
+            }
+            outputStream = new FileOutputStream(file);
+            final long now = SystemClock.elapsedRealtime();
+            final byte[] bytes = new byte[1024];
+            while (true) {
+                int len = inputStream.read(bytes);
+                if (len <= 0) {
+                    break;
+                }
+                outputStream.write(bytes, 0, len);
+                if (SystemClock.elapsedRealtime() - now > READ_TIMEOUT) {
+                    throw new IOException("Timed out reading attachment data");
+                }
+            }
+            outputStream.flush();
+            String cachedFileUri = file.getAbsolutePath();
+            LogUtils.d(LOG_TAG, "Cached %s to %s", attachment.contentUri, cachedFileUri);
+
+            final long usableSpace = cacheDir.getUsableSpace();
+            if (isLowSpace(totalSpace, usableSpace)) {
+                file.delete();
+                LogUtils.w(LOG_TAG, "Low memory (%d/%d). Can't cache attachment %s",
+                        usableSpace, totalSpace, attachment);
+                cachedFileUri = null;
+            }
+
+            return cachedFileUri;
+        } catch (IOException e) {
+            // Catch any exception here to allow for unexpected failures during caching se we don't
+            // leave app in inconsistent state as we call this method outside of a transaction for
+            // performance reasons.
+            LogUtils.e(LOG_TAG, e, "Failed to cache attachment %s", attachment);
+            if (file != null) {
+                file.delete();
+            }
+            return null;
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (IOException e) {
+                LogUtils.w(LOG_TAG, e, "Failed to close stream");
+            }
+        }
+    }
+
+    private static boolean isLowSpace(long totalSpace, long usableSpace) {
+        // For caching attachments we want to enable caching if there is
+        // more than 100MB available, or if 25% of total space is free on devices
+        // where the cache partition is < 400MB.
+        return usableSpace <
+                Math.min(totalSpace * MIN_CACHE_THRESHOLD, MIN_CACHE_AVAILABLE_SPACE_BYTES);
+    }
+
+    /**
      * Checks if the attachment can be downloaded with the current network
      * connection.
      *
diff --git a/src/com/android/mail/utils/LogUtils.java b/src/com/android/mail/utils/LogUtils.java
index 6176ec8..9ec466b 100644
--- a/src/com/android/mail/utils/LogUtils.java
+++ b/src/com/android/mail/utils/LogUtils.java
@@ -60,7 +60,7 @@
      * Used to enable/disable logging that we don't want included in
      * production releases.
      */
-    private static final int MAX_ENABLED_LOG_LEVEL = DEBUG;
+    private static final int MAX_ENABLED_LOG_LEVEL = VERBOSE;
 
     private static Boolean sDebugLoggingEnabledForTests = null;
 
diff --git a/src/com/android/mail/utils/NotificationActionUtils.java b/src/com/android/mail/utils/NotificationActionUtils.java
index 617959b..2136620 100644
--- a/src/com/android/mail/utils/NotificationActionUtils.java
+++ b/src/com/android/mail/utils/NotificationActionUtils.java
@@ -30,7 +30,6 @@
 import android.os.SystemClock;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.TaskStackBuilder;
-import android.text.TextUtils;
 import android.widget.RemoteViews;
 
 import com.android.mail.MailIntentService;
@@ -47,7 +46,7 @@
 import com.google.common.collect.ImmutableMap;
 
 import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -76,7 +75,8 @@
             @Override
             public boolean shouldDisplayPrimary(final Folder folder,
                     final Conversation conversation, final Message message) {
-                return folder == null || folder.type == FolderType.INBOX;
+                return folder == null || folder.type == FolderType.INBOX
+                        || folder.type == FolderType.INBOX_SECTION;
             }
         }),
         DELETE("delete", true, R.drawable.ic_menu_delete_holo_dark,
@@ -181,31 +181,22 @@
 
     /**
      * Adds the appropriate notification actions to the specified
-     * {@link android.app.Notification.Builder}
+     * {@link android.support.v4.app.NotificationCompat.Builder}
      *
      * @param notificationIntent The {@link Intent} used when the notification is clicked
      * @param when The value passed into {@link android.app.Notification.Builder#setWhen(long)}.
      *        This is used for maintaining notification ordering with the undo bar
-     * @param notificationActionsString A comma-delimited {@link String} of the actions to display
+     * @param notificationActions A {@link Set} set of the actions to display
      */
     public static void addNotificationActions(final Context context,
-            final Intent notificationIntent, final Notification.Builder notification,
+            final Intent notificationIntent, final NotificationCompat.Builder notification,
             final Account account, final Conversation conversation, final Message message,
             final Folder folder, final int notificationId, final long when,
-            final String notificationActionsString) {
-        final String[] notificationActionsValueArray =
-                TextUtils.isEmpty(notificationActionsString) ? new String[0]
-                        : notificationActionsString.split(",");
-        final List<NotificationActionType> notificationActions =
-                new ArrayList<NotificationActionType>(notificationActionsValueArray.length);
-        for (int i = 0; i < notificationActionsValueArray.length; i++) {
-            notificationActions.add(
-                    NotificationActionType.getActionType(notificationActionsValueArray[i]));
-        }
+            final Set<String> notificationActions) {
+        final List<NotificationActionType> sortedActions =
+                getSortedNotificationActions(folder, notificationActions);
 
-        sortNotificationActions(folder, notificationActions);
-
-        for (final NotificationActionType notificationAction : notificationActions) {
+        for (final NotificationActionType notificationAction : sortedActions) {
             notification.addAction(notificationAction.getActionIconResId(
                     folder, conversation, message), context.getString(notificationAction
                     .getDisplayStringResId(folder, conversation, message)),
@@ -218,15 +209,20 @@
      * Sorts the notification actions into the appropriate order, based on current label
      *
      * @param folder The {@link Folder} being notified
-     * @param notificationActions The actions to sort
+     * @param notificationActionStrings The action strings to sort
      */
-    private static void sortNotificationActions(
-            final Folder folder, final List<NotificationActionType> notificationActions) {
-        final Set<NotificationActionType> tempActions =
-                new HashSet<NotificationActionType>(notificationActions);
-        notificationActions.clear();
+    private static List<NotificationActionType> getSortedNotificationActions(
+            final Folder folder, final Collection<String> notificationActionStrings) {
+        final List<NotificationActionType> unsortedActions =
+                new ArrayList<NotificationActionType>(notificationActionStrings.size());
+        for (final String action : notificationActionStrings) {
+            unsortedActions.add(NotificationActionType.getActionType(action));
+        }
 
-        if (folder.type == FolderType.INBOX) {
+        final List<NotificationActionType> sortedActions =
+                new ArrayList<NotificationActionType>(unsortedActions.size());
+
+        if (folder.type == FolderType.INBOX || folder.type == FolderType.INBOX_SECTION) {
             // Inbox
             /*
              * Action 1: Archive, Delete, Mute, Mark read, Add star, Mark important, Reply, Reply
@@ -236,23 +232,23 @@
              * Action 2: Reply, Reply all, Forward, Mark important, Add star, Mark read, Mute,
              * Delete, Archive
              */
-            if (tempActions.contains(NotificationActionType.ARCHIVE_REMOVE_LABEL)) {
-                notificationActions.add(NotificationActionType.ARCHIVE_REMOVE_LABEL);
+            if (unsortedActions.contains(NotificationActionType.ARCHIVE_REMOVE_LABEL)) {
+                sortedActions.add(NotificationActionType.ARCHIVE_REMOVE_LABEL);
             }
-            if (tempActions.contains(NotificationActionType.DELETE)) {
-                notificationActions.add(NotificationActionType.DELETE);
+            if (unsortedActions.contains(NotificationActionType.DELETE)) {
+                sortedActions.add(NotificationActionType.DELETE);
             }
-            if (tempActions.contains(NotificationActionType.MARK_READ)) {
-                notificationActions.add(NotificationActionType.MARK_READ);
+            if (unsortedActions.contains(NotificationActionType.MARK_READ)) {
+                sortedActions.add(NotificationActionType.MARK_READ);
             }
-            if (tempActions.contains(NotificationActionType.REPLY)) {
-                notificationActions.add(NotificationActionType.REPLY);
+            if (unsortedActions.contains(NotificationActionType.REPLY)) {
+                sortedActions.add(NotificationActionType.REPLY);
             }
-            if (tempActions.contains(NotificationActionType.REPLY_ALL)) {
-                notificationActions.add(NotificationActionType.REPLY_ALL);
+            if (unsortedActions.contains(NotificationActionType.REPLY_ALL)) {
+                sortedActions.add(NotificationActionType.REPLY_ALL);
             }
-            if (tempActions.contains(NotificationActionType.FORWARD)) {
-                notificationActions.add(NotificationActionType.FORWARD);
+            if (unsortedActions.contains(NotificationActionType.FORWARD)) {
+                sortedActions.add(NotificationActionType.FORWARD);
             }
         } else if (folder.isProviderFolder()) {
             // Gmail system labels
@@ -264,20 +260,20 @@
              * Action 2: Reply, Reply all, Forward, Mark important, Add star, Mark read, Mute,
              * Delete
              */
-            if (tempActions.contains(NotificationActionType.DELETE)) {
-                notificationActions.add(NotificationActionType.DELETE);
+            if (unsortedActions.contains(NotificationActionType.DELETE)) {
+                sortedActions.add(NotificationActionType.DELETE);
             }
-            if (tempActions.contains(NotificationActionType.MARK_READ)) {
-                notificationActions.add(NotificationActionType.MARK_READ);
+            if (unsortedActions.contains(NotificationActionType.MARK_READ)) {
+                sortedActions.add(NotificationActionType.MARK_READ);
             }
-            if (tempActions.contains(NotificationActionType.REPLY)) {
-                notificationActions.add(NotificationActionType.REPLY);
+            if (unsortedActions.contains(NotificationActionType.REPLY)) {
+                sortedActions.add(NotificationActionType.REPLY);
             }
-            if (tempActions.contains(NotificationActionType.REPLY_ALL)) {
-                notificationActions.add(NotificationActionType.REPLY_ALL);
+            if (unsortedActions.contains(NotificationActionType.REPLY_ALL)) {
+                sortedActions.add(NotificationActionType.REPLY_ALL);
             }
-            if (tempActions.contains(NotificationActionType.FORWARD)) {
-                notificationActions.add(NotificationActionType.FORWARD);
+            if (unsortedActions.contains(NotificationActionType.FORWARD)) {
+                sortedActions.add(NotificationActionType.FORWARD);
             }
         } else {
             // Gmail user created labels
@@ -288,25 +284,27 @@
             /*
              * Action 2: Reply, Reply all, Forward, Mark important, Add star, Mark read, Delete
              */
-            if (tempActions.contains(NotificationActionType.ARCHIVE_REMOVE_LABEL)) {
-                notificationActions.add(NotificationActionType.ARCHIVE_REMOVE_LABEL);
+            if (unsortedActions.contains(NotificationActionType.ARCHIVE_REMOVE_LABEL)) {
+                sortedActions.add(NotificationActionType.ARCHIVE_REMOVE_LABEL);
             }
-            if (tempActions.contains(NotificationActionType.DELETE)) {
-                notificationActions.add(NotificationActionType.DELETE);
+            if (unsortedActions.contains(NotificationActionType.DELETE)) {
+                sortedActions.add(NotificationActionType.DELETE);
             }
-            if (tempActions.contains(NotificationActionType.MARK_READ)) {
-                notificationActions.add(NotificationActionType.MARK_READ);
+            if (unsortedActions.contains(NotificationActionType.MARK_READ)) {
+                sortedActions.add(NotificationActionType.MARK_READ);
             }
-            if (tempActions.contains(NotificationActionType.REPLY)) {
-                notificationActions.add(NotificationActionType.REPLY);
+            if (unsortedActions.contains(NotificationActionType.REPLY)) {
+                sortedActions.add(NotificationActionType.REPLY);
             }
-            if (tempActions.contains(NotificationActionType.REPLY_ALL)) {
-                notificationActions.add(NotificationActionType.REPLY_ALL);
+            if (unsortedActions.contains(NotificationActionType.REPLY_ALL)) {
+                sortedActions.add(NotificationActionType.REPLY_ALL);
             }
-            if (tempActions.contains(NotificationActionType.FORWARD)) {
-                notificationActions.add(NotificationActionType.FORWARD);
+            if (unsortedActions.contains(NotificationActionType.FORWARD)) {
+                sortedActions.add(NotificationActionType.FORWARD);
             }
         }
+
+        return sortedActions;
     }
 
     /**
@@ -327,15 +325,16 @@
                 // reply activity.
                 final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
 
-                final Intent replyIntent = createReplyIntent(context, account, messageUri, false);
-                replyIntent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder);
+                final Intent intent = createReplyIntent(context, account, messageUri, false);
+                intent.setPackage(context.getPackageName());
+                intent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder);
                 // To make sure that the reply intents one notification don't clobber over
                 // intents for other notification, force a data uri on the intent
                 final Uri notificationUri =
-                        Uri.parse("gmailfrom://gmail-ls/account/" + "reply/" + notificationId);
-                replyIntent.setData(notificationUri);
+                        Uri.parse("mailfrom://mail/account/" + "reply/" + notificationId);
+                intent.setData(notificationUri);
 
-                taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(replyIntent);
+                taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(intent);
 
                 return taskStackBuilder.getPendingIntent(
                         notificationId, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -344,15 +343,16 @@
                 // reply activity.
                 final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
 
-                final Intent replyIntent = createReplyIntent(context, account, messageUri, true);
-                replyIntent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder);
+                final Intent intent = createReplyIntent(context, account, messageUri, true);
+                intent.setPackage(context.getPackageName());
+                intent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder);
                 // To make sure that the reply intents one notification don't clobber over
                 // intents for other notification, force a data uri on the intent
                 final Uri notificationUri =
-                        Uri.parse("gmailfrom://gmail-ls/account/" + "replyall/" + notificationId);
-                replyIntent.setData(notificationUri);
+                        Uri.parse("mailfrom://mail/account/" + "replyall/" + notificationId);
+                intent.setData(notificationUri);
 
-                taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(replyIntent);
+                taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(intent);
 
                 return taskStackBuilder.getPendingIntent(
                         notificationId, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -361,15 +361,16 @@
                 // reply activity.
                 final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
 
-                final Intent replyIntent = createForwardIntent(context, account, messageUri);
-                replyIntent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder);
+                final Intent intent = createForwardIntent(context, account, messageUri);
+                intent.setPackage(context.getPackageName());
+                intent.putExtra(ComposeActivity.EXTRA_NOTIFICATION_FOLDER, folder);
                 // To make sure that the reply intents one notification don't clobber over
                 // intents for other notification, force a data uri on the intent
                 final Uri notificationUri =
-                        Uri.parse("gmailfrom://gmail-ls/account/" + "forward/" + notificationId);
-                replyIntent.setData(notificationUri);
+                        Uri.parse("mailfrom://mail/account/" + "forward/" + notificationId);
+                intent.setData(notificationUri);
 
-                taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(replyIntent);
+                taskStackBuilder.addNextIntent(notificationIntent).addNextIntent(intent);
 
                 return taskStackBuilder.getPendingIntent(
                         notificationId, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -378,6 +379,7 @@
                         NotificationActionIntentService.ACTION_ARCHIVE_REMOVE_LABEL;
 
                 final Intent intent = new Intent(intentAction);
+                intent.setPackage(context.getPackageName());
                 intent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION,
                         notificationAction);
 
@@ -387,6 +389,7 @@
                 final String intentAction = NotificationActionIntentService.ACTION_DELETE;
 
                 final Intent intent = new Intent(intentAction);
+                intent.setPackage(context.getPackageName());
                 intent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION,
                         notificationAction);
 
@@ -396,6 +399,7 @@
                 final String intentAction = NotificationActionIntentService.ACTION_MARK_READ;
 
                 final Intent intent = new Intent(intentAction);
+                intent.setPackage(context.getPackageName());
                 intent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION,
                         notificationAction);
 
@@ -491,7 +495,8 @@
         public int getActionTextResId() {
             switch (mNotificationActionType) {
                 case ARCHIVE_REMOVE_LABEL:
-                    if (mFolder.type == FolderType.INBOX) {
+                    if (mFolder.type == FolderType.INBOX
+                            || mFolder.type == FolderType.INBOX_SECTION) {
                         return R.string.notification_action_undo_archive;
                     } else {
                         return R.string.notification_action_undo_remove_label;
@@ -566,7 +571,10 @@
         undoView.setTextViewText(
                 R.id.description_text, context.getString(notificationAction.getActionTextResId()));
 
+        final String packageName = context.getPackageName();
+
         final Intent clickIntent = new Intent(NotificationActionIntentService.ACTION_UNDO);
+        clickIntent.setPackage(packageName);
         clickIntent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION,
                 notificationAction);
         final PendingIntent clickPendingIntent = PendingIntent.getService(context, notificationId,
@@ -578,6 +586,7 @@
 
         // When the notification is cleared, we perform the destructive action
         final Intent deleteIntent = new Intent(NotificationActionIntentService.ACTION_DESTRUCT);
+        deleteIntent.setPackage(packageName);
         deleteIntent.putExtra(NotificationActionIntentService.EXTRA_NOTIFICATION_ACTION,
                 notificationAction);
         final PendingIntent deletePendingIntent = PendingIntent.getService(context,
@@ -661,7 +670,7 @@
 
         switch (destructAction) {
             case ARCHIVE_REMOVE_LABEL: {
-                if (folder.type == FolderType.INBOX) {
+                if (folder.type == FolderType.INBOX || folder.type == FolderType.INBOX_SECTION) {
                     // Inbox, so archive
                     final ContentValues values = new ContentValues(1);
                     values.put(UIProvider.ConversationOperations.OPERATION_KEY,
@@ -679,12 +688,12 @@
                     contentResolver.update(uri, values, null, null);
                 }
 
-                markSeen(context, notificationAction.mAccount.toString(), folder);
+                markSeen(context, folder, conversation);
                 break;
             }
             case DELETE: {
                 contentResolver.delete(uri, null, null);
-                markSeen(context, notificationAction.mAccount.toString(), folder);
+                markSeen(context, folder, conversation);
                 break;
             }
             default:
@@ -693,10 +702,11 @@
         }
     }
 
-    private static void markSeen(final Context context, final String account, final Folder folder) {
+    private static void markSeen(
+            final Context context, final Folder folder, final Conversation conversation) {
         final Intent intent = new Intent(MailIntentService.ACTION_MARK_SEEN);
-        intent.putExtra(MailIntentService.ACCOUNT_EXTRA, account);
         intent.putExtra(MailIntentService.FOLDER_EXTRA, folder);
+        intent.putExtra(MailIntentService.CONVERSATION_EXTRA, conversation);
 
         context.startService(intent);
     }
@@ -706,7 +716,7 @@
      */
     public static void createUndoNotification(final Context context,
             final NotificationAction notificationAction) {
-        final int notificationId = getNotificationId(
+        final int notificationId = NotificationUtils.getNotificationId(
                 notificationAction.getAccount().name, notificationAction.getFolder());
 
         final Notification notification =
@@ -722,7 +732,7 @@
 
     public static void cancelUndoNotification(final Context context,
             final NotificationAction notificationAction) {
-        final int notificationId = getNotificationId(
+        final int notificationId = NotificationUtils.getNotificationId(
                 notificationAction.getAccount().name, notificationAction.getFolder());
         removeUndoNotification(context, notificationId, false);
         resendNotifications(context);
@@ -734,7 +744,7 @@
      */
     public static void processUndoNotification(final Context context,
             final NotificationAction notificationAction) {
-        final int notificationId = getNotificationId(
+        final int notificationId = NotificationUtils.getNotificationId(
                 notificationAction.getAccount().name, notificationAction.getFolder());
         removeUndoNotification(context, notificationId, true);
         sNotificationTimestamps.delete(notificationId);
@@ -760,13 +770,6 @@
         }
     }
 
-    public static int getNotificationId(final String account, final Folder folder) {
-        // TODO(skennedy): When notifications are fully in UnifiedEmail, remove this method and use
-        // the one in Utils
-        // 1 == Gmail.NOTIFICATION_ID
-        return 1 ^ account.hashCode() ^ folder.hashCode();
-    }
-
     /**
      * Broadcasts an {@link Intent} to inform the app to resend its notifications.
      */
diff --git a/src/com/android/mail/utils/NotificationUtils.java b/src/com/android/mail/utils/NotificationUtils.java
new file mode 100644
index 0000000..dbc0113
--- /dev/null
+++ b/src/com/android/mail/utils/NotificationUtils.java
@@ -0,0 +1,1706 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.mail.utils;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.Contacts.Photo;
+import android.support.v4.app.NotificationCompat;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.text.style.CharacterStyle;
+import android.text.style.TextAppearanceSpan;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.mail.EmailAddress;
+import com.android.mail.MailIntentService;
+import com.android.mail.R;
+import com.android.mail.browse.MessageCursor;
+import com.android.mail.browse.SendersView;
+import com.android.mail.preferences.AccountPreferences;
+import com.android.mail.preferences.FolderPreferences;
+import com.android.mail.preferences.MailPrefs;
+import com.android.mail.providers.Account;
+import com.android.mail.providers.Conversation;
+import com.android.mail.providers.Folder;
+import com.android.mail.providers.Message;
+import com.android.mail.providers.UIProvider;
+import com.android.mail.utils.NotificationActionUtils.NotificationAction;
+import com.google.android.common.html.parser.HTML;
+import com.google.android.common.html.parser.HTML4;
+import com.google.android.common.html.parser.HtmlDocument;
+import com.google.android.common.html.parser.HtmlTree;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class NotificationUtils {
+    public static final String LOG_TAG = LogTag.getLogTag();
+
+    /** Contains a list of <(account, label), unread conversations> */
+    private static NotificationMap sActiveNotificationMap = null;
+
+    private static final SparseArray<Bitmap> sNotificationIcons = new SparseArray<Bitmap>();
+
+    private static TextAppearanceSpan sNotificationUnreadStyleSpan;
+    private static CharacterStyle sNotificationReadStyleSpan;
+
+    private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap();
+    private static final SimpleStringSplitter SENDER_LIST_SPLITTER =
+            new SimpleStringSplitter(Utils.SENDER_LIST_SEPARATOR);
+    private static String[] sSenderFragments = new String[8];
+
+    /** A factory that produces a plain text converter that removes elided text. */
+    private static final HtmlTree.PlainTextConverterFactory MESSAGE_CONVERTER_FACTORY =
+            new HtmlTree.PlainTextConverterFactory() {
+                @Override
+                public HtmlTree.PlainTextConverter createInstance() {
+                    return new MailMessagePlainTextConverter();
+                }
+            };
+
+    /**
+     * Clears all notifications in response to the user tapping "Clear" in the status bar.
+     */
+    public static void clearAllNotfications(Context context) {
+        LogUtils.v(LOG_TAG, "Clearing all notifications.");
+        final NotificationMap notificationMap = getNotificationMap(context);
+        notificationMap.clear();
+        notificationMap.saveNotificationMap(context);
+    }
+
+    /**
+     * Returns the notification map, creating it if necessary.
+     */
+    private static synchronized NotificationMap getNotificationMap(Context context) {
+        if (sActiveNotificationMap == null) {
+            sActiveNotificationMap = new NotificationMap();
+
+            // populate the map from the cached data
+            sActiveNotificationMap.loadNotificationMap(context);
+        }
+        return sActiveNotificationMap;
+    }
+
+    /**
+     * Class representing the existing notifications, and the number of unread and
+     * unseen conversations that triggered each.
+     */
+    private static class NotificationMap
+            extends ConcurrentHashMap<NotificationKey, Pair<Integer, Integer>> {
+
+        private static final String NOTIFICATION_PART_SEPARATOR = " ";
+        private static final int NUM_NOTIFICATION_PARTS= 4;
+
+        /**
+         * Retuns the unread count for the given NotificationKey.
+         */
+        public Integer getUnread(NotificationKey key) {
+            final Pair<Integer, Integer> value = get(key);
+            return value != null ? value.first : null;
+        }
+
+        /**
+         * Retuns the unread unseen count for the given NotificationKey.
+         */
+        public Integer getUnseen(NotificationKey key) {
+            final Pair<Integer, Integer> value = get(key);
+            return value != null ? value.second : null;
+        }
+
+        /**
+         * Store the unread and unseen value for the given NotificationKey
+         */
+        public void put(NotificationKey key, int unread, int unseen) {
+            final Pair<Integer, Integer> value =
+                    new Pair<Integer, Integer>(Integer.valueOf(unread), Integer.valueOf(unseen));
+            put(key, value);
+        }
+
+        /**
+         * Populates the notification map with previously cached data.
+         */
+        public synchronized void loadNotificationMap(final Context context) {
+            final MailPrefs mailPrefs = MailPrefs.get(context);
+            final Set<String> notificationSet = mailPrefs.getActiveNotificationSet();
+            if (notificationSet != null) {
+                for (String notificationEntry : notificationSet) {
+                    // Get the parts of the string that make the notification entry
+                    final String[] notificationParts =
+                            TextUtils.split(notificationEntry, NOTIFICATION_PART_SEPARATOR);
+                    if (notificationParts.length == NUM_NOTIFICATION_PARTS) {
+                        final Uri accountUri = Uri.parse(notificationParts[0]);
+                        final Cursor accountCursor = context.getContentResolver().query(
+                                accountUri, UIProvider.ACCOUNTS_PROJECTION, null, null, null);
+                        final Account account;
+                        try {
+                            if (accountCursor.moveToFirst()) {
+                                account = new Account(accountCursor);
+                            } else {
+                                continue;
+                            }
+                        } finally {
+                            accountCursor.close();
+                        }
+
+                        final Uri folderUri = Uri.parse(notificationParts[1]);
+                        final Cursor folderCursor = context.getContentResolver().query(
+                                folderUri, UIProvider.FOLDERS_PROJECTION, null, null, null);
+                        final Folder folder;
+                        try {
+                            if (folderCursor.moveToFirst()) {
+                                folder = new Folder(folderCursor);
+                            } else {
+                                continue;
+                            }
+                        } finally {
+                            folderCursor.close();
+                        }
+
+                        final NotificationKey key = new NotificationKey(account, folder);
+                        final Integer unreadValue = Integer.valueOf(notificationParts[2]);
+                        final Integer unseenValue = Integer.valueOf(notificationParts[3]);
+                        final Pair<Integer, Integer> unreadUnseenValue =
+                                new Pair<Integer, Integer>(unreadValue, unseenValue);
+                        put(key, unreadUnseenValue);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Cache the notification map.
+         */
+        public synchronized void saveNotificationMap(Context context) {
+            final Set<String> notificationSet = Sets.newHashSet();
+            final Set<NotificationKey> keys = keySet();
+            for (NotificationKey key : keys) {
+                final Pair<Integer, Integer> value = get(key);
+                final Integer unreadCount = value.first;
+                final Integer unseenCount = value.second;
+                if (value != null && unreadCount != null && unseenCount != null) {
+                    final String[] partValues = new String[] {
+                            key.account.uri.toString(), key.folder.uri.toString(),
+                            unreadCount.toString(), unseenCount.toString()};
+                    notificationSet.add(TextUtils.join(NOTIFICATION_PART_SEPARATOR, partValues));
+                }
+            }
+            final MailPrefs mailPrefs = MailPrefs.get(context);
+            mailPrefs.cacheActiveNotificationSet(notificationSet);
+        }
+    }
+
+    /**
+     * @return the title of this notification with each account and the number of unread and unseen
+     * conversations for it. Also remove any account in the map that has 0 unread.
+     */
+    private static String createNotificationString(NotificationMap notifications) {
+        StringBuilder result = new StringBuilder();
+        int i = 0;
+        Set<NotificationKey> keysToRemove = Sets.newHashSet();
+        for (NotificationKey key : notifications.keySet()) {
+            Integer unread = notifications.getUnread(key);
+            Integer unseen = notifications.getUnseen(key);
+            if (unread == null || unread.intValue() == 0) {
+                keysToRemove.add(key);
+            } else {
+                if (i > 0) result.append(", ");
+                result.append(key.toString() + " (" + unread + ", " + unseen + ")");
+                i++;
+            }
+        }
+
+        for (NotificationKey key : keysToRemove) {
+            notifications.remove(key);
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Get all notifications for all accounts and cancel them.
+     **/
+    public static void cancelAllNotifications(Context context) {
+        NotificationManager nm = (NotificationManager) context.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        nm.cancelAll();
+        clearAllNotfications(context);
+    }
+
+    /**
+     * Get all notifications for all accounts, cancel them, and repost.
+     * This happens when locale changes.
+     **/
+    public static void cancelAndResendNotifications(Context context) {
+        resendNotifications(context, true);
+    }
+
+    /**
+     * Get all notifications for all accounts, optionally cancel them, and repost.
+     * This happens when locale changes.
+     **/
+    public static void resendNotifications(Context context, final boolean cancelExisting) {
+        if (cancelExisting) {
+            NotificationManager nm =
+                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            nm.cancelAll();
+        }
+        // Re-validate the notifications.
+        final NotificationMap notificationMap = getNotificationMap(context);
+        final Set<NotificationKey> keys = notificationMap.keySet();
+        for (NotificationKey notification : keys) {
+            final Folder folder = notification.folder;
+            final int notificationId = getNotificationId(notification.account.name, folder);
+
+            final NotificationAction undoableAction =
+                    NotificationActionUtils.sUndoNotifications.get(notificationId);
+            if (undoableAction == null) {
+                validateNotifications(context, folder, notification.account, true, false,
+                        notification);
+            } else {
+                // Create an undo notification
+                NotificationActionUtils.createUndoNotification(context, undoableAction);
+            }
+        }
+    }
+
+    /**
+     * Validate the notifications for the specified account.
+     */
+    public static void validateAccountNotifications(Context context, String account) {
+        List<NotificationKey> notificationsToCancel = Lists.newArrayList();
+        // Iterate through the notification map to see if there are any entries that correspond to
+        // labels that are not in the sync set.
+        final NotificationMap notificationMap = getNotificationMap(context);
+        Set<NotificationKey> keys = notificationMap.keySet();
+        final AccountPreferences accountPreferences = new AccountPreferences(context, account);
+        final boolean enabled = accountPreferences.areNotificationsEnabled();
+        if (!enabled) {
+            // Cancel all notifications for this account
+            for (NotificationKey notification : keys) {
+                if (notification.account.name.equals(account)) {
+                    notificationsToCancel.add(notification);
+                }
+            }
+        } else {
+            // Iterate through the notification map to see if there are any entries that
+            // correspond to labels that are not in the notification set.
+            for (NotificationKey notification : keys) {
+                if (notification.account.name.equals(account)) {
+                    // If notification is not enabled for this label, remember this NotificationKey
+                    // to later cancel the notification, and remove the entry from the map
+                    final Folder folder = notification.folder;
+                    final boolean isInbox =
+                            notification.account.settings.defaultInbox.equals(folder.uri);
+                    final FolderPreferences folderPreferences = new FolderPreferences(
+                            context, notification.account.name, folder, isInbox);
+
+                    if (!folderPreferences.areNotificationsEnabled()) {
+                        notificationsToCancel.add(notification);
+                    }
+                }
+            }
+        }
+
+        // Cancel & remove the invalid notifications.
+        if (notificationsToCancel.size() > 0) {
+            NotificationManager nm = (NotificationManager) context.getSystemService(
+                    Context.NOTIFICATION_SERVICE);
+            for (NotificationKey notification : notificationsToCancel) {
+                final Folder folder = notification.folder;
+                final int notificationId = getNotificationId(notification.account.name, folder);
+                nm.cancel(notificationId);
+                notificationMap.remove(notification);
+                NotificationActionUtils.sUndoNotifications.remove(notificationId);
+                NotificationActionUtils.sNotificationTimestamps.delete(notificationId);
+            }
+            notificationMap.saveNotificationMap(context);
+        }
+    }
+
+    /**
+     * Display only one notification.
+     */
+    public static void setNewEmailIndicator(Context context, final int unreadCount,
+            final int unseenCount, final Account account, final Folder folder,
+            final boolean getAttention) {
+        boolean ignoreUnobtrusiveSetting = false;
+
+        final int notificationId = getNotificationId(account.name, folder);
+
+        // Update the notification map
+        final NotificationMap notificationMap = getNotificationMap(context);
+        final NotificationKey key = new NotificationKey(account, folder);
+        if (unreadCount == 0) {
+            notificationMap.remove(key);
+            ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+                    .cancel(notificationId);
+        } else {
+            if (!notificationMap.containsKey(key)) {
+                // This account previously didn't have any unread mail; ignore the "unobtrusive
+                // notifications" setting and play sound and/or vibrate the device even if a
+                // notification already exists (bug 2412348).
+                ignoreUnobtrusiveSetting = true;
+            }
+            notificationMap.put(key, unreadCount, unseenCount);
+        }
+        notificationMap.saveNotificationMap(context);
+
+        if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
+            LogUtils.v(LOG_TAG, "New email: %s mapSize: %d getAttention: %b",
+                    createNotificationString(notificationMap), notificationMap.size(),
+                    getAttention);
+        }
+
+        if (NotificationActionUtils.sUndoNotifications.get(notificationId) == null) {
+            validateNotifications(context, folder, account, getAttention, ignoreUnobtrusiveSetting,
+                    key);
+        }
+    }
+
+    /**
+     * Validate the notifications notification.
+     */
+    private static void validateNotifications(Context context, final Folder folder,
+            final Account account, boolean getAttention, boolean ignoreUnobtrusiveSetting,
+            NotificationKey key) {
+
+        NotificationManager nm = (NotificationManager)
+                context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        final NotificationMap notificationMap = getNotificationMap(context);
+        if (LogUtils.isLoggable(LOG_TAG, LogUtils.VERBOSE)) {
+            LogUtils.v(LOG_TAG, "Validating Notification: %s mapSize: %d folder: %s "
+                    + "getAttention: %b", createNotificationString(notificationMap),
+                    notificationMap.size(), folder.name, getAttention);
+        }
+        // The number of unread messages for this account and label.
+        final Integer unread = notificationMap.getUnread(key);
+        final int unreadCount = unread != null ? unread.intValue() : 0;
+        final Integer unseen = notificationMap.getUnseen(key);
+        int unseenCount = unseen != null ? unseen.intValue() : 0;
+
+        Cursor cursor = null;
+
+        try {
+            final Uri.Builder uriBuilder = folder.conversationListUri.buildUpon();
+            uriBuilder.appendQueryParameter(
+                    UIProvider.SEEN_QUERY_PARAMETER, Boolean.FALSE.toString());
+            cursor = context.getContentResolver().query(uriBuilder.build(),
+                    UIProvider.CONVERSATION_PROJECTION, null, null, null);
+            final int cursorUnseenCount = cursor.getCount();
+
+            // Make sure the unseen count matches the number of items in the cursor.  But, we don't
+            // want to overwrite a 0 unseen count that was specified in the intent
+            if (unseenCount != 0 && unseenCount != cursorUnseenCount) {
+                LogUtils.d(LOG_TAG,
+                        "Unseen count doesn't match cursor count.  unseen: %d cursor count: %d",
+                        unseenCount, cursorUnseenCount);
+                unseenCount = cursorUnseenCount;
+            }
+
+            // For the purpose of the notifications, the unseen count should be capped at the num of
+            // unread conversations.
+            if (unseenCount > unreadCount) {
+                unseenCount = unreadCount;
+            }
+
+            final int notificationId = getNotificationId(account.name, folder);
+
+            if (unseenCount == 0) {
+                nm.cancel(notificationId);
+                return;
+            }
+
+            // We now have all we need to create the notification and the pending intent
+            PendingIntent clickIntent;
+
+            NotificationCompat.Builder notification = new NotificationCompat.Builder(context);
+            notification.setSmallIcon(R.drawable.stat_notify_email);
+            notification.setTicker(account.name);
+
+            final long when;
+
+            final long oldWhen =
+                    NotificationActionUtils.sNotificationTimestamps.get(notificationId);
+            if (oldWhen != 0) {
+                when = oldWhen;
+            } else {
+                when = System.currentTimeMillis();
+            }
+
+            notification.setWhen(when);
+
+            // The timestamp is now stored in the notification, so we can remove it from here
+            NotificationActionUtils.sNotificationTimestamps.delete(notificationId);
+
+            // Dispatch a CLEAR_NEW_MAIL_NOTIFICATIONS intent if the user taps the "X" next to a
+            // notification.  Also this intent gets fired when the user taps on a notification as
+            // the AutoCancel flag has been set
+            final Intent cancelNotificationIntent =
+                    new Intent(MailIntentService.ACTION_CLEAR_NEW_MAIL_NOTIFICATIONS);
+            cancelNotificationIntent.setPackage(context.getPackageName());
+            cancelNotificationIntent.setData(Utils.appendVersionQueryParameter(context,
+                    folder.uri));
+            cancelNotificationIntent.putExtra(MailIntentService.ACCOUNT_EXTRA, account);
+            cancelNotificationIntent.putExtra(MailIntentService.FOLDER_EXTRA, folder);
+
+            notification.setDeleteIntent(PendingIntent.getService(
+                    context, notificationId, cancelNotificationIntent, 0));
+
+            // Ensure that the notification is cleared when the user selects it
+            notification.setAutoCancel(true);
+
+            boolean eventInfoConfigured = false;
+
+            final boolean isInbox = account.settings.defaultInbox.equals(folder.uri);
+            final FolderPreferences folderPreferences =
+                    new FolderPreferences(context, account.name, folder, isInbox);
+
+            if (isInbox) {
+                final AccountPreferences accountPreferences =
+                        new AccountPreferences(context, account.name);
+                moveNotificationSetting(accountPreferences, folderPreferences);
+            }
+
+            if (!folderPreferences.areNotificationsEnabled()) {
+                // Don't notify
+                return;
+            }
+
+            if (unreadCount > 0) {
+                // How can I order this properly?
+                if (cursor.moveToNext()) {
+                    Intent notificationIntent = createViewConversationIntent(context, account,
+                            folder, null);
+
+                    // Launch directly to the conversation, if the
+                    // number of unseen conversations == 1
+                    if (unseenCount == 1) {
+                        notificationIntent = createViewConversationIntent(context, account, folder,
+                                cursor);
+                    }
+
+                    if (notificationIntent == null) {
+                        LogUtils.e(LOG_TAG, "Null intent when building notification");
+                        return;
+                    }
+
+                    clickIntent = PendingIntent.getActivity(context, -1, notificationIntent, 0);
+                    configureLatestEventInfoFromConversation(context, account, folderPreferences,
+                            notification, cursor, clickIntent, notificationIntent,
+                            account.name, unreadCount, unseenCount, folder, when);
+                    eventInfoConfigured = true;
+                }
+            }
+
+            final boolean vibrate = folderPreferences.isNotificationVibrateEnabled();
+            final String ringtoneUri = folderPreferences.getNotificationRingtoneUri();
+            final boolean notifyOnce = !folderPreferences.isEveryMessageNotificationEnabled();
+
+            if (!ignoreUnobtrusiveSetting && account != null && notifyOnce) {
+                // If the user has "unobtrusive notifications" enabled, only alert the first time
+                // new mail is received in this account.  This is the default behavior.  See
+                // bugs 2412348 and 2413490.
+                notification.setOnlyAlertOnce(true);
+            }
+
+            if (account != null) {
+                LogUtils.d(LOG_TAG, "Account: %s vibrate: %s", account.name,
+                        Boolean.toString(folderPreferences.isNotificationVibrateEnabled()));
+            }
+
+            int defaults = 0;
+
+            /*
+             * We do not want to notify if this is coming back from an Undo notification, hence the
+             * oldWhen check.
+             */
+            if (getAttention && account != null && oldWhen == 0) {
+                final AccountPreferences accountPreferences =
+                        new AccountPreferences(context, account.name);
+                if (accountPreferences.areNotificationsEnabled()) {
+                    if (vibrate) {
+                        defaults |= Notification.DEFAULT_VIBRATE;
+                    }
+
+                    notification.setSound(TextUtils.isEmpty(ringtoneUri) ? null
+                            : Uri.parse(ringtoneUri));
+                    LogUtils.d(LOG_TAG, "New email in %s vibrateWhen: %s, playing notification: %s",
+                            account.name, vibrate, ringtoneUri);
+                }
+            }
+
+            if (eventInfoConfigured) {
+                defaults |= Notification.DEFAULT_LIGHTS;
+                notification.setDefaults(defaults);
+
+                if (oldWhen != 0) {
+                    // We do not want to display the ticker again if we are re-displaying this
+                    // notification (like from an Undo notification)
+                    notification.setTicker(null);
+                }
+
+                nm.notify(notificationId, notification.build());
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    /**
+     * @return an {@link Intent} which, if launched, will display the corresponding conversation
+     */
+    private static Intent createViewConversationIntent(final Context context, final Account account,
+            final Folder folder, final Cursor cursor) {
+        if (folder == null || account == null) {
+            LogUtils.e(LOG_TAG, "Null account or folder.  account: %s folder: %s",
+                    account, folder);
+            return null;
+        }
+
+        final Intent intent;
+
+        if (cursor == null) {
+            intent = Utils.createViewFolderIntent(context, folder.uri, account);
+        } else {
+            // A conversation cursor has been specified, so this intent is intended to be go
+            // directly to the one new conversation
+
+            // Get the Conversation object
+            final Conversation conversation = new Conversation(cursor);
+            intent = Utils.createViewConversationIntent(context, conversation, folder.uri,
+                    account);
+        }
+
+        return intent;
+    }
+
+    private static Bitmap getDefaultNotificationIcon(
+            final Context context, final Folder folder, final boolean multipleNew) {
+        final Bitmap icon;
+        if (folder.notificationIconResId != 0) {
+            icon = getIcon(context, folder.notificationIconResId);
+        } else if (multipleNew) {
+            icon = getIcon(context, R.drawable.ic_notification_multiple_mail_holo_dark);
+        } else {
+            icon = getIcon(context, R.drawable.ic_contact_picture);
+        }
+        return icon;
+    }
+
+    private static Bitmap getIcon(final Context context, final int resId) {
+        final Bitmap cachedIcon = sNotificationIcons.get(resId);
+        if (cachedIcon != null) {
+            return cachedIcon;
+        }
+
+        final Bitmap icon = BitmapFactory.decodeResource(context.getResources(), resId);
+        sNotificationIcons.put(resId, icon);
+
+        return icon;
+    }
+
+    private static void configureLatestEventInfoFromConversation(final Context context,
+            final Account account, final FolderPreferences folderPreferences,
+            final NotificationCompat.Builder notification, final Cursor conversationCursor,
+            final PendingIntent clickIntent, final Intent notificationIntent,
+            final String notificationAccount, final int unreadCount, final int unseenCount,
+            final Folder folder, final long when) {
+        final Resources res = context.getResources();
+
+        LogUtils.w(LOG_TAG, "Showing notification with unreadCount of %d and "
+                + "unseenCount of %d", unreadCount, unseenCount);
+
+        String notificationTicker = null;
+
+        // Boolean indicating that this notification is for a non-inbox label.
+        final boolean isInbox = account.settings.defaultInbox.equals(folder.uri);
+
+        // Notification label name for user label notifications.
+        final String notificationLabelName = isInbox ? null : folder.name;
+
+        if (unseenCount > 1) {
+            // Build the string that describes the number of new messages
+            final String newMessagesString = res.getString(R.string.new_messages, unseenCount);
+
+            // Use the default notification icon
+            notification.setLargeIcon(
+                    getDefaultNotificationIcon(context, folder, true /* multiple new messages */));
+
+            // The ticker initially start as the new messages string.
+            notificationTicker = newMessagesString;
+
+            // The title of the notification is the new messages string
+            notification.setContentTitle(newMessagesString);
+
+            if (com.android.mail.utils.Utils.isRunningJellybeanOrLater()) {
+                // For a new-style notification
+                final int maxNumDigestItems = context.getResources().getInteger(
+                        R.integer.max_num_notification_digest_items);
+
+                // The body of the notification is the account name, or the label name.
+                notification.setSubText(isInbox ? notificationAccount : notificationLabelName);
+
+                final NotificationCompat.InboxStyle digest =
+                        new NotificationCompat.InboxStyle(notification);
+
+                digest.setBigContentTitle(newMessagesString);
+
+                int numDigestItems = 0;
+                do {
+                    final Conversation conversation = new Conversation(conversationCursor);
+
+                    if (!conversation.read) {
+                        boolean multipleUnreadThread = false;
+                        // TODO(cwren) extract this pattern into a helper
+
+                        Cursor cursor = null;
+                        MessageCursor messageCursor = null;
+                        try {
+                            final Uri.Builder uriBuilder = conversation.messageListUri.buildUpon();
+                            uriBuilder.appendQueryParameter(
+                                    UIProvider.LABEL_QUERY_PARAMETER, notificationLabelName);
+                            cursor = context.getContentResolver().query(uriBuilder.build(),
+                                    UIProvider.MESSAGE_PROJECTION, null, null, null);
+                            messageCursor = new MessageCursor(cursor);
+
+                            String from = "";
+                            String fromAddress = "";
+                            if (messageCursor.moveToPosition(messageCursor.getCount() - 1)) {
+                                final Message message = messageCursor.getMessage();
+                                fromAddress = message.getFrom();
+                                if (fromAddress == null) {
+                                    fromAddress = "";
+                                }
+                                from = getDisplayableSender(fromAddress);
+                            }
+                            while (messageCursor.moveToPosition(messageCursor.getPosition() - 1)) {
+                                final Message message = messageCursor.getMessage();
+                                if (!message.read &&
+                                        !fromAddress.contentEquals(message.getFrom())) {
+                                    multipleUnreadThread = true;
+                                    break;
+                                }
+                            }
+                            final SpannableStringBuilder sendersBuilder;
+                            if (multipleUnreadThread) {
+                                final int sendersLength =
+                                        res.getInteger(R.integer.swipe_senders_length);
+
+                                sendersBuilder = getStyledSenders(context, conversationCursor,
+                                        sendersLength, notificationAccount);
+                            } else {
+                                sendersBuilder = new SpannableStringBuilder(from);
+                            }
+                            final CharSequence digestLine = getSingleMessageInboxLine(context,
+                                    sendersBuilder.toString(),
+                                    conversation.subject,
+                                    conversation.snippet);
+                            digest.addLine(digestLine);
+                            numDigestItems++;
+                        } finally {
+                            if (messageCursor != null) {
+                                messageCursor.close();
+                            }
+                            if (cursor != null) {
+                                cursor.close();
+                            }
+                        }
+                    }
+                } while (numDigestItems <= maxNumDigestItems && conversationCursor.moveToNext());
+            } else {
+                // The body of the notification is the account name, or the label name.
+                notification.setContentText(
+                        isInbox ? notificationAccount : notificationLabelName);
+            }
+        } else {
+            // For notifications for a single new conversation, we want to get the information from
+            // the conversation
+
+            // Move the cursor to the most recent unread conversation
+            seekToLatestUnreadConversation(conversationCursor);
+
+            final Conversation conversation = new Conversation(conversationCursor);
+
+            Cursor cursor = null;
+            MessageCursor messageCursor = null;
+            boolean multipleUnseenThread = false;
+            String from = null;
+            try {
+                final Uri uri = conversation.messageListUri.buildUpon().appendQueryParameter(
+                        UIProvider.LABEL_QUERY_PARAMETER, folder.persistentId).build();
+                cursor = context.getContentResolver().query(uri, UIProvider.MESSAGE_PROJECTION,
+                        null, null, null);
+                messageCursor = new MessageCursor(cursor);
+                // Use the information from the last sender in the conversation that triggered
+                // this notification.
+
+                String fromAddress = "";
+                if (messageCursor.moveToPosition(messageCursor.getCount() - 1)) {
+                    final Message message = messageCursor.getMessage();
+                    fromAddress = message.getFrom();
+                    from = getDisplayableSender(fromAddress);
+                    notification.setLargeIcon(
+                            getContactIcon(context, getSenderAddress(fromAddress), folder));
+                }
+
+                // Assume that the last message in this conversation is unread
+                int firstUnseenMessagePos = messageCursor.getPosition();
+                while (messageCursor.moveToPosition(messageCursor.getPosition() - 1)) {
+                    final Message message = messageCursor.getMessage();
+                    final boolean unseen = !message.seen;
+                    if (unseen) {
+                        firstUnseenMessagePos = messageCursor.getPosition();
+                        if (!multipleUnseenThread
+                                && !fromAddress.contentEquals(message.getFrom())) {
+                            multipleUnseenThread = true;
+                        }
+                    }
+                }
+
+                if (Utils.isRunningJellybeanOrLater()) {
+                    // For a new-style notification
+
+                    if (multipleUnseenThread) {
+                        // The title of a single conversation is the list of senders.
+                        int sendersLength = res.getInteger(R.integer.swipe_senders_length);
+
+                        final SpannableStringBuilder sendersBuilder = getStyledSenders(
+                                context, conversationCursor, sendersLength, notificationAccount);
+
+                        notification.setContentTitle(sendersBuilder);
+                        // For a single new conversation, the ticker is based on the sender's name.
+                        notificationTicker = sendersBuilder.toString();
+                    } else {
+                        // The title of a single message the sender.
+                        notification.setContentTitle(from);
+                        // For a single new conversation, the ticker is based on the sender's name.
+                        notificationTicker = from;
+                    }
+
+                    // The notification content will be the subject of the conversation.
+                    notification.setContentText(
+                            getSingleMessageLittleText(context, conversation.subject));
+
+                    // The notification subtext will be the subject of the conversation for inbox
+                    // notifications, or will based on the the label name for user label
+                    // notifications.
+                    notification.setSubText(isInbox ? notificationAccount : notificationLabelName);
+
+                    if (multipleUnseenThread) {
+                        notification.setLargeIcon(
+                                getDefaultNotificationIcon(context, folder, true));
+                    }
+                    final NotificationCompat.BigTextStyle bigText =
+                            new NotificationCompat.BigTextStyle(notification);
+
+                    // Seek the message cursor to the first unread message
+                    final Message message;
+                    if (messageCursor.moveToPosition(firstUnseenMessagePos)) {
+                        message = messageCursor.getMessage();
+                        bigText.bigText(getSingleMessageBigText(context,
+                                conversation.subject, message));
+                    } else {
+                        LogUtils.e(LOG_TAG, "Failed to load message");
+                        message = null;
+                    }
+
+                    if (message != null) {
+                        final Set<String> notificationActions =
+                                folderPreferences.getNotificationActions();
+
+                        final int notificationId = getNotificationId(notificationAccount, folder);
+
+                        NotificationActionUtils.addNotificationActions(context, notificationIntent,
+                                notification, account, conversation, message, folder,
+                                notificationId, when, notificationActions);
+                    }
+                } else {
+                    // For an old-style notification
+
+                    // The title of a single conversation notification is built from both the sender
+                    // and subject of the new message.
+                    notification.setContentTitle(getSingleMessageNotificationTitle(context,
+                            from, conversation.subject));
+
+                    // The notification content will be the subject of the conversation for inbox
+                    // notifications, or will based on the the label name for user label
+                    // notifications.
+                    notification.setContentText(
+                            isInbox ? notificationAccount : notificationLabelName);
+
+                    // For a single new conversation, the ticker is based on the sender's name.
+                    notificationTicker = from;
+                }
+            } finally {
+                if (messageCursor != null) {
+                    messageCursor.close();
+                }
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+
+        // Build the notification ticker
+        if (notificationLabelName != null && notificationTicker != null) {
+            // This is a per label notification, format the ticker with that information
+            notificationTicker = res.getString(R.string.label_notification_ticker,
+                    notificationLabelName, notificationTicker);
+        }
+
+        if (notificationTicker != null) {
+            // If we didn't generate a notification ticker, it will default to account name
+            notification.setTicker(notificationTicker);
+        }
+
+        // Set the number in the notification
+        if (unreadCount > 1) {
+            notification.setNumber(unreadCount);
+        }
+
+        notification.setContentIntent(clickIntent);
+    }
+
+    private static SpannableStringBuilder getStyledSenders(final Context context,
+            final Cursor conversationCursor, final int maxLength, final String account) {
+        final Conversation conversation = new Conversation(conversationCursor);
+        final com.android.mail.providers.ConversationInfo conversationInfo =
+                conversation.conversationInfo;
+        final ArrayList<SpannableString> senders = new ArrayList<SpannableString>();
+        if (sNotificationUnreadStyleSpan == null) {
+            sNotificationUnreadStyleSpan = new TextAppearanceSpan(
+                    context, R.style.NotificationSendersUnreadTextAppearance);
+            sNotificationReadStyleSpan =
+                    new TextAppearanceSpan(context, R.style.NotificationSendersReadTextAppearance);
+        }
+        SendersView.format(context, conversationInfo, "", maxLength, senders, null, null, account,
+                sNotificationUnreadStyleSpan, sNotificationReadStyleSpan, false);
+
+        return ellipsizeStyledSenders(context, senders);
+    }
+
+    private static String sSendersSplitToken = null;
+    private static String sElidedPaddingToken = null;
+
+    private static SpannableStringBuilder ellipsizeStyledSenders(final Context context,
+            ArrayList<SpannableString> styledSenders) {
+        if (sSendersSplitToken == null) {
+            sSendersSplitToken = context.getString(R.string.senders_split_token);
+            sElidedPaddingToken = context.getString(R.string.elided_padding_token);
+        }
+
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        SpannableString prevSender = null;
+        for (SpannableString sender : styledSenders) {
+            if (sender == null) {
+                LogUtils.e(LOG_TAG, "null sender while iterating over styledSenders");
+                continue;
+            }
+            CharacterStyle[] spans = sender.getSpans(0, sender.length(), CharacterStyle.class);
+            if (SendersView.sElidedString.equals(sender.toString())) {
+                prevSender = sender;
+                sender = copyStyles(spans, sElidedPaddingToken + sender + sElidedPaddingToken);
+            } else if (builder.length() > 0
+                    && (prevSender == null || !SendersView.sElidedString.equals(prevSender
+                            .toString()))) {
+                prevSender = sender;
+                sender = copyStyles(spans, sSendersSplitToken + sender);
+            } else {
+                prevSender = sender;
+            }
+            builder.append(sender);
+        }
+        return builder;
+    }
+
+    private static SpannableString copyStyles(CharacterStyle[] spans, CharSequence newText) {
+        SpannableString s = new SpannableString(newText);
+        if (spans != null && spans.length > 0) {
+            s.setSpan(spans[0], 0, s.length(), 0);
+        }
+        return s;
+    }
+
+    /**
+     * Seeks the cursor to the position of the most recent unread conversation. If no unread
+     * conversation is found, the position of the cursor will be restored, and false will be
+     * returned.
+     */
+    private static boolean seekToLatestUnreadConversation(final Cursor cursor) {
+        final int initialPosition = cursor.getPosition();
+        do {
+            final Conversation conversation = new Conversation(cursor);
+            if (!conversation.read) {
+                return true;
+            }
+        } while (cursor.moveToNext());
+
+        // Didn't find an unread conversation, reset the position.
+        cursor.moveToPosition(initialPosition);
+        return false;
+    }
+
+    /**
+     * Sets the bigtext for a notification for a single new conversation
+     *
+     * @param context
+     * @param senders Sender of the new message that triggered the notification.
+     * @param subject Subject of the new message that triggered the notification
+     * @param snippet Snippet of the new message that triggered the notification
+     * @return a {@link CharSequence} suitable for use in
+     *         {@link android.support.v4.app.NotificationCompat.BigTextStyle}
+     */
+    private static CharSequence getSingleMessageInboxLine(Context context,
+            String senders, String subject, String snippet) {
+        // TODO(cwren) finish this step toward commmon code with getSingleMessageBigText
+
+        final String subjectSnippet = !TextUtils.isEmpty(subject) ? subject : snippet;
+
+        final TextAppearanceSpan notificationPrimarySpan =
+                new TextAppearanceSpan(context, R.style.NotificationPrimaryText);
+
+        if (TextUtils.isEmpty(senders)) {
+            // If the senders are empty, just use the subject/snippet.
+            return subjectSnippet;
+        } else if (TextUtils.isEmpty(subjectSnippet)) {
+            // If the subject/snippet is empty, just use the senders.
+            final SpannableString spannableString = new SpannableString(senders);
+            spannableString.setSpan(notificationPrimarySpan, 0, senders.length(), 0);
+
+            return spannableString;
+        } else {
+            final String formatString = context.getResources().getString(
+                    R.string.multiple_new_message_notification_item);
+            final TextAppearanceSpan notificationSecondarySpan =
+                    new TextAppearanceSpan(context, R.style.NotificationSecondaryText);
+
+            final String instantiatedString = String.format(formatString, senders, subjectSnippet);
+
+            final SpannableString spannableString = new SpannableString(instantiatedString);
+
+            final boolean isOrderReversed = formatString.indexOf("%2$s") <
+                    formatString.indexOf("%1$s");
+            final int primaryOffset =
+                    (isOrderReversed ? instantiatedString.lastIndexOf(senders) :
+                     instantiatedString.indexOf(senders));
+            final int secondaryOffset =
+                    (isOrderReversed ? instantiatedString.lastIndexOf(subjectSnippet) :
+                     instantiatedString.indexOf(subjectSnippet));
+            spannableString.setSpan(notificationPrimarySpan,
+                    primaryOffset, primaryOffset + senders.length(), 0);
+            spannableString.setSpan(notificationSecondarySpan,
+                    secondaryOffset, secondaryOffset + subjectSnippet.length(), 0);
+            return spannableString;
+        }
+    }
+
+    /**
+     * Sets the bigtext for a notification for a single new conversation
+     * @param context
+     * @param subject Subject of the new message that triggered the notification
+     * @return a {@link CharSequence} suitable for use in {@link Notification.ContentText}
+     */
+    private static CharSequence getSingleMessageLittleText(Context context, String subject) {
+        final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
+                context, R.style.NotificationPrimaryText);
+
+        final SpannableString spannableString = new SpannableString(subject);
+        spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
+
+        return spannableString;
+    }
+
+    /**
+     * Sets the bigtext for a notification for a single new conversation
+     *
+     * @param context
+     * @param subject Subject of the new message that triggered the notification
+     * @param message the {@link Message} to be displayed.
+     * @return a {@link CharSequence} suitable for use in
+     *         {@link android.support.v4.app.NotificationCompat.BigTextStyle}
+     */
+    private static CharSequence getSingleMessageBigText(Context context, String subject,
+            final Message message) {
+
+        final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
+                context, R.style.NotificationPrimaryText);
+
+        final String snippet = getMessageBodyWithoutElidedText(message);
+
+        // Change multiple newlines (with potential white space between), into a single new line
+        final String collapsedSnippet =
+                !TextUtils.isEmpty(snippet) ? snippet.replaceAll("\\n\\s+", "\n") : "";
+
+        if (TextUtils.isEmpty(subject)) {
+            // If the subject is empty, just use the snippet.
+            return snippet;
+        } else if (TextUtils.isEmpty(collapsedSnippet)) {
+            // If the snippet is empty, just use the subject.
+            final SpannableString spannableString = new SpannableString(subject);
+            spannableString.setSpan(notificationSubjectSpan, 0, subject.length(), 0);
+
+            return spannableString;
+        } else {
+            final String notificationBigTextFormat = context.getResources().getString(
+                    R.string.single_new_message_notification_big_text);
+
+            // Localizers may change the order of the parameters, look at how the format
+            // string is structured.
+            final boolean isSubjectFirst = notificationBigTextFormat.indexOf("%2$s") >
+                    notificationBigTextFormat.indexOf("%1$s");
+            final String bigText =
+                    String.format(notificationBigTextFormat, subject, collapsedSnippet);
+            final SpannableString spannableString = new SpannableString(bigText);
+
+            final int subjectOffset =
+                    (isSubjectFirst ? bigText.indexOf(subject) : bigText.lastIndexOf(subject));
+            spannableString.setSpan(notificationSubjectSpan,
+                    subjectOffset, subjectOffset + subject.length(), 0);
+
+            return spannableString;
+        }
+    }
+
+    /**
+     * Gets the title for a notification for a single new conversation
+     * @param context
+     * @param sender Sender of the new message that triggered the notification.
+     * @param subject Subject of the new message that triggered the notification
+     * @return a {@link CharSequence} suitable for use as a {@link Notification} title.
+     */
+    private static CharSequence getSingleMessageNotificationTitle(Context context,
+            String sender, String subject) {
+
+        if (TextUtils.isEmpty(subject)) {
+            // If the subject is empty, just set the title to the sender's information.
+            return sender;
+        } else {
+            final String notificationTitleFormat = context.getResources().getString(
+                    R.string.single_new_message_notification_title);
+
+            // Localizers may change the order of the parameters, look at how the format
+            // string is structured.
+            final boolean isSubjectLast = notificationTitleFormat.indexOf("%2$s") >
+                    notificationTitleFormat.indexOf("%1$s");
+            final String titleString = String.format(notificationTitleFormat, sender, subject);
+
+            // Format the string so the subject is using the secondaryText style
+            final SpannableString titleSpannable = new SpannableString(titleString);
+
+            // Find the offset of the subject.
+            final int subjectOffset =
+                    isSubjectLast ? titleString.lastIndexOf(subject) : titleString.indexOf(subject);
+            final TextAppearanceSpan notificationSubjectSpan =
+                    new TextAppearanceSpan(context, R.style.NotificationSecondaryText);
+            titleSpannable.setSpan(notificationSubjectSpan,
+                    subjectOffset, subjectOffset + subject.length(), 0);
+            return titleSpannable;
+        }
+    }
+
+    /**
+     * Adds a fragment with given style to a string builder.
+     *
+     * @param builder the current string builder
+     * @param fragment the fragment to be added
+     * @param style the style of the fragment
+     * @param withSpaces whether to add the whole fragment or to divide it into
+     *            smaller ones
+     */
+    private static void addStyledFragment(SpannableStringBuilder builder, String fragment,
+            CharacterStyle style, boolean withSpaces) {
+        if (withSpaces) {
+            int pos = builder.length();
+            builder.append(fragment);
+            builder.setSpan(CharacterStyle.wrap(style), pos, builder.length(),
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        } else {
+            int start = 0;
+            while (true) {
+                int pos = fragment.substring(start).indexOf(' ');
+                if (pos == -1) {
+                    addStyledFragment(builder, fragment.substring(start), style, true);
+                    break;
+                } else {
+                    pos += start;
+                    if (start < pos) {
+                        addStyledFragment(builder, fragment.substring(start, pos), style, true);
+                        builder.append(' ');
+                    }
+                    start = pos + 1;
+                    if (start >= fragment.length()) {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Uses sender instructions to build a formatted string.
+     *
+     * <p>Sender list instructions contain compact information about the sender list. Most work that
+     * can be done without knowing how much room will be availble for the sender list is done when
+     * creating the instructions.
+     *
+     * <p>The instructions string consists of tokens separated by SENDER_LIST_SEPARATOR. Here are
+     * the tokens, one per line:<ul>
+     * <li><tt>n</tt></li>
+     * <li><em>int</em>, the number of non-draft messages in the conversation</li>
+     * <li><tt>d</tt</li>
+     * <li><em>int</em>, the number of drafts in the conversation</li>
+     * <li><tt>l</tt></li>
+     * <li><em>literal html to be included in the output</em></li>
+     * <li><tt>s</tt> indicates that the message is sending (in the outbox without errors)</li>
+     * <li><tt>f</tt> indicates that the message failed to send (in the outbox with errors)</li>
+     * <li><em>for each message</em><ul>
+     *   <li><em>int</em>, 0 for read, 1 for unread</li>
+     *   <li><em>int</em>, the priority of the message. Zero is the most important</li>
+     *   <li><em>text</em>, the sender text or blank for messages from 'me'</li>
+     * </ul></li>
+     * <li><tt>e</tt> to indicate that one or more messages have been elided</li>
+     *
+     * <p>The instructions indicate how many messages and drafts are in the conversation and then
+     * describe the most important messages in order, indicating the priority of each message and
+     * whether the message is unread.
+     *
+     * @param instructions instructions as described above
+     * @param senderBuilder the SpannableStringBuilder to append to for sender information
+     * @param statusBuilder the SpannableStringBuilder to append to for status
+     * @param maxChars the number of characters available to display the text
+     * @param unreadStyle the CharacterStyle for unread messages, or null
+     * @param draftsStyle the CharacterStyle for draft messages, or null
+     * @param sendingString the string to use when there are messages scheduled to be sent
+     * @param sendFailedString the string to use when there are messages that mailed to send
+     * @param meString the string to use for messages sent by this user
+     * @param draftString the string to use for "Draft"
+     * @param draftPluralString the string to use for "Drafts"
+     * @param showNumMessages false means do not show the message count
+     * @param onlyShowUnread true means the only information from unread messages should be included
+     */
+    public static synchronized void getSenderSnippet(
+            String instructions, SpannableStringBuilder senderBuilder,
+            SpannableStringBuilder statusBuilder, int maxChars,
+            CharacterStyle unreadStyle,
+            CharacterStyle readStyle,
+            CharacterStyle draftsStyle,
+            CharSequence meString, CharSequence draftString, CharSequence draftPluralString,
+            CharSequence sendingString, CharSequence sendFailedString,
+            boolean forceAllUnread, boolean forceAllRead, boolean allowDraft,
+            boolean showNumMessages, boolean onlyShowUnread) {
+        assert !(forceAllUnread && forceAllRead);
+        boolean unreadStatusIsForced = forceAllUnread || forceAllRead;
+        boolean forcedUnreadStatus = forceAllUnread;
+
+        // Measure each fragment. It's ok to iterate over the entire set of fragments because it is
+        // never a long list, even if there are many senders.
+        final Map<Integer, Integer> priorityToLength = sPriorityToLength;
+        priorityToLength.clear();
+
+        int maxFoundPriority = Integer.MIN_VALUE;
+        int numMessages = 0;
+        int numDrafts = 0;
+        CharSequence draftsFragment = "";
+        CharSequence sendingFragment = "";
+        CharSequence sendFailedFragment = "";
+
+        SENDER_LIST_SPLITTER.setString(instructions);
+        int numFragments = 0;
+        String[] fragments = sSenderFragments;
+        int currentSize = fragments.length;
+        while (SENDER_LIST_SPLITTER.hasNext()) {
+            fragments[numFragments++] = SENDER_LIST_SPLITTER.next();
+            if (numFragments == currentSize) {
+                sSenderFragments = new String[2 * currentSize];
+                System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize);
+                currentSize *= 2;
+                fragments = sSenderFragments;
+            }
+        }
+
+        for (int i = 0; i < numFragments;) {
+            String fragment0 = fragments[i++];
+            if ("".equals(fragment0)) {
+                // This should be the final fragment.
+            } else if (Utils.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
+                // ignore
+            } else if (Utils.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
+                numMessages = Integer.valueOf(fragments[i++]);
+            } else if (Utils.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
+                String numDraftsString = fragments[i++];
+                numDrafts = Integer.parseInt(numDraftsString);
+                draftsFragment = numDrafts == 1 ? draftString :
+                        draftPluralString + " (" + numDraftsString + ")";
+            } else if (Utils.SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) {
+                senderBuilder.append(Html.fromHtml(fragments[i++]));
+                return;
+            } else if (Utils.SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
+                sendingFragment = sendingString;
+            } else if (Utils.SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
+                sendFailedFragment = sendFailedString;
+            } else {
+                final String unreadString = fragment0;
+                final boolean unread = unreadStatusIsForced
+                        ? forcedUnreadStatus : Integer.parseInt(unreadString) != 0;
+                String priorityString = fragments[i++];
+                CharSequence nameString = fragments[i++];
+                if (nameString.length() == 0) nameString = meString;
+                int priority = Integer.parseInt(priorityString);
+
+                // We want to include this entry if
+                //   1) The onlyShowUnread flags is not set
+                //   2) The above flag is set, and the message is unread
+                if (!onlyShowUnread || unread) {
+                    priorityToLength.put(priority, nameString.length());
+                    maxFoundPriority = Math.max(maxFoundPriority, priority);
+                }
+            }
+        }
+        final String numMessagesFragment =
+                (numMessages != 0 && showNumMessages) ?
+                " \u00A0" + Integer.toString(numMessages + numDrafts) : "";
+
+        // Don't allocate fixedFragment unless we need it
+        SpannableStringBuilder fixedFragment = null;
+        int fixedFragmentLength = 0;
+        if (draftsFragment.length() != 0 && allowDraft) {
+            if (fixedFragment == null) {
+                fixedFragment = new SpannableStringBuilder();
+            }
+            fixedFragment.append(draftsFragment);
+            if (draftsStyle != null) {
+                fixedFragment.setSpan(
+                        CharacterStyle.wrap(draftsStyle),
+                        0, fixedFragment.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+        if (sendingFragment.length() != 0) {
+            if (fixedFragment == null) {
+                fixedFragment = new SpannableStringBuilder();
+            }
+            if (fixedFragment.length() != 0) fixedFragment.append(", ");
+            fixedFragment.append(sendingFragment);
+        }
+        if (sendFailedFragment.length() != 0) {
+            if (fixedFragment == null) {
+                fixedFragment = new SpannableStringBuilder();
+            }
+            if (fixedFragment.length() != 0) fixedFragment.append(", ");
+            fixedFragment.append(sendFailedFragment);
+        }
+
+        if (fixedFragment != null) {
+            fixedFragmentLength = fixedFragment.length();
+        }
+        maxChars -= fixedFragmentLength;
+
+        int maxPriorityToInclude = -1; // inclusive
+        int numCharsUsed = numMessagesFragment.length();
+        int numSendersUsed = 0;
+        while (maxPriorityToInclude < maxFoundPriority) {
+            if (priorityToLength.containsKey(maxPriorityToInclude + 1)) {
+                int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1);
+                if (numCharsUsed > 0) length += 2;
+                // We must show at least two senders if they exist. If we don't have space for both
+                // then we will truncate names.
+                if (length > maxChars && numSendersUsed >= 2) {
+                    break;
+                }
+                numCharsUsed = length;
+                numSendersUsed++;
+            }
+            maxPriorityToInclude++;
+        }
+
+        int numCharsToRemovePerWord = 0;
+        if (numCharsUsed > maxChars) {
+            numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed;
+        }
+
+        String lastFragment = null;
+        CharacterStyle lastStyle = null;
+        for (int i = 0; i < numFragments;) {
+            String fragment0 = fragments[i++];
+            if ("".equals(fragment0)) {
+                // This should be the final fragment.
+            } else if (Utils.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
+                if (lastFragment != null) {
+                    addStyledFragment(senderBuilder, lastFragment, lastStyle, false);
+                    senderBuilder.append(" ");
+                    addStyledFragment(senderBuilder, "..", lastStyle, true);
+                    senderBuilder.append(" ");
+                }
+                lastFragment = null;
+            } else if (Utils.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
+                i++;
+            } else if (Utils.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
+                i++;
+            } else if (Utils.SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
+            } else if (Utils.SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
+            } else {
+                final String unreadString = fragment0;
+                final String priorityString = fragments[i++];
+                String nameString = fragments[i++];
+                final boolean unread = unreadStatusIsForced
+                        ? forcedUnreadStatus : Integer.parseInt(unreadString) != 0;
+
+                // We want to include this entry if
+                //   1) The onlyShowUnread flags is not set
+                //   2) The above flag is set, and the message is unread
+                if (!onlyShowUnread || unread) {
+                    if (nameString.length() == 0) {
+                        nameString = meString.toString();
+                    } else {
+                        nameString = Html.fromHtml(nameString).toString();
+                    }
+                    if (numCharsToRemovePerWord != 0) {
+                        nameString = nameString.substring(
+                                0, Math.max(nameString.length() - numCharsToRemovePerWord, 0));
+                    }
+                    final int priority = Integer.parseInt(priorityString);
+                    if (priority <= maxPriorityToInclude) {
+                        if (lastFragment != null && !lastFragment.equals(nameString)) {
+                            addStyledFragment(
+                                    senderBuilder, lastFragment.concat(","), lastStyle, false);
+                            senderBuilder.append(" ");
+                        }
+                        lastFragment = nameString;
+                        lastStyle = unread ? unreadStyle : readStyle;
+                    } else {
+                        if (lastFragment != null) {
+                            addStyledFragment(senderBuilder, lastFragment, lastStyle, false);
+                            // Adjacent spans can cause the TextView in Gmail widget
+                            // confused and leads to weird behavior on scrolling.
+                            // Our workaround here is to separate the spans by
+                            // spaces.
+                            senderBuilder.append(" ");
+                            addStyledFragment(senderBuilder, "..", lastStyle, true);
+                            senderBuilder.append(" ");
+                        }
+                        lastFragment = null;
+                    }
+                }
+            }
+        }
+        if (lastFragment != null) {
+            addStyledFragment(senderBuilder, lastFragment, lastStyle, false);
+        }
+        senderBuilder.append(numMessagesFragment);
+        if (fixedFragmentLength != 0) {
+            statusBuilder.append(fixedFragment);
+        }
+    }
+
+    /**
+     * Clears the notifications for the specified account/folder/conversation.
+     */
+    public static void clearFolderNotification(Context context, Account account, Folder folder) {
+        LogUtils.v(LOG_TAG, "Clearing all notifications for %s/%s", account.name, folder.name);
+        final NotificationMap notificationMap = getNotificationMap(context);
+        final NotificationKey key = new NotificationKey(account, folder);
+        notificationMap.remove(key);
+        notificationMap.saveNotificationMap(context);
+
+        markSeen(context, folder);
+    }
+
+    private static ArrayList<Long> findContacts(Context context, Collection<String> addresses) {
+        ArrayList<String> whereArgs = new ArrayList<String>();
+        StringBuilder whereBuilder = new StringBuilder();
+        String[] questionMarks = new String[addresses.size()];
+
+        whereArgs.addAll(addresses);
+        Arrays.fill(questionMarks, "?");
+        whereBuilder.append(Email.DATA1 + " IN (").
+                append(TextUtils.join(",", questionMarks)).
+                append(")");
+
+        ContentResolver resolver = context.getContentResolver();
+        Cursor c = resolver.query(Email.CONTENT_URI,
+                new String[]{Email.CONTACT_ID}, whereBuilder.toString(),
+                whereArgs.toArray(new String[0]), null);
+
+        ArrayList<Long> contactIds = new ArrayList<Long>();
+        if (c == null) {
+            return contactIds;
+        }
+        try {
+            while (c.moveToNext()) {
+                contactIds.add(c.getLong(0));
+            }
+        } finally {
+            c.close();
+        }
+        return contactIds;
+    }
+
+    private static Bitmap getContactIcon(
+            Context context, String senderAddress, final Folder folder) {
+        if (senderAddress == null) {
+            return null;
+        }
+        Bitmap icon = null;
+        ArrayList<Long> contactIds = findContacts(
+                context, Arrays.asList(new String[] { senderAddress }));
+
+        if (contactIds != null) {
+            // Get the ideal size for this icon.
+            final Resources res = context.getResources();
+            final int idealIconHeight =
+                    res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
+            final int idealIconWidth =
+                    res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
+            for (long id : contactIds) {
+                final Uri contactUri =
+                        ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
+                final Uri photoUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY);
+                final Cursor cursor = context.getContentResolver().query(
+                        photoUri, new String[] { Photo.PHOTO }, null, null, null);
+
+                if (cursor != null) {
+                    try {
+                        if (cursor.moveToFirst()) {
+                            byte[] data = cursor.getBlob(0);
+                            if (data != null) {
+                                icon = BitmapFactory.decodeStream(new ByteArrayInputStream(data));
+                                if (icon != null && icon.getHeight() < idealIconHeight) {
+                                    // We should scale this image to fit the intended size
+                                    icon = Bitmap.createScaledBitmap(
+                                            icon, idealIconWidth, idealIconHeight, true);
+                                }
+                                if (icon != null) {
+                                    break;
+                                }
+                            }
+                        }
+                    } finally {
+                        cursor.close();
+                    }
+                }
+            }
+        }
+        if (icon == null) {
+            // icon should be the default gmail icon.
+            icon = getDefaultNotificationIcon(context, folder, false /* single new message */);
+        }
+        return icon;
+    }
+
+    private static String getMessageBodyWithoutElidedText(final Message message) {
+        return getMessageBodyWithoutElidedText(message.getBodyAsHtml());
+    }
+
+    public static String getMessageBodyWithoutElidedText(String html) {
+        if (TextUtils.isEmpty(html)) {
+            return "";
+        }
+        // Get the html "tree" for this message body
+        final HtmlTree htmlTree = com.android.mail.utils.Utils.getHtmlTree(html);
+        htmlTree.setPlainTextConverterFactory(MESSAGE_CONVERTER_FACTORY);
+
+        return htmlTree.getPlainText();
+    }
+
+    public static void markSeen(final Context context, final Folder folder) {
+        final Uri uri = folder.uri;
+
+        final ContentValues values = new ContentValues(1);
+        values.put(UIProvider.ConversationColumns.SEEN, 1);
+
+        context.getContentResolver().update(uri, values, null, null);
+    }
+
+    /**
+     * Returns a displayable string representing
+     * the message sender. It has a preference toward showing the name,
+     * but will fall back to the address if that is all that is available.
+     */
+    private static String getDisplayableSender(String sender) {
+        final EmailAddress address = EmailAddress.getEmailAddress(sender);
+
+        String displayableSender = address.getName();
+        // If that fails, default to the sender address.
+        if (TextUtils.isEmpty(displayableSender)) {
+            displayableSender = address.getAddress();
+        }
+        // If we were unable to tokenize a name or address,
+        // just use whatever was in the sender.
+        if (TextUtils.isEmpty(displayableSender)) {
+            displayableSender = sender;
+        }
+        return displayableSender;
+    }
+
+    /**
+     * Returns only the address portion of a message sender.
+     */
+    private static String getSenderAddress(String sender) {
+        final EmailAddress address = EmailAddress.getEmailAddress(sender);
+
+        String tokenizedAddress = address.getAddress();
+
+        // If we were unable to tokenize a name or address,
+        // just use whatever was in the sender.
+        if (TextUtils.isEmpty(tokenizedAddress)) {
+            tokenizedAddress = sender;
+        }
+        return tokenizedAddress;
+    }
+
+    public static int getNotificationId(final String account, final Folder folder) {
+        return 1 ^ account.hashCode() ^ folder.hashCode();
+    }
+
+    private static class NotificationKey {
+        public final Account account;
+        public final Folder folder;
+
+        public NotificationKey(Account account, Folder folder) {
+            this.account = account;
+            this.folder = folder;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof NotificationKey)) {
+                return false;
+            }
+            NotificationKey key = (NotificationKey) other;
+            return account.equals(key.account) && folder.equals(key.folder);
+        }
+
+        @Override
+        public String toString() {
+            return account.toString() + " " + folder.name;
+        }
+
+        @Override
+        public int hashCode() {
+            final int accountHashCode = account.hashCode();
+            final int folderHashCode = folder.hashCode();
+            return accountHashCode ^ folderHashCode;
+        }
+    }
+
+    /**
+     * Contains the logic for converting the contents of one HtmlTree into
+     * plaintext.
+     */
+    public static class MailMessagePlainTextConverter extends HtmlTree.DefaultPlainTextConverter {
+        // Strings for parsing html message bodies
+        private static final String ELIDED_TEXT_ELEMENT_NAME = "div";
+        private static final String ELIDED_TEXT_ELEMENT_ATTRIBUTE_NAME = "class";
+        private static final String ELIDED_TEXT_ELEMENT_ATTRIBUTE_CLASS_VALUE = "elided-text";
+
+        private static final HTML.Attribute ELIDED_TEXT_ATTRIBUTE =
+                new HTML.Attribute(ELIDED_TEXT_ELEMENT_ATTRIBUTE_NAME, HTML.Attribute.NO_TYPE);
+
+        private static final HtmlDocument.Node ELIDED_TEXT_REPLACEMENT_NODE =
+                HtmlDocument.createSelfTerminatingTag(HTML4.BR_ELEMENT, null, null, null);
+
+        private int mEndNodeElidedTextBlock = -1;
+
+        @Override
+        public void addNode(HtmlDocument.Node n, int nodeNum, int endNum) {
+            // If we are in the middle of an elided text block, don't add this node
+            if (nodeNum < mEndNodeElidedTextBlock) {
+                return;
+            } else if (nodeNum == mEndNodeElidedTextBlock) {
+                super.addNode(ELIDED_TEXT_REPLACEMENT_NODE, nodeNum, endNum);
+                return;
+            }
+
+            // If this tag starts another elided text block, we want to remember the end
+            if (n instanceof HtmlDocument.Tag) {
+                boolean foundElidedTextTag = false;
+                final HtmlDocument.Tag htmlTag = (HtmlDocument.Tag)n;
+                final HTML.Element htmlElement = htmlTag.getElement();
+                if (ELIDED_TEXT_ELEMENT_NAME.equals(htmlElement.getName())) {
+                    // Make sure that the class is what is expected
+                    final List<HtmlDocument.TagAttribute> attributes =
+                            htmlTag.getAttributes(ELIDED_TEXT_ATTRIBUTE);
+                    for (HtmlDocument.TagAttribute attribute : attributes) {
+                        if (ELIDED_TEXT_ELEMENT_ATTRIBUTE_CLASS_VALUE.equals(
+                                attribute.getValue())) {
+                            // Found an "elided-text" div.  Remember information about this tag
+                            mEndNodeElidedTextBlock = endNum;
+                            foundElidedTextTag = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (foundElidedTextTag) {
+                    return;
+                }
+            }
+
+            super.addNode(n, nodeNum, endNum);
+        }
+    }
+
+    /**
+     * During account setup in Email, we may not have an inbox yet, so the notification setting had
+     * to be stored in {@link AccountPreferences}. If it is still there, we need to move it to the
+     * {@link FolderPreferences} now.
+     */
+    public static void moveNotificationSetting(final AccountPreferences accountPreferences,
+            final FolderPreferences folderPreferences) {
+        if (accountPreferences.isDefaultInboxNotificationsEnabledSet()) {
+            // If this setting has been changed some other way, don't overwrite it
+            if (!folderPreferences.isNotificationsEnabledSet()) {
+                final boolean notificationsEnabled =
+                        accountPreferences.getDefaultInboxNotificationsEnabled();
+
+                folderPreferences.setNotificationsEnabled(notificationsEnabled);
+            }
+
+            accountPreferences.clearDefaultInboxNotificationsEnabled();
+        }
+    }
+}
diff --git a/src/com/android/mail/utils/Observable.java b/src/com/android/mail/utils/Observable.java
new file mode 100644
index 0000000..81e8d83
--- /dev/null
+++ b/src/com/android/mail/utils/Observable.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mail.utils;
+
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+
+/**
+ * A Utility class to register observers and return logging and counts for the number of registered
+ * observers.
+ */
+public class Observable extends DataSetObservable {
+    protected static final String LOG_TAG = LogTag.getLogTag();
+    private final String mName;
+
+    public Observable(String name) {
+        mName = name;
+    }
+
+    @Override
+    public void registerObserver(DataSetObserver observer) {
+        final int count = mObservers.size();
+        super.registerObserver(observer);
+        LogUtils.d(LOG_TAG, "IN register(%s)Observer: %s before=%d after=%d",
+                mName,  observer, count, mObservers.size());
+    }
+
+    @Override
+    public void unregisterObserver(DataSetObserver observer) {
+        final int count = mObservers.size();
+        super.unregisterObserver(observer);
+        LogUtils.d(LOG_TAG, "IN unregister(%s)Observer: %s before=%d after=%d",
+                mName, observer, count, mObservers.size());
+    }
+}
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 8e8e860..a0453a5 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -26,11 +26,11 @@
 import android.app.SearchManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Typeface;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -59,12 +59,12 @@
 import com.android.mail.R;
 import com.android.mail.browse.ConversationCursor;
 import com.android.mail.compose.ComposeActivity;
+import com.android.mail.perf.SimpleTimer;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.providers.UIProvider.EditSettingsExtras;
-import com.android.mail.ui.ControllableActivity;
 import com.android.mail.ui.FeedbackEnabledActivity;
 
 import org.json.JSONObject;
@@ -97,7 +97,6 @@
     public static final String EXTRA_FOLDER_URI = "folderUri";
     public static final String EXTRA_COMPOSE_URI = "composeUri";
     public static final String EXTRA_CONVERSATION = "conversationUri";
-    public static final String EXTRA_FOLDER = "folder";
 
     /** Extra tag for debugging the blank fragment problem. */
     public static final String VIEW_DEBUGGING_TAG = "MailBlankFragment";
@@ -119,8 +118,14 @@
 
     private static final int SCALED_SCREENSHOT_MAX_HEIGHT_WIDTH = 600;
 
+    private static final String APP_VERSION_QUERY_PARAMETER = "appVersion";
+
     private static final String LOG_TAG = LogTag.getLogTag();
 
+    public static final boolean ENABLE_CONV_LOAD_TIMER = false;
+    public static final SimpleTimer sConvLoadTimer =
+            new SimpleTimer(ENABLE_CONV_LOAD_TIMER).withSessionName("ConvLoadTimer");
+
     public static boolean isRunningJellybeanOrLater() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
     }
@@ -661,14 +666,15 @@
      * @param account
      * @return
      */
-    public static Intent createViewConversationIntent(Conversation conversation, Folder folder,
-            Account account) {
+    public static Intent createViewConversationIntent(final Context context,
+            Conversation conversation, final Uri folderUri, Account account) {
         final Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
-                Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-        intent.setDataAndType(conversation.uri, account.mimeType);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+        intent.setDataAndType(appendVersionQueryParameter(context, conversation.uri),
+                account.mimeType);
         intent.putExtra(Utils.EXTRA_ACCOUNT, account.serialize());
-        intent.putExtra(Utils.EXTRA_FOLDER, Folder.toString(folder));
+        intent.putExtra(Utils.EXTRA_FOLDER_URI, folderUri);
         intent.putExtra(Utils.EXTRA_CONVERSATION, conversation);
         return intent;
     }
@@ -680,18 +686,19 @@
      * @param account
      * @return
      */
-    public static Intent createViewFolderIntent(Folder folder, Account account) {
-        if (folder == null || account == null) {
-            LogUtils.wtf(
-                    LOG_TAG, "Utils.createViewFolderIntent(%s,%s): Bad input", folder, account);
+    public static Intent createViewFolderIntent(final Context context, final Uri folderUri,
+            Account account) {
+        if (folderUri == null || account == null) {
+            LogUtils.wtf(LOG_TAG, "Utils.createViewFolderIntent(%s,%s): Bad input", folderUri,
+                    account);
             return null;
         }
         final Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
                 | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-        intent.setDataAndType(folder.uri, account.mimeType);
+        intent.setDataAndType(appendVersionQueryParameter(context, folderUri), account.mimeType);
         intent.putExtra(Utils.EXTRA_ACCOUNT, account.serialize());
-        intent.putExtra(Utils.EXTRA_FOLDER, Folder.toString(folder));
+        intent.putExtra(Utils.EXTRA_FOLDER_URI, folderUri);
         return intent;
     }
 
@@ -825,17 +832,18 @@
      * Show the settings screen for the supplied account.
      */
      public static void showFolderSettings(Context context, Account account, Folder folder) {
-         if (account == null || folder == null) {
-             LogUtils.e(LOG_TAG, "Invalid attempt to show folder settings. account: %s folder: %s",
-                     account, folder);
-             return;
-         }
-         final Intent settingsIntent = new Intent(Intent.ACTION_EDIT, account.settingsIntentUri);
+        if (account == null || folder == null) {
+            LogUtils.e(LOG_TAG, "Invalid attempt to show folder settings. account: %s folder: %s",
+                    account, folder);
+            return;
+        }
+        final Intent settingsIntent = new Intent(Intent.ACTION_EDIT,
+                appendVersionQueryParameter(context, account.settingsIntentUri));
 
-         settingsIntent.putExtra(EditSettingsExtras.EXTRA_ACCOUNT, account);
-         settingsIntent.putExtra(EditSettingsExtras.EXTRA_FOLDER, folder);
-         settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-         context.startActivity(settingsIntent);
+        settingsIntent.putExtra(EditSettingsExtras.EXTRA_ACCOUNT, account);
+        settingsIntent.putExtra(EditSettingsExtras.EXTRA_FOLDER, folder);
+        settingsIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        context.startActivity(settingsIntent);
     }
 
     /**
@@ -859,7 +867,7 @@
      */
     public static void sendFeedback(FeedbackEnabledActivity activity, Account account,
             boolean reportingProblem) {
-        if (activity != null && account != null && account.sendFeedbackIntentUri != null) {
+        if (activity != null && account != null && !isEmpty(account.sendFeedbackIntentUri)) {
             final Bundle optionalExtras = new Bundle(2);
             optionalExtras.putBoolean(
                     UIProvider.SendFeedbackExtras.EXTRA_REPORTING_PROBLEM, reportingProblem);
@@ -1228,4 +1236,19 @@
         final Intent intent = ComposeActivity.createForwardIntent(context, account, messageUri);
         return intent;
     }
+
+    public static Uri appendVersionQueryParameter(final Context context, final Uri uri) {
+        int appVersion = 0;
+
+        try {
+            final PackageInfo packageInfo =
+                    context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+            appVersion = packageInfo.versionCode;
+        } catch (final NameNotFoundException e) {
+            LogUtils.wtf(LOG_TAG, e, "Couldn't find our own PackageInfo");
+        }
+
+        return uri.buildUpon().appendQueryParameter(APP_VERSION_QUERY_PARAMETER,
+                Integer.toString(appVersion)).build();
+    }
 }
diff --git a/src/com/android/mail/widget/BaseWidgetProvider.java b/src/com/android/mail/widget/BaseWidgetProvider.java
index c02c5ae..d3f3364 100644
--- a/src/com/android/mail/widget/BaseWidgetProvider.java
+++ b/src/com/android/mail/widget/BaseWidgetProvider.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.view.View;
@@ -47,12 +48,14 @@
 
 public abstract class BaseWidgetProvider extends AppWidgetProvider {
     public static final String EXTRA_ACCOUNT = "account";
-    public static final String EXTRA_FOLDER = "folder";
+    public static final String EXTRA_FOLDER_URI = "folder-uri";
+    public static final String EXTRA_FOLDER_CONVERSATION_LIST_URI = "folder-conversation-list-uri";
+    public static final String EXTRA_FOLDER_DISPLAY_NAME = "folder-display-name";
     public static final String EXTRA_UNREAD = "unread";
     public static final String EXTRA_UPDATE_ALL_WIDGETS = "update-all-widgets";
     public static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
 
-    static final String ACCOUNT_FOLDER_PREFERENCE_SEPARATOR = " ";
+    public static final String ACCOUNT_FOLDER_PREFERENCE_SEPARATOR = " ";
 
 
     protected static final String ACTION_UPDATE_WIDGET = "com.android.mail.ACTION_UPDATE_WIDGET";
@@ -118,9 +121,14 @@
         if (ACTION_UPDATE_WIDGET.equals(action)) {
             final int widgetId = intent.getIntExtra(EXTRA_WIDGET_ID, -1);
             final Account account = Account.newinstance(intent.getStringExtra(EXTRA_ACCOUNT));
-            Folder folder = Folder.fromString(intent.getStringExtra(EXTRA_FOLDER));
-            if (widgetId != -1 && account != null && folder != null) {
-                updateWidgetInternal(context, widgetId, account, folder);
+            final Uri folderUri = intent.getParcelableExtra(EXTRA_FOLDER_URI);
+            final Uri folderConversationListUri =
+                    intent.getParcelableExtra(EXTRA_FOLDER_CONVERSATION_LIST_URI);
+            final String folderDisplayName = intent.getStringExtra(EXTRA_FOLDER_DISPLAY_NAME);
+
+            if (widgetId != -1 && account != null && folderUri != null) {
+                updateWidgetInternal(context, widgetId, account, folderUri,
+                        folderConversationListUri, folderDisplayName);
             }
         } else if (Utils.ACTION_NOTIFY_DATASET_CHANGED.equals(action)) {
             // Receive notification for a certain account.
@@ -173,55 +181,75 @@
 
         super.onUpdate(context, appWidgetManager, appWidgetIds);
         // Update each of the widgets with a remote adapter
-        ContentResolver resolver = context.getContentResolver();
-        for (int i = 0; i < appWidgetIds.length; ++i) {
-            // Get the account for this widget from preference
-            final String accountFolder = MailPrefs.get(context).getWidgetConfiguration(
-                    appWidgetIds[i]);
-            String accountUri = null;
-            Uri folderUri = null;
-            if (!TextUtils.isEmpty(accountFolder)) {
-                final String[] parsedInfo = TextUtils.split(accountFolder,
-                        ACCOUNT_FOLDER_PREFERENCE_SEPARATOR);
-                if (parsedInfo.length == 2) {
-                    accountUri = parsedInfo[0];
-                    folderUri = Uri.parse(parsedInfo[1]);
-                } else {
-                    accountUri = accountFolder;
-                    folderUri =  Uri.EMPTY;
-                }
-            }
-            // account will be null the first time a widget is created. This is
-            // OK, as isAccountValid will return false, allowing the widget to
-            // be configured.
 
-            // Lookup the account by URI.
-            Account account = null;
-            if (!TextUtils.isEmpty(accountUri)) {
-                account = getAccountObject(context, accountUri);
-            }
-            if (Utils.isEmpty(folderUri) && account != null) {
-                folderUri = account.settings.defaultInbox;
-            }
-            Folder folder = null;
-            if (!Utils.isEmpty(folderUri)) {
-                Cursor folderCursor = null;
-                try {
-                    folderCursor = resolver.query(folderUri,
-                            UIProvider.FOLDERS_PROJECTION, null, null, null);
-                    if (folderCursor != null) {
+        new BulkUpdateAsyncTask(context, appWidgetIds).execute((Void[]) null);
+    }
+
+    private class BulkUpdateAsyncTask extends AsyncTask<Void, Void, Void> {
+        private final Context mContext;
+        private final int[] mAppWidgetIds;
+
+        public BulkUpdateAsyncTask(final Context context, final int[] appWidgetIds) {
+            mContext = context;
+            mAppWidgetIds = appWidgetIds;
+        }
+
+        @Override
+        protected Void doInBackground(final Void... params) {
+            for (int i = 0; i < mAppWidgetIds.length; ++i) {
+                // Get the account for this widget from preference
+                final String accountFolder = MailPrefs.get(mContext).getWidgetConfiguration(
+                        mAppWidgetIds[i]);
+                String accountUri = null;
+                Uri folderUri = null;
+                if (!TextUtils.isEmpty(accountFolder)) {
+                    final String[] parsedInfo = TextUtils.split(accountFolder,
+                            ACCOUNT_FOLDER_PREFERENCE_SEPARATOR);
+                    if (parsedInfo.length == 2) {
+                        accountUri = parsedInfo[0];
+                        folderUri = Uri.parse(parsedInfo[1]);
+                    } else {
+                        accountUri = accountFolder;
+                        folderUri =  Uri.EMPTY;
+                    }
+                }
+                // account will be null the first time a widget is created. This is
+                // OK, as isAccountValid will return false, allowing the widget to
+                // be configured.
+
+                // Lookup the account by URI.
+                Account account = null;
+                if (!TextUtils.isEmpty(accountUri)) {
+                    account = getAccountObject(mContext, accountUri);
+                }
+                if (Utils.isEmpty(folderUri) && account != null) {
+                    folderUri = account.settings.defaultInbox;
+                }
+
+                Folder folder = null;
+
+                if (folderUri != null) {
+                    final Cursor folderCursor =
+                            mContext.getContentResolver().query(folderUri,
+                                    UIProvider.FOLDERS_PROJECTION, null, null, null);
+
+                    try {
                         if (folderCursor.moveToFirst()) {
                             folder = new Folder(folderCursor);
                         }
-                    }
-                } finally {
-                    if (folderCursor != null) {
+                    } finally {
                         folderCursor.close();
                     }
                 }
+
+                updateWidgetInternal(mContext, mAppWidgetIds[i], account, folderUri,
+                        folder == null ? null : folder.conversationListUri, folder == null ? null
+                                : folder.name);
             }
-            updateWidgetInternal(context, appWidgetIds[i], account, folder);
+
+            return null;
         }
+
     }
 
     protected Account getAccountObject(Context context, String accountUri) {
@@ -248,10 +276,11 @@
      * Update the widget appWidgetId with the given account and folder
      */
     public static void updateWidget(Context context, int appWidgetId, Account account,
-                Folder folder) {
-        if (account == null || folder == null) {
+            final Uri folderUri, final Uri folderConversationListUri,
+            final String folderDisplayName) {
+        if (account == null || folderUri == null) {
             LogUtils.e(LOG_TAG,
-                    "Missing account or folder.  account: %s folder %s", account, folder);
+                    "Missing account or folder.  account: %s folder %s", account, folderUri);
             return;
         }
         final Intent updateWidgetIntent = new Intent(ACTION_UPDATE_WIDGET);
@@ -259,17 +288,20 @@
         updateWidgetIntent.setType(account.mimeType);
         updateWidgetIntent.putExtra(EXTRA_WIDGET_ID, appWidgetId);
         updateWidgetIntent.putExtra(EXTRA_ACCOUNT, account.serialize());
-        updateWidgetIntent.putExtra(EXTRA_FOLDER, Folder.toString(folder));
+        updateWidgetIntent.putExtra(EXTRA_FOLDER_URI, folderUri);;
+        updateWidgetIntent.putExtra(EXTRA_FOLDER_CONVERSATION_LIST_URI, folderConversationListUri);
+        updateWidgetIntent.putExtra(EXTRA_FOLDER_DISPLAY_NAME, folderDisplayName);
 
         context.sendBroadcast(updateWidgetIntent);
     }
 
     protected void updateWidgetInternal(Context context, int appWidgetId, Account account,
-                Folder folder) {
+            final Uri folderUri, final Uri folderConversationListUri,
+            final String folderDisplayName) {
         RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
         final boolean isAccountValid = isAccountValid(context, account);
 
-        if (!isAccountValid || folder == null) {
+        if (!isAccountValid || folderUri == null) {
             // Widget has not been configured yet
             remoteViews.setViewVisibility(R.id.widget_folder, View.GONE);
             remoteViews.setViewVisibility(R.id.widget_account, View.GONE);
@@ -291,7 +323,8 @@
             remoteViews.setOnClickPendingIntent(R.id.widget_configuration, clickIntent);
         } else {
             // Set folder to a space here to avoid flicker.
-            configureValidAccountWidget(context, remoteViews, appWidgetId, account, folder, " ");
+            configureValidAccountWidget(context, remoteViews, appWidgetId, account, folderUri,
+                    folderConversationListUri, folderDisplayName == null ? " " : folderDisplayName);
 
         }
         AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
@@ -310,9 +343,10 @@
     }
 
     protected void configureValidAccountWidget(Context context, RemoteViews remoteViews,
-                int appWidgetId, Account account, Folder folder, String folderDisplayName) {
+            int appWidgetId, Account account, final Uri folderUri,
+            final Uri folderConversationListUri, String folderDisplayName) {
         WidgetService.configureValidAccountWidget(context, remoteViews, appWidgetId, account,
-                folder, folderDisplayName, WidgetService.class);
+                folderUri, folderConversationListUri, folderDisplayName, WidgetService.class);
     }
 
     private final void migrateAllLegacyWidgetInformation(Context context) {
@@ -334,5 +368,4 @@
      * Abstract method allowing extending classes to perform widget migration
      */
     protected abstract void migrateLegacyWidgetInformation(Context context, int widgetId);
-
 }
diff --git a/src/com/android/mail/widget/WidgetConversationViewBuilder.java b/src/com/android/mail/widget/WidgetConversationViewBuilder.java
index 6c30570..4a4ae64 100644
--- a/src/com/android/mail/widget/WidgetConversationViewBuilder.java
+++ b/src/com/android/mail/widget/WidgetConversationViewBuilder.java
@@ -17,7 +17,6 @@
 package com.android.mail.widget;
 
 import com.android.mail.R;
-import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.ui.FolderDisplayer;
@@ -27,6 +26,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Typeface;
+import android.net.Uri;
 import android.text.Spannable;
 import android.text.SpannableStringBuilder;
 import android.text.style.AbsoluteSizeSpan;
@@ -66,11 +66,11 @@
          * Load Conversation Labels
          */
         @Override
-        public void loadConversationFolders(Conversation conv, Folder ignoreFolder) {
-            super.loadConversationFolders(conv, ignoreFolder);
+        public void loadConversationFolders(Conversation conv, final Uri ignoreFolderUri) {
+            super.loadConversationFolders(conv, ignoreFolderUri);
         }
 
-        private int getFolderViewId(int position) {
+        private static int getFolderViewId(int position) {
             switch (position) {
                 case 0:
                     return R.id.widget_folder_0;
@@ -111,7 +111,7 @@
     /*
      * Get font sizes and bitmaps from Resources
      */
-    public WidgetConversationViewBuilder(Context context, Account account) {
+    public WidgetConversationViewBuilder(Context context) {
         mContext = context;
         Resources res = context.getResources();
 
@@ -131,7 +131,7 @@
     /*
      * Add size, color and style to a given text
      */
-    private CharSequence addStyle(CharSequence text, int size, int color) {
+    private static CharSequence addStyle(CharSequence text, int size, int color) {
         SpannableStringBuilder builder = new SpannableStringBuilder(text);
         builder.setSpan(
                 new AbsoluteSizeSpan(size), 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
@@ -145,9 +145,8 @@
     /*
      * Return the full View
      */
-    public RemoteViews getStyledView(CharSequence status, CharSequence date,
-            Conversation conversation, Folder currentFolder, SpannableStringBuilder senders,
-            String filteredSubject) {
+    public RemoteViews getStyledView(CharSequence date, Conversation conversation,
+            final Uri folderUri, SpannableStringBuilder senders, String filteredSubject) {
 
         final boolean isUnread = !conversation.read;
         String snippet = conversation.getSnippet();
@@ -195,7 +194,7 @@
         }
         if (mContext.getResources().getBoolean(R.bool.display_folder_colors_in_widget)) {
             mFolderDisplayer = new WidgetFolderDisplayer(mContext);
-            mFolderDisplayer.loadConversationFolders(conversation, currentFolder);
+            mFolderDisplayer.loadConversationFolders(conversation, folderUri);
             mFolderDisplayer.displayFolders(remoteViews);
         }
 
diff --git a/src/com/android/mail/widget/WidgetService.java b/src/com/android/mail/widget/WidgetService.java
index fd31bf1..3db1dcf 100644
--- a/src/com/android/mail/widget/WidgetService.java
+++ b/src/com/android/mail/widget/WidgetService.java
@@ -43,8 +43,6 @@
 import com.android.mail.preferences.MailPrefs;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
-import com.android.mail.providers.ConversationInfo;
-import com.android.mail.providers.Folder;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.providers.UIProvider.ConversationListQueryParameters;
 import com.android.mail.utils.AccountUtils;
@@ -68,19 +66,19 @@
         return new MailFactory(getApplicationContext(), intent, this);
     }
 
-
     protected void configureValidAccountWidget(Context context, RemoteViews remoteViews,
-            int appWidgetId, Account account, Folder folder, String folderName) {
-        configureValidAccountWidget(context, remoteViews, appWidgetId, account, folder, folderName,
-                WidgetService.class);
+            int appWidgetId, Account account, final Uri folderUri,
+            final Uri folderConversationListUri, String folderName) {
+        configureValidAccountWidget(context, remoteViews, appWidgetId, account, folderUri,
+                folderConversationListUri, folderName, WidgetService.class);
     }
 
     /**
      * Modifies the remoteView for the given account and folder.
      */
     public static void configureValidAccountWidget(Context context, RemoteViews remoteViews,
-            int appWidgetId, Account account, Folder folder, String folderDisplayName,
-            Class<?> widgetService) {
+            int appWidgetId, Account account, final Uri folderUri,
+            final Uri folderConversationListUri, String folderDisplayName, Class<?> widgetService) {
         remoteViews.setViewVisibility(R.id.widget_folder, View.VISIBLE);
 
         // If the folder or account name are empty, we don't want to overwrite the valid data that
@@ -107,11 +105,12 @@
         remoteViews.setEmptyView(R.id.conversation_list, R.id.empty_conversation_list);
 
         WidgetService.configureValidWidgetIntents(context, remoteViews, appWidgetId, account,
-                folder, folderDisplayName, widgetService);
+                folderUri, folderConversationListUri, folderDisplayName, widgetService);
     }
 
     public static void configureValidWidgetIntents(Context context, RemoteViews remoteViews,
-            int appWidgetId, Account account, Folder folder, String folderDisplayName,
+            int appWidgetId, Account account, final Uri folderUri,
+            final Uri folderConversationListUri, final String folderDisplayName,
             Class<?> serviceClass) {
         remoteViews.setViewVisibility(R.id.widget_configuration, View.GONE);
 
@@ -120,11 +119,14 @@
         final Intent intent = new Intent(context, serviceClass);
         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
         intent.putExtra(BaseWidgetProvider.EXTRA_ACCOUNT, account.serialize());
-        intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER, Folder.toString(folder));
+        intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER_URI, folderUri);
+        intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER_CONVERSATION_LIST_URI,
+                folderConversationListUri);
+        intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER_DISPLAY_NAME, folderDisplayName);
         intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
         remoteViews.setRemoteAdapter(R.id.conversation_list, intent);
         // Open mail app when click on header
-        final Intent mailIntent = Utils.createViewFolderIntent(folder, account);
+        final Intent mailIntent = Utils.createViewFolderIntent(context, folderUri, account);
         PendingIntent clickIntent = PendingIntent.getActivity(context, 0, mailIntent,
                 PendingIntent.FLAG_UPDATE_CURRENT);
         remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
@@ -159,15 +161,14 @@
      * Persists the information about the specified widget.
      */
     public static void saveWidgetInformation(Context context, int appWidgetId, Account account,
-                Folder folder) {
-        MailPrefs.get(context).configureWidget(appWidgetId, account, folder);
+                final String folderUri) {
+        MailPrefs.get(context).configureWidget(appWidgetId, account, folderUri);
     }
 
     /**
      * Returns true if this widget id has been configured and saved.
      */
-    public boolean isWidgetConfigured(Context context, int appWidgetId, Account account,
-            Folder folder) {
+    public boolean isWidgetConfigured(Context context, int appWidgetId, Account account) {
         return isAccountValid(context, account) &&
                 MailPrefs.get(context).isWidgetConfigured(appWidgetId);
     }
@@ -198,7 +199,9 @@
         private final Context mContext;
         private final int mAppWidgetId;
         private final Account mAccount;
-        private Folder mFolder;
+        private final Uri mFolderUri;
+        private final Uri mFolderConversationListUri;
+        private final String mFolderDisplayName;
         private final WidgetConversationViewBuilder mWidgetConversationViewBuilder;
         private CursorLoader mConversationCursorLoader;
         private Cursor mConversationCursor;
@@ -218,9 +221,12 @@
             mAppWidgetId = intent.getIntExtra(
                     AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
             mAccount = Account.newinstance(intent.getStringExtra(WidgetProvider.EXTRA_ACCOUNT));
-            mFolder = Folder.fromString(intent.getStringExtra(WidgetProvider.EXTRA_FOLDER));
-            mWidgetConversationViewBuilder = new WidgetConversationViewBuilder(context,
-                    mAccount);
+            mFolderUri = intent.getParcelableExtra(WidgetProvider.EXTRA_FOLDER_URI);
+            mFolderConversationListUri =
+                    intent.getParcelableExtra(WidgetProvider.EXTRA_FOLDER_CONVERSATION_LIST_URI);
+            mFolderDisplayName = intent.getStringExtra(WidgetProvider.EXTRA_FOLDER_DISPLAY_NAME);
+
+            mWidgetConversationViewBuilder = new WidgetConversationViewBuilder(context);
             mService = service;
         }
 
@@ -228,12 +234,13 @@
         public void onCreate() {
 
             // Save the map between widgetId and account to preference
-            saveWidgetInformation(mContext, mAppWidgetId, mAccount, mFolder);
+            saveWidgetInformation(mContext, mAppWidgetId, mAccount, mFolderUri.toString());
 
             // If the account of this widget has been removed, we want to update the widget to
             // "Tap to configure" mode.
-            if (!mService.isWidgetConfigured(mContext, mAppWidgetId, mAccount, mFolder)) {
-                BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolder);
+            if (!mService.isWidgetConfigured(mContext, mAppWidgetId, mAccount)) {
+                BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderUri,
+                        mFolderConversationListUri, mFolderDisplayName);
             }
 
             mFolderInformationShown = false;
@@ -244,7 +251,7 @@
             // the user made locally, the default policy of the UI provider is to not send
             // notifications for.  But in this case, since the widget is not using the
             // ConversationCursor instance that the UI is using, the widget would not be updated.
-            final Uri.Builder builder = mFolder.conversationListUri.buildUpon();
+            final Uri.Builder builder = mFolderConversationListUri.buildUpon();
             final String maxConversations = Integer.toString(MAX_CONVERSATIONS_COUNT);
             final Uri widgetConversationQueryUri = builder
                     .appendQueryParameter(ConversationListQueryParameters.LIMIT, maxConversations)
@@ -262,7 +269,7 @@
             mConversationCursorLoader.startLoading();
             mSendersSplitToken = res.getString(R.string.senders_split_token);
             mElidedPaddingToken = res.getString(R.string.elided_padding_token);
-            mFolderLoader = new CursorLoader(mContext, mFolder.uri, UIProvider.FOLDERS_PROJECTION,
+            mFolderLoader = new CursorLoader(mContext, mFolderUri, UIProvider.FOLDERS_PROJECTION,
                     null, null, null);
             mFolderLoader.registerListener(FOLDER_LOADER_ID, this);
             mFolderUpdateHandler = new FolderUpdateHandler(
@@ -358,14 +365,12 @@
                 Conversation conversation = new Conversation(mConversationCursor);
                 // Split the senders and status from the instructions.
                 SpannableStringBuilder senderBuilder = new SpannableStringBuilder();
-                SpannableStringBuilder statusBuilder = new SpannableStringBuilder();
 
                 if (conversation.conversationInfo != null) {
                     ArrayList<SpannableString> senders = new ArrayList<SpannableString>();
                     SendersView.format(mContext, conversation.conversationInfo, "",
-                            MAX_SENDERS_LENGTH, senders, null, null, mAccount.name);
-                    senderBuilder = ellipsizeStyledSenders(conversation.conversationInfo,
-                            MAX_SENDERS_LENGTH, senders);
+                            MAX_SENDERS_LENGTH, senders, null, null, mAccount.name, true);
+                    senderBuilder = ellipsizeStyledSenders(senders);
                 } else {
                     senderBuilder.append(conversation.senders);
                     senderBuilder.setSpan(conversation.read ? getReadStyle() : getUnreadStyle(), 0,
@@ -376,13 +381,14 @@
                         conversation.dateMs);
 
                 // Load up our remote view.
-                RemoteViews remoteViews = mWidgetConversationViewBuilder.getStyledView(
-                        statusBuilder, date, conversation, mFolder, senderBuilder,
-                        filterTag(conversation.subject));
+                RemoteViews remoteViews =
+                        mWidgetConversationViewBuilder.getStyledView(date, conversation,
+                                mFolderUri, senderBuilder, filterTag(conversation.subject));
 
                 // On click intent.
                 remoteViews.setOnClickFillInIntent(R.id.widget_conversation,
-                        Utils.createViewConversationIntent(conversation, mFolder, mAccount));
+                        Utils.createViewConversationIntent(mContext, conversation, mFolderUri,
+                                mAccount));
 
                 return remoteViews;
             }
@@ -403,7 +409,7 @@
             return CharacterStyle.wrap(mReadStyle);
         }
 
-        private SpannableStringBuilder ellipsizeStyledSenders(ConversationInfo info, int maxChars,
+        private SpannableStringBuilder ellipsizeStyledSenders(
                 ArrayList<SpannableString> styledSenders) {
             SpannableStringBuilder builder = new SpannableStringBuilder();
             SpannableString prevSender = null;
@@ -429,7 +435,7 @@
             return builder;
         }
 
-        private SpannableString copyStyles(CharacterStyle[] spans, CharSequence newText) {
+        private static SpannableString copyStyles(CharacterStyle[] spans, CharSequence newText) {
             SpannableString s = new SpannableString(newText);
             if (spans != null && spans.length > 0) {
                 s.setSpan(spans[0], 0, s.length(), 0);
@@ -445,7 +451,7 @@
             view.setTextViewText(
                     R.id.loading_text, mContext.getText(R.string.view_more_conversations));
             view.setOnClickFillInIntent(R.id.widget_loading,
-                    Utils.createViewFolderIntent(mFolder, mAccount));
+                    Utils.createViewFolderIntent(mContext, mFolderUri, mAccount));
             return view;
         }
 
@@ -493,8 +499,8 @@
                     // manager doesn't cache the state of the remote views when doing a partial
                     // widget update. This causes the folder name to be shown as blank if the state
                     // of the widget is restored.
-                    mService.configureValidAccountWidget(
-                            mContext, remoteViews, mAppWidgetId, mAccount, mFolder, folderName);
+                    mService.configureValidAccountWidget(mContext, remoteViews, mAppWidgetId,
+                            mAccount, mFolderUri, mFolderConversationListUri, folderName);
                     appWidgetManager.updateAppWidget(mAppWidgetId, remoteViews);
                     mFolderInformationShown = true;
                 }
@@ -539,7 +545,7 @@
          * Returns a boolean indicating whether this cursor has valid data.
          * Note: This seeks to the first position in the cursor
          */
-        private boolean isDataValid(Cursor cursor) {
+        private static boolean isDataValid(Cursor cursor) {
             return cursor != null && !cursor.isClosed() && cursor.moveToFirst();
         }
 
diff --git a/tests/src/com/android/mail/EmailAddressTest.java b/tests/src/com/android/mail/EmailAddressTest.java
new file mode 100644
index 0000000..c4e0a94
--- /dev/null
+++ b/tests/src/com/android/mail/EmailAddressTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.mail;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class EmailAddressTest extends AndroidTestCase {
+
+    public void testNameRegex() {
+        {
+            EmailAddress email = EmailAddress.getEmailAddress("email@gmail.com");
+            assertEquals("", email.getName());
+        }
+
+        {
+            EmailAddress nameKnown = EmailAddress.getEmailAddress("john doe <coolguy@doe.com>");
+            assertEquals("john doe", nameKnown.getName());
+        }
+
+        {
+            EmailAddress withQuotes = EmailAddress
+                    .getEmailAddress("\"john doe\" <coolguy@doe.com>");
+            assertEquals("john doe", withQuotes.getName());
+        }
+
+        {
+            EmailAddress noSpace = EmailAddress.getEmailAddress("john doe<coolguy@doe.com>");
+            assertEquals("john doe", noSpace.getName());
+        }
+
+        {
+            EmailAddress noSpaceWithQuotes = EmailAddress
+                    .getEmailAddress("\"john doe\"<coolguy@doe.com>");
+            assertEquals("john doe", noSpaceWithQuotes.getName());
+        }
+
+    }
+
+    /**
+     * Test the parsing of email addresses
+     */
+    public void testEmailAddressParsing() {
+        EmailAddress address = EmailAddress.getEmailAddress("test name <test@localhost.com>");
+        assertEquals("test name", address.getName());
+        assertEquals("test@localhost.com", address.getAddress());
+
+        address = EmailAddress.getEmailAddress("\"test name\" <test@localhost.com>");
+        assertEquals("test name", address.getName());
+        assertEquals("test@localhost.com", address.getAddress());
+
+        address = EmailAddress.getEmailAddress("<test@localhost.com>");
+        assertEquals("", address.getName());
+        assertEquals("test@localhost.com", address.getAddress());
+
+        address = EmailAddress.getEmailAddress("test@localhost.com");
+        assertEquals("", address.getName());
+        assertEquals("test@localhost.com", address.getAddress());
+
+        address = EmailAddress.getEmailAddress("O'brian <test@localhost.com>");
+        assertEquals("O'brian", address.getName());
+        assertEquals("test@localhost.com", address.getAddress());
+
+        address = EmailAddress.getEmailAddress("\"O'brian\" <test@localhost.com>");
+        assertEquals("O'brian", address.getName());
+        assertEquals("test@localhost.com", address.getAddress());
+
+        address = EmailAddress.getEmailAddress("\"\\\"O'brian\\\"\" <test@localhost.com>");
+        assertEquals("\"O'brian\"", address.getName());
+        assertEquals("test@localhost.com", address.getAddress());
+
+
+        // Ensure that white space is trimmed from the name
+
+        // Strings that will match the regular expression
+        address = EmailAddress.getEmailAddress("\" \" <test@localhost.com>");
+        assertEquals("", address.getName());
+
+        address = EmailAddress.getEmailAddress("\" test name \" <test@localhost.com>");
+        assertEquals("test name", address.getName());
+
+        // Strings that will fallthrough to the rfc822 tokenizer
+        address = EmailAddress.getEmailAddress("\"\\\" O'brian \\\"\" <test@localhost.com>");
+        assertEquals("\" O'brian \"", address.getName());
+
+        address = EmailAddress.getEmailAddress("\" \\\"O'brian\\\" \" <test@localhost.com>");
+        assertEquals("\"O'brian\"", address.getName());
+    }
+}
diff --git a/tests/src/com/android/mail/browse/SendersFormattingTests.java b/tests/src/com/android/mail/browse/SendersFormattingTests.java
index 2aa625f..2020c05 100644
--- a/tests/src/com/android/mail/browse/SendersFormattingTests.java
+++ b/tests/src/com/android/mail/browse/SendersFormattingTests.java
@@ -43,8 +43,8 @@
         conv.addMessage(info);
         ArrayList<SpannableString> strings = new ArrayList<SpannableString>();
         ArrayList<String> emailDisplays = null;
-        SendersView
-                .format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays, null);
+        SendersView.format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays,
+                null, false);
         assertEquals(1, strings.size());
         assertEquals(strings.get(0).toString(), "me");
 
@@ -52,8 +52,8 @@
         MessageInfo info2 = new MessageInfo(read, starred, "", -1, null);
         strings.clear();
         conv2.addMessage(info2);
-        SendersView
-                .format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays, null);
+        SendersView.format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays,
+                null, false);
         assertEquals(1, strings.size());
         assertEquals(strings.get(0).toString(), "me");
 
@@ -63,8 +63,8 @@
         MessageInfo info4 = new MessageInfo(read, starred, "", -1, null);
         conv3.addMessage(info4);
         strings.clear();
-        SendersView
-                .format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays, null);
+        SendersView.format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays,
+                null, false);
         assertEquals(1, strings.size());
         assertEquals(strings.get(0).toString(), "me");
     }
@@ -80,8 +80,8 @@
         conv.addMessage(info);
         MessageInfo info2 = new MessageInfo(read, starred, sender, -1, null);
         conv.addMessage(info2);
-        SendersView
-                .format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays, null);
+        SendersView.format(getContext(), conv, "", 100, strings, emailDisplays, emailDisplays,
+                null, false);
         // We actually don't remove the item, we just set it to null, so count
         // just the non-null items.
         int count = 0;
diff --git a/tests/src/com/android/mail/providers/AccountTests.java b/tests/src/com/android/mail/providers/AccountTests.java
index dfeadd6..e99e369 100644
--- a/tests/src/com/android/mail/providers/AccountTests.java
+++ b/tests/src/com/android/mail/providers/AccountTests.java
@@ -31,8 +31,6 @@
         dest.writeString("foldersList");
         dest.writeString("searchUri");
         dest.writeString("fromAddresses");
-        dest.writeString("saveDraftUri");
-        dest.writeString("sendMessageUri");
         dest.writeString("expungeMessageUri");
         dest.writeString("undoUri");
         dest.writeString("settingIntentUri");
@@ -48,8 +46,6 @@
         assertEquals(outAccount.uri, account.uri);
         assertEquals(outAccount.folderListUri, account.folderListUri);
         assertEquals(outAccount.searchUri, account.searchUri);
-        assertEquals(outAccount.saveDraftUri, account.saveDraftUri);
-        assertEquals(outAccount.sendMessageUri, account.sendMessageUri);
         assertEquals(outAccount.expungeMessageUri, account.expungeMessageUri);
     }
 }
\ No newline at end of file
diff --git a/unified_src/com/android/mail/preferences/PreferenceMigrator.java b/unified_src/com/android/mail/preferences/PreferenceMigrator.java
new file mode 100644
index 0000000..ea7dd97
--- /dev/null
+++ b/unified_src/com/android/mail/preferences/PreferenceMigrator.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.mail.preferences;
+
+import android.content.Context;
+
+/**
+ * Basic {@link BasePreferenceMigrator} implementation. Projects that extend UnifiedEmail need a
+ * class with the same name and package that actually performs migration (if necessary).
+ */
+public class PreferenceMigrator extends BasePreferenceMigrator {
+    @Override
+    protected void migrate(final Context context, final int oldVersion, final int newVersion) {
+        // Nothing required here.
+    }
+}
diff --git a/unified_src/com/android/mail/providers/protos/boot/AccountReceiver.java b/unified_src/com/android/mail/providers/protos/boot/AccountReceiver.java
deleted file mode 100644
index 9e2412f..0000000
--- a/unified_src/com/android/mail/providers/protos/boot/AccountReceiver.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright (c) 2011, Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.mail.providers.protos.boot;
-
-import com.android.mail.providers.protos.mock.MockUiProvider;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class AccountReceiver extends BroadcastReceiver {
-    /**
-     * Intent used to notify interested parties that the Mail provider has been created.
-     */
-    public static final String ACTION_PROVIDER_CREATED
-            = "com.android.mail.providers.protos.boot.intent.ACTION_PROVIDER_CREATED";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        MockUiProvider.initializeMockProvider();
-    }
-}