Implement prefetch of IMAP/EAS attachments
* Load attachments in the background for IMAP/EAS messages * Download an attachment from account X if: 1) 25% of total storage free 2) Attachments for X use < 1/N of 25% of total storage, where N is the number of AccountManager accounts * Add accountKey to Attachment table for performance Change-Id: I913aa710f34f48fcc4210ddf77393ab38323fe59
This commit is contained in:
parent
f946ff0019
commit
75a873be84
@ -374,6 +374,7 @@ public class LegacyConversions {
|
||||
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);
|
||||
|
@ -1871,6 +1871,8 @@ public abstract class EmailContent {
|
||||
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 abstract class EmailContent {
|
||||
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 abstract class EmailContent {
|
||||
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 @@ public abstract class EmailContent {
|
||||
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 @@ public abstract class EmailContent {
|
||||
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 @@ public abstract class EmailContent {
|
||||
dest.writeString(mEncoding);
|
||||
dest.writeString(mContent);
|
||||
dest.writeInt(mFlags);
|
||||
dest.writeLong(mAccountKey);
|
||||
if (mContentBytes == null) {
|
||||
dest.writeInt(-1);
|
||||
} else {
|
||||
@ -2083,6 +2091,7 @@ public abstract class EmailContent {
|
||||
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 abstract class EmailContent {
|
||||
public String toString() {
|
||||
return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
|
||||
+ mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding + ", "
|
||||
+ mFlags + ", " + mContentBytes + "]";
|
||||
+ mFlags + ", " + mContentBytes + ", " + mAccountKey + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,8 @@ public class EmailProvider extends ContentProvider {
|
||||
// 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 @@ public class EmailProvider extends ContentProvider {
|
||||
+ 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 @@ public class EmailProvider extends ContentProvider {
|
||||
}
|
||||
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 @@ public class EmailProvider extends ContentProvider {
|
||||
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:
|
||||
|
@ -25,9 +25,11 @@ import com.android.email.provider.AttachmentProvider;
|
||||
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.ContentValues;
|
||||
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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
// 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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
|
||||
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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
res = (req1.time > req2.time) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
//Log.d(TAG, "Compare " + req1.attachmentId + " to " + req2.attachmentId + " = " + res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@ -257,17 +303,44 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
* @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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
|
||||
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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
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 @@ public class AttachmentDownloadService extends Service implements Runnable {
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
@ -303,6 +303,9 @@ public abstract class AbstractSyncService implements Runnable {
|
||||
|
||||
public boolean hasPendingRequests() {
|
||||
return !mRequestQueue.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearRequests() {
|
||||
mRequestQueue.clear();
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import com.android.email.mail.internet.MimeMessage;
|
||||
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.Mailbox;
|
||||
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 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
||||
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 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
|
||||
att.mFileName = fileName;
|
||||
att.mLocation = location;
|
||||
att.mMimeType = getMimeTypeFromFileName(fileName);
|
||||
att.mAccountKey = mService.mAccount.mId;
|
||||
atts.add(att);
|
||||
msg.mFlagAttachment = true;
|
||||
}
|
||||
|
@ -21,14 +21,14 @@ import com.android.email.mail.Body;
|
||||
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.internet.TextBody;
|
||||
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 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
||||
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 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
||||
}
|
||||
assertTrue(fromPart != null);
|
||||
// 2. Check values
|
||||
checkAttachment(attachment.mFileName, fromPart, attachment);
|
||||
checkAttachment(attachment.mFileName, fromPart, attachment, accountId);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
@ -421,8 +423,8 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
||||
* 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 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
||||
}
|
||||
assertEquals(tag, expectedPartId, actual.mLocation);
|
||||
assertEquals(tag, "B", actual.mEncoding);
|
||||
assertEquals(tag, accountKey, actual.mAccountKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -239,6 +239,7 @@ public class ProviderTestUtils extends Assert {
|
||||
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 @@ public class ProviderTestUtils extends Assert {
|
||||
assertEquals(caller + " mFlags", expect.mFlags, actual.mFlags);
|
||||
MoreAsserts.assertEquals(caller + " mContentBytes",
|
||||
expect.mContentBytes, actual.mContentBytes);
|
||||
assertEquals(caller + " mAccountKey", expect.mAccountKey, actual.mAccountKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,16 +18,17 @@ package com.android.email.service;
|
||||
|
||||
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 @@ public class AttachmentDownloadServiceTests extends AccountTestCase {
|
||||
private Mailbox mMailbox;
|
||||
private long mAccountId;
|
||||
private long mMailboxId;
|
||||
private AttachmentDownloadService.AccountManagerStub mAccountManagerStub;
|
||||
private MockDirectory mMockDirectory;
|
||||
|
||||
private DownloadSet mDownloadSet;
|
||||
|
||||
@Override
|
||||
@ -62,7 +66,11 @@ public class AttachmentDownloadServiceTests extends AccountTestCase {
|
||||
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 @@ public class AttachmentDownloadServiceTests extends AccountTestCase {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user