Merge "Import translations. DO NOT MERGE" into jb-ub-mail-ur10
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d3ace59..91a8b3c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -113,8 +113,7 @@
         </activity>
         <activity
                 android:name=".browse.EmlViewerActivity"
-                android:label="@string/app_name"
-                android:exported="false">
+                android:label="@string/app_name" >
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/res/drawable/radio_button.xml b/res/drawable/radio_button.xml
new file mode 100644
index 0000000..e9cb939
--- /dev/null
+++ b/res/drawable/radio_button.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:drawable="@drawable/ic_radiobutton_selected" />
+    <item android:drawable="@drawable/ic_radiobutton" />
+</selector>
diff --git a/res/layout/eml_viewer_activity.xml b/res/layout/eml_viewer_activity.xml
index b7394c3..4999b6f 100644
--- a/res/layout/eml_viewer_activity.xml
+++ b/res/layout/eml_viewer_activity.xml
@@ -16,8 +16,9 @@
      limitations under the License.
 -->
 
-<WebView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:id="@+id/webview"
-          android:layout_width="match_parent"
-          android:layout_height="match_parent">
-</WebView>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/eml_root"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:background="@android:color/white" >
+</FrameLayout>
diff --git a/res/menu-sw600dp-land/conversation_actions.xml b/res/menu-sw600dp-land/conversation_actions.xml
index f463841..3a74345 100644
--- a/res/menu-sw600dp-land/conversation_actions.xml
+++ b/res/menu-sw600dp-land/conversation_actions.xml
@@ -84,6 +84,11 @@
         android:icon="@drawable/ic_menu_folders_holo_light" />
 
     <item
+        android:id="@+id/move_to_inbox"
+        android:showAsAction="never"
+        android:title="@string/menu_move_to_inbox" />
+
+    <item
         android:id="@+id/mark_important"
         android:showAsAction="never"
         android:title="@string/mark_important" />
diff --git a/res/menu/conversation_actions.xml b/res/menu/conversation_actions.xml
index e70d6db..9d63506 100644
--- a/res/menu/conversation_actions.xml
+++ b/res/menu/conversation_actions.xml
@@ -72,6 +72,11 @@
         android:showAsAction="never"
         android:icon="@drawable/ic_menu_folders_holo_light" />
 
+    <item
+        android:id="@+id/move_to_inbox"
+        android:showAsAction="never"
+        android:title="@string/menu_move_to_inbox" />
+
     <!-- Always available -->
     <item
         android:id="@+id/mark_important"
diff --git a/res/menu/conversation_list_selection_actions_menu.xml b/res/menu/conversation_list_selection_actions_menu.xml
index f635a4f..b866497 100644
--- a/res/menu/conversation_list_selection_actions_menu.xml
+++ b/res/menu/conversation_list_selection_actions_menu.xml
@@ -78,6 +78,11 @@
         android:title="@string/menu_change_folders"
         android:icon="@drawable/ic_menu_folders_holo_light" />
 
+    <item
+        android:id="@+id/move_to_inbox"
+        android:showAsAction="never"
+        android:title="@string/menu_move_to_inbox" />
+
     <item android:id="@+id/star"
         android:title="@string/add_star"
         android:showAsAction="never" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4498192..cb17a11 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -151,6 +151,8 @@
     <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: moves current or selected conversation(s) to Inbox. [CHAR LIMIT = 30] -->
+    <string name="menu_move_to_inbox">Move to Inbox</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 -->
@@ -874,8 +876,6 @@
     <string name="prefDialogTitle_removal_action">Archive &amp; delete actions</string>
     <!-- The default value -->
     <string translatable="false" name="prefDefault_removal_action">archive</string>
-    <!-- Dialog title for the choosing whether to use archive or delete as remove action, displayed the first time the user archives or deletes a message [CHAR LIMIT=150] -->
-    <string name="prefDialogTitle_removal_action_first_time">Set archive &amp; delete preference\n(change in General settings)</string>
 
     <!--  Settings screen, Reply to all default setting title  [CHAR LIMIT=30] -->
     <string name="preferences_default_reply_all_title">Reply all</string>
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index 56100a8..a5fda5f 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -279,19 +279,24 @@
 
             @Override
             public Void doInBackground(Void... param) {
-                for (int i = mStartPos; i < getCount(); i++) {
-                    if (isCancelled()) {
-                        break;
-                    }
+                try {
+                    Utils.traceBeginSection("backgroundCaching");
+                    for (int i = mStartPos; i < getCount(); i++) {
+                        if (isCancelled()) {
+                            break;
+                        }
 
-                    final UnderlyingRowData rowData = mRowCache.get(i);
-                    if (rowData.conversation == null) {
-                        // We are running in a background thread.  Set the position to the row
-                        // we are interested in.
-                        if (moveToPosition(i)) {
-                            cacheConversation(new Conversation(UnderlyingCursorWrapper.this));
+                        final UnderlyingRowData rowData = mRowCache.get(i);
+                        if (rowData.conversation == null) {
+                            // We are running in a background thread.  Set the position to the row
+                            // we are interested in.
+                            if (moveToPosition(i)) {
+                                cacheConversation(new Conversation(UnderlyingCursorWrapper.this));
+                            }
                         }
                     }
+                } finally {
+                    Utils.traceEndSection();
                 }
                 return null;
             }
@@ -342,6 +347,7 @@
             final UnderlyingRowData[] cache;
             final int count;
             int numCached = 0;
+            Utils.traceBeginSection("blockingCaching");
             if (result != null && super.moveToFirst()) {
                 count = super.getCount();
                 cache = new UnderlyingRowData[count];
@@ -420,6 +426,8 @@
                     " %sms n=%s cached=%s CONV_PRECACHING=%s",
                     (end-start), count, numCached, ENABLE_CONVERSATION_PRECACHING);
 
+            Utils.traceEndSection();
+
             // If we haven't cached all of the conversations, start a task to do that
             if (ENABLE_CONVERSATION_PRECACHING && numCached < count) {
                 mCacheLoaderTask = new CacheLoaderTask(numCached);
diff --git a/src/com/android/mail/browse/ConversationMessage.java b/src/com/android/mail/browse/ConversationMessage.java
index dbc9b07..e66494f 100644
--- a/src/com/android/mail/browse/ConversationMessage.java
+++ b/src/com/android/mail/browse/ConversationMessage.java
@@ -53,7 +53,6 @@
 
     public ConversationMessage(MimeMessage mimeMessage) throws MessagingException {
         super(mimeMessage);
-        // TODO - synthesize conversation
     }
 
     public void setController(ConversationController controller) {
@@ -61,7 +60,7 @@
     }
 
     public Conversation getConversation() {
-        return mController.getConversation();
+        return mController != null ? mController.getConversation() : null;
     }
 
     /**
diff --git a/src/com/android/mail/browse/ConversationViewAdapter.java b/src/com/android/mail/browse/ConversationViewAdapter.java
index 97e7642..89ccb09 100644
--- a/src/com/android/mail/browse/ConversationViewAdapter.java
+++ b/src/com/android/mail/browse/ConversationViewAdapter.java
@@ -119,7 +119,10 @@
 
     }
 
-    public class MessageHeaderItem extends ConversationOverlayItem {
+    public static class MessageHeaderItem extends ConversationOverlayItem {
+
+        private final ConversationViewAdapter mAdapter;
+
         private ConversationMessage mMessage;
 
         // view state variables
@@ -132,7 +135,9 @@
         public CharSequence timestampLong;
         public CharSequence recipientSummaryText;
 
-        MessageHeaderItem(ConversationMessage message, boolean expanded, boolean showImages) {
+        MessageHeaderItem(ConversationViewAdapter adapter, ConversationMessage message,
+                boolean expanded, boolean showImages) {
+            mAdapter = adapter;
             mMessage = message;
             mExpanded = expanded;
             mShowImages = showImages;
@@ -153,10 +158,11 @@
         public View createView(Context context, LayoutInflater inflater, ViewGroup parent) {
             final MessageHeaderView v = (MessageHeaderView) inflater.inflate(
                     R.layout.conversation_message_header, parent, false);
-            v.initialize(mDateBuilder, mAccountController, mAddressCache);
-            v.setCallbacks(mMessageCallbacks);
-            v.setContactInfoSource(mContactInfoSource);
-            v.setVeiledMatcher(mMatcher);
+            v.initialize(mAdapter.mDateBuilder, mAdapter.mAccountController,
+                    mAdapter.mAddressCache);
+            v.setCallbacks(mAdapter.mMessageCallbacks);
+            v.setContactInfoSource(mAdapter.mContactInfoSource);
+            v.setVeiledMatcher(mAdapter.mMatcher);
             return v;
         }
 
@@ -402,16 +408,16 @@
     }
 
     public int addMessageHeader(ConversationMessage msg, boolean expanded, boolean showImages) {
-        return addItem(new MessageHeaderItem(msg, expanded, showImages));
+        return addItem(new MessageHeaderItem(this, msg, expanded, showImages));
     }
 
     public int addMessageFooter(MessageHeaderItem headerItem) {
         return addItem(new MessageFooterItem(headerItem));
     }
 
-    public MessageHeaderItem newMessageHeaderItem(ConversationMessage message, boolean expanded,
-            boolean showImages) {
-        return new MessageHeaderItem(message, expanded, showImages);
+    public static MessageHeaderItem newMessageHeaderItem(ConversationViewAdapter adapter,
+            ConversationMessage message, boolean expanded, boolean showImages) {
+        return new MessageHeaderItem(adapter, message, expanded, showImages);
     }
 
     public MessageFooterItem newMessageFooterItem(MessageHeaderItem headerItem) {
diff --git a/src/com/android/mail/browse/EmlMessageLoader.java b/src/com/android/mail/browse/EmlMessageLoader.java
new file mode 100644
index 0000000..09e5b6a
--- /dev/null
+++ b/src/com/android/mail/browse/EmlMessageLoader.java
@@ -0,0 +1,180 @@
+/*
+ * 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
+ *and
+ * 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.AsyncTaskLoader;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.emailcommon.TempDirectory;
+import com.android.emailcommon.internet.MimeMessage;
+import com.android.emailcommon.mail.MessagingException;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Loader that builds a ConversationMessage from an EML file Uri.
+ */
+public class EmlMessageLoader extends AsyncTaskLoader<ConversationMessage> {
+    private static final String LOG_TAG = LogTag.getLogTag();
+
+    private Uri mEmlFileUri;
+    private ConversationMessage mMessage;
+
+    public EmlMessageLoader(Context context, Uri emlFileUri) {
+        super(context);
+        mEmlFileUri = emlFileUri;
+    }
+
+    @Override
+    public ConversationMessage loadInBackground() {
+        final Context context = getContext();
+        TempDirectory.setTempDirectory(context);
+        final ContentResolver resolver = context.getContentResolver();
+        final InputStream stream;
+        try {
+            stream = resolver.openInputStream(mEmlFileUri);
+        } catch (FileNotFoundException e) {
+            LogUtils.e(LOG_TAG, e, "Could not find eml file at uri: %s", mEmlFileUri);
+            return null;
+        }
+
+        final MimeMessage mimeMessage;
+        final ConversationMessage convMessage;
+        try {
+            mimeMessage = new MimeMessage(stream);
+            convMessage = new ConversationMessage(mimeMessage);
+        } catch (IOException e) {
+            LogUtils.e(LOG_TAG, e, "Could not read eml file");
+            return null;
+        } catch (MessagingException e) {
+            LogUtils.e(LOG_TAG, e, "Error in parsing eml file");
+            return null;
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                return null;
+            }
+        }
+
+        return convMessage;
+    }
+
+    /**
+     * Called when there is new data to deliver to the client.  The
+     * super class will take care of delivering it; the implementation
+     * here just adds a little more logic.
+     */
+    @Override
+    public void deliverResult(ConversationMessage result) {
+        if (isReset()) {
+            // An async query came in while the loader is stopped.  We
+            // don't need the result.
+            if (result != null) {
+                onReleaseResources(result);
+            }
+        }
+        ConversationMessage oldMessage = mMessage;
+        mMessage = result;
+
+        if (isStarted()) {
+            // If the Loader is currently started, we can immediately
+            // deliver its results.
+            super.deliverResult(result);
+        }
+
+        // At this point we can release the resources associated with
+        // 'oldMessage' if needed; now that the new result is delivered we
+        // know that it is no longer in use.
+        if (oldMessage != null && oldMessage != mMessage) {
+            onReleaseResources(oldMessage);
+        }
+    }
+
+    /**
+     * Handles a request to start the Loader.
+     */
+    @Override
+    protected void onStartLoading() {
+        if (mMessage != null) {
+            // If we currently have a result available, deliver it immediately.
+            deliverResult(mMessage);
+        }
+
+        if (takeContentChanged() || mMessage == null) {
+            // If the data has changed since the last time it was loaded
+            // or is not currently available, start a load.
+            forceLoad();
+        }
+    }
+
+    /**
+     * Handles a request to stop the Loader.
+     */
+    @Override protected void onStopLoading() {
+        // Attempt to cancel the current load task if possible.
+        cancelLoad();
+    }
+
+    /**
+     * Handles a request to cancel a load.
+     */
+    @Override
+    public void onCanceled(ConversationMessage result) {
+        super.onCanceled(result);
+
+        // At this point we can release the resources associated with
+        // the message, if needed.
+        if (result != null) {
+            onReleaseResources(result);
+        }
+    }
+
+    /**
+     * Handles a request to completely reset the Loader.
+     */
+    @Override
+    protected void onReset() {
+        super.onReset();
+
+        // Ensure the loader is stopped
+        onStopLoading();
+
+        // At this point we can release the resources associated with
+        // the message, if needed.
+        if (mMessage != null) {
+            onReleaseResources(mMessage);
+            mMessage = null;
+        }
+    }
+
+
+    /**
+     * Helper function to take care of releasing resources associated
+     * with an actively loaded data set.
+     */
+    protected void onReleaseResources(ConversationMessage message) {
+        // DO NOTHING
+    }
+}
diff --git a/src/com/android/mail/browse/EmlMessageViewFragment.java b/src/com/android/mail/browse/EmlMessageViewFragment.java
new file mode 100644
index 0000000..13a3e54
--- /dev/null
+++ b/src/com/android/mail/browse/EmlMessageViewFragment.java
@@ -0,0 +1,234 @@
+/*
+ * 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.app.Fragment;
+import android.app.LoaderManager;
+import android.content.Loader;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+
+import com.android.mail.providers.Account;
+import com.android.mail.providers.Address;
+import com.android.mail.ui.AbstractConversationWebViewClient;
+import com.android.mail.ui.ContactLoaderCallbacks;
+import com.android.mail.ui.SecureConversationViewController;
+import com.android.mail.ui.SecureConversationViewControllerCallbacks;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Fragment that is used to view EML files. It is mostly stubs
+ * that calls {@link SecureConversationViewController} to do most
+ * of the rendering work.
+ */
+public class EmlMessageViewFragment extends Fragment
+        implements SecureConversationViewControllerCallbacks,
+        LoaderManager.LoaderCallbacks<ConversationMessage> {
+    private static final String ARG_EML_FILE_URI = "eml_file_uri";
+    private static final String BASE_URI = "x-thread://message/rfc822/";
+    private static final int MESSAGE_LOADER = 0;
+    private static final int CONTACT_LOADER = 1;
+
+    private final Handler mHandler = new Handler();
+
+    private EmlWebViewClient mWebViewClient;
+    private SecureConversationViewController mViewController;
+    private ContactLoaderCallbacks mContactLoaderCallbacks;
+
+    private Uri mEmlFileUri;
+
+    /**
+     * Cache of email address strings to parsed Address objects.
+     * <p>
+     * Remember to synchronize on the map when reading or writing to this cache, because some
+     * instances use it off the UI thread (e.g. from WebView).
+     */
+    protected final Map<String, Address> mAddressCache = Collections.synchronizedMap(
+            new HashMap<String, Address>());
+
+    private class EmlWebViewClient extends AbstractConversationWebViewClient {
+        public EmlWebViewClient(Account account) {
+            super(account);
+        }
+
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            mViewController.dismissLoadingStatus();
+
+            final Set<String> emailAddresses = Sets.newHashSet();
+            final List<Address> cacheCopy;
+            synchronized (mAddressCache) {
+                cacheCopy = ImmutableList.copyOf(mAddressCache.values());
+            }
+            for (Address addr : cacheCopy) {
+                emailAddresses.add(addr.getAddress());
+            }
+            final ContactLoaderCallbacks callbacks = getContactInfoSource();
+            callbacks.setSenders(emailAddresses);
+            getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
+        }
+    };
+
+    /**
+     * Creates a new instance of {@link EmlMessageViewFragment},
+     * initialized to display an eml file from the specified {@link Uri}.
+     */
+    public static EmlMessageViewFragment newInstance(Uri emlFileUri) {
+        EmlMessageViewFragment f = new EmlMessageViewFragment();
+        Bundle args = new Bundle();
+        args.putParcelable(ARG_EML_FILE_URI, emlFileUri);
+        f.setArguments(args);
+        return f;
+    }
+
+    /**
+     * Constructor needs to be public to handle orientation changes and activity
+     * lifecycle events.
+     */
+    public EmlMessageViewFragment() {
+        super();
+    }
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        Bundle args = getArguments();
+        mEmlFileUri = args.getParcelable(ARG_EML_FILE_URI);
+
+        mWebViewClient = new EmlWebViewClient(null);
+        mViewController = new SecureConversationViewController(this);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return mViewController.onCreateView(inflater, container, savedInstanceState);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mWebViewClient.setActivity(getActivity());
+        mViewController.onActivityCreated(savedInstanceState);
+    }
+
+    // Start SecureConversationViewControllerCallbacks
+
+    @Override
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    @Override
+    public AbstractConversationWebViewClient getWebViewClient() {
+        return mWebViewClient;
+    }
+
+    @Override
+    public Fragment getFragment() {
+        return this;
+    }
+
+    @Override
+    public void setupConversationHeaderView(ConversationViewHeader headerView) {
+        // DO NOTHING
+    }
+
+    @Override
+    public boolean isViewVisibleToUser() {
+        return true;
+    }
+
+    @Override
+    public ContactLoaderCallbacks getContactInfoSource() {
+        if (mContactLoaderCallbacks == null) {
+            mContactLoaderCallbacks = new ContactLoaderCallbacks(getActivity());
+        }
+        return mContactLoaderCallbacks;
+    }
+
+    @Override
+    public ConversationAccountController getConversationAccountController() {
+        return null;
+    }
+
+    @Override
+    public Map<String, Address> getAddressCache() {
+        return mAddressCache;
+    }
+
+    @Override
+    public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView) {
+        // DO NOTHING
+    }
+
+    @Override
+    public void startMessageLoader() {
+        getLoaderManager().initLoader(MESSAGE_LOADER, null, this);
+    }
+
+    @Override
+    public String getBaseUri() {
+        return BASE_URI;
+    }
+
+    @Override
+    public boolean isViewOnlyMode() {
+        return true;
+    }
+
+    // End SecureConversationViewControllerCallbacks
+
+    // Start LoaderCallbacks
+
+    @Override
+    public Loader<ConversationMessage> onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            case MESSAGE_LOADER:
+                return new EmlMessageLoader(getActivity(), mEmlFileUri);
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void onLoadFinished(Loader<ConversationMessage> loader, ConversationMessage data) {
+        mViewController.setSubject(data.subject);
+        mViewController.renderMessage(data);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<ConversationMessage> loader) {
+        // Do nothing
+    }
+
+    // End LoaderCallbacks
+}
diff --git a/src/com/android/mail/browse/EmlViewerActivity.java b/src/com/android/mail/browse/EmlViewerActivity.java
index a1c33d0..2524b0f 100644
--- a/src/com/android/mail/browse/EmlViewerActivity.java
+++ b/src/com/android/mail/browse/EmlViewerActivity.java
@@ -19,30 +19,21 @@
 
 import android.app.ActionBar;
 import android.app.Activity;
-import android.content.ContentResolver;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
 import android.content.Intent;
-import android.net.Uri;
 import android.os.Bundle;
 import android.view.MenuItem;
-import android.webkit.WebView;
 
-import com.android.emailcommon.TempDirectory;
-import com.android.emailcommon.internet.MimeMessage;
-import com.android.emailcommon.mail.MessagingException;
 import com.android.mail.R;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.MimeType;
 
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
 public class EmlViewerActivity extends Activity {
     private static final String LOG_TAG = LogTag.getLogTag();
 
-    private WebView mWebView;
-
+    private static final String FRAGMENT_TAG = "eml_message_fragment";
 
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -50,15 +41,21 @@
 
         final ActionBar actionBar = getActionBar();
         actionBar.setDisplayHomeAsUpEnabled(true);
-        mWebView = (WebView) findViewById(R.id.webview);
 
         final Intent intent = getIntent();
         final String action = intent.getAction();
         final String type = intent.getType();
 
         if (Intent.ACTION_VIEW.equals(action) &&
-                MimeType.EML_ATTACHMENT_CONTENT_TYPES.contains(type)) {
-            openEmlFile(intent.getData());
+                MimeType.isEmlMimeType(type)) {
+            final FragmentManager manager = getFragmentManager();
+
+            if (manager.findFragmentByTag(FRAGMENT_TAG) == null) {
+                final FragmentTransaction transaction = manager.beginTransaction();
+                transaction.add(R.id.eml_root,
+                        EmlMessageViewFragment.newInstance(intent.getData()), FRAGMENT_TAG);
+                transaction.commit();
+            }
         } else {
             LogUtils.wtf(LOG_TAG,
                     "Entered EmlViewerActivity with wrong intent action or type: %s, %s",
@@ -67,32 +64,7 @@
         }
     }
 
-    private void openEmlFile(Uri uri) {
-        TempDirectory.setTempDirectory(this);
-        final ContentResolver resolver = getContentResolver();
-        final InputStream stream;
-        try {
-            stream = resolver.openInputStream(uri);
-        } catch (FileNotFoundException e) {
-            // TODO handle exception
-            return;
-        }
 
-        final MimeMessage mimeMessage;
-        final ConversationMessage convMessage;
-        try {
-            mimeMessage = new MimeMessage(stream);
-            convMessage = new ConversationMessage(mimeMessage);
-        } catch (IOException e) {
-            // TODO handle exception
-            return;
-        } catch (MessagingException e) {
-            // TODO handle exception
-            return;
-        }
-
-        mWebView.loadDataWithBaseURL("", convMessage.getBodyAsHtml(), "text/html", "utf-8", null);
-    }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
diff --git a/src/com/android/mail/browse/MessageAttachmentBar.java b/src/com/android/mail/browse/MessageAttachmentBar.java
index 3c6f4b4..e61fdcf 100644
--- a/src/com/android/mail/browse/MessageAttachmentBar.java
+++ b/src/com/android/mail/browse/MessageAttachmentBar.java
@@ -283,8 +283,17 @@
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                 | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+
+        final String contentType = mAttachment.getContentType();
         Utils.setIntentDataAndTypeAndNormalize(
-                intent, mAttachment.contentUri, mAttachment.getContentType());
+                intent, mAttachment.contentUri, contentType);
+
+        // For EML files, we want to open our dedicated
+        // viewer rather than let any activity open it.
+        if (MimeType.isEmlMimeType(contentType)) {
+            intent.setClass(getContext(), EmlViewerActivity.class);
+        }
+
         try {
             getContext().startActivity(intent);
         } catch (ActivityNotFoundException e) {
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index 1083002..5036818 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -55,6 +55,7 @@
 import com.android.mail.preferences.MailPrefs;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Address;
+import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.Message;
 import com.android.mail.providers.UIProvider;
@@ -217,6 +218,8 @@
 
     private VeiledAddressMatcher mVeiledMatcher;
 
+    private boolean mIsViewOnlyMode = false;
+
     public interface MessageHeaderViewCallbacks {
         void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight);
 
@@ -230,6 +233,7 @@
         void showExternalResources(String senderRawAddress);
 
         boolean supportsMessageTransforms();
+
         String getMessageTransforms(Message msg);
     }
 
@@ -331,7 +335,7 @@
             mLeftSpacer.setVisibility(View.VISIBLE);
             mRightSpacer.setVisibility(View.VISIBLE);
         } else {
-            setBackgroundColor(android.R.color.white);
+            setBackgroundColor(getResources().getColor(android.R.color.white));
             // scrolling layer does have padding so we don't need spacers
             mLeftSpacer.setVisibility(View.GONE);
             mRightSpacer.setVisibility(View.GONE);
@@ -368,7 +372,7 @@
     }
 
     private Account getAccount() {
-        return mAccountController.getAccount();
+        return mAccountController != null ? mAccountController.getAccount() : null;
     }
 
     public void bind(MessageHeaderItem headerItem, boolean measureOnly) {
@@ -437,10 +441,14 @@
         mStarView.setContentDescription(getResources().getString(
                 mStarView.isSelected() ? R.string.remove_star : R.string.add_star));
         mStarShown = true;
-        for (Folder folder : mMessage.getConversation().getRawFolders()) {
-            if (folder.isTrash()) {
-                mStarShown = false;
-                break;
+
+        final Conversation conversation = mMessage.getConversation();
+        if (conversation != null) {
+            for (Folder folder : conversation.getRawFolders()) {
+                if (folder.isTrash()) {
+                    mStarShown = false;
+                    break;
+                }
             }
         }
 
@@ -600,7 +608,18 @@
     private void updateChildVisibility() {
         // Too bad this can't be done with an XML state list...
 
-        if (isExpanded()) {
+        if (mIsViewOnlyMode) {
+            setMessageDetailsVisibility(VISIBLE);
+
+
+            setChildVisibility(GONE, mReplyButton, mReplyAllButton, mForwardButton,
+                    mOverflowButton, mDraftIcon, mEditDraftButton, mStarView,
+                    mAttachmentIcon, mUpperDateView);
+            setChildVisibility(VISIBLE, mPhotoView, mPhotoSpacerView,
+                    mSenderEmailView);
+
+            setChildMarginRight(mTitleContainerView, 0);
+        } else if (isExpanded()) {
             int normalVis, draftVis;
 
             setMessageDetailsVisibility((mIsSnappy) ? GONE : VISIBLE);
@@ -669,8 +688,9 @@
             return;
         }
 
-        final boolean defaultReplyAll = getAccount().settings.replyBehavior
-                == UIProvider.DefaultReplyBehavior.REPLY_ALL;
+        final Account account = getAccount();
+        final boolean defaultReplyAll = (account != null) ? account.settings.replyBehavior
+                == UIProvider.DefaultReplyBehavior.REPLY_ALL : false;
         setChildVisibility(defaultReplyAll ? GONE : VISIBLE, mReplyButton);
         setChildVisibility(defaultReplyAll ? VISIBLE : GONE, mReplyAllButton);
     }
@@ -702,7 +722,8 @@
             final String address = email.getAddress();
             // Check if the address here is a veiled address.  If it is, we need to display an
             // alternate layout
-            final boolean isVeiledAddress = mVeiledMatcher.isVeiledAddress(address);
+            final boolean isVeiledAddress = mVeiledMatcher != null &&
+                    mVeiledMatcher.isVeiledAddress(address);
             final String addressShown;
             if (isVeiledAddress) {
                 // Add the warning at the end of the name, and remove the address.  The alternate
@@ -797,7 +818,7 @@
                 final Address email = getAddress(mAddressCache, rawAddrs[i]);
                 final String emailAddress = email.getAddress();
                 final String name;
-                if (mMatcher.isVeiledAddress(emailAddress)) {
+                if (mMatcher != null && mMatcher.isVeiledAddress(emailAddress)) {
                     if (TextUtils.isEmpty(email.getName())) {
                         // Let's write something more readable.
                         name = mContext.getString(VeiledAddressMatcher.VEILED_SUMMARY_UNKNOWN);
@@ -983,6 +1004,16 @@
         return handled;
     }
 
+    /**
+     * Set to true if the user should not be able to perfrom message actions
+     * on the message such as reply/reply all/forward/star/etc.
+     *
+     * Default is false.
+     */
+    public void setViewOnlyMode(boolean isViewOnlyMode) {
+        mIsViewOnlyMode = isViewOnlyMode;
+    }
+
     public void setExpandable(boolean expandable) {
         mExpandable = expandable;
     }
@@ -1194,7 +1225,11 @@
                 if (mMessageHeaderItem != null) {
                     mMessageHeaderItem.setShowImages(true);
                 }
-                showImagePromptAlways(false);
+                if (mIsViewOnlyMode) {
+                    hideShowImagePrompt();
+                } else {
+                    showImagePromptAlways(false);
+                }
                 break;
             case SHOW_IMAGE_PROMPT_ALWAYS:
                 mMessage.markAlwaysShowImages(getQueryHandler(), 0 /* token */, null /* cookie */);
@@ -1233,8 +1268,10 @@
         }
         if (!mCollapsedDetailsValid) {
             if (mMessageHeaderItem.recipientSummaryText == null) {
+                final Account account = getAccount();
+                final String name = (account != null) ? account.name : "";
                 mMessageHeaderItem.recipientSummaryText = getRecipientSummaryText(getContext(),
-                        getAccount().name, mMyName, mTo, mCc, mBcc, mAddressCache, mVeiledMatcher);
+                        name, mMyName, mTo, mCc, mBcc, mAddressCache, mVeiledMatcher);
             }
             ((TextView) findViewById(R.id.recipients_summary))
                     .setText(mMessageHeaderItem.recipientSummaryText);
diff --git a/src/com/android/mail/browse/SelectedConversationsActionMenu.java b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
index 6b05fbe..604be37 100644
--- a/src/com/android/mail/browse/SelectedConversationsActionMenu.java
+++ b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.view.ActionMode;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -43,14 +44,17 @@
 import com.android.mail.ui.ConversationSetObserver;
 import com.android.mail.ui.ConversationUpdater;
 import com.android.mail.ui.DestructiveAction;
+import com.android.mail.ui.FolderOperation;
 import com.android.mail.ui.FolderSelectionDialog;
 import com.android.mail.ui.MailActionBarView;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
 
 import java.util.Collection;
+import java.util.List;
 
 /**
  * A component that displays a custom view for an {@code ActionBar}'s {@code
@@ -198,6 +202,25 @@
                     }
                 }
                 break;
+            case R.id.move_to_inbox:
+                new AsyncTask<Void, Void, Folder>() {
+                    @Override
+                    protected Folder doInBackground(final Void... params) {
+                        // Get the "move to" inbox
+                        return Utils.getFolder(mContext, mAccount.settings.moveToInbox,
+                                true /* allowHidden */);
+                    }
+
+                    @Override
+                    protected void onPostExecute(final Folder moveToInbox) {
+                        final List<FolderOperation> ops = Lists.newArrayListWithCapacity(1);
+                        // Add inbox
+                        ops.add(new FolderOperation(moveToInbox, true));
+                        mUpdater.assignFolder(ops, mSelectionSet.values(), true,
+                                true /* showUndo */, false /* isMoveTo */);
+                    }
+                }.execute((Void[]) null);
+                break;
             case R.id.mark_important:
                 markConversationsImportant(true);
                 break;
@@ -276,7 +299,7 @@
     private void destroy(int actionId, final Collection<Conversation> target,
             final DestructiveAction action) {
         LogUtils.i(LOG_TAG, "About to remove %d converations", target.size());
-        mUpdater.delete(actionId, target, action, true, true /* allowDialog */);
+        mUpdater.delete(actionId, target, action, true);
     }
 
     /**
@@ -382,13 +405,18 @@
         // 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 MenuItem moveToInbox = menu.findItem(R.id.move_to_inbox);
         final boolean showRemoveFolder = mFolder != null && mFolder.isType(FolderType.DEFAULT)
                 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
                 && !mFolder.isProviderFolder();
         final boolean showMoveTo = mFolder != null
                 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION);
+        final boolean showMoveToInbox = mFolder != null
+                && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX);
         removeFolder.setVisible(showRemoveFolder);
         moveTo.setVisible(showMoveTo);
+        moveToInbox.setVisible(showMoveToInbox);
+
         if (mFolder != null && showRemoveFolder) {
             removeFolder.setTitle(mActivity.getActivityContext().getString(R.string.remove_folder,
                     mFolder.name));
diff --git a/src/com/android/mail/providers/Account.java b/src/com/android/mail/providers/Account.java
index 391ffe0..256b30a 100644
--- a/src/com/android/mail/providers/Account.java
+++ b/src/com/android/mail/providers/Account.java
@@ -756,6 +756,7 @@
                 settings.conversationViewMode);
         map.put(AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN,
                 settings.veiledAddressPattern);
+        map.put(AccountColumns.SettingsColumns.MOVE_TO_INBOX, settings.moveToInbox);
 
         return map;
     }
diff --git a/src/com/android/mail/providers/Settings.java b/src/com/android/mail/providers/Settings.java
index 9a8fddd..d07a60e 100644
--- a/src/com/android/mail/providers/Settings.java
+++ b/src/com/android/mail/providers/Settings.java
@@ -89,6 +89,12 @@
     public final Uri setupIntentUri;
     public final String veiledAddressPattern;
 
+    /**
+     * The {@link Uri} to use when moving a conversation to the inbox. May
+     * differ from {@link #defaultInbox}.
+     */
+    public final Uri moveToInbox;
+
     /** Cached value of hashCode */
     private int mHashCode;
 
@@ -114,6 +120,7 @@
         setupIntentUri = Uri.EMPTY;
         conversationViewMode = UIProvider.ConversationViewMode.UNDEFINED;
         veiledAddressPattern = null;
+        moveToInbox = Uri.EMPTY;
     }
 
     public Settings(Parcel inParcel) {
@@ -135,6 +142,7 @@
         setupIntentUri = Utils.getValidUri(inParcel.readString());
         conversationViewMode = inParcel.readInt();
         veiledAddressPattern = inParcel.readString();
+        moveToInbox = Utils.getValidUri(inParcel.readString());
     }
 
     public Settings(Cursor cursor) {
@@ -164,6 +172,8 @@
                 cursor.getInt(cursor.getColumnIndex(SettingsColumns.CONVERSATION_VIEW_MODE));
         veiledAddressPattern =
                 cursor.getString(cursor.getColumnIndex(SettingsColumns.VEILED_ADDRESS_PATTERN));
+        moveToInbox = Utils.getValidUri(
+                cursor.getString(cursor.getColumnIndex(SettingsColumns.MOVE_TO_INBOX)));
     }
 
     private Settings(JSONObject json) {
@@ -190,6 +200,7 @@
         conversationViewMode = json.optInt(SettingsColumns.CONVERSATION_VIEW_MODE,
                 UIProvider.ConversationViewMode.UNDEFINED);
         veiledAddressPattern = json.optString(SettingsColumns.VEILED_ADDRESS_PATTERN, null);
+        moveToInbox = Utils.getValidUri(json.optString(SettingsColumns.MOVE_TO_INBOX));
     }
 
     /**
@@ -232,6 +243,8 @@
             json.put(SettingsColumns.SETUP_INTENT_URI, setupIntentUri);
             json.put(SettingsColumns.CONVERSATION_VIEW_MODE, conversationViewMode);
             json.put(SettingsColumns.VEILED_ADDRESS_PATTERN, veiledAddressPattern);
+            json.put(SettingsColumns.MOVE_TO_INBOX,
+                    getNonNull(moveToInbox, sDefault.moveToInbox));
         } catch (JSONException e) {
             LogUtils.wtf(LOG_TAG, e, "Could not serialize settings");
         }
@@ -298,6 +311,7 @@
         dest.writeString(((Uri) getNonNull(setupIntentUri, sDefault.setupIntentUri)).toString());
         dest.writeInt(conversationViewMode);
         dest.writeString(veiledAddressPattern);
+        dest.writeString(((Uri) getNonNull(moveToInbox, sDefault.moveToInbox)).toString());
     }
 
     /**
@@ -403,7 +417,8 @@
                 && priorityArrowsEnabled == that.priorityArrowsEnabled
                 && setupIntentUri == that.setupIntentUri
                 && conversationViewMode == that.conversationViewMode
-                && TextUtils.equals(veiledAddressPattern, that.veiledAddressPattern));
+                && TextUtils.equals(veiledAddressPattern, that.veiledAddressPattern))
+                && Objects.equal(moveToInbox, that.moveToInbox);
     }
 
     @Override
@@ -423,6 +438,6 @@
                         snapHeaders, replyBehavior, convListIcon, confirmDelete, confirmArchive,
                         confirmSend, defaultInbox, forceReplyFromDefault, maxAttachmentSize, swipe,
                         priorityArrowsEnabled, setupIntentUri, conversationViewMode,
-                        veiledAddressPattern);
+                        veiledAddressPattern, moveToInbox);
     }
 }
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index 34a1355..7fd379d 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -170,6 +170,7 @@
             .put(AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN, String.class)
             .put(AccountColumns.UPDATE_SETTINGS_URI, String.class)
             .put(AccountColumns.ENABLE_MESSAGE_TRANSFORMS, Integer.class)
+            .put(AccountColumns.SettingsColumns.MOVE_TO_INBOX, String.class)
             .build();
 
     public static final Map<String, Class<?>> ACCOUNTS_COLUMNS =
@@ -557,6 +558,11 @@
              * constants from  {@link ConversationViewMode}
              */
             public static final String CONVERSATION_VIEW_MODE = "conversation_view_mode";
+            /**
+             * String containing the URI for the inbox conversations should be moved to for this
+             * account.
+             */
+            public static final String MOVE_TO_INBOX = "move_to_inbox";
         }
     }
 
@@ -676,9 +682,9 @@
         public static final int STARRED = 1 << 7;
         /** Any other system label that we do not have a specific name for. */
         public static final int OTHER_PROVIDER_FOLDER = 1 << 8;
-        /** All mail folder **/
+        /** All mail folder */
         public static final int ALL_MAIL = 1 << 9;
-        /** Gmail's inbox sections **/
+        /** Gmail's inbox sections */
         public static final int INBOX_SECTION = 1 << 10;
     }
 
@@ -753,6 +759,12 @@
          * {@link com.android.mail.ui.MultiFoldersSelectionDialog}).
          */
         public static final int MULTI_MOVE = 0x8000;
+
+        /**
+         * This flag indicates that a conversation may be moved from this folder into the account's
+         * inbox.
+         */
+        public static final int ALLOWS_MOVE_TO_INBOX = 0x10000;
     }
 
     public static final class FolderColumns {
@@ -1945,6 +1957,11 @@
      */
     public static final String FORCE_UI_NOTIFICATIONS_QUERY_PARAMETER = "forceUiNotifications";
 
+    /**
+     * Parameter used to allow returning hidden folders.
+     */
+    public static final String ALLOW_HIDDEN_FOLDERS_QUERY_PARAM = "allowHiddenFolders";
+
     public static final String AUTO_ADVANCE_MODE_OLDER = "older";
     public static final String AUTO_ADVANCE_MODE_NEWER = "newer";
     public static final String AUTO_ADVANCE_MODE_LIST = "list";
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 0dae8c0..d91a450 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -91,7 +91,6 @@
 import com.android.mail.providers.UIProvider.ConversationOperations;
 import com.android.mail.providers.UIProvider.FolderCapabilities;
 import com.android.mail.ui.ActionableToastBar.ActionClickedListener;
-import com.android.mail.ui.RemovalActionPreferenceDialogFragment.RemovalActionPreferenceDialogListener;
 import com.android.mail.utils.ContentProviderTask;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
@@ -1316,8 +1315,7 @@
             }
             case R.id.remove_folder:
                 delete(R.id.remove_folder, target,
-                        getDeferredRemoveFolder(target, mFolder, true, isBatch, true), isBatch,
-                        true /* allowDialog */);
+                        getDeferredRemoveFolder(target, mFolder, true, isBatch, true), isBatch);
                 break;
             case R.id.delete: {
                 final boolean showDialog = (settings != null && settings.confirmDelete);
@@ -1337,34 +1335,29 @@
             case R.id.mark_not_important:
                 if (mFolder != null && mFolder.isImportantOnly()) {
                     delete(R.id.mark_not_important, target,
-                            getDeferredAction(R.id.mark_not_important, target, isBatch), isBatch,
-                            true /* allowDialog */);
+                            getDeferredAction(R.id.mark_not_important, target, isBatch), isBatch);
                 } else {
                     updateConversation(Conversation.listOf(mCurrentConversation),
                             ConversationColumns.PRIORITY, UIProvider.ConversationPriority.LOW);
                 }
                 break;
             case R.id.mute:
-                delete(R.id.mute, target, getDeferredAction(R.id.mute, target, isBatch), isBatch,
-                        true /* allowDialog */);
+                delete(R.id.mute, target, getDeferredAction(R.id.mute, target, isBatch), isBatch);
                 break;
             case R.id.report_spam:
                 delete(R.id.report_spam, target,
-                        getDeferredAction(R.id.report_spam, target, isBatch), isBatch,
-                        true /* allowDialog */);
+                        getDeferredAction(R.id.report_spam, target, isBatch), isBatch);
                 break;
             case R.id.mark_not_spam:
                 // Currently, since spam messages are only shown in list with
                 // other spam messages,
                 // marking a message not as spam is a destructive action
                 delete(R.id.mark_not_spam, target,
-                        getDeferredAction(R.id.mark_not_spam, target, isBatch), isBatch,
-                        true /* allowDialog */);
+                        getDeferredAction(R.id.mark_not_spam, target, isBatch), isBatch);
                 break;
             case R.id.report_phishing:
                 delete(R.id.report_phishing, target,
-                        getDeferredAction(R.id.report_phishing, target, isBatch), isBatch,
-                        true /* allowDialog */);
+                        getDeferredAction(R.id.report_phishing, target, isBatch), isBatch);
                 break;
             case android.R.id.home:
                 onUpPressed();
@@ -1401,6 +1394,25 @@
                     dialog.show();
                 }
                 break;
+            case R.id.move_to_inbox:
+                new AsyncTask<Void, Void, Folder>() {
+                    @Override
+                    protected Folder doInBackground(final Void... params) {
+                        // Get the "move to" inbox
+                        return Utils.getFolder(mContext, mAccount.settings.moveToInbox,
+                                true /* allowHidden */);
+                    }
+
+                    @Override
+                    protected void onPostExecute(final Folder moveToInbox) {
+                        final List<FolderOperation> ops = Lists.newArrayListWithCapacity(1);
+                        // Add inbox
+                        ops.add(new FolderOperation(moveToInbox, true));
+                        assignFolder(ops, Conversation.listOf(mCurrentConversation), true,
+                                true /* showUndo */, false /* isMoveTo */);
+                    }
+                }.execute((Void[]) null);
+                break;
             case R.id.empty_trash:
                 showEmptyDialog();
                 break;
@@ -1577,8 +1589,7 @@
             // Conversations are neither marked read, nor viewed, and we don't want to show
             // the next conversation.
             LogUtils.d(LOG_TAG, ". . doing full mark unread");
-            markConversationsRead(Collections.singletonList(conv), false, false, false,
-                    true /* allowDialog */);
+            markConversationsRead(Collections.singletonList(conv), false, false, false);
         } else {
             if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
                 final ConversationInfo info = ConversationInfo.fromBlob(originalConversationInfo);
@@ -1633,28 +1644,28 @@
             mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
                 @Override
                 public void onLoadFinished() {
-                    markConversationsRead(targets, read, viewed, true, true /* allowDialog */);
+                    markConversationsRead(targets, read, viewed, true);
                 }
             });
         } else {
             // We want to show the next conversation if we are marking unread.
-            markConversationsRead(targets, read, viewed, true, true /* allowDialog */);
+            markConversationsRead(targets, read, viewed, true);
         }
     }
 
     private void markConversationsRead(final Collection<Conversation> targets, final boolean read,
-            final boolean markViewed, final boolean showNext, final boolean allowDialog) {
+            final boolean markViewed, final boolean showNext) {
         LogUtils.d(LOG_TAG, "performing markConversationsRead");
         // Auto-advance if requested and the current conversation is being marked unread
         if (showNext && !read) {
             final Runnable operation = new Runnable() {
                 @Override
                 public void run() {
-                    markConversationsRead(targets, read, markViewed, showNext, false);
+                    markConversationsRead(targets, read, markViewed, showNext);
                 }
             };
 
-            if (!showNextConversation(targets, operation, allowDialog)) {
+            if (!showNextConversation(targets, operation)) {
                 // This method will be called again if the user selects an autoadvance option
                 return;
             }
@@ -1704,7 +1715,7 @@
      */
     @Override
     public void showNextConversation(final Collection<Conversation> target) {
-        showNextConversation(target, null, true /* allowDialog */);
+        showNextConversation(target, null);
     }
 
     /**
@@ -1730,7 +1741,7 @@
      * <code>true</code> otherwise.
      */
     private boolean showNextConversation(final Collection<Conversation> target,
-            final Runnable operation, final boolean allowDialog) {
+            final Runnable operation) {
         final int viewMode = mViewMode.getMode();
         final boolean currentConversationInView = (viewMode == ViewMode.CONVERSATION
                 || viewMode == ViewMode.SEARCH_RESULTS_CONVERSATION)
@@ -1739,7 +1750,7 @@
         if (currentConversationInView) {
             final int autoAdvanceSetting = mAccount.settings.getAutoAdvanceSetting();
 
-            if (allowDialog && autoAdvanceSetting == AutoAdvance.UNSET && mIsTablet) {
+            if (autoAdvanceSetting == AutoAdvance.UNSET && mIsTablet) {
                 displayAutoAdvanceDialogAndPerformAction(operation);
                 return false;
             } else {
@@ -1812,46 +1823,6 @@
                 .show();
     }
 
-    private Runnable mRemovalActionDialogRunnable = null;
-
-    private void attachRemovalActionDialogListener() {
-        final RemovalActionPreferenceDialogFragment fragment =
-                (RemovalActionPreferenceDialogFragment) mActivity.getFragmentManager()
-                .findFragmentByTag(RemovalActionPreferenceDialogFragment.FRAGMENT_TAG);
-
-        if (fragment != null) {
-            fragment.setListener(mRemovalActionPreferenceDialogListener);
-        }
-    }
-
-    private final RemovalActionPreferenceDialogListener mRemovalActionPreferenceDialogListener =
-            new RemovalActionPreferenceDialogListener() {
-        @Override
-        public void onDismiss() {
-            if (mRemovalActionDialogRunnable != null) {
-                mRemovalActionDialogRunnable.run();
-                mRemovalActionDialogRunnable = null;
-            }
-        }
-    };
-
-    /**
-     * Displays a the removal action dialog, and when the user makes a selection, the preference is
-     * stored, and the specified operation is run.
-     *
-     * @return <code>true</code> if the dialog was shown, <code>false</code> otherwise
-     */
-    private boolean displayRemovalActionDialogAndPerformAction(final Runnable operation) {
-        final boolean shown =  RemovalActionPreferenceDialogFragment.showIfNecessary(mContext,
-                mAccount, mActivity.getFragmentManager(), mRemovalActionPreferenceDialogListener);
-
-        if (shown) {
-            mRemovalActionDialogRunnable = operation;
-        }
-
-        return shown;
-    }
-
     @Override
     public void starMessage(ConversationMessage msg, boolean starred) {
         if (msg.starred == starred) {
@@ -1921,14 +1892,13 @@
             final ConfirmDialogFragment c = ConfirmDialogFragment.newInstance(message);
             c.displayDialog(mActivity.getFragmentManager());
         } else {
-            delete(actionId, target, getDeferredAction(actionId, target, isBatch), isBatch,
-                    true /* allowDialog */);
+            delete(0, target, getDeferredAction(actionId, target, isBatch), isBatch);
         }
     }
 
     @Override
     public void delete(final int actionId, final Collection<Conversation> target,
-            final DestructiveAction action, final boolean isBatch, final boolean allowDialog) {
+                       final DestructiveAction action, final boolean isBatch) {
         // Order of events is critical! The Conversation View Fragment must be
         // notified of the next conversation with showConversation(next) *before* the
         // conversation list
@@ -1939,18 +1909,13 @@
         final Runnable operation = new Runnable() {
             @Override
             public void run() {
-                delete(actionId, target, action, isBatch, false);
+                delete(actionId, target, action, isBatch);
             }
         };
 
-        if (!showNextConversation(target, operation, allowDialog)) {
+        if (!showNextConversation(target, operation)) {
             // This method will be called again if the user selects an autoadvance option
             return;
-        } else if (allowDialog && (actionId == R.id.delete || actionId == R.id.archive)
-                && displayRemovalActionDialogAndPerformAction(operation)) {
-            // Show a dialog to inform the user that they can configure the removal setting
-            // This method will be called again when the dialog is dismissed
-            return;
         }
         // If the conversation is in the selected set, remove it from the set.
         // Batch selections are cleared in the end of the action, so not done for batch actions.
@@ -2010,7 +1975,6 @@
         mSafeToModifyFragments = true;
 
         attachEmptyFolderDialogFragmentListener();
-        attachRemovalActionDialogListener();
 
         // Invalidating the options menu so that when we make changes in settings,
         // the changes will always be updated in the action bar/options menu/
@@ -2897,7 +2861,7 @@
 
             folderChange = getDeferredFolderChange(target, folderOps, isDestructive,
                     batch, showUndo, isMoveTo, actionFolder);
-            delete(0, target, folderChange, batch, true /* allowDialog */);
+            delete(0, target, folderChange, batch);
         } else {
             folderChange = getFolderChange(target, folderOps, isDestructive,
                     batch, showUndo, false /* isMoveTo */, mFolder);
@@ -3176,7 +3140,7 @@
                 getFolderChange(conversations, dragDropOperations, isDestructive,
                         true /* isBatch */, true /* showUndo */, true /* isMoveTo */, folder);
         if (isDestructive) {
-            delete(0, conversations, action, true, true /* allowDialog */);
+            delete(0, conversations, action, true);
         } else {
             action.performAction();
         }
@@ -4034,7 +3998,7 @@
         mDialogListener = new AlertDialog.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
-                delete(action, target, destructiveAction, isBatch, true /* allowDialog */);
+                delete(action, target, destructiveAction, isBatch);
                 // Afterwards, let's remove references to the listener and the action.
                 setListener(null, -1);
             }
diff --git a/src/com/android/mail/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index b4bd5fd..29cedef 100644
--- a/src/com/android/mail/ui/AbstractConversationViewFragment.java
+++ b/src/com/android/mail/ui/AbstractConversationViewFragment.java
@@ -30,14 +30,12 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
-import com.android.mail.FormattedDateBuilder;
 import com.android.mail.R;
 import com.android.mail.browse.ConversationAccountController;
 import com.android.mail.browse.ConversationMessage;
 import com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks;
 import com.android.mail.browse.MessageCursor;
 import com.android.mail.browse.MessageCursor.ConversationController;
-import com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
 import com.android.mail.content.ObjectCursor;
 import com.android.mail.content.ObjectCursorLoader;
 import com.android.mail.providers.Account;
@@ -59,7 +57,7 @@
 
 
 public abstract class AbstractConversationViewFragment extends Fragment implements
-        ConversationController, ConversationAccountController, MessageHeaderViewCallbacks,
+        ConversationController, ConversationAccountController,
         ConversationViewHeaderCallbacks {
 
     private static final String ARG_ACCOUNT = "account";
@@ -70,7 +68,6 @@
     protected static final int CONTACT_LOADER = 1;
     protected ControllableActivity mActivity;
     private final MessageLoaderCallbacks mMessageLoaderCallbacks = new MessageLoaderCallbacks();
-    protected FormattedDateBuilder mDateBuilder;
     private ContactLoaderCallbacks mContactLoaderCallbacks;
     private MenuItem mChangeFoldersMenuItem;
     protected Conversation mConversation;
@@ -82,6 +79,7 @@
      * Must be instantiated in a derived class's onCreate.
      */
     protected AbstractConversationWebViewClient mWebViewClient;
+
     /**
      * Cache of email address strings to parsed Address objects.
      * <p>
@@ -241,7 +239,7 @@
         }
         mActivity = (ControllableActivity) activity;
         mContext = activity.getApplicationContext();
-        mDateBuilder = new FormattedDateBuilder((Context) mActivity);
+        mWebViewClient.setActivity(activity);
         mAccount = mAccountObserver.initialize(mActivity.getAccountController());
         mWebViewClient.setAccount(mAccount);
     }
@@ -331,6 +329,8 @@
                 mHasConversationBeenTransformed && !mHasConversationTransformBeenReverted);
     }
 
+    abstract boolean supportsMessageTransforms();
+
     // BEGIN conversation header callbacks
     @Override
     public void onFoldersClicked() {
diff --git a/src/com/android/mail/ui/AbstractConversationWebViewClient.java b/src/com/android/mail/ui/AbstractConversationWebViewClient.java
index 139eaa5..0684b0c 100644
--- a/src/com/android/mail/ui/AbstractConversationWebViewClient.java
+++ b/src/com/android/mail/ui/AbstractConversationWebViewClient.java
@@ -17,8 +17,8 @@
 
 package com.android.mail.ui;
 
+import android.app.Activity;
 import android.content.ActivityNotFoundException;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
@@ -45,20 +45,23 @@
     private static final String LOG_TAG = LogTag.getLogTag();
 
     private Account mAccount;
-    private final Context mContext;
+    private Activity mActivity;
 
-    public AbstractConversationWebViewClient(Context context, Account account) {
+    public AbstractConversationWebViewClient(Account account) {
         mAccount = account;
-        mContext = context;
     }
 
     public void setAccount(Account account) {
         mAccount = account;
     }
 
+    public void setActivity(Activity activity) {
+        mActivity = activity;
+    }
+
     @Override
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
-        if (mContext == null) {
+        if (mActivity == null) {
             return false;
         }
 
@@ -69,12 +72,12 @@
             intent = generateProxyIntent(uri);
         } else {
             intent = new Intent(Intent.ACTION_VIEW, uri);
-            intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName());
+            intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName());
         }
 
         try {
             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-            mContext.startActivity(intent);
+            mActivity.startActivity(intent);
             result = true;
         } catch (ActivityNotFoundException ex) {
             // If no application can handle the URL, assume that the
@@ -93,7 +96,7 @@
         // We need to catch the exception to make CanvasConversationHeaderView
         // test pass.  Bug: http://b/issue?id=3470653.
         try {
-            manager = mContext.getPackageManager();
+            manager = mActivity.getPackageManager();
         } catch (UnsupportedOperationException e) {
             LogUtils.e(LOG_TAG, e, "Error getting package manager");
         }
@@ -103,7 +106,7 @@
             final List<ResolveInfo> resolvedActivities = manager.queryIntentActivities(
                     intent, PackageManager.MATCH_DEFAULT_ONLY);
 
-            final String packageName = mContext.getPackageName();
+            final String packageName = mActivity.getPackageName();
 
             // Now try and find one that came from this package, if one is not found, the UI
             // provider must have specified an intent that is to be handled by a different apk.
diff --git a/src/com/android/mail/ui/ConversationUpdater.java b/src/com/android/mail/ui/ConversationUpdater.java
index cb6bad6..c3b71af 100644
--- a/src/com/android/mail/ui/ConversationUpdater.java
+++ b/src/com/android/mail/ui/ConversationUpdater.java
@@ -82,11 +82,10 @@
      * @param target the conversations to act upon.
      * @param action to perform after the UI has been updated to remove the conversations
      * @param isBatch true if this is a batch action, false otherwise.
-     * @param allowDialog <code>true</code> to allow dialogs to be displayed
      */
     void delete(
             int actionId, final Collection<Conversation> target, final DestructiveAction action,
-            boolean isBatch, boolean allowDialog);
+            boolean isBatch);
 
     /**
      * Mark a number of conversations as read or unread.
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index a110ce2..6de375e 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -17,7 +17,6 @@
 
 package com.android.mail.ui;
 
-
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Loader;
@@ -42,7 +41,6 @@
 import android.webkit.WebChromeClient;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
-import android.webkit.WebViewClient;
 import android.widget.TextView;
 
 import com.android.mail.FormattedDateBuilder;
@@ -85,13 +83,12 @@
 import java.util.Map;
 import java.util.Set;
 
-
 /**
  * The conversation view UI component.
  */
 public final class ConversationViewFragment extends AbstractConversationViewFragment implements
-        SuperCollapsedBlock.OnClickListener,
-        OnLayoutChangeListener {
+        SuperCollapsedBlock.OnClickListener, OnLayoutChangeListener,
+        MessageHeaderView.MessageHeaderViewCallbacks {
 
     private static final String LOG_TAG = LogTag.getLogTag();
     public static final String LAYOUT_TAG = "ConvLayout";
@@ -323,7 +320,7 @@
     public void onCreate(Bundle savedState) {
         super.onCreate(savedState);
 
-        mWebViewClient = new ConversationWebViewClient(getContext(), mAccount);
+        mWebViewClient = new ConversationWebViewClient(mAccount);
 
         if (savedState != null) {
             mWebViewYPercent = savedState.getFloat(BUNDLE_KEY_WEBVIEW_Y_PERCENT);
@@ -738,8 +735,8 @@
         for (int i = blockToReplace.getStart(), end = blockToReplace.getEnd(); i <= end; i++) {
             cursor.moveToPosition(i);
             final ConversationMessage msg = cursor.getMessage();
-            final MessageHeaderItem header = mAdapter.newMessageHeaderItem(msg,
-                    false /* expanded */, mViewState.getShouldShowImages(msg));
+            final MessageHeaderItem header = ConversationViewAdapter.newMessageHeaderItem(
+                    mAdapter, msg, false /* expanded */, mViewState.getShouldShowImages(msg));
             final MessageFooterItem footer = mAdapter.newMessageFooterItem(header);
 
             final int headerPx = measureOverlayHeight(header);
@@ -979,8 +976,8 @@
     }
 
     private class ConversationWebViewClient extends AbstractConversationWebViewClient {
-        public ConversationWebViewClient(Context context, Account account) {
-            super(context, account);
+        public ConversationWebViewClient(Account account) {
+            super(account);
         }
 
         @Override
@@ -1012,8 +1009,8 @@
             for (Address addr : cacheCopy) {
                 emailAddresses.add(addr.getAddress());
             }
-            ContactLoaderCallbacks callbacks = getContactInfoSource();
-            getContactInfoSource().setSenders(emailAddresses);
+            final ContactLoaderCallbacks callbacks = getContactInfoSource();
+            callbacks.setSenders(emailAddresses);
             getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
         }
 
diff --git a/src/com/android/mail/ui/MailActionBarView.java b/src/com/android/mail/ui/MailActionBarView.java
index d7bf719..c967530 100644
--- a/src/com/android/mail/ui/MailActionBarView.java
+++ b/src/com/android/mail/ui/MailActionBarView.java
@@ -894,6 +894,9 @@
                 && !mFolder.isProviderFolder());
         Utils.setMenuItemVisibility(menu, R.id.move_to, mFolder != null
                 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION));
+        Utils.setMenuItemVisibility(menu, R.id.move_to_inbox, mFolder != null
+                && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX));
+
         final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
         if (mFolder != null && removeFolder != null) {
             removeFolder.setTitle(mActivity.getApplicationContext().getString(
diff --git a/src/com/android/mail/ui/RemovalActionPreferenceDialogFragment.java b/src/com/android/mail/ui/RemovalActionPreferenceDialogFragment.java
deleted file mode 100644
index 8b177a5..0000000
--- a/src/com/android/mail/ui/RemovalActionPreferenceDialogFragment.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.FragmentManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.Bundle;
-
-import com.android.mail.R;
-import com.android.mail.preferences.MailPrefs;
-import com.android.mail.providers.Account;
-import com.android.mail.providers.UIProvider.AccountCapabilities;
-
-import java.lang.ref.WeakReference;
-
-/**
- * This dialog is shown when a user first archives or deletes a message, and asks them what there
- * preferred action is (archive, delete, or both).
- */
-public class RemovalActionPreferenceDialogFragment extends DialogFragment {
-    /**
-     * A listener that can be attached to a {@link RemovalActionPreferenceDialogFragment} to receive
-     * onDismiss() events.
-     */
-    public interface RemovalActionPreferenceDialogListener {
-        void onDismiss();
-    }
-
-    public static final String FRAGMENT_TAG = "ArchiveDeletePreferenceDialogFragment";
-
-    private static final String ARG_DEFAULT_VALUE = "defaultValue";
-
-    private String mDefaultValue;
-
-    private WeakReference<RemovalActionPreferenceDialogListener> mListener = null;
-
-    /**
-     * Create a new {@link ArchiveDeletePreferenceDialogFragment}.
-     *
-     * @param defaultValue the initial value to show as checked
-     */
-    public static RemovalActionPreferenceDialogFragment newInstance(final String defaultValue) {
-        final RemovalActionPreferenceDialogFragment fragment =
-                new RemovalActionPreferenceDialogFragment();
-
-        final Bundle args = new Bundle(1);
-        args.putString(ARG_DEFAULT_VALUE, defaultValue);
-        fragment.setArguments(args);
-
-        return fragment;
-    }
-
-    public void setListener(final RemovalActionPreferenceDialogListener listener) {
-        mListener = new WeakReference<RemovalActionPreferenceDialogListener>(listener);
-    }
-
-    @Override
-    public Dialog onCreateDialog(final Bundle savedInstanceState) {
-        final String[] entries = getResources().getStringArray(R.array.prefEntries_removal_action);
-        final String[] entryValues =
-                getResources().getStringArray(R.array.prefValues_removal_action);
-
-        final String defaultValue = getArguments().getString(ARG_DEFAULT_VALUE);
-
-        // Find the default value in the entryValues array
-        // If we can't find it, we end up on index 0, which is "archive"
-        int defaultItem;
-        for (defaultItem = entryValues.length - 1; defaultItem >= 0; defaultItem--) {
-            if (entryValues[defaultItem].equals(defaultValue)) {
-                break;
-            }
-        }
-
-        mDefaultValue = entryValues[defaultItem];
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
-                .setTitle(R.string.prefDialogTitle_removal_action_first_time)
-                .setSingleChoiceItems(entries, defaultItem, new OnClickListener() {
-                    @Override
-                    public void onClick(final DialogInterface dialog, final int which) {
-                        mDefaultValue = entryValues[which];
-                        dismiss();
-                    }
-                });
-
-        return builder.create();
-    }
-
-    @Override
-    public void onDismiss(final DialogInterface dialog) {
-        super.onDismiss(dialog);
-
-        // Use whatever was originally selected
-        saveRemovalAction(mDefaultValue);
-    }
-
-    /**
-     * Shows the dialog, if necessary.
-     *
-     * @param account The account this is being requested for
-     * @param mRemovalActionPreferenceDialogListener
-     * @param defaultValue The default removal action to show checked
-     * @return <code>true</code> if the dialog was shown, <code>false</code> otherwise
-     */
-    public static boolean showIfNecessary(final Context context, final Account account,
-            final FragmentManager fragmentManager,
-            final RemovalActionPreferenceDialogListener removalActionPreferenceDialogListener) {
-        final boolean supportsArchive = account.supportsCapability(AccountCapabilities.ARCHIVE);
-
-        if (shouldDisplayDialog(context, supportsArchive)) {
-            final String defaultValue = MailPrefs.get(context).getRemovalAction(supportsArchive);
-
-            final RemovalActionPreferenceDialogFragment fragment = newInstance(defaultValue);
-            fragment.setListener(removalActionPreferenceDialogListener);
-            fragment.show(fragmentManager, FRAGMENT_TAG);
-
-            return true;
-        }
-
-         return false;
-    }
-
-    /**
-     * Checks whether we should show the dialog. We show it if:
-     * <ol>
-     * <li>Archive is supported by the account</li>
-     * <li>We have not previously shown the dialog</li>
-     * </ol>
-     *
-     * @param supportsArchive <code>true</code> if the current account supports
-     *            archive, <code>false</code> otherwise
-     * @return <code>true</code> if the dialog needs to be displayed (because it
-     *         hasn't been shown yet), <code>false</code> otherwise
-     */
-    private static boolean shouldDisplayDialog(final Context context,
-            final boolean supportsArchive) {
-        return supportsArchive && !MailPrefs.get(context).hasRemovalActionDialogShown();
-    }
-
-    private void saveRemovalAction(final String removalAction) {
-        final MailPrefs mailPrefs = MailPrefs.get(getActivity());
-        mailPrefs.setRemovalAction(removalAction);
-        mailPrefs.setRemovalActionDialogShown();
-
-        if (mListener != null) {
-            final RemovalActionPreferenceDialogListener listener = mListener.get();
-            if (listener != null) {
-                listener.onDismiss();
-            }
-        }
-    }
-}
diff --git a/src/com/android/mail/ui/SecureConversationViewController.java b/src/com/android/mail/ui/SecureConversationViewController.java
new file mode 100644
index 0000000..a3e6ec8
--- /dev/null
+++ b/src/com/android/mail/ui/SecureConversationViewController.java
@@ -0,0 +1,210 @@
+/*
+ * 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.Fragment;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebSettings;
+
+import com.android.mail.FormattedDateBuilder;
+import com.android.mail.R;
+import com.android.mail.browse.ConversationMessage;
+import com.android.mail.browse.ConversationViewAdapter;
+import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
+import com.android.mail.browse.ConversationViewHeader;
+import com.android.mail.browse.MessageFooterView;
+import com.android.mail.browse.MessageHeaderView;
+import com.android.mail.browse.MessageScrollView;
+import com.android.mail.browse.MessageWebView;
+import com.android.mail.providers.Message;
+import com.android.mail.utils.ConversationViewUtils;
+
+/**
+ * Controller to do most of the heavy lifting for {@link SecureConversationViewFragment}
+ * and {@link com.android.mail.browse.EmlMessageViewFragment}. Currently that work is
+ * pretty much the rendering logic.
+ */
+public class SecureConversationViewController implements
+        MessageHeaderView.MessageHeaderViewCallbacks {
+    private static final String BEGIN_HTML =
+            "<body style=\"margin: 0 %spx;\"><div style=\"margin: 16px 0; font-size: 80%%\">";
+    private static final String END_HTML = "</div></body>";
+
+    private final SecureConversationViewControllerCallbacks mCallbacks;
+
+    private MessageWebView mWebView;
+    private ConversationViewHeader mConversationHeaderView;
+    private MessageHeaderView mMessageHeaderView;
+    private MessageFooterView mMessageFooterView;
+    private ConversationMessage mMessage;
+    private MessageScrollView mScrollView;
+
+    private ConversationViewProgressController mProgressController;
+    private FormattedDateBuilder mDateBuilder;
+
+    private int mSideMarginInWebPx;
+
+    public SecureConversationViewController(SecureConversationViewControllerCallbacks callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.secure_conversation_view, container, false);
+        mScrollView = (MessageScrollView) rootView.findViewById(R.id.scroll_view);
+        mConversationHeaderView = (ConversationViewHeader) rootView.findViewById(R.id.conv_header);
+        mMessageHeaderView = (MessageHeaderView) rootView.findViewById(R.id.message_header);
+        mMessageFooterView = (MessageFooterView) rootView.findViewById(R.id.message_footer);
+
+        mProgressController = new ConversationViewProgressController(
+                mCallbacks.getFragment(), mCallbacks.getHandler());
+        mProgressController.instantiateProgressIndicators(rootView);
+        mWebView = (MessageWebView) rootView.findViewById(R.id.webview);
+        mWebView.setWebViewClient(mCallbacks.getWebViewClient());
+        mWebView.setFocusable(false);
+        final WebSettings settings = mWebView.getSettings();
+
+        settings.setJavaScriptEnabled(false);
+        settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
+
+        ConversationViewUtils.setTextZoom(mCallbacks.getFragment().getResources(), settings);
+
+        settings.setSupportZoom(true);
+        settings.setBuiltInZoomControls(true);
+        settings.setDisplayZoomControls(false);
+
+        mScrollView.setInnerScrollableView(mWebView);
+
+        return rootView;
+    }
+
+    public void onActivityCreated(Bundle savedInstanceState) {
+        mCallbacks.setupConversationHeaderView(mConversationHeaderView);
+
+        final Fragment fragment = mCallbacks.getFragment();
+
+        mDateBuilder = new FormattedDateBuilder(fragment.getActivity());
+        mMessageHeaderView.initialize(mDateBuilder,
+                mCallbacks.getConversationAccountController(), mCallbacks.getAddressCache());
+        mMessageHeaderView.setExpandMode(MessageHeaderView.POPUP_MODE);
+        mMessageHeaderView.setContactInfoSource(mCallbacks.getContactInfoSource());
+        mMessageHeaderView.setCallbacks(this);
+        mMessageHeaderView.setExpandable(false);
+        mMessageHeaderView.setViewOnlyMode(mCallbacks.isViewOnlyMode());
+
+        mCallbacks.setupMessageHeaderVeiledMatcher(mMessageHeaderView);
+
+        mMessageFooterView.initialize(fragment.getLoaderManager(), fragment.getFragmentManager());
+
+        mCallbacks.startMessageLoader();
+
+        mProgressController.showLoadingStatus(mCallbacks.isViewVisibleToUser());
+
+        final Resources r = mCallbacks.getFragment().getResources();
+        mSideMarginInWebPx = (int) ((r.getDimensionPixelOffset(
+                R.dimen.conversation_view_margin_side) + r.getDimensionPixelOffset(
+                R.dimen.conversation_message_content_margin_side)) / r.getDisplayMetrics().density);
+    }
+
+    /**
+     * Populate the adapter with overlay views (message headers, super-collapsed
+     * blocks, a conversation header), and return an HTML document with spacer
+     * divs inserted for all overlays.
+     */
+    public void renderMessage(ConversationMessage message) {
+        mMessage = message;
+
+        mWebView.getSettings().setBlockNetworkImage(!mMessage.alwaysShowImages);
+
+        // Add formatting to message body
+        // At this point, only adds margins.
+        StringBuilder dataBuilder = new StringBuilder(
+                String.format(BEGIN_HTML, mSideMarginInWebPx));
+        dataBuilder.append(mMessage.getBodyAsHtml());
+        dataBuilder.append(END_HTML);
+
+        mWebView.loadDataWithBaseURL(mCallbacks.getBaseUri(), dataBuilder.toString(),
+                "text/html", "utf-8", null);
+        final MessageHeaderItem item = ConversationViewAdapter.newMessageHeaderItem(
+                null, mMessage, true, mMessage.alwaysShowImages);
+        mMessageHeaderView.bind(item, false);
+        if (mMessage.hasAttachments) {
+            mMessageFooterView.setVisibility(View.VISIBLE);
+            mMessageFooterView.bind(item, false);
+        }
+    }
+
+    public ConversationMessage getMessage() {
+        return mMessage;
+    }
+
+    public ConversationViewHeader getConversationHeaderView() {
+        return mConversationHeaderView;
+    }
+
+    public void dismissLoadingStatus() {
+        mProgressController.dismissLoadingStatus();
+    }
+
+    public void setSubject(String subject) {
+        mConversationHeaderView.setSubject(subject);
+    }
+
+    // Start MessageHeaderViewCallbacks implementations
+
+    @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);
+    }
+
+    @Override
+    public void showExternalResources(final String rawSenderAddress) {
+        mWebView.getSettings().setBlockNetworkImage(false);
+    }
+
+    @Override
+    public boolean supportsMessageTransforms() {
+        return false;
+    }
+
+    @Override
+    public String getMessageTransforms(final Message msg) {
+        return null;
+    }
+
+    // End MessageHeaderViewCallbacks implementations
+}
diff --git a/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java b/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java
new file mode 100644
index 0000000..06857ed
--- /dev/null
+++ b/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java
@@ -0,0 +1,47 @@
+/*
+ * 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.Fragment;
+import android.os.Handler;
+
+import com.android.mail.ContactInfoSource;
+import com.android.mail.browse.ConversationAccountController;
+import com.android.mail.browse.ConversationViewHeader;
+import com.android.mail.browse.MessageHeaderView;
+import com.android.mail.providers.Address;
+
+import java.util.Map;
+
+/**
+ * Callbacks for fragments that use the {@link SecureConversationViewController}.
+ */
+public interface SecureConversationViewControllerCallbacks {
+    public Handler getHandler();
+    public AbstractConversationWebViewClient getWebViewClient();
+    public Fragment getFragment();
+    public void setupConversationHeaderView(ConversationViewHeader headerView);
+    public boolean isViewVisibleToUser();
+    public ContactInfoSource getContactInfoSource();
+    public ConversationAccountController getConversationAccountController();
+    public Map<String, Address> getAddressCache();
+    public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView);
+    public void startMessageLoader();
+    public String getBaseUri();
+    public boolean isViewOnlyMode();
+}
diff --git a/src/com/android/mail/ui/SecureConversationViewFragment.java b/src/com/android/mail/ui/SecureConversationViewFragment.java
index b94be5d..c3efd59 100644
--- a/src/com/android/mail/ui/SecureConversationViewFragment.java
+++ b/src/com/android/mail/ui/SecureConversationViewFragment.java
@@ -17,60 +17,43 @@
 
 package com.android.mail.ui;
 
-import android.content.Context;
+import android.app.Fragment;
 import android.content.Loader;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.webkit.WebSettings;
-import android.webkit.WebSettings.LayoutAlgorithm;
 import android.webkit.WebView;
-import android.webkit.WebViewClient;
 
-import com.android.mail.R;
+import com.android.mail.browse.ConversationAccountController;
 import com.android.mail.browse.ConversationMessage;
-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.MessageFooterView;
 import com.android.mail.browse.MessageHeaderView;
-import com.android.mail.browse.MessageScrollView;
-import com.android.mail.browse.MessageWebView;
 import com.android.mail.content.ObjectCursor;
 import com.android.mail.providers.Account;
+import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
-import com.android.mail.providers.Message;
-import com.android.mail.utils.ConversationViewUtils;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
-public class SecureConversationViewFragment extends AbstractConversationViewFragment {
+public class SecureConversationViewFragment extends AbstractConversationViewFragment
+        implements SecureConversationViewControllerCallbacks {
     private static final String LOG_TAG = LogTag.getLogTag();
 
-    private static final String BEGIN_HTML =
-            "<body style=\"margin: 0 %spx;\"><div style=\"margin: 16px 0; font-size: 80%%\">";
-    private static final String END_HTML = "</div></body>";
-
-    private MessageWebView mWebView;
-    private ConversationViewHeader mConversationHeaderView;
-    private MessageHeaderView mMessageHeaderView;
-    private MessageFooterView mMessageFooterView;
-    private ConversationMessage mMessage;
-    private MessageScrollView mScrollView;
-
-    private int mSideMarginInWebPx;
-
-    private ConversationViewProgressController mProgressController;
+    private SecureConversationViewController mViewController;
 
     private class SecureConversationWebViewClient extends AbstractConversationWebViewClient {
-        public SecureConversationWebViewClient(Context context, Account account) {
-            super(context, account);
+        public SecureConversationWebViewClient(Account account) {
+            super(account);
         }
 
         @Override
@@ -79,7 +62,19 @@
                 onConversationSeen();
             }
 
-            mProgressController.dismissLoadingStatus();
+            mViewController.dismissLoadingStatus();
+
+            final Set<String> emailAddresses = Sets.newHashSet();
+            final List<Address> cacheCopy;
+            synchronized (mAddressCache) {
+                cacheCopy = ImmutableList.copyOf(mAddressCache.values());
+            }
+            for (Address addr : cacheCopy) {
+                emailAddresses.add(addr.getAddress());
+            }
+            final ContactLoaderCallbacks callbacks = getContactInfoSource();
+            callbacks.setSenders(emailAddresses);
+            getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
         }
     };
 
@@ -109,76 +104,93 @@
     public void onCreate(Bundle savedState) {
         super.onCreate(savedState);
 
-        mWebViewClient = new SecureConversationWebViewClient(getContext(), mAccount);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        mConversationHeaderView.setCallbacks(this, this);
-        mConversationHeaderView.setFolders(mConversation);
-        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());
-        mProgressController.showLoadingStatus(isUserVisible());
-
-        final Resources r = getResources();
-        mSideMarginInWebPx = (int) ((r.getDimensionPixelOffset(
-                R.dimen.conversation_view_margin_side) + r.getDimensionPixelOffset(
-                R.dimen.conversation_message_content_margin_side)) / r.getDisplayMetrics().density);
+        mWebViewClient = new SecureConversationWebViewClient(mAccount);
+        mViewController = new SecureConversationViewController(this);
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
-        View rootView = inflater.inflate(R.layout.secure_conversation_view, container, false);
-        mScrollView = (MessageScrollView) rootView.findViewById(R.id.scroll_view);
-        mConversationHeaderView = (ConversationViewHeader) rootView.findViewById(R.id.conv_header);
-        mMessageHeaderView = (MessageHeaderView) rootView.findViewById(R.id.message_header);
-        mMessageFooterView = (MessageFooterView) rootView.findViewById(R.id.message_footer);
-
-        mProgressController = new ConversationViewProgressController(this, getHandler());
-        mProgressController.instantiateProgressIndicators(rootView);
-        mWebView = (MessageWebView) rootView.findViewById(R.id.webview);
-        mWebView.setWebViewClient(mWebViewClient);
-        mWebView.setFocusable(false);
-        final WebSettings settings = mWebView.getSettings();
-
-        settings.setJavaScriptEnabled(false);
-        settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
-
-        ConversationViewUtils.setTextZoom(getResources(), settings);
-
-        settings.setSupportZoom(true);
-        settings.setBuiltInZoomControls(true);
-        settings.setDisplayZoomControls(false);
-
-        mScrollView.setInnerScrollableView(mWebView);
-
-        return rootView;
+        return mViewController.onCreateView(inflater, container, savedInstanceState);
     }
 
     @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mViewController.onActivityCreated(savedInstanceState);
+    }
+
+    // Start implementations of SecureConversationViewControllerCallbacks
+
+    @Override
+    public Fragment getFragment() {
+        return this;
+    }
+
+    @Override
+    public AbstractConversationWebViewClient getWebViewClient() {
+        return mWebViewClient;
+    }
+
+    @Override
+    public void setupConversationHeaderView(ConversationViewHeader headerView) {
+        headerView.setCallbacks(this, this);
+        headerView.setFolders(mConversation);
+        headerView.setSubject(mConversation.subject);
+    }
+
+    @Override
+    public boolean isViewVisibleToUser() {
+        return isUserVisible();
+    }
+
+    @Override
+    public ConversationAccountController getConversationAccountController() {
+        return this;
+    }
+
+    @Override
+    public Map<String, Address> getAddressCache() {
+        return mAddressCache;
+    }
+
+    @Override
+    public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView) {
+        messageHeaderView.setVeiledMatcher(
+                ((ControllableActivity) getActivity()).getAccountController()
+                        .getVeiledAddressMatcher());
+    }
+
+    @Override
+    public void startMessageLoader() {
+        getLoaderManager().initLoader(MESSAGE_LOADER, null, getMessageLoaderCallbacks());
+    }
+
+    @Override
+    public String getBaseUri() {
+        return mBaseUri;
+    }
+
+    @Override
+    public boolean isViewOnlyMode() {
+        return false;
+    }
+
+    // End implementations of SecureConversationViewControllerCallbacks
+
+    @Override
     protected void markUnread() {
         super.markUnread();
         // Ignore unsafe calls made after a fragment is detached from an activity
         final ControllableActivity activity = (ControllableActivity) getActivity();
-        if (activity == null || mConversation == null || mMessage == null) {
+        final ConversationMessage message = mViewController.getMessage();
+        if (activity == null || mConversation == null || message == null) {
             LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s",
                     mConversation != null ? mConversation.id : 0);
             return;
         }
         final HashSet<Uri> uris = new HashSet<Uri>();
-        uris.add(mMessage.uri);
+        uris.add(message.uri);
         activity.getConversationUpdater().markConversationMessagesUnread(mConversation, uris,
                 mViewState.getConversationInfo());
     }
@@ -204,41 +216,6 @@
     }
 
     @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);
-    }
-
-    @Override
-    public void showExternalResources(final String rawSenderAddress) {
-        mWebView.getSettings().setBlockNetworkImage(false);
-    }
-
-    @Override
-    public boolean supportsMessageTransforms() {
-        return false;
-    }
-
-    @Override
-    public String getMessageTransforms(final Message msg) {
-        return null;
-    }
-
-    @Override
     protected void onMessageCursorLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader,
             MessageCursor newCursor, MessageCursor oldCursor) {
         // ignore cursors that are still loading results
@@ -250,48 +227,25 @@
             // Activity is finishing, just bail.
             return;
         }
-        renderMessageBodies(newCursor);
-    }
-
-    /**
-     * Populate the adapter with overlay views (message headers, super-collapsed
-     * blocks, a conversation header), and return an HTML document with spacer
-     * divs inserted for all overlays.
-     */
-    private void renderMessageBodies(MessageCursor messageCursor) {
-        if (!messageCursor.moveToFirst()) {
+        if (!newCursor.moveToFirst()) {
             LogUtils.e(LOG_TAG, "unable to open message cursor");
             return;
         }
-        mMessage = messageCursor.getMessage();
-        mWebView.getSettings().setBlockNetworkImage(!mMessage.alwaysShowImages);
 
-        // Add formatting to message body
-        // At this point, only adds margins.
-        StringBuilder dataBuilder = new StringBuilder(
-                String.format(BEGIN_HTML, mSideMarginInWebPx));
-        dataBuilder.append(mMessage.getBodyAsHtml());
-        dataBuilder.append(END_HTML);
-
-        mWebView.loadDataWithBaseURL(mBaseUri, dataBuilder.toString(), "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);
-        }
+        mViewController.renderMessage(newCursor.getMessage());
     }
 
     @Override
     public void onConversationUpdated(Conversation conv) {
-        final ConversationViewHeader headerView = mConversationHeaderView;
+        final ConversationViewHeader headerView = mViewController.getConversationHeaderView();
         if (headerView != null) {
             headerView.onConversationUpdated(conv);
             headerView.setSubject(conv.subject);
         }
     }
 
+    // Need this stub here
+    public boolean supportsMessageTransforms() {
+        return false;
+    }
 }
diff --git a/src/com/android/mail/utils/MimeType.java b/src/com/android/mail/utils/MimeType.java
index b17cdcb..ab2c6e4 100644
--- a/src/com/android/mail/utils/MimeType.java
+++ b/src/com/android/mail/utils/MimeType.java
@@ -41,7 +41,7 @@
     static final String GENERIC_MIMETYPE = "application/octet-stream";
 
     @VisibleForTesting
-    public static final Set<String> EML_ATTACHMENT_CONTENT_TYPES = ImmutableSet.of(
+    private static final Set<String> EML_ATTACHMENT_CONTENT_TYPES = ImmutableSet.of(
             "message/rfc822", "application/eml");
     public static final String EML_ATTACHMENT_CONTENT_TYPE = "message/rfc822";
     private static final String NULL_ATTACHMENT_CONTENT_TYPE = "null";
@@ -164,4 +164,14 @@
             }
         }
     }
+
+    /**
+     * Checks the supplied mime type to determine if it is a valid eml file.
+     * Valid mime types are "message/rfc822" and "application/eml".
+     * @param mimeType the mime type to check
+     * @return {@code true} if the mime type is one of the valid mime types.
+     */
+    public static boolean isEmlMimeType(String mimeType) {
+        return EML_ATTACHMENT_CONTENT_TYPES.contains(mimeType);
+    }
 }
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 2327e1f..e5b9e10 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -1287,4 +1287,64 @@
         return uri.buildUpon().appendQueryParameter(APP_VERSION_QUERY_PARAMETER,
                 Integer.toString(appVersion)).build();
     }
+
+    /**
+     * Gets the specified {@link Folder} object.
+     *
+     * @param folderUri The {@link Uri} for the folder
+     * @param allowHidden <code>true</code> to allow a hidden folder to be returned,
+     *        <code>false</code> to return <code>null</code> instead
+     * @return the specified {@link Folder} object, or <code>null</code>
+     */
+    public static Folder getFolder(final Context context, final Uri folderUri,
+            final boolean allowHidden) {
+        final Uri uri = folderUri
+                .buildUpon()
+                .appendQueryParameter(UIProvider.ALLOW_HIDDEN_FOLDERS_QUERY_PARAM,
+                        Boolean.toString(allowHidden))
+                .build();
+
+        final Cursor cursor = context.getContentResolver().query(uri,
+                UIProvider.FOLDERS_PROJECTION, null, null, null);
+
+        if (cursor == null) {
+            return null;
+        }
+
+        try {
+            if (cursor.moveToFirst()) {
+                return new Folder(cursor);
+            } else {
+                return null;
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Begins systrace tracing for a given tag. No-op on unsupported platform versions.
+     *
+     * @param tag systrace tag to use
+     *
+     * @see android.os.Trace#beginSection(String)
+     */
+    public static void traceBeginSection(String tag) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            android.os.Trace.beginSection(tag);
+        }
+    }
+
+    /**
+     * Ends systrace tracing for the most recently begun section. No-op on unsupported platform
+     * versions.
+     *
+     * @see android.os.Trace#endSection()
+     */
+    public static void traceEndSection() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            android.os.Trace.endSection();
+        }
+    }
+
 }