Merge "Fix unit tests"
diff --git a/res/values-xlarge-land/dimensions.xml b/res/values-xlarge-land/dimensions.xml
index cd25e73..34cb239 100644
--- a/res/values-xlarge-land/dimensions.xml
+++ b/res/values-xlarge-land/dimensions.xml
@@ -23,7 +23,7 @@
     <!-- XL activity dimensions -->
 
     <!-- width of mailbox list -->
-    <dimen name="mailbox_list_width">312dip</dimen>
+    <dimen name="mailbox_list_width">304dip</dimen>
     <!-- width of the message list, on the message list + message view mode. -->
     <dimen name="message_list_width">466dip</dimen>
 </resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index aab644a..7038419 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -17,14 +17,19 @@
 <resources>
     <dimen name="button_minWidth">100sp</dimen>
     <dimen name="message_list_item_text_size">16dip</dimen>
-    <dimen name="message_list_item_checkbox_hit_width">60dip</dimen>
-    <dimen name="message_list_item_favorite_hit_width">50dip</dimen>
+    <dimen name="message_list_item_checkbox_hit_width">64dip</dimen>
+    <dimen name="message_list_item_favorite_hit_width">64dip</dimen>
+    <dimen name="message_list_item_favorite_padding_right">16dip</dimen>
+    <dimen name="message_list_item_date_icon_width_wide">144dip</dimen>
+    <dimen name="message_list_item_date_icon_width_narrow">112dip</dimen>
+    <dimen name="message_list_item_sender_padding_top_narrow">10dip</dimen>
+    <dimen name="message_list_item_sender_width">200dip</dimen>
+    <dimen name="message_list_item_padding_large">32dip</dimen>
     <dimen name="message_list_item_padding_medium">6dip</dimen>
     <dimen name="message_list_item_padding_small">4dip</dimen>
     <dimen name="message_list_item_padding_very_small">2dip</dimen>
-    <dimen name="message_list_item_minimum_date_width">64dip</dimen>
     <dimen name="message_list_item_height_wide">64dip</dimen>
-    <dimen name="message_list_item_height_narrow">72dip</dimen>
+    <dimen name="message_list_item_height_narrow">80dip</dimen>
     <dimen name="message_list_item_minimum_width_wide_mode">720dip</dimen>
     <dimen name="message_list_item_color_tip_width">35dip</dimen>
     <dimen name="message_list_item_color_tip_height">8dip</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fc67947..dfd6b3d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -443,9 +443,9 @@
     <!-- The label of the previous button on the message view screen. -->
     <string name="message_view_move_to_older">Older</string>
 
-    <!-- The message snippet, of the form subject + separator + start of text  -->
-    <string name="message_list_snippet"><xliff:g id="subject" example="Re: Foo">
-        %1$s</xliff:g> - <xliff:g id="text" example="Hi, John. Blah...">%2$s</xliff:g></string>
+    <!-- A simple divider between subject and message snippet in the message list view
+        [CHAR LIMIT=4]-->
+    <string name="message_list_subject_snippet_divider">\u0020\u2014\u0020</string>
 
     <!-- Title of screen when setting up new email account [CHAR LIMIT=45] -->
     <string name="account_setup_basics_title">Account setup</string>
diff --git a/src/com/android/email/Email.java b/src/com/android/email/Email.java
index 3bc12ae..b9efaf4 100644
--- a/src/com/android/email/Email.java
+++ b/src/com/android/email/Email.java
@@ -54,8 +54,9 @@
 
     /**
      * If true, logging regarding activity/fragment lifecycle will be enabled.
+     * Do not check in as "true".
      */
-    public static final boolean DEBUG_LIFECYCLE = true; // STOPSHIP Turn this off.
+    public static final boolean DEBUG_LIFECYCLE = false;
 
     /**
      * If this is enabled then logging that normally hides sensitive information
diff --git a/src/com/android/email/LegacyConversions.java b/src/com/android/email/LegacyConversions.java
index c6acece..9def43e 100644
--- a/src/com/android/email/LegacyConversions.java
+++ b/src/com/android/email/LegacyConversions.java
@@ -374,6 +374,7 @@
         localAttachment.mMessageKey = localMessage.mId;
         localAttachment.mLocation = partId;
         localAttachment.mEncoding = "B";        // TODO - convert other known encodings
+        localAttachment.mAccountKey = localMessage.mAccountKey;
 
         if (DEBUG_ATTACHMENTS) {
             Log.d(Email.LOG_TAG, "Add attachment " + localAttachment);
diff --git a/src/com/android/email/activity/MessageListItem.java b/src/com/android/email/activity/MessageListItem.java
index fc882b7..d69ab18 100644
--- a/src/com/android/email/activity/MessageListItem.java
+++ b/src/com/android/email/activity/MessageListItem.java
@@ -28,11 +28,15 @@
 import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Typeface;
 import android.text.Layout.Alignment;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
 import android.text.StaticLayout;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.text.format.DateUtils;
+import android.text.style.StyleSpan;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -88,12 +92,15 @@
     private static Bitmap sInviteIcon;
     private static Bitmap sFavoriteIconOff;
     private static Bitmap sFavoriteIconOn;
-    private static int sFavoriteIconLeft;
+    private static int sFavoriteIconWidth;
     private static Bitmap sSelectedIconOn;
     private static Bitmap sSelectedIconOff;
+    private static String sSubjectSnippetDivider;
 
     public String mSender;
+    public CharSequence mText;
     public String mSnippet;
+    public String mSubject;
     public boolean mRead;
     public long mTimestamp;
     public boolean mHasAttachment = false;
@@ -111,8 +118,13 @@
     private int mDateFaveWidth;
 
     private static int sCheckboxHitWidth;
-    private static int sMinimumDateWidth;
+    private static int sDateIconWidthWide;
+    private static int sDateIconWidthNarrow;
     private static int sFavoriteHitWidth;
+    private static int sFavoritePaddingRight;
+    private static int sSenderPaddingTopNarrow;
+    private static int sSenderWidth;
+    private static int sPaddingLarge;
     private static int sPaddingVerySmall;
     private static int sPaddingSmall;
     private static int sPaddingMedium;
@@ -133,13 +145,23 @@
     private void init(Context context) {
         if (!sInit) {
             Resources r = context.getResources();
-
+            sSubjectSnippetDivider = r.getString(R.string.message_list_subject_snippet_divider);
             sCheckboxHitWidth =
                 r.getDimensionPixelSize(R.dimen.message_list_item_checkbox_hit_width);
             sFavoriteHitWidth =
                 r.getDimensionPixelSize(R.dimen.message_list_item_favorite_hit_width);
-            sMinimumDateWidth =
-                r.getDimensionPixelSize(R.dimen.message_list_item_minimum_date_width);
+            sFavoritePaddingRight =
+                r.getDimensionPixelSize(R.dimen.message_list_item_favorite_padding_right);
+            sSenderPaddingTopNarrow =
+                r.getDimensionPixelSize(R.dimen.message_list_item_sender_padding_top_narrow);
+            sDateIconWidthWide =
+                r.getDimensionPixelSize(R.dimen.message_list_item_date_icon_width_wide);
+            sDateIconWidthNarrow =
+                r.getDimensionPixelSize(R.dimen.message_list_item_date_icon_width_narrow);
+            sSenderWidth =
+                r.getDimensionPixelSize(R.dimen.message_list_item_sender_width);
+            sPaddingLarge =
+                r.getDimensionPixelSize(R.dimen.message_list_item_padding_large);
             sPaddingMedium =
                 r.getDimensionPixelSize(R.dimen.message_list_item_padding_medium);
             sPaddingSmall =
@@ -184,8 +206,7 @@
             sSelectedIconOn =
                 BitmapFactory.decodeResource(r, R.drawable.btn_check_on_normal_holo_light);
 
-            sFavoriteIconLeft =
-                sFavoriteHitWidth - ((sFavoriteHitWidth - sFavoriteIconOff.getWidth()) / 2);
+            sFavoriteIconWidth = sFavoriteIconOff.getWidth();
             sInit = true;
         }
     }
@@ -205,27 +226,38 @@
     }
 
     private void calculateDrawingData() {
+        SpannableStringBuilder ssb = new SpannableStringBuilder();
+        boolean hasSubject = false;
+        if (!TextUtils.isEmpty(mSubject)) {
+            SpannableString ss = new SpannableString(mSubject);
+            ss.setSpan(new StyleSpan(mRead ? Typeface.NORMAL : Typeface.BOLD), 0, ss.length(),
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            ssb.append(ss);
+            hasSubject = true;
+        }
+        if (!TextUtils.isEmpty(mSnippet)) {
+            if (hasSubject) {
+                ssb.append(sSubjectSnippetDivider);
+            }
+            ssb.append(mSnippet);
+        }
+        mText = ssb;
+
         if (mMode == MODE_WIDE) {
-            mDateFaveWidth = sFavoriteHitWidth + sMinimumDateWidth;
+            mDateFaveWidth = sFavoriteHitWidth + sDateIconWidthWide;
         } else {
-            mDateFaveWidth = sMinimumDateWidth;
+            mDateFaveWidth = sDateIconWidthNarrow;
         }
         mSenderSnippetWidth = mViewWidth - mDateFaveWidth - sCheckboxHitWidth;
 
         // In wide mode, we use 3/4 for snippet and 1/4 for sender
         mSnippetWidth = mSenderSnippetWidth;
         if (mMode == MODE_WIDE) {
-            mSnippetWidth = mSenderSnippetWidth * 3 / 4;
-        }
-        if (mHasAttachment) {
-            mSnippetWidth -= (sAttachmentIcon.getWidth() + sPaddingSmall);
-        }
-        if (mHasInvite) {
-            mSnippetWidth -= (sInviteIcon.getWidth() + sPaddingSmall);
+            mSnippetWidth = mSenderSnippetWidth - sSenderWidth - sPaddingLarge;
         }
 
-        // First, we create a StaticLayout with our snippet to get the line breaks
-        StaticLayout layout = new StaticLayout(mSnippet, 0, mSnippet.length(), sDefaultPaint,
+        // Create a StaticLayout with our snippet to get the line breaks
+        StaticLayout layout = new StaticLayout(mText, 0, mText.length(), sDefaultPaint,
                 mSnippetWidth, Alignment.ALIGN_NORMAL, 1, 0, true);
         // Get the number of lines needed to render the whole snippet
         mSnippetLineCount = layout.getLineCount();
@@ -234,26 +266,26 @@
         for (int i = 0; i < MAX_SUBJECT_SNIPPET_LINES; i++) {
             int start = layout.getLineStart(i);
             if (i == MAX_SUBJECT_SNIPPET_LINES - 1) {
+                int end = mText.length() - 1;
+                if (start > end) continue;
                 // For the final line, ellipsize the text to our width
-                mSnippetLines[i] = TextUtils.ellipsize(mSnippet.substring(start), sDefaultPaint,
+                mSnippetLines[i] = TextUtils.ellipsize(mText.subSequence(start, end), sDefaultPaint,
                         mSnippetWidth, TruncateAt.END);
             } else {
                 // Just extract from start to end
-                mSnippetLines[i] = mSnippet.substring(start, layout.getLineEnd(i));
+                mSnippetLines[i] = mText.subSequence(start, layout.getLineEnd(i));
             }
         }
 
         // Now, format the sender for its width
         TextPaint senderPaint = mRead ? sDefaultPaint : sBoldPaint;
-        // In wide mode, we use 1/4 of the width, otherwise, the whole width
-        int senderWidth = (mMode == MODE_WIDE) ? mSenderSnippetWidth / 4 : mSenderSnippetWidth;
+        int senderWidth = (mMode == MODE_WIDE) ? sSenderWidth : mSenderSnippetWidth;
         // And get the ellipsized string for the calculated width
-        mFormattedSender = TextUtils.ellipsize(mSender, senderPaint, senderWidth - sPaddingMedium,
-                TruncateAt.END);
+        mFormattedSender = TextUtils.ellipsize(mSender, senderPaint, senderWidth, TruncateAt.END);
         // Get a nicely formatted date string (relative to today)
         String date = DateUtils.getRelativeTimeSpanString(getContext(), mTimestamp).toString();
         // And make it fit to our size
-        mFormattedDate = TextUtils.ellipsize(date, sDatePaint, sMinimumDateWidth, TruncateAt.END);
+        mFormattedDate = TextUtils.ellipsize(date, sDatePaint, sDateIconWidthWide, TruncateAt.END);
     }
 
     @Override
@@ -317,13 +349,13 @@
         int senderY;
 
         if (mMode == MODE_WIDE) {
-            // In wide mode, we'll use 1/4 for sender and 3/4 for snippet
-            snippetX += mSenderSnippetWidth / 4;
+            // Get the right starting point for the snippet
+            snippetX += sSenderWidth + sPaddingLarge;
             // And center the sender and snippet
             senderY = (mViewHeight - descent - ascent) / 2;
             snippetY = ((mViewHeight - (2 * lineHeight)) / 2) - ascent;
         } else {
-            senderY = 20;  // TODO Remove magic number
+            senderY = -ascent + sSenderPaddingTopNarrow;
             snippetY = senderY + lineHeight + sPaddingVerySmall;
         }
 
@@ -346,19 +378,49 @@
                 mRead ? sDefaultPaint : sBoldPaint);
 
         // Draw each of the snippet lines
+        int subjectEnd = (mSubject == null) ? 0 : mSubject.length();
+        int lineStart = 0;
+        TextPaint subjectPaint = mRead ? sDefaultPaint : sBoldPaint;
         for (int i = 0; i < MAX_SUBJECT_SNIPPET_LINES; i++) {
             CharSequence line = mSnippetLines[i];
+            int drawX = snippetX;
             if (line != null) {
-                canvas.drawText(line, 0, line.length(), snippetX, snippetY, sDefaultPaint);
+                int defaultPaintStart = 0;
+                if (lineStart <= subjectEnd) {
+                    int boldPaintEnd = subjectEnd - lineStart;
+                    if (boldPaintEnd > line.length()) {
+                        boldPaintEnd = line.length();
+                    }
+                    // From 0 to end, do in bold or default depending on the read flag
+                    canvas.drawText(line, 0, boldPaintEnd, drawX, snippetY, subjectPaint);
+                    defaultPaintStart = boldPaintEnd;
+                    drawX += subjectPaint.measureText(line, 0, boldPaintEnd);
+                }
+                canvas.drawText(line, defaultPaintStart, line.length(), drawX, snippetY,
+                        sDefaultPaint);
                 snippetY += lineHeight;
+                lineStart += line.length();
             }
         }
 
         // Draw the attachment and invite icons, if necessary
-        int left = mSenderSnippetWidth + sCheckboxHitWidth;
+        int datePaddingRight;
+        if (mMode == MODE_WIDE) {
+            datePaddingRight = sFavoriteHitWidth;
+        } else {
+            datePaddingRight = sPaddingLarge;
+        }
+        int left = mViewWidth - datePaddingRight - (int)sDefaultPaint.measureText(mFormattedDate,
+                0, mFormattedDate.length()) - sPaddingMedium;
+
         if (mHasAttachment) {
             left -= sAttachmentIcon.getWidth() + sPaddingSmall;
-            int iconTop = (mViewHeight - sAttachmentIcon.getHeight()) / 2;
+            int iconTop;
+            if (mMode == MODE_WIDE) {
+                iconTop = (mViewHeight - sAttachmentIcon.getHeight()) / 2;
+            } else {
+                iconTop = senderY - sAttachmentIcon.getHeight();
+            }
             canvas.drawBitmap(sAttachmentIcon, left, iconTop, sDefaultPaint);
         }
         if (mHasInvite) {
@@ -368,17 +430,19 @@
         }
 
         // Draw the date
-        int dateRight = mViewWidth - sPaddingMedium;
-        if (mMode == MODE_WIDE) {
-            dateRight -= sFavoriteHitWidth;
-        }
-        canvas.drawText(mFormattedDate, 0, mFormattedDate.length(), dateRight, senderY, sDatePaint);
+        canvas.drawText(mFormattedDate, 0, mFormattedDate.length(), mViewWidth - datePaddingRight,
+                senderY, sDatePaint);
 
         // Draw the favorite icon
-        int faveLeft = mViewWidth - sFavoriteIconLeft;
+        int faveLeft = mViewWidth - sFavoriteIconWidth;
+        if (mMode == MODE_WIDE) {
+            faveLeft -= sFavoritePaddingRight;
+        } else {
+            faveLeft -= sPaddingLarge;
+        }
         int faveTop = (mViewHeight - sFavoriteIconOff.getHeight()) / 2;
         if (mMode == MODE_NARROW) {
-            faveTop += sPaddingMedium;
+            faveTop += sSenderPaddingTopNarrow;
         }
         canvas.drawBitmap(mIsFavorite ? sFavoriteIconOn : sFavoriteIconOff, faveLeft, faveTop,
                 sDefaultPaint);
diff --git a/src/com/android/email/activity/MessageListXL.java b/src/com/android/email/activity/MessageListXL.java
index 188cdce..7b3d344 100644
--- a/src/com/android/email/activity/MessageListXL.java
+++ b/src/com/android/email/activity/MessageListXL.java
@@ -29,7 +29,6 @@
 import com.android.email.mail.MessagingException;
 import com.android.email.provider.EmailContent.Account;
 import com.android.email.provider.EmailContent.Mailbox;
-import com.android.email.provider.EmailContent.Message;
 
 import android.app.ActionBar;
 import android.app.Activity;
@@ -199,7 +198,7 @@
         final long accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1);
         final long mailboxId = i.getLongExtra(EXTRA_MAILBOX_ID, -1);
         final long messageId = i.getLongExtra(EXTRA_MESSAGE_ID, -1);
-        if (Email.DEBUG) {
+        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
             Log.d(Email.LOG_TAG, String.format("initFromIntent: %d %d", accountId, mailboxId));
         }
 
@@ -638,7 +637,9 @@
     private class ActionBarNavigationCallback implements ActionBar.OnNavigationListener {
         @Override
         public boolean onNavigationItemSelected(int itemPosition, long accountId) {
-            if (Email.DEBUG) Log.d(Email.LOG_TAG, "Account selected: accountId=" + accountId);
+            if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+                Log.d(Email.LOG_TAG, "Account selected: accountId=" + accountId);
+            }
             mFragmentManager.selectAccount(accountId, -1, -1, true);
             return true;
         }
diff --git a/src/com/android/email/activity/MessageListXLFragmentManager.java b/src/com/android/email/activity/MessageListXLFragmentManager.java
index d0f24ca..657b7c7 100644
--- a/src/com/android/email/activity/MessageListXLFragmentManager.java
+++ b/src/com/android/email/activity/MessageListXLFragmentManager.java
@@ -254,7 +254,7 @@
         long mailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, -1);
         long messageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, -1);
         mMessageListFragmentState = savedInstanceState.getParcelable(BUNDLE_KEY_MESSAGE_LIST_STATE);
-        if (Email.DEBUG) {
+        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
             Log.d(Email.LOG_TAG, "MessageListXLFragmentManager: Restoring "
                     + accountId + "," + mailboxId + "," + messageId);
         }
diff --git a/src/com/android/email/activity/MessagesAdapter.java b/src/com/android/email/activity/MessagesAdapter.java
index 07717f8..c5a245c 100644
--- a/src/com/android/email/activity/MessagesAdapter.java
+++ b/src/com/android/email/activity/MessagesAdapter.java
@@ -17,7 +17,6 @@
 package com.android.email.activity;
 
 import com.android.email.Email;
-import com.android.email.R;
 import com.android.email.ResourceHelper;
 import com.android.email.Utility;
 import com.android.email.data.ThrottlingCursorLoader;
@@ -29,7 +28,6 @@
 import android.content.Loader;
 import android.database.Cursor;
 import android.os.Bundle;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -146,23 +144,10 @@
         itemView.mTimestamp = cursor.getLong(COLUMN_DATE);
         itemView.mSender = cursor.getString(COLUMN_DISPLAY_NAME);
         itemView.mSnippet = cursor.getString(COLUMN_SNIPPET);
+        itemView.mSubject = cursor.getString(COLUMN_SUBJECT);
         itemView.mSnippetLineCount = MessageListItem.NEEDS_LAYOUT;
         itemView.mColorChipPaint =
-                mShowColorChips ? mResourceHelper.getAccountColorPaint(accountId) : null;
-
-        String text = cursor.getString(COLUMN_SUBJECT);
-        String snippet = cursor.getString(COLUMN_SNIPPET);
-        if (!TextUtils.isEmpty(snippet)) {
-            if (TextUtils.isEmpty(text)) {
-                text = snippet;
-            } else {
-                text = context.getString(R.string.message_list_snippet, text, snippet);
-            }
-        }
-        if (text == null) {
-            text = "";
-        }
-        itemView.mSnippet = text;
+            mShowColorChips ? mResourceHelper.getAccountColorPaint(accountId) : null;
     }
 
     @Override
diff --git a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java
index 5e92d08..3037f11 100644
--- a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java
+++ b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java
@@ -159,8 +159,15 @@
                     new AccountCheckTask(checkMode, checkAccount)
                     .execute();
         }
+    }
 
-        // if reattaching, update progress/error UI by re-reporting the previous values
+    /**
+     * When resuming, restart the progress/error UI if necessary by re-reporting previous values
+     */
+    @Override
+    public void onResume() {
+        super.onResume();
+
         if (mState != STATE_START) {
             reportProgress(mState, mErrorStringId, mErrorMessage, mAutoDiscoverResult);
         }
@@ -220,23 +227,27 @@
                 case STATE_CHECK_SHOW_SECURITY:
                     // 1. get rid of progress dialog (if any)
                     recoverAndDismissCheckingDialog();
-                    // 2. launch the error dialog
-                    SecurityRequiredDialog securityRequiredDialog =
-                            SecurityRequiredDialog.newInstance(this, mErrorMessage);
-                    fm.openTransaction()
-                            .add(securityRequiredDialog, SecurityRequiredDialog.TAG)
-                            .commit();
+                    // 2. launch the error dialog, if needed
+                    if (fm.findFragmentByTag(SecurityRequiredDialog.TAG) == null) {
+                        SecurityRequiredDialog securityRequiredDialog =
+                                SecurityRequiredDialog.newInstance(this, mErrorMessage);
+                        fm.openTransaction()
+                                .add(securityRequiredDialog, SecurityRequiredDialog.TAG)
+                                .commit();
+                    }
                     break;
                 case STATE_CHECK_ERROR:
                 case STATE_AUTODISCOVER_AUTH_DIALOG:
                     // 1. get rid of progress dialog (if any)
                     recoverAndDismissCheckingDialog();
-                    // 2. launch the error dialog
-                    ErrorDialog errorDialog =
-                            ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage);
-                    fm.openTransaction()
-                            .add(errorDialog, ErrorDialog.TAG)
-                            .commit();
+                    // 2. launch the error dialog, if needed
+                    if (fm.findFragmentByTag(ErrorDialog.TAG) == null) {
+                        ErrorDialog errorDialog =
+                                ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage);
+                        fm.openTransaction()
+                                .add(errorDialog, ErrorDialog.TAG)
+                                .commit();
+                    }
                     break;
                 case STATE_AUTODISCOVER_RESULT:
                     // 1. get rid of progress dialog (if any)
diff --git a/src/com/android/email/activity/setup/AccountSetupExchange.java b/src/com/android/email/activity/setup/AccountSetupExchange.java
index 1b6c61f..367c18e 100644
--- a/src/com/android/email/activity/setup/AccountSetupExchange.java
+++ b/src/com/android/email/activity/setup/AccountSetupExchange.java
@@ -183,7 +183,7 @@
         AccountCheckSettingsFragment checkerFragment =
             AccountCheckSettingsFragment.newInstance(checkMode, target);
         FragmentTransaction transaction = getFragmentManager().openTransaction();
-        transaction.replace(R.id.setup_fragment, checkerFragment);
+        transaction.add(checkerFragment, AccountCheckSettingsFragment.TAG);
         transaction.addToBackStack("back");
         transaction.commit();
     }
diff --git a/src/com/android/email/activity/setup/AccountSetupIncoming.java b/src/com/android/email/activity/setup/AccountSetupIncoming.java
index 3e49375..84a6a65 100644
--- a/src/com/android/email/activity/setup/AccountSetupIncoming.java
+++ b/src/com/android/email/activity/setup/AccountSetupIncoming.java
@@ -88,7 +88,7 @@
         AccountCheckSettingsFragment checkerFragment =
             AccountCheckSettingsFragment.newInstance(checkMode, target);
         FragmentTransaction transaction = getFragmentManager().openTransaction();
-        transaction.replace(R.id.setup_fragment, checkerFragment);
+        transaction.add(checkerFragment, AccountCheckSettingsFragment.TAG);
         transaction.addToBackStack("back");
         transaction.commit();
     }
diff --git a/src/com/android/email/activity/setup/AccountSetupOutgoing.java b/src/com/android/email/activity/setup/AccountSetupOutgoing.java
index 694ec95..5c26727 100644
--- a/src/com/android/email/activity/setup/AccountSetupOutgoing.java
+++ b/src/com/android/email/activity/setup/AccountSetupOutgoing.java
@@ -88,7 +88,7 @@
         AccountCheckSettingsFragment checkerFragment =
             AccountCheckSettingsFragment.newInstance(checkMode, target);
         FragmentTransaction transaction = getFragmentManager().openTransaction();
-        transaction.replace(R.id.setup_fragment, checkerFragment);
+        transaction.add(checkerFragment, AccountCheckSettingsFragment.TAG);
         transaction.addToBackStack("back");
         transaction.commit();
     }
diff --git a/src/com/android/email/provider/EmailContent.java b/src/com/android/email/provider/EmailContent.java
index 67f8ac9..6c0f5f2 100644
--- a/src/com/android/email/provider/EmailContent.java
+++ b/src/com/android/email/provider/EmailContent.java
@@ -1871,6 +1871,8 @@
         public static final String FLAGS = "flags";
         // Content that is actually contained in the Attachment row
         public static final String CONTENT_BYTES = "content_bytes";
+        // A foreign key into the Account table (for the message owning this attachment)
+        public static final String ACCOUNT_KEY = "accountKey";
     }
 
     public static final class Attachment extends EmailContent implements AttachmentColumns {
@@ -1892,6 +1894,7 @@
         public String mContent; // Not currently used
         public int mFlags;
         public byte[] mContentBytes;
+        public long mAccountKey;
 
         public static final int CONTENT_ID_COLUMN = 0;
         public static final int CONTENT_FILENAME_COLUMN = 1;
@@ -1905,11 +1908,13 @@
         public static final int CONTENT_CONTENT_COLUMN = 9; // Not currently used
         public static final int CONTENT_FLAGS_COLUMN = 10;
         public static final int CONTENT_CONTENT_BYTES_COLUMN = 11;
+        public static final int CONTENT_ACCOUNT_KEY_COLUMN = 12;
         public static final String[] CONTENT_PROJECTION = new String[] {
             RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
             AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
             AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING,
-            AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES
+            AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES,
+            AttachmentColumns.ACCOUNT_KEY
         };
 
         // Bits used in mFlags
@@ -2025,6 +2030,7 @@
             mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
             mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
             mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN);
+            mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
             return this;
         }
 
@@ -2042,6 +2048,7 @@
             values.put(AttachmentColumns.CONTENT, mContent);
             values.put(AttachmentColumns.FLAGS, mFlags);
             values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes);
+            values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey);
             return values;
         }
 
@@ -2062,6 +2069,7 @@
             dest.writeString(mEncoding);
             dest.writeString(mContent);
             dest.writeInt(mFlags);
+            dest.writeLong(mAccountKey);
             if (mContentBytes == null) {
                 dest.writeInt(-1);
             } else {
@@ -2083,6 +2091,7 @@
             mEncoding = in.readString();
             mContent = in.readString();
             mFlags = in.readInt();
+            mAccountKey = in.readLong();
             final int contentBytesLen = in.readInt();
             if (contentBytesLen == -1) {
                 mContentBytes = null;
@@ -2107,7 +2116,7 @@
         public String toString() {
             return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
                     + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding  + ", "
-                    + mFlags + ", " + mContentBytes + "]";
+                    + mFlags + ", " + mContentBytes + ", " + mAccountKey + "]";
         }
     }
 
diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java
index 54bf96e..6fe8189 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -106,7 +106,8 @@
     // Version 13: Add messageCount to Mailbox table.
     // Version 14: Add snippet to Message table
     // Version 15: Fix upgrade problem in version 14.
-    public static final int DATABASE_VERSION = 15;
+    // Version 16: Add accountKey to Attachment table
+    public static final int DATABASE_VERSION = 16;
 
     // Any changes to the database format *must* include update-in-place code.
     // Original version: 2
@@ -574,7 +575,8 @@
             + AttachmentColumns.ENCODING + " text, "
             + AttachmentColumns.CONTENT + " text, "
             + AttachmentColumns.FLAGS + " integer, "
-            + AttachmentColumns.CONTENT_BYTES + " blob"
+            + AttachmentColumns.CONTENT_BYTES + " blob, "
+            + AttachmentColumns.ACCOUNT_KEY + " integer"
             + ");";
         db.execSQL("create table " + Attachment.TABLE_NAME + s);
         db.execSQL(createIndex(Attachment.TABLE_NAME, AttachmentColumns.MESSAGE_KEY));
@@ -896,6 +898,22 @@
                 }
                 oldVersion = 15;
             }
+            if (oldVersion == 15) {
+                try {
+                    db.execSQL("alter table " + Attachment.TABLE_NAME
+                            + " add column " + Attachment.ACCOUNT_KEY +" integer" + ";");
+                    // Update all existing attachments to add the accountKey data
+                    db.execSQL("update " + Attachment.TABLE_NAME + " set " +
+                            Attachment.ACCOUNT_KEY + "= (SELECT " + Message.TABLE_NAME + "." +
+                            Message.ACCOUNT_KEY + " from " + Message.TABLE_NAME + " where " +
+                            Message.TABLE_NAME + "." + Message.RECORD_ID + " = " +
+                            Attachment.TABLE_NAME + "." + Attachment.MESSAGE_KEY + ")");
+                } catch (SQLException e) {
+                    // Shouldn't be needed unless we're debugging and interrupt the process
+                    Log.w(TAG, "Exception upgrading EmailProvider.db from 15 to 16 " + e);
+                }
+                oldVersion = 16;
+            }
         }
 
         @Override
@@ -1150,10 +1168,12 @@
                         throw new IllegalArgumentException("Unknown URL " + uri);
                     }
                     if (match == ATTACHMENT) {
+                        int flags = 0;
                         if (values.containsKey(Attachment.FLAGS)) {
-                            int flags = values.getAsInteger(Attachment.FLAGS);
-                            AttachmentDownloadService.attachmentChanged(id, flags);
+                            flags = values.getAsInteger(Attachment.FLAGS);
                         }
+                        // Report all new attachments to the download service
+                        AttachmentDownloadService.attachmentChanged(id, flags);
                     }
                     break;
                 case MAILBOX_ID:
diff --git a/src/com/android/email/service/AttachmentDownloadService.java b/src/com/android/email/service/AttachmentDownloadService.java
index 291f0b2..6189291 100644
--- a/src/com/android/email/service/AttachmentDownloadService.java
+++ b/src/com/android/email/service/AttachmentDownloadService.java
@@ -25,9 +25,11 @@
 import com.android.email.provider.EmailContent;
 import com.android.email.provider.EmailContent.Account;
 import com.android.email.provider.EmailContent.Attachment;
+import com.android.email.provider.EmailContent.AttachmentColumns;
 import com.android.email.provider.EmailContent.Message;
 import com.android.exchange.ExchangeService;
 
+import android.accounts.AccountManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -36,6 +38,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.text.format.DateUtils;
@@ -69,12 +72,20 @@
     // High priority is for user requests
     private static final int PRIORITY_HIGH = 2;
 
+    // Minimum free storage in order to perform prefetch (25% of total memory)
+    private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F;
+    // Maximum prefetch storage (also 25% of total memory)
+    private static final float PREFETCH_MAXIMUM_ATTACHMENT_STORAGE = 0.25F;
+
     // We can try various values here; I think 2 is completely reasonable as a first pass
     private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2;
     // Limit on the number of simultaneous downloads per account
     // Note that a limit of 1 is currently enforced by both Services (MailService and Controller)
     private static final int MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT = 1;
 
+    private static final Uri SINGLE_ATTACHMENT_URI =
+        EmailContent.uriWithLimit(Attachment.CONTENT_URI, 1);
+
     /*package*/ static AttachmentDownloadService sRunningService = null;
 
     /*package*/ Context mContext;
@@ -82,10 +93,46 @@
 
     private final HashMap<Long, Class<? extends Service>> mAccountServiceMap =
         new HashMap<Long, Class<? extends Service>>();
+    // A map of attachment storage used per account
+    // NOTE: This map is not kept current in terms of deletions (i.e. it stores the last calculated
+    // amount plus the size of any new attachments laoded).  If and when we reach the per-account
+    // limit, we recalculate the actual usage
+    /*package*/ final HashMap<Long, Long> mAttachmentStorageMap = new HashMap<Long, Long>();
     private final ServiceCallback mServiceCallback = new ServiceCallback();
+
     private final Object mLock = new Object();
     private volatile boolean mStop = false;
 
+    /*package*/ AccountManagerStub mAccountManagerStub;
+
+    /**
+     * We only use the getAccounts() call from AccountManager, so this class wraps that call and
+     * allows us to build a mock account manager stub in the unit tests
+     */
+    /*package*/ static class AccountManagerStub {
+        private int mNumberOfAccounts;
+        private final AccountManager mAccountManager;
+
+        AccountManagerStub(Context context) {
+            if (context != null) {
+                mAccountManager = AccountManager.get(context);
+            } else {
+                mAccountManager = null;
+            }
+        }
+
+        /*package*/ int getNumberOfAccounts() {
+            if (mAccountManager != null) {
+                return mAccountManager.getAccounts().length;
+            } else {
+                return mNumberOfAccounts;
+            }
+        }
+
+        /*package*/ void setNumberOfAccounts(int numberOfAccounts) {
+            mNumberOfAccounts = numberOfAccounts;
+        }
+    }
 
     /**
      * Watchdog alarm receiver; responsible for making sure that downloads in progress are not
@@ -160,7 +207,6 @@
                     res = (req1.time > req2.time) ? -1 : 1;
                 }
             }
-            //Log.d(TAG, "Compare " + req1.attachmentId + " to " + req2.attachmentId + " = " + res);
             return res;
         }
     }
@@ -257,17 +303,44 @@
             while (iterator.hasNext() &&
                     (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) {
                 DownloadRequest req = iterator.next();
+                 // Enforce per-account limit here
+                if (downloadsForAccount(req.accountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) {
+                    if (Email.DEBUG) {
+                        Log.d(TAG, "== Skip #" + req.attachmentId + "; maxed for acct #" +
+                                req.accountId);
+                    }
+                    continue;
+                }
+
                 if (!req.inProgress) {
                     mDownloadSet.tryStartDownload(req);
                 }
             }
             // Then, try opportunistic download of appropriate attachments
             int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size();
-            if (backgroundDownloads > 0) {
-                // TODO Code for background downloads here
-                if (Email.DEBUG) {
-                    Log.d(TAG, "== We'd look for up to " + backgroundDownloads +
-                            " background download(s) now...");
+            // Always leave one slot for user requested download
+            if (backgroundDownloads > (MAX_SIMULTANEOUS_DOWNLOADS - 1)) {
+                // We'll take the most recent unloaded attachment
+                // TODO It would be more correct to look at other attachments if we are prevented
+                // from preloading due to per-account storage constraints, but this would be a
+                // very unusual case.
+                Long prefetchId = Utility.getFirstRowLong(mContext,
+                        SINGLE_ATTACHMENT_URI,
+                        Attachment.ID_PROJECTION,
+                        AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0",
+                        null,
+                        Attachment.RECORD_ID + " DESC",
+                        Attachment.ID_PROJECTION_COLUMN);
+                if (prefetchId != null) {
+                    if (Email.DEBUG) {
+                        Log.d(TAG, ">> Prefetch attachment " + prefetchId);
+                    }
+                    Attachment att = Attachment.restoreAttachmentWithId(mContext, prefetchId);
+                    if (att != null && canPrefetchForAccount(att.mAccountKey,
+                            AttachmentProvider.getAttachmentDirectory(mContext, att.mAccountKey))) {
+                        DownloadRequest req = new DownloadRequest(mContext, att);
+                        mDownloadSet.tryStartDownload(req);
+                    }
                 }
             }
         }
@@ -294,8 +367,7 @@
                 long timeSinceCallback = now - req.lastCallbackTime;
                 if (timeSinceCallback > CALLBACK_TIMEOUT) {
                     if (Email.DEBUG) {
-                        Log.d(TAG, "== ,  Download of " + req.attachmentId +
-                                " timed out");
+                        Log.d(TAG, "== Download of " + req.attachmentId + " timed out");
                     }
                    cancelDownload(req);
                 // STOPSHIP Remove this before ship
@@ -348,6 +420,10 @@
                     mWatchdogPendingIntent);
         }
 
+        private synchronized DownloadRequest getDownloadInProgress(long attachmentId) {
+            return mDownloadsInProgress.get(attachmentId);
+        }
+
         /**
          * Attempt to execute the DownloadRequest, enforcing the maximum downloads per account
          * parameter
@@ -355,14 +431,6 @@
          * @return whether or not the download was started
          */
         /*package*/ synchronized boolean tryStartDownload(DownloadRequest req) {
-            // Enforce per-account limit
-            if (downloadsForAccount(req.accountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) {
-                if (Email.DEBUG) {
-                    Log.d(TAG, "== Skip #" + req.attachmentId + "; maxed for acct #" +
-                            req.accountId);
-                }
-                return false;
-            }
             Class<? extends Service> serviceClass = getServiceClassForAccount(req.accountId);
             if (serviceClass == null) return false;
             try {
@@ -420,6 +488,13 @@
 
             Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId);
             if (attachment != null) {
+                long accountId = attachment.mAccountKey;
+                // Update our attachment storage for this account
+                Long currentStorage = mAttachmentStorageMap.get(accountId);
+                if (currentStorage == null) {
+                    currentStorage = 0L;
+                }
+                mAttachmentStorageMap.put(accountId, currentStorage + attachment.mSize);
                 boolean deleted = false;
                 if ((attachment.mFlags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) {
                     if (statusCode == EmailServiceStatus.ATTACHMENT_NOT_FOUND) {
@@ -491,24 +566,22 @@
     private class ServiceCallback extends IEmailServiceCallback.Stub {
         public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode,
                 int progress) {
-            if (Email.DEBUG) {
-                String code;
-                switch(statusCode) {
-                    case EmailServiceStatus.SUCCESS:
-                        code = "Success";
-                        break;
-                    case EmailServiceStatus.IN_PROGRESS:
-                        code = "In progress";
-                        break;
-                    default:
-                        code = Integer.toString(statusCode);
-                }
-                Log.d(TAG, "loadAttachmentStatus, id = " + attachmentId + " code = "+ code +
-                        ", " + progress + "%");
-            }
             // Record status and progress
-            DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId);
+            DownloadRequest req = mDownloadSet.getDownloadInProgress(attachmentId);
             if (req != null) {
+                if (Email.DEBUG) {
+                    String code;
+                    switch(statusCode) {
+                        case EmailServiceStatus.SUCCESS: code = "Success"; break;
+                        case EmailServiceStatus.IN_PROGRESS: code = "In progress"; break;
+                        default: code = Integer.toString(statusCode); break;
+                    }
+                    if (statusCode != EmailServiceStatus.IN_PROGRESS) {
+                        Log.d(TAG, ">> Attachment " + attachmentId + ": " + code);
+                    } else if (progress >= (req.lastProgress + 15)) {
+                        Log.d(TAG, ">> Attachment " + attachmentId + ": " + progress + "%");
+                    }
+                }
                 req.lastStatusCode = statusCode;
                 req.lastProgress = progress;
                 req.lastCallbackTime = System.currentTimeMillis();
@@ -650,8 +723,56 @@
             }});
     }
 
+    /**
+     * Determine whether an attachment can be prefetched for the given account
+     * @return true if download is allowed, false otherwise
+     */
+    /*package*/ boolean canPrefetchForAccount(long accountId, File dir) {
+        long totalStorage = dir.getTotalSpace();
+        long usableStorage = dir.getUsableSpace();
+        long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE);
+
+        // If there's not enough overall storage available, stop now
+        if (usableStorage < minAvailable) {
+            return false;
+        }
+
+        int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts();
+        long perAccountMaxStorage =
+            (long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts);
+
+        // Retrieve our idea of currently used attachment storage; since we don't track deletions,
+        // this number is the "worst case".  If the number is greater than what's allowed per
+        // account, we walk the directory to determine the actual number
+        Long accountStorage = mAttachmentStorageMap.get(accountId);
+        if (accountStorage == null || (accountStorage > perAccountMaxStorage)) {
+            // Calculate the exact figure for attachment storage for this account
+            accountStorage = 0L;
+            File[] files = dir.listFiles();
+            if (files != null) {
+                for (File file : files) {
+                    accountStorage += file.length();
+                }
+            }
+            // Cache the value
+            mAttachmentStorageMap.put(accountId, accountStorage);
+        }
+
+        // Return true if we're using less than the maximum per account
+        if (accountStorage < perAccountMaxStorage) {
+            return true;
+        } else {
+            if (Email.DEBUG) {
+                Log.d(TAG, ">> Prefetch not allowed for account " + accountId + "; used " +
+                        accountStorage + ", limit " + perAccountMaxStorage);
+            }
+            return false;
+        }
+    }
+
     public void run() {
         mContext = this;
+        mAccountManagerStub = new AccountManagerStub(this);
         // Run through all attachments in the database that require download and add them to
         // the queue
         int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
diff --git a/src/com/android/exchange/AbstractSyncService.java b/src/com/android/exchange/AbstractSyncService.java
index c676cce..4fa03b4 100644
--- a/src/com/android/exchange/AbstractSyncService.java
+++ b/src/com/android/exchange/AbstractSyncService.java
@@ -303,6 +303,9 @@
 
     public boolean hasPendingRequests() {
         return !mRequestQueue.isEmpty();
-}
+    }
 
+    public void clearRequests() {
+        mRequestQueue.clear();
+    }
 }
diff --git a/src/com/android/exchange/adapter/EmailSyncAdapter.java b/src/com/android/exchange/adapter/EmailSyncAdapter.java
index b005bcb..d4af3ce 100644
--- a/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -28,7 +28,6 @@
 import com.android.email.mail.internet.MimeUtility;
 import com.android.email.provider.AttachmentProvider;
 import com.android.email.provider.EmailContent;
-import com.android.email.provider.EmailProvider;
 import com.android.email.provider.EmailContent.Account;
 import com.android.email.provider.EmailContent.AccountColumns;
 import com.android.email.provider.EmailContent.Attachment;
@@ -37,6 +36,7 @@
 import com.android.email.provider.EmailContent.Message;
 import com.android.email.provider.EmailContent.MessageColumns;
 import com.android.email.provider.EmailContent.SyncColumns;
+import com.android.email.provider.EmailProvider;
 import com.android.email.service.MailService;
 import com.android.exchange.Eas;
 import com.android.exchange.EasSyncService;
@@ -114,6 +114,7 @@
                 Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
         mContentResolver.delete(Message.UPDATED_CONTENT_URI,
                 Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
+        mService.clearRequests();
         // Delete attachments...
         AttachmentProvider.deleteAllMailboxAttachmentFiles(mContext, mAccount.mId, mMailbox.mId);
     }
@@ -533,6 +534,7 @@
                 att.mFileName = fileName;
                 att.mLocation = location;
                 att.mMimeType = getMimeTypeFromFileName(fileName);
+                att.mAccountKey = mService.mAccount.mId;
                 atts.add(att);
                 msg.mFlagAttachment = true;
             }
diff --git a/tests/src/com/android/email/LegacyConversionsTests.java b/tests/src/com/android/email/LegacyConversionsTests.java
index fd4be31..fc0f9bd 100644
--- a/tests/src/com/android/email/LegacyConversionsTests.java
+++ b/tests/src/com/android/email/LegacyConversionsTests.java
@@ -21,14 +21,14 @@
 import com.android.email.mail.BodyPart;
 import com.android.email.mail.Flag;
 import com.android.email.mail.Folder;
-import com.android.email.mail.Message;
-import com.android.email.mail.MessageTestUtils;
-import com.android.email.mail.MessagingException;
-import com.android.email.mail.Part;
 import com.android.email.mail.Folder.OpenMode;
+import com.android.email.mail.Message;
 import com.android.email.mail.Message.RecipientType;
+import com.android.email.mail.MessageTestUtils;
 import com.android.email.mail.MessageTestUtils.MessageBuilder;
 import com.android.email.mail.MessageTestUtils.MultipartBuilder;
+import com.android.email.mail.MessagingException;
+import com.android.email.mail.Part;
 import com.android.email.mail.internet.MimeBodyPart;
 import com.android.email.mail.internet.MimeHeader;
 import com.android.email.mail.internet.MimeMessage;
@@ -37,10 +37,10 @@
 import com.android.email.mail.store.LocalStore;
 import com.android.email.mail.store.LocalStoreUnitTests;
 import com.android.email.provider.EmailContent;
-import com.android.email.provider.EmailProvider;
-import com.android.email.provider.ProviderTestUtils;
 import com.android.email.provider.EmailContent.Attachment;
 import com.android.email.provider.EmailContent.Mailbox;
+import com.android.email.provider.EmailProvider;
+import com.android.email.provider.ProviderTestUtils;
 
 import android.content.ContentUris;
 import android.content.Context;
@@ -243,9 +243,11 @@
             while (c.moveToNext()) {
                 Attachment attachment = Attachment.getContent(c, Attachment.class);
                 if ("101".equals(attachment.mLocation)) {
-                    checkAttachment("attachment1Part", attachments.get(0), attachment);
+                    checkAttachment("attachment1Part", attachments.get(0), attachment,
+                            localMessage.mAccountKey);
                 } else if ("102".equals(attachment.mLocation)) {
-                    checkAttachment("attachment2Part", attachments.get(1), attachment);
+                    checkAttachment("attachment2Part", attachments.get(1), attachment,
+                            localMessage.mAccountKey);
                 } else {
                     fail("Unexpected attachment with location " + attachment.mLocation);
                 }
@@ -332,7 +334,7 @@
                 }
                 assertTrue(fromPart != null);
                 // 2. Check values
-                checkAttachment(attachment.mFileName, fromPart, attachment);
+                checkAttachment(attachment.mFileName, fromPart, attachment, accountId);
             }
         } finally {
             c.close();
@@ -421,8 +423,8 @@
      * TODO content URI should only be set if we also saved a file
      * TODO other data encodings
      */
-    private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual)
-            throws MessagingException {
+    private void checkAttachment(String tag, Part expected, EmailContent.Attachment actual,
+            long accountKey) throws MessagingException {
         String contentType = MimeUtility.unfoldAndDecode(expected.getContentType());
         String contentTypeName = MimeUtility.getHeaderParameter(contentType, "name");
         assertEquals(tag, expected.getMimeType(), actual.mMimeType);
@@ -459,6 +461,7 @@
         }
         assertEquals(tag, expectedPartId, actual.mLocation);
         assertEquals(tag, "B", actual.mEncoding);
+        assertEquals(tag, accountKey, actual.mAccountKey);
     }
 
     /**
diff --git a/tests/src/com/android/email/provider/ProviderTestUtils.java b/tests/src/com/android/email/provider/ProviderTestUtils.java
index 7c56b53..eee103e 100644
--- a/tests/src/com/android/email/provider/ProviderTestUtils.java
+++ b/tests/src/com/android/email/provider/ProviderTestUtils.java
@@ -239,6 +239,7 @@
         att.mContent = "content " + fileName;
         att.mFlags = flags;
         att.mContentBytes = Utility.toUtf8("content " + fileName);
+        att.mAccountKey = messageId + 0x1000;
         if (saveIt) {
             att.save(context);
         }
@@ -420,6 +421,7 @@
         assertEquals(caller + " mFlags", expect.mFlags, actual.mFlags);
         MoreAsserts.assertEquals(caller + " mContentBytes",
                 expect.mContentBytes, actual.mContentBytes);
+        assertEquals(caller + " mAccountKey", expect.mAccountKey, actual.mAccountKey);
     }
 
     /**
diff --git a/tests/src/com/android/email/service/AttachmentDownloadServiceTests.java b/tests/src/com/android/email/service/AttachmentDownloadServiceTests.java
index a4215f5..a1398b3 100644
--- a/tests/src/com/android/email/service/AttachmentDownloadServiceTests.java
+++ b/tests/src/com/android/email/service/AttachmentDownloadServiceTests.java
@@ -18,16 +18,17 @@
 
 import com.android.email.AccountTestCase;
 import com.android.email.ExchangeUtils.NullEmailService;
-import com.android.email.provider.ProviderTestUtils;
 import com.android.email.provider.EmailContent.Account;
 import com.android.email.provider.EmailContent.Attachment;
 import com.android.email.provider.EmailContent.Mailbox;
 import com.android.email.provider.EmailContent.Message;
+import com.android.email.provider.ProviderTestUtils;
 import com.android.email.service.AttachmentDownloadService.DownloadRequest;
 import com.android.email.service.AttachmentDownloadService.DownloadSet;
 
 import android.content.Context;
 
+import java.io.File;
 import java.util.Iterator;
 
 /**
@@ -43,6 +44,9 @@
     private Mailbox mMailbox;
     private long mAccountId;
     private long mMailboxId;
+    private AttachmentDownloadService.AccountManagerStub mAccountManagerStub;
+    private MockDirectory mMockDirectory;
+
     private DownloadSet mDownloadSet;
 
     @Override
@@ -62,7 +66,11 @@
         mService = new AttachmentDownloadService();
         mService.mContext = mMockContext;
         mService.addServiceClass(mAccountId, NullEmailService.class);
+        mAccountManagerStub = new AttachmentDownloadService.AccountManagerStub(null);
+        mService.mAccountManagerStub = mAccountManagerStub;
         mDownloadSet = mService.mDownloadSet;
+        mMockDirectory =
+            new MockDirectory(mService.mContext.getCacheDir().getAbsolutePath());
     }
 
     @Override
@@ -143,4 +151,89 @@
         assertTrue(req.inProgress);
         assertTrue(mDownloadSet.mDownloadsInProgress.containsKey(att4.mId));
     }
+
+    /**
+     * A mock file directory containing a single (Mock)File.  The total space, usable space, and
+     * length of the single file can be set
+     */
+    static class MockDirectory extends File {
+        private static final long serialVersionUID = 1L;
+        private long mTotalSpace;
+        private long mUsableSpace;
+        private MockFile[] mFiles;
+        private final MockFile mMockFile = new MockFile();
+
+
+        public MockDirectory(String path) {
+            super(path);
+            mFiles = new MockFile[1];
+            mFiles[0] = mMockFile;
+        }
+
+        private void setTotalAndUsableSpace(long total, long usable) {
+            mTotalSpace = total;
+            mUsableSpace = usable;
+        }
+
+        public long getTotalSpace() {
+            return mTotalSpace;
+        }
+
+        public long getUsableSpace() {
+            return mUsableSpace;
+        }
+
+        public void setFileLength(long length) {
+            mMockFile.mLength = length;
+        }
+
+        public File[] listFiles() {
+            return mFiles;
+        }
+    }
+
+    /**
+     * A mock file that reports back a pre-set length
+     */
+    static class MockFile extends File {
+        private static final long serialVersionUID = 1L;
+        private long mLength = 0;
+
+        public MockFile() {
+            super("_mock");
+        }
+
+        public long length() {
+            return mLength;
+        }
+    }
+
+    public void testCanPrefetchForAccount() {
+        // First, test our "global" limits (based on free storage)
+        // Mock storage @ 100 total and 26 available
+        // Note that all file lengths in this test are in arbitrary units
+        mMockDirectory.setTotalAndUsableSpace(100L, 26L);
+        // Mock 2 accounts in total
+        mAccountManagerStub.setNumberOfAccounts(2);
+        // With 26% available, we should be ok to prefetch
+        assertTrue(mService.canPrefetchForAccount(1, mMockDirectory));
+        // Now change to 24 available
+        mMockDirectory.setTotalAndUsableSpace(100L, 24L);
+        // With 24% available, we should NOT be ok to prefetch
+        assertFalse(mService.canPrefetchForAccount(1, mMockDirectory));
+
+        // Now, test per-account storage
+        // Mock storage @ 100 total and 50 available
+        mMockDirectory.setTotalAndUsableSpace(100L, 50L);
+        // Mock a file of length 24, but need to uncache previous amount first
+        mService.mAttachmentStorageMap.remove(1L);
+        mMockDirectory.setFileLength(24);
+        // We can prefetch since 24 < half of 50
+        assertTrue(mService.canPrefetchForAccount(1, mMockDirectory));
+        // Mock a file of length 26, but need to uncache previous amount first
+        mService.mAttachmentStorageMap.remove(1L);
+        mMockDirectory.setFileLength(26);
+        // We can't prefetch since 26 > half of 50
+        assertFalse(mService.canPrefetchForAccount(1, mMockDirectory));
+    }
 }
diff --git a/tests/src/com/android/exchange/adapter/SyncAdapterTestCase.java b/tests/src/com/android/exchange/adapter/SyncAdapterTestCase.java
index 3ed82dd..d6172a9 100644
--- a/tests/src/com/android/exchange/adapter/SyncAdapterTestCase.java
+++ b/tests/src/com/android/exchange/adapter/SyncAdapterTestCase.java
@@ -16,9 +16,9 @@
 
 package com.android.exchange.adapter;
 
-import com.android.email.provider.EmailProvider;
 import com.android.email.provider.EmailContent.Account;
 import com.android.email.provider.EmailContent.Mailbox;
+import com.android.email.provider.EmailProvider;
 import com.android.exchange.EasSyncService;
 import com.android.exchange.adapter.EmailSyncAdapter.EasEmailSyncParser;
 import com.android.exchange.provider.MockProvider;
@@ -90,8 +90,8 @@
         EasSyncService service = getTestService();
         Constructor<T> c;
         try {
-            c = klass.getDeclaredConstructor(new Class[] {Mailbox.class, EasSyncService.class});
-            return c.newInstance(service.mMailbox, service);
+            c = klass.getDeclaredConstructor(new Class[] {EasSyncService.class});
+            return c.newInstance(service);
         } catch (SecurityException e) {
         } catch (NoSuchMethodException e) {
         } catch (IllegalArgumentException e) {