Merge "Move to Inbox" 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/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/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..724fd13
--- /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.setContext(getActivity().getApplicationContext());
+        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/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index b4bd5fd..bb37c29 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.setContext(mContext);
         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..2605c72 100644
--- a/src/com/android/mail/ui/AbstractConversationWebViewClient.java
+++ b/src/com/android/mail/ui/AbstractConversationWebViewClient.java
@@ -45,17 +45,20 @@
     private static final String LOG_TAG = LogTag.getLogTag();
 
     private Account mAccount;
-    private final Context mContext;
+    private Context mContext;
 
-    public AbstractConversationWebViewClient(Context context, Account account) {
+    public AbstractConversationWebViewClient(Account account) {
         mAccount = account;
-        mContext = context;
     }
 
     public void setAccount(Account account) {
         mAccount = account;
     }
 
+    public void setContext(Context context) {
+        mContext = context;
+    }
+
     @Override
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
         if (mContext == null) {
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/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);
+    }
 }