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) {