Introduction of DownloadQueue and associated tests.

Also a bit of cleanup around log tags and DownloadRequest.

Change-Id: I33221f099ed691e5258e1741cf0a63381404f520
This commit is contained in:
Anthony Lee 2014-05-12 10:17:43 -07:00
parent d222c6b817
commit 0e49862711
3 changed files with 687 additions and 137 deletions

View File

@ -139,6 +139,21 @@
-keep class com.android.emailcommon.mail.Flag -keep class com.android.emailcommon.mail.Flag
-keepclasseswithmembers class com.android.email.service.AttachmentService$DownloadQueue {
*** addRequest(com.android.email.service.AttachmentService$DownloadRequest);
*** removeRequest(com.android.email.service.AttachmentService$DownloadRequest);
*** getNextRequest();
*** findRequestById(long);
*** getSize();
*** isEmpty();
}
-keepclasseswithmembers class com.android.email.service.AttachmentService$DownloadRequest {
<init>(int, long);
*** hashCode();
*** equals(java.lang.Object);
}
-keepclasseswithmembers class com.android.emailcommon.mail.Folder { -keepclasseswithmembers class com.android.emailcommon.mail.Folder {
*** getUnreadMessageCount(); *** getUnreadMessageCount();
*** delete(boolean); *** delete(boolean);

View File

@ -48,6 +48,7 @@ import com.android.emailcommon.utility.AttachmentUtilities;
import com.android.emailcommon.utility.Utility; import com.android.emailcommon.utility.Utility;
import com.android.mail.providers.UIProvider.AttachmentState; import com.android.mail.providers.UIProvider.AttachmentState;
import com.android.mail.utils.LogUtils; import com.android.mail.utils.LogUtils;
import com.google.common.annotations.VisibleForTesting;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
@ -55,13 +56,15 @@ import java.io.PrintWriter;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
public class AttachmentService extends Service implements Runnable { public class AttachmentService extends Service implements Runnable {
public static final String TAG = LogUtils.TAG; // For logging.
public static final String LOG_TAG = "AttachmentService";
// Minimum wait time before retrying a download that failed due to connection error // Minimum wait time before retrying a download that failed due to connection error
private static final long CONNECTION_ERROR_RETRY_MILLIS = 10 * DateUtils.SECOND_IN_MILLIS; private static final long CONNECTION_ERROR_RETRY_MILLIS = 10 * DateUtils.SECOND_IN_MILLIS;
@ -78,14 +81,16 @@ public class AttachmentService extends Service implements Runnable {
private static final int CALLBACK_TIMEOUT = 30 * ((int)DateUtils.SECOND_IN_MILLIS); private static final int CALLBACK_TIMEOUT = 30 * ((int)DateUtils.SECOND_IN_MILLIS);
// Try to download an attachment in the background this many times before giving up // Try to download an attachment in the background this many times before giving up
private static final int MAX_DOWNLOAD_RETRIES = 5; private static final int MAX_DOWNLOAD_RETRIES = 5;
private static final int PRIORITY_NONE = -1;
@SuppressWarnings("unused") /* package */ static final int PRIORITY_NONE = -1;
// Low priority will be used for opportunistic downloads
private static final int PRIORITY_BACKGROUND = 0;
// Normal priority is for forwarded downloads in outgoing mail
private static final int PRIORITY_SEND_MAIL = 1;
// High priority is for user requests // High priority is for user requests
private static final int PRIORITY_FOREGROUND = 2; /* package */ static final int PRIORITY_FOREGROUND = 0;
/* package */ static final int PRIORITY_HIGHEST = PRIORITY_FOREGROUND;
// Normal priority is for forwarded downloads in outgoing mail
/* package */ static final int PRIORITY_SEND_MAIL = 1;
// Low priority will be used for opportunistic downloads
/* package */ static final int PRIORITY_BACKGROUND = 2;
/* package */ static final int PRIORITY_LOWEST = PRIORITY_BACKGROUND;
// Minimum free storage in order to perform prefetch (25% of total memory) // Minimum free storage in order to perform prefetch (25% of total memory)
private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F; private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F;
@ -174,62 +179,86 @@ public class AttachmentService extends Service implements Runnable {
} }
} }
public static class DownloadRequest { /**
final int priority; * This class is used to contain the details and state of a particular request to download
final long time; * an attachment. These objects are constructed and either placed in the {@link DownloadQueue}
final long attachmentId; * or in the in-progress map used to keep track of downloads that are currently happening
final long messageId; * in the system
final long accountId; */
boolean inProgress = false; // public static class DownloadRequest {
int lastStatusCode; /*package*/ static class DownloadRequest {
int lastProgress; // Details of the request.
long lastCallbackTime; final int mPriority;
long startTime; final long mTime;
long retryCount; final long mAttachmentId;
long retryStartTime; final long mMessageId;
final long mAccountId;
private DownloadRequest(Context context, Attachment attachment) { // Status of the request.
attachmentId = attachment.mId; boolean mInProgress = false;
Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey); int mLastStatusCode;
int mLastProgress;
long mLastCallbackTime;
long mStartTime;
long mRetryCount;
long mRetryStartTime;
/**
* This constructor is mainly used for tests
* @param attPriority The priority of this attachment
* @param attId The id of the row in the attachment table.
*/
@VisibleForTesting
DownloadRequest(final int attPriority, final long attId) {
// This constructor should only be used for unit tests.
mTime = SystemClock.elapsedRealtime();
mPriority = attPriority;
mAttachmentId = attId;
mAccountId = -1;
mMessageId = -1;
}
private DownloadRequest(final Context context, final Attachment attachment) {
mAttachmentId = attachment.mId;
final Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey);
if (msg != null) { if (msg != null) {
accountId = msg.mAccountKey; mAccountId = msg.mAccountKey;
messageId = msg.mId; mMessageId = msg.mId;
} else { } else {
accountId = messageId = -1; mAccountId = mMessageId = -1;
} }
priority = getPriority(attachment); mPriority = getPriority(attachment);
time = SystemClock.elapsedRealtime(); mTime = SystemClock.elapsedRealtime();
} }
private DownloadRequest(DownloadRequest orig, long newTime) { private DownloadRequest(final DownloadRequest orig, final long newTime) {
priority = orig.priority; mPriority = orig.mPriority;
attachmentId = orig.attachmentId; mAttachmentId = orig.mAttachmentId;
messageId = orig.messageId; mMessageId = orig.mMessageId;
accountId = orig.accountId; mAccountId = orig.mAccountId;
time = newTime; mTime = newTime;
inProgress = orig.inProgress; mInProgress = orig.mInProgress;
lastStatusCode = orig.lastStatusCode; mLastStatusCode = orig.mLastStatusCode;
lastProgress = orig.lastProgress; mLastProgress = orig.mLastProgress;
lastCallbackTime = orig.lastCallbackTime; mLastCallbackTime = orig.mLastCallbackTime;
startTime = orig.startTime; mStartTime = orig.mStartTime;
retryCount = orig.retryCount; mRetryCount = orig.mRetryCount;
retryStartTime = orig.retryStartTime; mRetryStartTime = orig.mRetryStartTime;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return (int)attachmentId; return (int)mAttachmentId;
} }
/** /**
* Two download requests are equals if their attachment id's are equals * Two download requests are equals if their attachment id's are equals
*/ */
@Override @Override
public boolean equals(Object object) { public boolean equals(final Object object) {
if (!(object instanceof DownloadRequest)) return false; if (!(object instanceof DownloadRequest)) return false;
DownloadRequest req = (DownloadRequest)object; final DownloadRequest req = (DownloadRequest)object;
return req.attachmentId == attachmentId; return req.mAttachmentId == mAttachmentId;
} }
} }
@ -237,17 +266,17 @@ public class AttachmentService extends Service implements Runnable {
* Comparator class for the download set; we first compare by priority. Requests with equal * Comparator class for the download set; we first compare by priority. Requests with equal
* priority are compared by the time the request was created (older requests come first) * priority are compared by the time the request was created (older requests come first)
*/ */
/*protected*/ static class DownloadComparator implements Comparator<DownloadRequest> { /*package*/ static class DownloadComparator implements Comparator<DownloadRequest> {
@Override @Override
public int compare(DownloadRequest req1, DownloadRequest req2) { public int compare(DownloadRequest req1, DownloadRequest req2) {
int res; int res;
if (req1.priority != req2.priority) { if (req1.mPriority != req2.mPriority) {
res = (req1.priority < req2.priority) ? -1 : 1; res = (req1.mPriority < req2.mPriority) ? -1 : 1;
} else { } else {
if (req1.time == req2.time) { if (req1.mTime == req2.mTime) {
res = 0; res = 0;
} else { } else {
res = (req1.time > req2.time) ? -1 : 1; res = (req1.mTime < req2.mTime) ? -1 : 1;
} }
} }
return res; return res;
@ -255,6 +284,122 @@ public class AttachmentService extends Service implements Runnable {
} }
/** /**
* This class is used to organize the various download requests that are pending.
* We need a class that allows us to prioritize a collection of {@link DownloadRequest} objects
* while being able to pull off request with the highest priority but we also need
* to be able to find a particular {@link DownloadRequest} by id or by reference for retrieval.
* Bonus points for an implementation that does not require an iterator to accomplish its tasks
* as we can avoid pesky ConcurrentModificationException when one thread has the iterator
* and another thread modifies the collection.
*/
/*package*/ static class DownloadQueue {
private final int DEFAULT_SIZE = 10;
// For synchronization
private final Object mLock = new Object();
// For prioritization of DownloadRequests.
/*package*/ final PriorityQueue<DownloadRequest> mRequestQueue =
new PriorityQueue<DownloadRequest>(DEFAULT_SIZE,
new AttachmentService.DownloadComparator());
// Secondary collection to quickly find objects w/o the help of an iterator.
// This class should be kept in lock step with the priority queue.
/*package*/ final ConcurrentHashMap<Long, DownloadRequest> mRequestMap =
new ConcurrentHashMap<Long, DownloadRequest>();
/**
* This function will add the request to our collections if it does not already
* exist. If it does exist, the function will silently succeed.
* @param request The {@link DownloadRequest} that should be added to our queue
* @return true if it was added (or already exists), false otherwise
*/
public synchronized boolean addRequest(final DownloadRequest request) {
// It is key to keep the map and queue in lock step
if (request == null) {
// We can't add a null entry into the queue
LogUtils.wtf(AttachmentService.LOG_TAG, "Adding a null DownloadRequest");
return false;
}
final long requestId = request.mAttachmentId;
if (requestId < 0) {
// Invalid request
LogUtils.wtf(AttachmentService.LOG_TAG,
"Adding a DownloadRequest with an invalid id");
return false;
}
synchronized (mLock) {
// Check to see if this request is is already in the queue
final boolean exists = mRequestMap.containsKey(requestId);
if (!exists) {
mRequestQueue.offer(request);
mRequestMap.put(requestId, request);
}
}
return true;
}
/**
* This function will remove the specified request from the internal collections.
* @param request The {@link DownloadRequest} that should be removed from our queue
* @return true if it was removed or the request was invalid (meaning that the request
* is not in our queue), false otherwise.
*/
public synchronized boolean removeRequest(final DownloadRequest request) {
if (request == null) {
// If it is invalid, its not in the queue.
return true;
}
final boolean result;
synchronized (mLock) {
// It is key to keep the map and queue in lock step
result = mRequestQueue.remove(request);
if (result) {
mRequestMap.remove(request.mAttachmentId);
}
return result;
}
}
/**
* Return the next request from our queue.
* @return The next {@link DownloadRequest} object or null if the queue is empty
*/
public synchronized DownloadRequest getNextRequest() {
// It is key to keep the map and queue in lock step
final DownloadRequest returnRequest;
synchronized (mLock) {
returnRequest = mRequestQueue.poll();
if (returnRequest != null) {
final long requestId = returnRequest.mAttachmentId;
mRequestMap.remove(requestId);
}
}
return returnRequest;
}
/**
* Return the {@link DownloadRequest} with the given ID (attachment ID)
* @param requestId The ID of the request in question
* @return The associated {@link DownloadRequest} object or null if it does not exist
*/
public DownloadRequest findRequestById(final long requestId) {
if (requestId < 0) {
return null;
}
return mRequestMap.get(requestId);
}
public int getSize() {
return mRequestMap.size();
}
public boolean isEmpty() {
return mRequestMap.isEmpty();
}
}
/**
* The DownloadSet is a TreeSet sorted by priority class (e.g. low, high, etc.) and the * The DownloadSet is a TreeSet sorted by priority class (e.g. low, high, etc.) and the
* time of the request. Higher priority requests * time of the request. Higher priority requests
* are always processed first; among equals, the oldest request is processed first. The * are always processed first; among equals, the oldest request is processed first. The
@ -293,15 +438,15 @@ public class AttachmentService extends Service implements Runnable {
DownloadRequest req = findDownloadRequest(att.mId); DownloadRequest req = findDownloadRequest(att.mId);
long priority = getPriority(att); long priority = getPriority(att);
if (priority == PRIORITY_NONE) { if (priority == PRIORITY_NONE) {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "== Attachment changed: " + att.mId); LogUtils.d(LOG_TAG, "== Attachment changed: " + att.mId);
} }
// In this case, there is no download priority for this attachment // In this case, there is no download priority for this attachment
if (req != null) { if (req != null) {
// If it exists in the map, remove it // If it exists in the map, remove it
// NOTE: We don't yet support deleting downloads in progress // NOTE: We don't yet support deleting downloads in progress
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "== Attachment " + att.mId + " was in queue, removing"); LogUtils.d(LOG_TAG, "== Attachment " + att.mId + " was in queue, removing");
} }
remove(req); remove(req);
} }
@ -338,9 +483,9 @@ public class AttachmentService extends Service implements Runnable {
} }
// If the request already existed, we'll update the priority (so that the time is // If the request already existed, we'll update the priority (so that the time is
// up-to-date); otherwise, we create a new request // up-to-date); otherwise, we create a new request
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "== Download queued for attachment " + att.mId + ", class " + LogUtils.d(LOG_TAG, "== Download queued for attachment " + att.mId + ", class " +
req.priority + ", priority time " + req.time); req.mPriority + ", priority time " + req.mTime);
} }
} }
// Process the queue if we're in a wait // Process the queue if we're in a wait
@ -356,7 +501,7 @@ public class AttachmentService extends Service implements Runnable {
Iterator<DownloadRequest> iterator = iterator(); Iterator<DownloadRequest> iterator = iterator();
while(iterator.hasNext()) { while(iterator.hasNext()) {
DownloadRequest req = iterator.next(); DownloadRequest req = iterator.next();
if (req.attachmentId == id) { if (req.mAttachmentId == id) {
return req; return req;
} }
} }
@ -373,8 +518,8 @@ public class AttachmentService extends Service implements Runnable {
* the limit on maximum downloads * the limit on maximum downloads
*/ */
/*package*/ synchronized void processQueue() { /*package*/ synchronized void processQueue() {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "== Checking attachment queue, " + mDownloadSet.size() LogUtils.d(LOG_TAG, "== Checking attachment queue, " + mDownloadSet.size()
+ " entries"); + " entries");
} }
Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator();
@ -383,19 +528,19 @@ public class AttachmentService extends Service implements Runnable {
(mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) { (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) {
DownloadRequest req = iterator.next(); DownloadRequest req = iterator.next();
// Enforce per-account limit here // Enforce per-account limit here
if (downloadsForAccount(req.accountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) { if (downloadsForAccount(req.mAccountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "== Skip #" + req.attachmentId + "; maxed for acct #" + LogUtils.d(LOG_TAG, "== Skip #" + req.mAttachmentId + "; maxed for acct #" +
req.accountId); req.mAccountId);
} }
continue; continue;
} else if (Attachment.restoreAttachmentWithId(mContext, req.attachmentId) == null) { } else if (Attachment.restoreAttachmentWithId(mContext, req.mAttachmentId) == null) {
continue; continue;
} }
if (!req.inProgress) { if (!req.mInProgress) {
final long currentTime = SystemClock.elapsedRealtime(); final long currentTime = SystemClock.elapsedRealtime();
if (req.retryCount > 0 && req.retryStartTime > currentTime) { if (req.mRetryCount > 0 && req.mRetryStartTime > currentTime) {
LogUtils.d(TAG, "== waiting to retry attachment %d", req.attachmentId); LogUtils.d(LOG_TAG, "== waiting to retry attachment %d", req.mAttachmentId);
setWatchdogAlarm(CONNECTION_ERROR_RETRY_MILLIS); setWatchdogAlarm(CONNECTION_ERROR_RETRY_MILLIS);
continue; continue;
} }
@ -462,7 +607,7 @@ public class AttachmentService extends Service implements Runnable {
// query results. We are most likely here for other reasons such // query results. We are most likely here for other reasons such
// as the inability to view the attachment. In that case, let's just // as the inability to view the attachment. In that case, let's just
// skip it for now. // skip it for now.
LogUtils.e(TAG, "== skip attachment %d, it is ineligible", att.mId); LogUtils.e(LOG_TAG, "== skip attachment %d, it is ineligible", att.mId);
} }
} }
} }
@ -480,7 +625,7 @@ public class AttachmentService extends Service implements Runnable {
/*package*/ synchronized int downloadsForAccount(long accountId) { /*package*/ synchronized int downloadsForAccount(long accountId) {
int count = 0; int count = 0;
for (DownloadRequest req: mDownloadsInProgress.values()) { for (DownloadRequest req: mDownloadsInProgress.values()) {
if (req.accountId == accountId) { if (req.mAccountId == accountId) {
count++; count++;
} }
} }
@ -499,10 +644,10 @@ public class AttachmentService extends Service implements Runnable {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
for (DownloadRequest req: mDownloadsInProgress.values()) { for (DownloadRequest req: mDownloadsInProgress.values()) {
// Check how long it's been since receiving a callback // Check how long it's been since receiving a callback
long timeSinceCallback = now - req.lastCallbackTime; long timeSinceCallback = now - req.mLastCallbackTime;
if (timeSinceCallback > CALLBACK_TIMEOUT) { if (timeSinceCallback > CALLBACK_TIMEOUT) {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "== Download of " + req.attachmentId + " timed out"); LogUtils.d(LOG_TAG, "== Download of " + req.mAttachmentId + " timed out");
} }
cancelDownload(req); cancelDownload(req);
} }
@ -513,8 +658,8 @@ public class AttachmentService extends Service implements Runnable {
} }
// If there are downloads in progress, reset alarm // If there are downloads in progress, reset alarm
if (!mDownloadsInProgress.isEmpty()) { if (!mDownloadsInProgress.isEmpty()) {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "Reschedule watchdog..."); LogUtils.d(LOG_TAG, "Reschedule watchdog...");
} }
setWatchdogAlarm(); setWatchdogAlarm();
} }
@ -528,15 +673,15 @@ public class AttachmentService extends Service implements Runnable {
*/ */
/*package*/ synchronized boolean tryStartDownload(DownloadRequest req) { /*package*/ synchronized boolean tryStartDownload(DownloadRequest req) {
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount( EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(
AttachmentService.this, req.accountId); AttachmentService.this, req.mAccountId);
// Do not download the same attachment multiple times // Do not download the same attachment multiple times
boolean alreadyInProgress = mDownloadsInProgress.get(req.attachmentId) != null; boolean alreadyInProgress = mDownloadsInProgress.get(req.mAttachmentId) != null;
if (alreadyInProgress) return false; if (alreadyInProgress) return false;
try { try {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, ">> Starting download for attachment #" + req.attachmentId); LogUtils.d(LOG_TAG, ">> Starting download for attachment #" + req.mAttachmentId);
} }
startDownload(service, req); startDownload(service, req);
} catch (RemoteException e) { } catch (RemoteException e) {
@ -578,25 +723,25 @@ public class AttachmentService extends Service implements Runnable {
*/ */
private void startDownload(EmailServiceProxy service, DownloadRequest req) private void startDownload(EmailServiceProxy service, DownloadRequest req)
throws RemoteException { throws RemoteException {
req.startTime = System.currentTimeMillis(); req.mStartTime = System.currentTimeMillis();
req.inProgress = true; req.mInProgress = true;
mDownloadsInProgress.put(req.attachmentId, req); mDownloadsInProgress.put(req.mAttachmentId, req);
service.loadAttachment(mServiceCallback, req.accountId, req.attachmentId, service.loadAttachment(mServiceCallback, req.mAccountId, req.mAttachmentId,
req.priority != PRIORITY_FOREGROUND); req.mPriority != PRIORITY_FOREGROUND);
setWatchdogAlarm(); setWatchdogAlarm();
} }
private void cancelDownload(DownloadRequest req) { private void cancelDownload(DownloadRequest req) {
LogUtils.d(TAG, "cancelDownload #%d", req.attachmentId); LogUtils.d(LOG_TAG, "cancelDownload #%d", req.mAttachmentId);
req.inProgress = false; req.mInProgress = false;
mDownloadsInProgress.remove(req.attachmentId); mDownloadsInProgress.remove(req.mAttachmentId);
// Remove the download from our queue, and then decide whether or not to add it back. // Remove the download from our queue, and then decide whether or not to add it back.
remove(req); remove(req);
req.retryCount++; req.mRetryCount++;
if (req.retryCount > CONNECTION_ERROR_MAX_RETRIES) { if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
LogUtils.d(TAG, "too many failures, giving up"); LogUtils.d(LOG_TAG, "too many failures, giving up");
} else { } else {
LogUtils.d(TAG, "moving to end of queue, will retry"); LogUtils.d(LOG_TAG, "moving to end of queue, will retry");
// The time field of DownloadRequest is final, because it's unsafe to change it // The time field of DownloadRequest is final, because it's unsafe to change it
// as long as the DownloadRequest is in the DownloadSet. It's needed for the // as long as the DownloadRequest is in the DownloadSet. It's needed for the
// comparator, so changing time would make the request unfindable. // comparator, so changing time would make the request unfindable.
@ -636,27 +781,27 @@ public class AttachmentService extends Service implements Runnable {
if (statusCode == EmailServiceStatus.CONNECTION_ERROR) { if (statusCode == EmailServiceStatus.CONNECTION_ERROR) {
// If this needs to be retried, just process the queue again // If this needs to be retried, just process the queue again
if (req != null) { if (req != null) {
req.retryCount++; req.mRetryCount++;
if (req.retryCount > CONNECTION_ERROR_MAX_RETRIES) { if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
LogUtils.d(TAG, "Connection Error #%d, giving up", attachmentId); LogUtils.d(LOG_TAG, "Connection Error #%d, giving up", attachmentId);
remove(req); remove(req);
} else if (req.retryCount > CONNECTION_ERROR_DELAY_THRESHOLD) { } else if (req.mRetryCount > CONNECTION_ERROR_DELAY_THRESHOLD) {
// TODO: I'm not sure this is a great retry/backoff policy, but we're // TODO: I'm not sure this is a great retry/backoff policy, but we're
// afraid of changing behavior too much in case something relies upon it. // afraid of changing behavior too much in case something relies upon it.
// So now, for the first five errors, we'll retry immediately. For the next // So now, for the first five errors, we'll retry immediately. For the next
// five tries, we'll add a ten second delay between each. After that, we'll // five tries, we'll add a ten second delay between each. After that, we'll
// give up. // give up.
LogUtils.d(TAG, "ConnectionError #%d, retried %d times, adding delay", LogUtils.d(LOG_TAG, "ConnectionError #%d, retried %d times, adding delay",
attachmentId, req.retryCount); attachmentId, req.mRetryCount);
req.inProgress = false; req.mInProgress = false;
req.retryStartTime = SystemClock.elapsedRealtime() + req.mRetryStartTime = SystemClock.elapsedRealtime() +
CONNECTION_ERROR_RETRY_MILLIS; CONNECTION_ERROR_RETRY_MILLIS;
setWatchdogAlarm(CONNECTION_ERROR_RETRY_MILLIS); setWatchdogAlarm(CONNECTION_ERROR_RETRY_MILLIS);
} else { } else {
LogUtils.d(TAG, "ConnectionError #%d, retried %d times, adding delay", LogUtils.d(LOG_TAG, "ConnectionError #%d, retried %d times, adding delay",
attachmentId, req.retryCount); attachmentId, req.mRetryCount);
req.inProgress = false; req.mInProgress = false;
req.retryStartTime = 0; req.mRetryStartTime = 0;
kick(); kick();
} }
} }
@ -667,14 +812,14 @@ public class AttachmentService extends Service implements Runnable {
if (req != null) { if (req != null) {
remove(req); remove(req);
} }
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
long secs = 0; long secs = 0;
if (req != null) { if (req != null) {
secs = (System.currentTimeMillis() - req.time) / 1000; secs = (System.currentTimeMillis() - req.mTime) / 1000;
} }
String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" : String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" :
"Error " + statusCode; "Error " + statusCode;
LogUtils.d(TAG, "<< Download finished for attachment #" + attachmentId + "; " + secs LogUtils.d(LOG_TAG, "<< Download finished for attachment #" + attachmentId + "; " + secs
+ " seconds from request, status: " + status); + " seconds from request, status: " + status);
} }
@ -703,9 +848,9 @@ public class AttachmentService extends Service implements Runnable {
// try to send pending mail now (as mediated by MailService) // try to send pending mail now (as mediated by MailService)
if ((req != null) && if ((req != null) &&
!Utility.hasUnloadedAttachments(mContext, attachment.mMessageKey)) { !Utility.hasUnloadedAttachments(mContext, attachment.mMessageKey)) {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "== Downloads finished for outgoing msg #" LogUtils.d(LOG_TAG, "== Downloads finished for outgoing msg #"
+ req.messageId); + req.mMessageId);
} }
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount( EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(
mContext, accountId); mContext, accountId);
@ -781,7 +926,7 @@ public class AttachmentService extends Service implements Runnable {
// Record status and progress // Record status and progress
DownloadRequest req = mDownloadSet.getDownloadInProgress(attachmentId); DownloadRequest req = mDownloadSet.getDownloadInProgress(attachmentId);
if (req != null) { if (req != null) {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
String code; String code;
switch(statusCode) { switch(statusCode) {
case EmailServiceStatus.SUCCESS: code = "Success"; break; case EmailServiceStatus.SUCCESS: code = "Success"; break;
@ -789,14 +934,14 @@ public class AttachmentService extends Service implements Runnable {
default: code = Integer.toString(statusCode); break; default: code = Integer.toString(statusCode); break;
} }
if (statusCode != EmailServiceStatus.IN_PROGRESS) { if (statusCode != EmailServiceStatus.IN_PROGRESS) {
LogUtils.d(TAG, ">> Attachment status " + attachmentId + ": " + code); LogUtils.d(LOG_TAG, ">> Attachment status " + attachmentId + ": " + code);
} else if (progress >= (req.lastProgress + 10)) { } else if (progress >= (req.mLastProgress + 10)) {
LogUtils.d(TAG, ">> Attachment progress %d: %d%%", attachmentId, progress); LogUtils.d(LOG_TAG, ">> Attachment progress %d: %d%%", attachmentId, progress);
} }
} }
req.lastStatusCode = statusCode; req.mLastStatusCode = statusCode;
req.lastProgress = progress; req.mLastProgress = progress;
req.lastCallbackTime = System.currentTimeMillis(); req.mLastCallbackTime = System.currentTimeMillis();
Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId);
if (attachment != null && statusCode == EmailServiceStatus.IN_PROGRESS) { if (attachment != null && statusCode == EmailServiceStatus.IN_PROGRESS) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
@ -836,8 +981,8 @@ public class AttachmentService extends Service implements Runnable {
/*package*/ boolean dequeue(long attachmentId) { /*package*/ boolean dequeue(long attachmentId) {
DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId);
if (req != null) { if (req != null) {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "Dequeued attachmentId: " + attachmentId); LogUtils.d(LOG_TAG, "Dequeued attachmentId: " + attachmentId);
} }
mDownloadSet.remove(req); mDownloadSet.remove(req);
return true; return true;
@ -982,8 +1127,8 @@ public class AttachmentService extends Service implements Runnable {
if (accountStorage < perAccountMaxStorage) { if (accountStorage < perAccountMaxStorage) {
return true; return true;
} else { } else {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, ">> Prefetch not allowed for account " + account.mId + "; used " + LogUtils.d(LOG_TAG, ">> Prefetch not allowed for account " + account.mId + "; used " +
accountStorage + ", limit " + perAccountMaxStorage); accountStorage + ", limit " + perAccountMaxStorage);
} }
return false; return false;
@ -994,7 +1139,7 @@ public class AttachmentService extends Service implements Runnable {
public void run() { public void run() {
// These fields are only used within the service thread // These fields are only used within the service thread
mContext = this; mContext = this;
mConnectivityManager = new EmailConnectivityManager(this, TAG); mConnectivityManager = new EmailConnectivityManager(this, LOG_TAG);
mAccountManagerStub = new AccountManagerStub(this); mAccountManagerStub = new AccountManagerStub(this);
// Run through all attachments in the database that require download and add them to // Run through all attachments in the database that require download and add them to
@ -1004,7 +1149,7 @@ public class AttachmentService extends Service implements Runnable {
EmailContent.ID_PROJECTION, "(" + AttachmentColumns.FLAGS + " & ?) != 0", EmailContent.ID_PROJECTION, "(" + AttachmentColumns.FLAGS + " & ?) != 0",
new String[] {Integer.toString(mask)}, null); new String[] {Integer.toString(mask)}, null);
try { try {
LogUtils.d(TAG, "Count: " + c.getCount()); LogUtils.d(LOG_TAG, "Count: " + c.getCount());
while (c.moveToNext()) { while (c.moveToNext()) {
Attachment attachment = Attachment.restoreAttachmentWithId( Attachment attachment = Attachment.restoreAttachmentWithId(
this, c.getLong(EmailContent.ID_PROJECTION_COLUMN)); this, c.getLong(EmailContent.ID_PROJECTION_COLUMN));
@ -1033,7 +1178,7 @@ public class AttachmentService extends Service implements Runnable {
} }
mDownloadSet.processQueue(); mDownloadSet.processQueue();
if (mDownloadSet.isEmpty()) { if (mDownloadSet.isEmpty()) {
LogUtils.d(TAG, "*** All done; shutting down service"); LogUtils.d(LOG_TAG, "*** All done; shutting down service");
stopSelf(); stopSelf();
break; break;
} }
@ -1101,10 +1246,10 @@ public class AttachmentService extends Service implements Runnable {
// First, start up any required downloads, in priority order // First, start up any required downloads, in priority order
while (iterator.hasNext()) { while (iterator.hasNext()) {
DownloadRequest req = iterator.next(); DownloadRequest req = iterator.next();
pw.println(" Account: " + req.accountId + ", Attachment: " + req.attachmentId); pw.println(" Account: " + req.mAccountId + ", Attachment: " + req.mAttachmentId);
pw.println(" Priority: " + req.priority + ", Time: " + req.time + pw.println(" Priority: " + req.mPriority + ", Time: " + req.mTime +
(req.inProgress ? " [In progress]" : "")); (req.mInProgress ? " [In progress]" : ""));
Attachment att = Attachment.restoreAttachmentWithId(this, req.attachmentId); Attachment att = Attachment.restoreAttachmentWithId(this, req.mAttachmentId);
if (att == null) { if (att == null) {
pw.println(" Attachment not in database?"); pw.println(" Attachment not in database?");
} else if (att.mFileName != null) { } else if (att.mFileName != null) {
@ -1127,14 +1272,14 @@ public class AttachmentService extends Service implements Runnable {
} }
pw.println(" Size: " + att.mSize); pw.println(" Size: " + att.mSize);
} }
if (req.inProgress) { if (req.mInProgress) {
pw.println(" Status: " + req.lastStatusCode + ", Progress: " + pw.println(" Status: " + req.mLastStatusCode + ", Progress: " +
req.lastProgress); req.mLastProgress);
pw.println(" Started: " + req.startTime + ", Callback: " + pw.println(" Started: " + req.mStartTime + ", Callback: " +
req.lastCallbackTime); req.mLastCallbackTime);
pw.println(" Elapsed: " + ((time - req.startTime) / 1000L) + "s"); pw.println(" Elapsed: " + ((time - req.mStartTime) / 1000L) + "s");
if (req.lastCallbackTime > 0) { if (req.mLastCallbackTime > 0) {
pw.println(" CB: " + ((time - req.lastCallbackTime) / 1000L) + "s"); pw.println(" CB: " + ((time - req.mLastCallbackTime) / 1000L) + "s");
} }
} }
} }

View File

@ -0,0 +1,390 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.service;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.emailcommon.provider.EmailContent;
import junit.framework.TestCase;
/**
* Tests of the AttachmentService
*
* You can run this entire test case with:
* runtest -c com.android.email.service.AttachmentServiceTests email
*/
@SmallTest
public class AttachmentServiceTests extends TestCase {
public void testDownloadRequestIsEquals() {
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
final AttachmentService.DownloadRequest dr2 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 2);
assertTrue(dr.equals(dr));
assertFalse(dr.equals(dr2));
}
public void testDownloadQueueEmptyQueue() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
assertEquals(0, dq.getSize());
assertTrue(dq.isEmpty());
}
public void testDownloadQueueAddRequest() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
final boolean result = dq.addRequest(dr);
assertTrue(result);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
}
public void testDownloadQueueAddRequestNull() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final boolean result = dq.addRequest(null);
assertFalse(result);
assertEquals(0, dq.getSize());
assertTrue(dq.isEmpty());
}
public void testDownloadQueueAddRequestExisting() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
boolean result = dq.addRequest(dr);
assertTrue(result);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
// Now try to add the same one again. The queue should remain the same size.
result = dq.addRequest(dr);
assertTrue(result);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
}
public void testDownloadQueueRemoveRequest() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
boolean result = dq.addRequest(dr);
assertTrue(result);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
// Now remove the request and check the status of the queue
result = dq.removeRequest(dr);
assertTrue(result);
// The queue should be empty.
assertEquals(0, dq.getSize());
assertTrue(dq.isEmpty());
}
public void testDownloadQueueRemoveRequestNull() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
boolean result = dq.addRequest(dr);
assertTrue(result);
assertEquals(dq.getSize(), 1);
assertFalse(dq.isEmpty());
// Now remove the request and check the status of the queue
result = dq.removeRequest(null);
assertTrue(result);
// The queue should still have 1.
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
}
public void testDownloadQueueRemoveRequestDoesNotExist() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
boolean result = dq.addRequest(dr);
assertTrue(result);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
// Generate a new request and try to remove it.
result = dq.removeRequest(new AttachmentService.DownloadRequest(
AttachmentService.PRIORITY_FOREGROUND, 2));
assertFalse(result);
// The queue should still have 1.
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
}
public void testDownloadQueueFindRequestById() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
final boolean result = dq.addRequest(dr);
assertTrue(result);
final AttachmentService.DownloadRequest drResult = dq.findRequestById(1);
assertNotNull(drResult);
// Now let's make sure that these objects are the same
assertEquals(dr, drResult);
}
public void testDownloadQueueFindRequestByIdInvalidId() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
final boolean result = dq.addRequest(dr);
assertTrue(result);
final AttachmentService.DownloadRequest drResult = dq.findRequestById(-1);
assertNull(drResult);
}
public void testDownloadQueueFindRequestByIdUnknownId() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
final boolean result = dq.addRequest(dr);
assertTrue(result);
final AttachmentService.DownloadRequest drResult = dq.findRequestById(5);
assertNull(drResult);
}
/**
* This is just to test the FIFOness of our queue. We test priorities in a latter
* test case.
*/
public void testDownloadQueueGetNextRequest() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
boolean result = dq.addRequest(dr);
assertTrue(result);
final AttachmentService.DownloadRequest dr2 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_SEND_MAIL, 2);
result = dq.addRequest(dr2);
assertTrue(result);
final AttachmentService.DownloadRequest dr3 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_BACKGROUND, 3);
result = dq.addRequest(dr3);
assertTrue(result);
assertEquals(3, dq.getSize());
assertFalse(dq.isEmpty());
AttachmentService.DownloadRequest drResult = dq.getNextRequest();
assertEquals(dr, drResult);
assertEquals(2, dq.getSize());
assertFalse(dq.isEmpty());
drResult = dq.getNextRequest();
assertEquals(dr2, drResult);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
drResult = dq.getNextRequest();
assertEquals(dr3, drResult);
assertEquals(0, dq.getSize());
assertTrue(dq.isEmpty());
}
public void testDownloadQueueGetNextRequestEmptyQueue() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
AttachmentService.DownloadRequest drResult = dq.getNextRequest();
assertNull(drResult);
}
public void testDownloadQueueSizeReporting() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
// Start adding some download request objects, note that the empty queue case has been
// tested in above.
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
// Add the first DownloadRequest to the queue
boolean result = dq.addRequest(dr);
assertTrue(result);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
// Add the same one again, the size should be the same.
result = dq.addRequest(dr);
assertTrue(result);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
final AttachmentService.DownloadRequest dr2 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 2);
// Add another DownloadRequest
result = dq.addRequest(dr2);
assertTrue(result);
assertEquals(2, dq.getSize());
assertFalse(dq.isEmpty());
final AttachmentService.DownloadRequest dr3 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 3);
result = dq.addRequest(dr3);
assertTrue(result);
assertEquals(3, dq.getSize());
assertFalse(dq.isEmpty());
// Remove a request and check new size.
AttachmentService.DownloadRequest returnRequest = dq.getNextRequest();
assertNotNull(returnRequest);
assertEquals(2, dq.getSize());
assertFalse(dq.isEmpty());
final AttachmentService.DownloadRequest dr4 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 4);
// Adding the last DownloadRequest
result = dq.addRequest(dr4);
assertTrue(result);
assertEquals(3, dq.getSize());
assertFalse(dq.isEmpty());
// Start removing all the final requests and check sizes.
returnRequest = dq.getNextRequest();
assertNotNull(returnRequest);
assertEquals(2, dq.getSize());
assertFalse(dq.isEmpty());
returnRequest = dq.getNextRequest();
assertNotNull(returnRequest);
assertEquals(1, dq.getSize());
assertFalse(dq.isEmpty());
returnRequest = dq.getNextRequest();
assertNotNull(returnRequest);
assertEquals(0, dq.getSize());
assertTrue(dq.isEmpty());
}
/**
* Insert DownloadRequest obje cts in a random priority sequence and make sure that
* The highest priority items come out of the queue first.
*/
public void testDownloadQueueTestPriority() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
// Start adding some download request objects, note that the empty queue case has been
// tested in above.
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
boolean result = dq.addRequest(dr);
assertTrue(result);
final AttachmentService.DownloadRequest dr2 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_BACKGROUND, 2);
result = dq.addRequest(dr2);
assertTrue(result);
final AttachmentService.DownloadRequest dr3 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_SEND_MAIL, 3);
result = dq.addRequest(dr3);
assertTrue(result);
final AttachmentService.DownloadRequest dr4 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_SEND_MAIL, 4);
result = dq.addRequest(dr4);
assertTrue(result);
final AttachmentService.DownloadRequest dr5 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 5);
result = dq.addRequest(dr5);
assertTrue(result);
final AttachmentService.DownloadRequest dr6 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_BACKGROUND, 6);
result = dq.addRequest(dr6);
assertTrue(result);
// Set the priority to the highest possible value and everything show be
// in descending order.
int lastPriority = AttachmentService.PRIORITY_HIGHEST;
for (int i = 0; i < dq.getSize(); i++){
final AttachmentService.DownloadRequest returnRequest = dq.getNextRequest();
assertNotNull(returnRequest);
final int requestPriority = returnRequest.mPriority;
// The values should be going up or staying the same...indicating a lower priority
assertTrue(requestPriority >= lastPriority);
lastPriority = requestPriority;
}
}
/**
* Insert DownloadRequest objects in a random time based sequence and make sure that
* The oldest requests come out of the queue first.
*/
public void testDownloadQueueTestDate() {
final AttachmentService.DownloadQueue dq = new AttachmentService.DownloadQueue();
// Start adding some unique attachments but with the same priority
final AttachmentService.DownloadRequest dr =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 1);
boolean result = dq.addRequest(dr);
assertTrue(result);
final AttachmentService.DownloadRequest dr2 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 2);
result = dq.addRequest(dr2);
assertTrue(result);
final AttachmentService.DownloadRequest dr3 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 3);
result = dq.addRequest(dr3);
assertTrue(result);
final AttachmentService.DownloadRequest dr4 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 4);
result = dq.addRequest(dr4);
assertTrue(result);
final AttachmentService.DownloadRequest dr5 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 5);
result = dq.addRequest(dr5);
assertTrue(result);
final AttachmentService.DownloadRequest dr6 =
new AttachmentService.DownloadRequest(AttachmentService.PRIORITY_FOREGROUND, 6);
result = dq.addRequest(dr6);
assertTrue(result);
// The output should return requests in increasing time.
long lastTime = 0;
for (int i = 0; i < dq.getSize(); i++){
final AttachmentService.DownloadRequest returnRequest = dq.getNextRequest();
assertNotNull(returnRequest);
final long requestTime = returnRequest.mTime;
// The time should be going up.
assertTrue(requestTime >= lastTime);
lastTime = requestTime;
}
}
}