diff --git a/src/com/android/email/AttachmentInfo.java b/src/com/android/email/AttachmentInfo.java index 655d1dec7..6acd2172f 100644 --- a/src/com/android/email/AttachmentInfo.java +++ b/src/com/android/email/AttachmentInfo.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -37,18 +38,41 @@ import java.util.List; * based on the attachment's filename and mime type. */ public class AttachmentInfo { + // Projection which can be used with the constructor taking a Cursor argument + public static final String[] PROJECTION = new String[] {Attachment.RECORD_ID, Attachment.SIZE, + Attachment.FILENAME, Attachment.MIME_TYPE, Attachment.ACCOUNT_KEY}; + // Offsets into PROJECTION + public static final int COLUMN_ID = 0; + public static final int COLUMN_SIZE = 1; + public static final int COLUMN_FILENAME = 2; + public static final int COLUMN_MIME_TYPE = 3; + public static final int COLUMN_ACCOUNT_KEY = 4; + + public final long mId; + public final long mSize; public final String mName; public final String mContentType; - public final long mSize; - public final long mId; + public final long mAccountKey; public final boolean mAllowView; public final boolean mAllowSave; public AttachmentInfo(Context context, Attachment attachment) { - mSize = attachment.mSize; - mContentType = AttachmentProvider.inferMimeType(attachment.mFileName, attachment.mMimeType); - mName = attachment.mFileName; - mId = attachment.mId; + this(context, attachment.mId, attachment.mSize, attachment.mFileName, attachment.mMimeType, + attachment.mAccountKey); + } + + public AttachmentInfo(Context context, Cursor c) { + this(context, c.getLong(COLUMN_ID), c.getLong(COLUMN_SIZE), c.getString(COLUMN_FILENAME), + c.getString(COLUMN_MIME_TYPE), c.getLong(COLUMN_ACCOUNT_KEY)); + } + + public AttachmentInfo(Context context, long id, long size, String fileName, String mimeType, + long accountKey) { + mSize = size; + mContentType = AttachmentProvider.inferMimeType(fileName, mimeType); + mName = fileName; + mId = id; + mAccountKey = accountKey; boolean canView = true; boolean canSave = true; @@ -121,7 +145,11 @@ public class AttachmentInfo { * An attachment is eligible for download if it can either be viewed or saved (or both) * @return whether the attachment is eligible for download */ - public boolean eligibleForDownload() { + public boolean isEligibleForDownload() { return mAllowView || mAllowSave; } + + public String toString() { + return "{Attachment " + mId + ":" + mName + "," + mContentType + "," + mSize + "}"; + } } diff --git a/src/com/android/email/service/AttachmentDownloadService.java b/src/com/android/email/service/AttachmentDownloadService.java index 191402e28..94c14670c 100644 --- a/src/com/android/email/service/AttachmentDownloadService.java +++ b/src/com/android/email/service/AttachmentDownloadService.java @@ -16,12 +16,13 @@ package com.android.email.service; +import com.android.email.AttachmentInfo; +import com.android.email.Controller.ControllerService; import com.android.email.Email; +import com.android.email.ExchangeUtils.NullEmailService; import com.android.email.NotificationController; import com.android.email.Preferences; import com.android.email.Utility; -import com.android.email.Controller.ControllerService; -import com.android.email.ExchangeUtils.NullEmailService; import com.android.email.provider.AttachmentProvider; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent.Account; @@ -83,9 +84,8 @@ public class AttachmentDownloadService extends Service implements Runnable { // 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); + // Limit on the number of attachments we'll check for background download + private static final int MAX_ATTACHMENTS_TO_CHECK = 25; /*package*/ static AttachmentDownloadService sRunningService = null; @@ -319,42 +319,41 @@ public class AttachmentDownloadService extends Service implements Runnable { } } - // Respect the user's preference for background downloads - if (!mPreferences.getBackgroundAttachments()) { - return; - } - // Then, try opportunistic download of appropriate attachments int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size(); // Always leave one slot for user requested download if (backgroundDownloads > (MAX_SIMULTANEOUS_DOWNLOADS - 1)) { - boolean repeat = true; - while (repeat) { - // We'll take the most recent unloaded attachment - 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) break; - if (Email.DEBUG) { - Log.d(TAG, ">> Prefetch attachment " + prefetchId); - } - Attachment att = Attachment.restoreAttachmentWithId(mContext, prefetchId); - // If att is null, the attachment must have been deleted out from under us - if (att == null) continue; - if (getServiceClassForAccount(att.mAccountKey) == null) { - // Clean up this orphaned attachment; there's no point in keeping it - // around; then try to find another one - EmailContent.delete(mContext, Attachment.CONTENT_URI, prefetchId); - continue; - } - repeat = false; - // TODO It's possible that we're just over limit for this particular account - // Handle this so that attachments from other accounts (if any) can be tried - if (canPrefetchForAccount(att.mAccountKey, mContext.getCacheDir())) { - DownloadRequest req = new DownloadRequest(mContext, att); - mDownloadSet.tryStartDownload(req); + // We'll load up the newest 25 attachments that aren't loaded or queued + Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI, + MAX_ATTACHMENTS_TO_CHECK); + Cursor c = mContext.getContentResolver().query(lookupUri, AttachmentInfo.PROJECTION, + AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0", + null, Attachment.RECORD_ID + " DESC"); + File cacheDir = mContext.getCacheDir(); + try { + while (c.moveToNext()) { + long accountKey = c.getLong(AttachmentInfo.COLUMN_ACCOUNT_KEY); + long id = c.getLong(AttachmentInfo.COLUMN_ID); + if (getServiceClassForAccount(accountKey) == null) { + // Clean up this orphaned attachment; there's no point in keeping it + // around; then try to find another one + EmailContent.delete(mContext, Attachment.CONTENT_URI, id); + } else if (canPrefetchForAccount(accountKey, cacheDir)) { + // Check that the attachment meets system requirements for download + AttachmentInfo info = new AttachmentInfo(mContext, c); + if (info.isEligibleForDownload()) { + Attachment att = Attachment.restoreAttachmentWithId(mContext, id); + if (att != null) { + // Start this download and we're done + DownloadRequest req = new DownloadRequest(mContext, att); + mDownloadSet.tryStartDownload(req); + break; + } + } + } } + } finally { + c.close(); } } } @@ -745,6 +744,12 @@ public class AttachmentDownloadService extends Service implements Runnable { * @return true if download is allowed, false otherwise */ /*package*/ boolean canPrefetchForAccount(long accountId, File dir) { + Account account = Account.restoreAccountWithId(mContext, accountId); + // Check account, just in case + if (account == null) return false; + // First, check preference and quickly return if prefetch isn't allowed + if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) return false; + long totalStorage = dir.getTotalSpace(); long usableStorage = dir.getUsableSpace(); long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE);