Cleanup of AttachmentService code and enabling of DownloadQueue.
Removed unused functions, 'final'ed variables and parameters, revisited some thread synchronization. Effectively no new code introduced so no unit tests for this CL. Change-Id: I714724a98d31a231ab10b0ad468b8efaa460bd1d
This commit is contained in:
parent
9768a05376
commit
e33511699a
@ -56,10 +56,8 @@ import java.io.PrintWriter;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.PriorityQueue;
|
import java.util.PriorityQueue;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
@ -81,15 +79,15 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
// 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;
|
||||||
|
|
||||||
/* package */ static final int PRIORITY_NONE = -1;
|
static final int PRIORITY_NONE = -1;
|
||||||
// High priority is for user requests
|
// High priority is for user requests
|
||||||
/* package */ static final int PRIORITY_FOREGROUND = 0;
|
static final int PRIORITY_FOREGROUND = 0;
|
||||||
/* package */ static final int PRIORITY_HIGHEST = PRIORITY_FOREGROUND;
|
static final int PRIORITY_HIGHEST = PRIORITY_FOREGROUND;
|
||||||
// Normal priority is for forwarded downloads in outgoing mail
|
// Normal priority is for forwarded downloads in outgoing mail
|
||||||
/* package */ static final int PRIORITY_SEND_MAIL = 1;
|
static final int PRIORITY_SEND_MAIL = 1;
|
||||||
// Low priority will be used for opportunistic downloads
|
// Low priority will be used for opportunistic downloads
|
||||||
/* package */ static final int PRIORITY_BACKGROUND = 2;
|
static final int PRIORITY_BACKGROUND = 2;
|
||||||
/* package */ static final int PRIORITY_LOWEST = PRIORITY_BACKGROUND;
|
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;
|
||||||
@ -106,20 +104,20 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
|
|
||||||
private static final String EXTRA_ATTACHMENT = "com.android.email.AttachmentService.attachment";
|
private static final String EXTRA_ATTACHMENT = "com.android.email.AttachmentService.attachment";
|
||||||
|
|
||||||
// This callback is invoked by the various service backends to give us download progress
|
// This callback is invoked by the various service implementations to give us download progress
|
||||||
// since those modules are responsible for the actual download.
|
// since those modules are responsible for the actual download.
|
||||||
private final ServiceCallback mServiceCallback = new ServiceCallback();
|
private final ServiceCallback mServiceCallback = new ServiceCallback();
|
||||||
|
|
||||||
// sRunningService is only set in the UI thread; it's visibility elsewhere is guaranteed
|
// sRunningService is only set in the UI thread; it's visibility elsewhere is guaranteed
|
||||||
// by the use of "volatile"
|
// by the use of "volatile"
|
||||||
/*package*/ static volatile AttachmentService sRunningService = null;
|
static volatile AttachmentService sRunningService = null;
|
||||||
|
|
||||||
// Signify that we are being shut down & destroyed.
|
// Signify that we are being shut down & destroyed.
|
||||||
private volatile boolean mStop = false;
|
private volatile boolean mStop = false;
|
||||||
|
|
||||||
/*package*/ Context mContext;
|
Context mContext;
|
||||||
/*package*/ EmailConnectivityManager mConnectivityManager;
|
EmailConnectivityManager mConnectivityManager;
|
||||||
/*package*/ final AttachmentWatchdog mWatchdog = new AttachmentWatchdog();
|
final AttachmentWatchdog mWatchdog = new AttachmentWatchdog();
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
@ -127,22 +125,18 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
// NOTE: This map is not kept current in terms of deletions (i.e. it stores the last calculated
|
// 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 loaded). If and when we reach the per-account
|
// amount plus the size of any new attachments loaded). If and when we reach the per-account
|
||||||
// limit, we recalculate the actual usage
|
// limit, we recalculate the actual usage
|
||||||
/*package*/ final ConcurrentHashMap<Long, Long> mAttachmentStorageMap =
|
final ConcurrentHashMap<Long, Long> mAttachmentStorageMap = new ConcurrentHashMap<Long, Long>();
|
||||||
new ConcurrentHashMap<Long, Long>();
|
|
||||||
|
|
||||||
// A map of attachment ids to the number of failed attempts to download the attachment
|
// A map of attachment ids to the number of failed attempts to download the attachment
|
||||||
// NOTE: We do not want to persist this. This allows us to retry background downloading
|
// NOTE: We do not want to persist this. This allows us to retry background downloading
|
||||||
// if any transient network errors are fixed & and the app is restarted
|
// if any transient network errors are fixed & and the app is restarted
|
||||||
/* package */ final ConcurrentHashMap<Long, Integer> mAttachmentFailureMap =
|
final ConcurrentHashMap<Long, Integer> mAttachmentFailureMap = new ConcurrentHashMap<Long, Integer>();
|
||||||
new ConcurrentHashMap<Long, Integer>();
|
|
||||||
|
|
||||||
// Keeps tracks of downloads in progress based on an attachment ID to DownloadRequest mapping.
|
// Keeps tracks of downloads in progress based on an attachment ID to DownloadRequest mapping.
|
||||||
/*package*/ final ConcurrentHashMap<Long, DownloadRequest> mDownloadsInProgress =
|
final ConcurrentHashMap<Long, DownloadRequest> mDownloadsInProgress =
|
||||||
new ConcurrentHashMap<Long, DownloadRequest>();
|
new ConcurrentHashMap<Long, DownloadRequest>();
|
||||||
|
|
||||||
// TODO: Remove in favor of the DownloadQueue when we are ready to move over (soon).
|
final DownloadQueue mDownloadQueue = new DownloadQueue();
|
||||||
/*package*/ final DownloadSet mDownloadSet = new DownloadSet(new DownloadComparator());
|
|
||||||
/*package*/ final DownloadQueue mDownloadQueue = new DownloadQueue();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to contain the details and state of a particular request to download
|
* This class is used to contain the details and state of a particular request to download
|
||||||
@ -150,8 +144,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* or in the in-progress map used to keep track of downloads that are currently happening
|
* or in the in-progress map used to keep track of downloads that are currently happening
|
||||||
* in the system
|
* in the system
|
||||||
*/
|
*/
|
||||||
// public static class DownloadRequest {
|
static class DownloadRequest {
|
||||||
/*package*/ static class DownloadRequest {
|
|
||||||
// Details of the request.
|
// Details of the request.
|
||||||
final int mPriority;
|
final int mPriority;
|
||||||
final long mTime;
|
final long mTime;
|
||||||
@ -227,11 +220,24 @@ 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.
|
||||||
|
*/
|
||||||
|
static class DownloadQueue {
|
||||||
|
private final int DEFAULT_SIZE = 10;
|
||||||
|
|
||||||
|
// For synchronization
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
||||||
* TODO: Move this into the DownloadQueue as a private static class when we finally remove
|
|
||||||
* the DownloadSet class from the implementation.
|
|
||||||
*/
|
*/
|
||||||
private static class DownloadComparator implements Comparator<DownloadRequest> {
|
private static class DownloadComparator implements Comparator<DownloadRequest> {
|
||||||
@Override
|
@Override
|
||||||
@ -250,29 +256,13 @@ 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.
|
// For prioritization of DownloadRequests.
|
||||||
/*package*/ final PriorityQueue<DownloadRequest> mRequestQueue =
|
final PriorityQueue<DownloadRequest> mRequestQueue =
|
||||||
new PriorityQueue<DownloadRequest>(DEFAULT_SIZE, new DownloadComparator());
|
new PriorityQueue<DownloadRequest>(DEFAULT_SIZE, new DownloadComparator());
|
||||||
|
|
||||||
// Secondary collection to quickly find objects w/o the help of an iterator.
|
// 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.
|
// This class should be kept in lock step with the priority queue.
|
||||||
/*package*/ final ConcurrentHashMap<Long, DownloadRequest> mRequestMap =
|
final ConcurrentHashMap<Long, DownloadRequest> mRequestMap = new ConcurrentHashMap<Long, DownloadRequest>();
|
||||||
new ConcurrentHashMap<Long, DownloadRequest>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function will add the request to our collections if it does not already
|
* This function will add the request to our collections if it does not already
|
||||||
@ -280,7 +270,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* @param request The {@link DownloadRequest} that should be added to our queue
|
* @param request The {@link DownloadRequest} that should be added to our queue
|
||||||
* @return true if it was added (or already exists), false otherwise
|
* @return true if it was added (or already exists), false otherwise
|
||||||
*/
|
*/
|
||||||
public synchronized boolean addRequest(final DownloadRequest request)
|
public boolean addRequest(final DownloadRequest request)
|
||||||
throws NullPointerException {
|
throws NullPointerException {
|
||||||
// It is key to keep the map and queue in lock step
|
// It is key to keep the map and queue in lock step
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
@ -312,7 +302,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* @return true if it was removed or the request was invalid (meaning that the request
|
* @return true if it was removed or the request was invalid (meaning that the request
|
||||||
* is not in our queue), false otherwise.
|
* is not in our queue), false otherwise.
|
||||||
*/
|
*/
|
||||||
public synchronized boolean removeRequest(final DownloadRequest request) {
|
public boolean removeRequest(final DownloadRequest request) {
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
// If it is invalid, its not in the queue.
|
// If it is invalid, its not in the queue.
|
||||||
return true;
|
return true;
|
||||||
@ -332,7 +322,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* Return the next request from our queue.
|
* Return the next request from our queue.
|
||||||
* @return The next {@link DownloadRequest} object or null if the queue is empty
|
* @return The next {@link DownloadRequest} object or null if the queue is empty
|
||||||
*/
|
*/
|
||||||
public synchronized DownloadRequest getNextRequest() {
|
public DownloadRequest getNextRequest() {
|
||||||
// It is key to keep the map and queue in lock step
|
// It is key to keep the map and queue in lock step
|
||||||
final DownloadRequest returnRequest;
|
final DownloadRequest returnRequest;
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
@ -354,17 +344,23 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
if (requestId < 0) {
|
if (requestId < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
synchronized (mLock) {
|
||||||
return mRequestMap.get(requestId);
|
return mRequestMap.get(requestId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getSize() {
|
public int getSize() {
|
||||||
|
synchronized (mLock) {
|
||||||
return mRequestMap.size();
|
return mRequestMap.size();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
|
synchronized (mLock) {
|
||||||
return mRequestMap.isEmpty();
|
return mRequestMap.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watchdog alarm receiver; responsible for making sure that downloads in progress are not
|
* Watchdog alarm receiver; responsible for making sure that downloads in progress are not
|
||||||
@ -420,7 +416,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* Watchdog for downloads; we use this in case we are hanging on a download, which might
|
* Watchdog for downloads; we use this in case we are hanging on a download, which might
|
||||||
* have failed silently (the connection dropped, for example)
|
* have failed silently (the connection dropped, for example)
|
||||||
*/
|
*/
|
||||||
/*package*/ void watchdogAlarm(final AttachmentService service, final int callbackTimeout) {
|
void watchdogAlarm(final AttachmentService service, final int callbackTimeout) {
|
||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
// We want to iterate on each of the downloads that are currently in progress and
|
// We want to iterate on each of the downloads that are currently in progress and
|
||||||
// cancel the ones that seem to be taking too long.
|
// cancel the ones that seem to be taking too long.
|
||||||
@ -433,6 +429,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
LogUtils.d(LOG_TAG, "== Download of " + req.mAttachmentId + " timed out");
|
LogUtils.d(LOG_TAG, "== Download of " + req.mAttachmentId + " timed out");
|
||||||
}
|
}
|
||||||
service.cancelDownload(req);
|
service.cancelDownload(req);
|
||||||
|
// TODO: Should we also mark the attachment as failed at this point in time?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check whether we can start new downloads...
|
// Check whether we can start new downloads...
|
||||||
@ -448,54 +445,26 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
boolean isConnected() {
|
||||||
* Temporary function implemented as a transition between DownloadSet to DownloadQueue.
|
|
||||||
* Will be property implemented and documented in a subsequent CL.
|
|
||||||
* @param req The {@link DownloadRequest} to be cancelled.
|
|
||||||
*/
|
|
||||||
/*package*/ void cancelDownload(final DownloadRequest req) {
|
|
||||||
mDownloadSet.cancelDownload(req);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary function implemented as a transition between DownloadSet to DownloadQueue
|
|
||||||
*/
|
|
||||||
/*package*/ void processQueue() {
|
|
||||||
mDownloadSet.processQueue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ boolean isConnected() {
|
|
||||||
if (mConnectivityManager != null) {
|
if (mConnectivityManager != null) {
|
||||||
return mConnectivityManager.hasConnectivity();
|
return mConnectivityManager.hasConnectivity();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ Collection<DownloadRequest> getInProgressDownloads() {
|
Collection<DownloadRequest> getInProgressDownloads() {
|
||||||
return mDownloadsInProgress.values();
|
return mDownloadsInProgress.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ boolean areDownloadsInProgress() {
|
boolean areDownloadsInProgress() {
|
||||||
return !mDownloadsInProgress.isEmpty();
|
return !mDownloadsInProgress.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DownloadSet is a TreeSet sorted by priority class (e.g. low, high, etc.) and the
|
* Set the bits in the provider to mark this download as failed.
|
||||||
* time of the request. Higher priority requests
|
* @param att The attachment that failed to download.
|
||||||
* are always processed first; among equals, the oldest request is processed first. The
|
|
||||||
* priority key represents this ordering. Note: All methods that change the attachment map are
|
|
||||||
* synchronized on the map itself
|
|
||||||
*/
|
*/
|
||||||
/*package*/ class DownloadSet extends TreeSet<DownloadRequest> {
|
void markAttachmentAsFailed(final Attachment att) {
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/*package*/ DownloadSet(Comparator<? super DownloadRequest> comparator) {
|
|
||||||
super(comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void markAttachmentAsFailed(final Attachment att) {
|
|
||||||
final ContentValues cv = new ContentValues();
|
final ContentValues cv = new ContentValues();
|
||||||
final int flags = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
|
final int flags = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
|
||||||
cv.put(AttachmentColumns.FLAGS, att.mFlags &= ~flags);
|
cv.put(AttachmentColumns.FLAGS, att.mFlags &= ~flags);
|
||||||
@ -509,9 +478,9 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* necessary that we detect a deleted attachment, as the code always checks for the
|
* necessary that we detect a deleted attachment, as the code always checks for the
|
||||||
* existence of an attachment before acting on it.
|
* existence of an attachment before acting on it.
|
||||||
*/
|
*/
|
||||||
public synchronized void onChange(Context context, Attachment att) {
|
public synchronized void onChange(final Context context, final Attachment att) {
|
||||||
DownloadRequest req = findDownloadRequest(att.mId);
|
DownloadRequest req = mDownloadQueue.findRequestById(att.mId);
|
||||||
long priority = getPriority(att);
|
final long priority = getPriority(att);
|
||||||
if (priority == PRIORITY_NONE) {
|
if (priority == PRIORITY_NONE) {
|
||||||
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
||||||
LogUtils.d(LOG_TAG, "== Attachment changed: " + att.mId);
|
LogUtils.d(LOG_TAG, "== Attachment changed: " + att.mId);
|
||||||
@ -523,7 +492,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
||||||
LogUtils.d(LOG_TAG, "== Attachment " + att.mId + " was in queue, removing");
|
LogUtils.d(LOG_TAG, "== Attachment " + att.mId + " was in queue, removing");
|
||||||
}
|
}
|
||||||
remove(req);
|
mDownloadQueue.removeRequest(req);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Ignore changes that occur during download
|
// Ignore changes that occur during download
|
||||||
@ -554,7 +523,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
// can't download it for policy reasons. Let's let this go through because
|
// can't download it for policy reasons. Let's let this go through because
|
||||||
// the final recipient of this forward email might be able to process it.
|
// the final recipient of this forward email might be able to process it.
|
||||||
}
|
}
|
||||||
add(req);
|
mDownloadQueue.addRequest(req);
|
||||||
}
|
}
|
||||||
// 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
|
||||||
@ -567,41 +536,22 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
kick();
|
kick();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a queued DownloadRequest, given the attachment's id
|
|
||||||
* @param id the id of the attachment
|
|
||||||
* @return the DownloadRequest for that attachment (or null, if none)
|
|
||||||
*/
|
|
||||||
/*package*/ synchronized DownloadRequest findDownloadRequest(long id) {
|
|
||||||
Iterator<DownloadRequest> iterator = iterator();
|
|
||||||
while(iterator.hasNext()) {
|
|
||||||
DownloadRequest req = iterator.next();
|
|
||||||
if (req.mAttachmentId == id) {
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized boolean isEmpty() {
|
|
||||||
return super.isEmpty() && mDownloadsInProgress.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing
|
* Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing
|
||||||
* the limit on maximum downloads
|
* the limit on maximum downloads
|
||||||
*/
|
*/
|
||||||
/*package*/ synchronized void processQueue() {
|
synchronized void processQueue() {
|
||||||
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
||||||
LogUtils.d(LOG_TAG, "== Checking attachment queue, " + mDownloadSet.size()
|
LogUtils.d(LOG_TAG, "== Checking attachment queue, " + mDownloadQueue.getSize()
|
||||||
+ " entries");
|
+ " entries");
|
||||||
}
|
}
|
||||||
Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator();
|
|
||||||
// First, start up any required downloads, in priority order
|
while (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS) {
|
||||||
while (iterator.hasNext() &&
|
final DownloadRequest req = mDownloadQueue.getNextRequest();
|
||||||
(mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) {
|
if (req == null) {
|
||||||
DownloadRequest req = iterator.next();
|
// No more queued requests? We are done for now.
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Enforce per-account limit here
|
// Enforce per-account limit here
|
||||||
if (downloadsForAccount(req.mAccountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) {
|
if (downloadsForAccount(req.mAccountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) {
|
||||||
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
||||||
@ -624,56 +574,64 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
// always possible that they made it in here regardless in the future. In a
|
// always possible that they made it in here regardless in the future. In a
|
||||||
// perfect world, we would make it bullet proof with a check for eligibility
|
// perfect world, we would make it bullet proof with a check for eligibility
|
||||||
// here instead/also.
|
// here instead/also.
|
||||||
mDownloadSet.tryStartDownload(req);
|
tryStartDownload(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't prefetch if background downloading is disallowed
|
// Check our ability to be opportunistic regarding background downloads.
|
||||||
EmailConnectivityManager ecm = mConnectivityManager;
|
final EmailConnectivityManager ecm = mConnectivityManager;
|
||||||
if (ecm == null) return;
|
if ((ecm == null) || !ecm.isAutoSyncAllowed() ||
|
||||||
if (!ecm.isAutoSyncAllowed()) return;
|
(ecm.getActiveNetworkType() != ConnectivityManager.TYPE_WIFI)) {
|
||||||
// Don't prefetch unless we're on a WiFi network
|
// Only prefetch if it if connectivity is available, prefetch is enabled
|
||||||
if (ecm.getActiveNetworkType() != ConnectivityManager.TYPE_WIFI) {
|
// and we are on WIFI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then, try opportunistic download of appropriate attachments
|
// Then, try opportunistic download of appropriate attachments
|
||||||
int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size();
|
final int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size();
|
||||||
// Always leave one slot for user requested download
|
if ((backgroundDownloads + 1) >= MAX_SIMULTANEOUS_DOWNLOADS) {
|
||||||
if (backgroundDownloads > (MAX_SIMULTANEOUS_DOWNLOADS - 1)) {
|
// We want to leave one spot open for a user requested download that we haven't
|
||||||
|
// started processing yet.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We'll load up the newest 25 attachments that aren't loaded or queued
|
// We'll load up the newest 25 attachments that aren't loaded or queued
|
||||||
Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI,
|
// TODO: We are always looking for MAX_ATTACHMENTS_TO_CHECK, shouldn't this be
|
||||||
|
// backgroundDownloads instead? We should fix and test this.
|
||||||
|
final Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI,
|
||||||
MAX_ATTACHMENTS_TO_CHECK);
|
MAX_ATTACHMENTS_TO_CHECK);
|
||||||
Cursor c = mContext.getContentResolver().query(lookupUri,
|
final Cursor c = mContext.getContentResolver().query(lookupUri,
|
||||||
Attachment.CONTENT_PROJECTION,
|
Attachment.CONTENT_PROJECTION,
|
||||||
EmailContent.Attachment.PRECACHE_INBOX_SELECTION,
|
EmailContent.Attachment.PRECACHE_INBOX_SELECTION,
|
||||||
null, AttachmentColumns._ID + " DESC");
|
null, AttachmentColumns._ID + " DESC");
|
||||||
File cacheDir = mContext.getCacheDir();
|
File cacheDir = mContext.getCacheDir();
|
||||||
try {
|
try {
|
||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
Attachment att = new Attachment();
|
final Attachment att = new Attachment();
|
||||||
att.restore(c);
|
att.restore(c);
|
||||||
Account account = Account.restoreAccountWithId(mContext, att.mAccountKey);
|
final Account account = Account.restoreAccountWithId(mContext, att.mAccountKey);
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
// Clean up this orphaned attachment; there's no point in keeping it
|
// Clean up this orphaned attachment; there's no point in keeping it
|
||||||
// around; then try to find another one
|
// around; then try to find another one
|
||||||
EmailContent.delete(mContext, Attachment.CONTENT_URI, att.mId);
|
EmailContent.delete(mContext, Attachment.CONTENT_URI, att.mId);
|
||||||
} else {
|
} else {
|
||||||
// Check that the attachment meets system requirements for download
|
// Check that the attachment meets system requirements for download
|
||||||
AttachmentInfo info = new AttachmentInfo(mContext, att);
|
// Note that there couple be policy that does not allow this attachment
|
||||||
|
// to be downloaded.
|
||||||
|
final AttachmentInfo info = new AttachmentInfo(mContext, att);
|
||||||
if (info.isEligibleForDownload()) {
|
if (info.isEligibleForDownload()) {
|
||||||
// Either the account must be able to prefetch or this must be
|
// Either the account must be able to prefetch or this must be
|
||||||
// an inline attachment
|
// an inline attachment.
|
||||||
if (att.mContentId != null ||
|
if (att.mContentId != null ||
|
||||||
(canPrefetchForAccount(account, cacheDir))) {
|
(canPrefetchForAccount(account, cacheDir))) {
|
||||||
Integer tryCount;
|
final Integer tryCount = mAttachmentFailureMap.get(att.mId);
|
||||||
tryCount = mAttachmentFailureMap.get(att.mId);
|
|
||||||
if (tryCount != null && tryCount > MAX_DOWNLOAD_RETRIES) {
|
if (tryCount != null && tryCount > MAX_DOWNLOAD_RETRIES) {
|
||||||
// move onto the next attachment
|
// move onto the next attachment
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Start this download and we're done
|
// Start this download and we're done
|
||||||
DownloadRequest req = new DownloadRequest(mContext, att);
|
final DownloadRequest req = new DownloadRequest(mContext, att);
|
||||||
mDownloadSet.tryStartDownload(req);
|
tryStartDownload(req);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -691,16 +649,15 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
c.close();
|
c.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count the number of running downloads in progress for this account
|
* Count the number of running downloads in progress for this account
|
||||||
* @param accountId the id of the account
|
* @param accountId the id of the account
|
||||||
* @return the count of running downloads
|
* @return the count of running downloads
|
||||||
*/
|
*/
|
||||||
/*package*/ synchronized int downloadsForAccount(long accountId) {
|
synchronized int downloadsForAccount(final long accountId) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (DownloadRequest req: mDownloadsInProgress.values()) {
|
for (final DownloadRequest req: mDownloadsInProgress.values()) {
|
||||||
if (req.mAccountId == accountId) {
|
if (req.mAccountId == accountId) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@ -714,8 +671,8 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* @param req the DownloadRequest
|
* @param req the DownloadRequest
|
||||||
* @return whether or not the download was started
|
* @return whether or not the download was started
|
||||||
*/
|
*/
|
||||||
/*package*/ synchronized boolean tryStartDownload(DownloadRequest req) {
|
synchronized boolean tryStartDownload(final DownloadRequest req) {
|
||||||
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(
|
final EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(
|
||||||
AttachmentService.this, req.mAccountId);
|
AttachmentService.this, req.mAccountId);
|
||||||
|
|
||||||
// Do not download the same attachment multiple times
|
// Do not download the same attachment multiple times
|
||||||
@ -735,11 +692,6 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized DownloadRequest getDownloadInProgress(long attachmentId) {
|
|
||||||
return mDownloadsInProgress.get(attachmentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do the work of starting an attachment download using the EmailService interface, and
|
* Do the work of starting an attachment download using the EmailService interface, and
|
||||||
* set our watchdog alarm
|
* set our watchdog alarm
|
||||||
@ -748,7 +700,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* @param req the DownloadRequest
|
* @param req the DownloadRequest
|
||||||
* @throws RemoteException
|
* @throws RemoteException
|
||||||
*/
|
*/
|
||||||
private void startDownload(EmailServiceProxy service, DownloadRequest req)
|
private void startDownload(final EmailServiceProxy service, final DownloadRequest req)
|
||||||
throws RemoteException {
|
throws RemoteException {
|
||||||
req.mStartTime = System.currentTimeMillis();
|
req.mStartTime = System.currentTimeMillis();
|
||||||
req.mInProgress = true;
|
req.mInProgress = true;
|
||||||
@ -758,12 +710,12 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
mWatchdog.setWatchdogAlarm(mContext);
|
mWatchdog.setWatchdogAlarm(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ synchronized void cancelDownload(DownloadRequest req) {
|
synchronized void cancelDownload(final DownloadRequest req) {
|
||||||
LogUtils.d(LOG_TAG, "cancelDownload #%d", req.mAttachmentId);
|
LogUtils.d(LOG_TAG, "cancelDownload #%d", req.mAttachmentId);
|
||||||
req.mInProgress = false;
|
req.mInProgress = false;
|
||||||
mDownloadsInProgress.remove(req.mAttachmentId);
|
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);
|
mDownloadQueue.removeRequest(req);
|
||||||
req.mRetryCount++;
|
req.mRetryCount++;
|
||||||
if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
|
if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
|
||||||
LogUtils.d(LOG_TAG, "too many failures, giving up");
|
LogUtils.d(LOG_TAG, "too many failures, giving up");
|
||||||
@ -774,8 +726,8 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
// comparator, so changing time would make the request unfindable.
|
// comparator, so changing time would make the request unfindable.
|
||||||
// Instead, we'll create a new DownloadRequest with an updated time.
|
// Instead, we'll create a new DownloadRequest with an updated time.
|
||||||
// This will sort at the end of the set.
|
// This will sort at the end of the set.
|
||||||
req = new DownloadRequest(req, SystemClock.elapsedRealtime());
|
final DownloadRequest newReq = new DownloadRequest(req, SystemClock.elapsedRealtime());
|
||||||
add(req);
|
mDownloadQueue.addRequest(newReq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -784,7 +736,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* @param attachmentId the id of the attachment whose download is finished
|
* @param attachmentId the id of the attachment whose download is finished
|
||||||
* @param statusCode the EmailServiceStatus code returned by the Service
|
* @param statusCode the EmailServiceStatus code returned by the Service
|
||||||
*/
|
*/
|
||||||
/*package*/ synchronized void endDownload(long attachmentId, int statusCode) {
|
synchronized void endDownload(final long attachmentId, final int statusCode) {
|
||||||
// Say we're no longer downloading this
|
// Say we're no longer downloading this
|
||||||
mDownloadsInProgress.remove(attachmentId);
|
mDownloadsInProgress.remove(attachmentId);
|
||||||
|
|
||||||
@ -804,14 +756,17 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
mAttachmentFailureMap.put(attachmentId, downloadCount);
|
mAttachmentFailureMap.put(attachmentId, downloadCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId);
|
final DownloadRequest req = mDownloadQueue.findRequestById(attachmentId);
|
||||||
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.mRetryCount++;
|
req.mRetryCount++;
|
||||||
if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
|
if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
|
||||||
|
// We are done, we maxed out our total number of tries.
|
||||||
LogUtils.d(LOG_TAG, "Connection Error #%d, giving up", attachmentId);
|
LogUtils.d(LOG_TAG, "Connection Error #%d, giving up", attachmentId);
|
||||||
remove(req);
|
mDownloadQueue.removeRequest(req);
|
||||||
|
// Note that we are not doing anything with the attachment right now
|
||||||
|
// We will annotate it later in this function if needed.
|
||||||
} else if (req.mRetryCount > 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.
|
||||||
@ -838,22 +793,23 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
|
|
||||||
// If the request is still in the queue, remove it
|
// If the request is still in the queue, remove it
|
||||||
if (req != null) {
|
if (req != null) {
|
||||||
remove(req);
|
mDownloadQueue.removeRequest(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LogUtils.isLoggable(LOG_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.mTime) / 1000;
|
secs = (System.currentTimeMillis() - req.mTime) / 1000;
|
||||||
}
|
}
|
||||||
String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" :
|
final String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" :
|
||||||
"Error " + statusCode;
|
"Error " + statusCode;
|
||||||
LogUtils.d(LOG_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
final Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
||||||
if (attachment != null) {
|
if (attachment != null) {
|
||||||
long accountId = attachment.mAccountKey;
|
final long accountId = attachment.mAccountKey;
|
||||||
// Update our attachment storage for this account
|
// Update our attachment storage for this account
|
||||||
Long currentStorage = mAttachmentStorageMap.get(accountId);
|
Long currentStorage = mAttachmentStorageMap.get(accountId);
|
||||||
if (currentStorage == null) {
|
if (currentStorage == null) {
|
||||||
@ -917,7 +873,6 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
// Process the queue
|
// Process the queue
|
||||||
kick();
|
kick();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the download priority of an Attachment. A priority of zero means that the
|
* Calculate the download priority of an Attachment. A priority of zero means that the
|
||||||
@ -925,9 +880,9 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* @param att the Attachment
|
* @param att the Attachment
|
||||||
* @return the priority key of the Attachment
|
* @return the priority key of the Attachment
|
||||||
*/
|
*/
|
||||||
private static int getPriority(Attachment att) {
|
private static int getPriority(final Attachment att) {
|
||||||
int priorityClass = PRIORITY_NONE;
|
int priorityClass = PRIORITY_NONE;
|
||||||
int flags = att.mFlags;
|
final int flags = att.mFlags;
|
||||||
if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) {
|
if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) {
|
||||||
priorityClass = PRIORITY_SEND_MAIL;
|
priorityClass = PRIORITY_SEND_MAIL;
|
||||||
} else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) {
|
} else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) {
|
||||||
@ -947,15 +902,15 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* come from either Controller (IMAP) or ExchangeService (EAS). Note that we only implement the
|
* come from either Controller (IMAP) or ExchangeService (EAS). Note that we only implement the
|
||||||
* single callback that's defined by the EmailServiceCallback interface.
|
* single callback that's defined by the EmailServiceCallback interface.
|
||||||
*/
|
*/
|
||||||
private class ServiceCallback extends IEmailServiceCallback.Stub {
|
class ServiceCallback extends IEmailServiceCallback.Stub {
|
||||||
@Override
|
@Override
|
||||||
public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode,
|
public void loadAttachmentStatus(final long messageId, final long attachmentId,
|
||||||
int progress) {
|
final int statusCode, final int progress) {
|
||||||
// Record status and progress
|
// Record status and progress
|
||||||
DownloadRequest req = mDownloadSet.getDownloadInProgress(attachmentId);
|
final DownloadRequest req = mDownloadsInProgress.get(attachmentId);
|
||||||
if (req != null) {
|
if (req != null) {
|
||||||
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
||||||
String code;
|
final String code;
|
||||||
switch(statusCode) {
|
switch(statusCode) {
|
||||||
case EmailServiceStatus.SUCCESS: code = "Success"; break;
|
case EmailServiceStatus.SUCCESS: code = "Success"; break;
|
||||||
case EmailServiceStatus.IN_PROGRESS: code = "In progress"; break;
|
case EmailServiceStatus.IN_PROGRESS: code = "In progress"; break;
|
||||||
@ -970,86 +925,30 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
req.mLastStatusCode = statusCode;
|
req.mLastStatusCode = statusCode;
|
||||||
req.mLastProgress = progress;
|
req.mLastProgress = progress;
|
||||||
req.mLastCallbackTime = System.currentTimeMillis();
|
req.mLastCallbackTime = System.currentTimeMillis();
|
||||||
Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
final Attachment attachment =
|
||||||
|
Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
||||||
if (attachment != null && statusCode == EmailServiceStatus.IN_PROGRESS) {
|
if (attachment != null && statusCode == EmailServiceStatus.IN_PROGRESS) {
|
||||||
ContentValues values = new ContentValues();
|
final ContentValues values = new ContentValues();
|
||||||
values.put(AttachmentColumns.UI_DOWNLOADED_SIZE,
|
values.put(AttachmentColumns.UI_DOWNLOADED_SIZE,
|
||||||
attachment.mSize * progress / 100);
|
attachment.mSize * progress / 100);
|
||||||
// Update UIProvider with updated download size
|
// Update UIProvider with updated download size
|
||||||
// Individual services will set contentUri and state when finished
|
// Individual services will set contentUri and state when finished
|
||||||
attachment.update(mContext, values);
|
attachment.update(mContext, values);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case EmailServiceStatus.IN_PROGRESS:
|
case EmailServiceStatus.IN_PROGRESS:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mDownloadSet.endDownload(attachmentId, statusCode);
|
endDownload(attachmentId, statusCode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// The only way that we can get a callback from the service implementation for
|
||||||
|
// an attachment that doesn't exist is if it was cancelled due to the
|
||||||
|
// AttachmentWatchdog. This is a valid scenario and the Watchdog should have already
|
||||||
|
// marked this attachment as failed/cancelled.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ void onChange(Attachment att) {
|
|
||||||
mDownloadSet.onChange(this, att);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ boolean isQueued(long attachmentId) {
|
|
||||||
return mDownloadSet.findDownloadRequest(attachmentId) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ int getSize() {
|
|
||||||
return mDownloadSet.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*package*/ boolean dequeue(long attachmentId) {
|
|
||||||
DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId);
|
|
||||||
if (req != null) {
|
|
||||||
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
|
||||||
LogUtils.d(LOG_TAG, "Dequeued attachmentId: " + attachmentId);
|
|
||||||
}
|
|
||||||
mDownloadSet.remove(req);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask the service for the number of items in the download queue
|
|
||||||
* @return the number of items queued for download
|
|
||||||
*/
|
|
||||||
public static int getQueueSize() {
|
|
||||||
AttachmentService service = sRunningService;
|
|
||||||
if (service != null) {
|
|
||||||
return service.getSize();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask the service whether a particular attachment is queued for download
|
|
||||||
* @param attachmentId the id of the Attachment (as stored by EmailProvider)
|
|
||||||
* @return whether or not the attachment is queued for download
|
|
||||||
*/
|
|
||||||
public static boolean isAttachmentQueued(long attachmentId) {
|
|
||||||
AttachmentService service = sRunningService;
|
|
||||||
if (service != null) {
|
|
||||||
return service.isQueued(attachmentId);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask the service to remove an attachment from the download queue
|
|
||||||
* @param attachmentId the id of the Attachment (as stored by EmailProvider)
|
|
||||||
* @return whether or not the attachment was removed from the queue
|
|
||||||
*/
|
|
||||||
public static boolean cancelQueuedAttachment(long attachmentId) {
|
|
||||||
AttachmentService service = sRunningService;
|
|
||||||
if (service != null) {
|
|
||||||
return service.dequeue(attachmentId);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The queue entries here are entries of the form {id, flags}, with the values passed in to
|
// The queue entries here are entries of the form {id, flags}, with the values passed in to
|
||||||
@ -1059,7 +958,9 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
private static AsyncTask<Void, Void, Void> sAttachmentChangedTask;
|
private static AsyncTask<Void, Void, Void> sAttachmentChangedTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called directly by EmailProvider whenever an attachment is inserted or changed
|
* Called directly by EmailProvider whenever an attachment is inserted or changed. Since this
|
||||||
|
* call is being invoked on the UI thread, we need to make sure that the downloads are
|
||||||
|
* happening in the background.
|
||||||
* @param context the caller's context
|
* @param context the caller's context
|
||||||
* @param id the attachment's id
|
* @param id the attachment's id
|
||||||
* @param flags the new flags for the attachment
|
* @param flags the new flags for the attachment
|
||||||
@ -1067,7 +968,6 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
public static void attachmentChanged(final Context context, final long id, final int flags) {
|
public static void attachmentChanged(final Context context, final long id, final int flags) {
|
||||||
synchronized (sAttachmentChangedQueue) {
|
synchronized (sAttachmentChangedQueue) {
|
||||||
sAttachmentChangedQueue.add(new long[]{id, flags});
|
sAttachmentChangedQueue.add(new long[]{id, flags});
|
||||||
|
|
||||||
if (sAttachmentChangedTask == null) {
|
if (sAttachmentChangedTask == null) {
|
||||||
sAttachmentChangedTask = new AsyncTask<Void, Void, Void>() {
|
sAttachmentChangedTask = new AsyncTask<Void, Void, Void>() {
|
||||||
@Override
|
@Override
|
||||||
@ -1092,6 +992,8 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
final Intent intent =
|
final Intent intent =
|
||||||
new Intent(context, AttachmentService.class);
|
new Intent(context, AttachmentService.class);
|
||||||
intent.putExtra(EXTRA_ATTACHMENT, attachment);
|
intent.putExtra(EXTRA_ATTACHMENT, attachment);
|
||||||
|
// This is result in a call to AttachmentService.onStartCommand()
|
||||||
|
// which will queue the attachment in its internal prioritized queue.
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1101,31 +1003,34 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether an attachment can be prefetched for the given account
|
* Determine whether an attachment can be prefetched for the given account based on
|
||||||
|
* total download size restrictions tied to the account.
|
||||||
* @return true if download is allowed, false otherwise
|
* @return true if download is allowed, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean canPrefetchForAccount(Account account, File dir) {
|
public boolean canPrefetchForAccount(final Account account, final File dir) {
|
||||||
// Check account, just in case
|
// Check account, just in case
|
||||||
if (account == null) return false;
|
if (account == null) return false;
|
||||||
|
|
||||||
// First, check preference and quickly return if prefetch isn't allowed
|
// First, check preference and quickly return if prefetch isn't allowed
|
||||||
if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) return false;
|
if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) return false;
|
||||||
|
|
||||||
long totalStorage = dir.getTotalSpace();
|
final long totalStorage = dir.getTotalSpace();
|
||||||
long usableStorage = dir.getUsableSpace();
|
final long usableStorage = dir.getUsableSpace();
|
||||||
long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE);
|
final long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE);
|
||||||
|
|
||||||
// If there's not enough overall storage available, stop now
|
// If there's not enough overall storage available, stop now
|
||||||
if (usableStorage < minAvailable) {
|
if (usableStorage < minAvailable) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts();
|
final int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts();
|
||||||
long perAccountMaxStorage =
|
// Calculate an even per-account storage although it would make a lot of sense to not
|
||||||
|
// do this as you may assign more storage to your corporate account rather than a personal
|
||||||
|
// account.
|
||||||
|
final long perAccountMaxStorage =
|
||||||
(long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts);
|
(long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts);
|
||||||
|
|
||||||
// Retrieve our idea of currently used attachment storage; since we don't track deletions,
|
// 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
|
// 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
|
// account, we walk the directory to determine the actual number.
|
||||||
Long accountStorage = mAttachmentStorageMap.get(account.mId);
|
Long accountStorage = mAttachmentStorageMap.get(account.mId);
|
||||||
if (accountStorage == null || (accountStorage > perAccountMaxStorage)) {
|
if (accountStorage == null || (accountStorage > perAccountMaxStorage)) {
|
||||||
// Calculate the exact figure for attachment storage for this account
|
// Calculate the exact figure for attachment storage for this account
|
||||||
@ -1136,22 +1041,24 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
accountStorage += file.length();
|
accountStorage += file.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cache the value
|
// Cache the value. No locking here since this is a concurrent collection object.
|
||||||
mAttachmentStorageMap.put(account.mId, accountStorage);
|
mAttachmentStorageMap.put(account.mId, accountStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if we're using less than the maximum per account
|
// Return true if we're using less than the maximum per account
|
||||||
if (accountStorage < perAccountMaxStorage) {
|
if (accountStorage >= perAccountMaxStorage) {
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
|
||||||
LogUtils.d(LOG_TAG, ">> Prefetch not allowed for account " + account.mId + "; used " +
|
LogUtils.d(LOG_TAG, ">> Prefetch not allowed for account " + account.mId +
|
||||||
accountStorage + ", limit " + perAccountMaxStorage);
|
"; used " + accountStorage + ", limit " + perAccountMaxStorage);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main routine for our AttachmentService service thread.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// These fields are only used within the service thread
|
// These fields are only used within the service thread
|
||||||
@ -1160,24 +1067,24 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
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
|
||||||
// the queue
|
// the queue. This is the case where a previous AttachmentService may have been notified
|
||||||
int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
|
// to stop before processing everything in its queue.
|
||||||
Cursor c = getContentResolver().query(Attachment.CONTENT_URI,
|
final int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
|
||||||
|
final Cursor c = getContentResolver().query(Attachment.CONTENT_URI,
|
||||||
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(LOG_TAG, "Count: " + c.getCount());
|
LogUtils.d(LOG_TAG, "Count: " + c.getCount());
|
||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
Attachment attachment = Attachment.restoreAttachmentWithId(
|
final Attachment attachment = Attachment.restoreAttachmentWithId(
|
||||||
this, c.getLong(EmailContent.ID_PROJECTION_COLUMN));
|
this, c.getLong(EmailContent.ID_PROJECTION_COLUMN));
|
||||||
if (attachment != null) {
|
if (attachment != null) {
|
||||||
mDownloadSet.onChange(this, attachment);
|
onChange(this, attachment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
c.close();
|
c.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1191,10 +1098,11 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
if (mStop) {
|
if (mStop) {
|
||||||
// We might be bailing out here due to the service shutting down
|
// We might be bailing out here due to the service shutting down
|
||||||
|
LogUtils.d(LOG_TAG, "*** AttachmentService has been instructed to stop");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
mDownloadSet.processQueue();
|
processQueue();
|
||||||
if (mDownloadSet.isEmpty()) {
|
if (mDownloadQueue.isEmpty()) {
|
||||||
LogUtils.d(LOG_TAG, "*** All done; shutting down service");
|
LogUtils.d(LOG_TAG, "*** All done; shutting down service");
|
||||||
stopSelf();
|
stopSelf();
|
||||||
break;
|
break;
|
||||||
@ -1217,32 +1125,37 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
||||||
if (sRunningService == null) {
|
if (sRunningService == null) {
|
||||||
sRunningService = this;
|
sRunningService = this;
|
||||||
}
|
}
|
||||||
if (intent != null && intent.hasExtra(EXTRA_ATTACHMENT)) {
|
if (intent != null && intent.hasExtra(EXTRA_ATTACHMENT)) {
|
||||||
Attachment att = intent.getParcelableExtra(EXTRA_ATTACHMENT);
|
Attachment att = intent.getParcelableExtra(EXTRA_ATTACHMENT);
|
||||||
onChange(att);
|
onChange(mContext, att);
|
||||||
|
} else {
|
||||||
|
LogUtils.wtf(LOG_TAG, "Received an invalid intent w/o EXTRA_ATTACHMENT");
|
||||||
}
|
}
|
||||||
return Service.START_STICKY;
|
return Service.START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
// Start up our service thread
|
// Start up our service thread.
|
||||||
new Thread(this, "AttachmentService").start();
|
new Thread(this, "AttachmentService").start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(final Intent intent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
// Mark this instance of the service as stopped
|
// Mark this instance of the service as stopped. Our main loop for the AttachmentService
|
||||||
|
// checks for this flag along with the AttachmentWatchdog.
|
||||||
mStop = true;
|
mStop = true;
|
||||||
if (sRunningService != null) {
|
if (sRunningService != null) {
|
||||||
|
// Kick it awake to get it to realize that we are stopping.
|
||||||
kick();
|
kick();
|
||||||
sRunningService = null;
|
sRunningService = null;
|
||||||
}
|
}
|
||||||
@ -1255,27 +1168,31 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
|
|
||||||
// For Debugging.
|
// For Debugging.
|
||||||
@Override
|
@Override
|
||||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
public void dump(final FileDescriptor fd, final PrintWriter pw, final String[] args) {
|
||||||
pw.println("AttachmentService");
|
pw.println("AttachmentService");
|
||||||
long time = System.currentTimeMillis();
|
final long time = System.currentTimeMillis();
|
||||||
synchronized(mDownloadSet) {
|
synchronized(mDownloadQueue) {
|
||||||
pw.println(" Queue, " + mDownloadSet.size() + " entries");
|
pw.println(" Queue, " + mDownloadQueue.getSize() + " entries");
|
||||||
Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator();
|
// If you iterate over the queue either via iterator or collection, they are not
|
||||||
// First, start up any required downloads, in priority order
|
// returned in any particular order. With all things being equal its better to go with
|
||||||
while (iterator.hasNext()) {
|
// a collection to avoid any potential ConcurrentModificationExceptions.
|
||||||
DownloadRequest req = iterator.next();
|
// If we really want this sorted, we can sort it manually since performance isn't a big
|
||||||
|
// concern with this debug method.
|
||||||
|
for (final DownloadRequest req : mDownloadQueue.mRequestMap.values()) {
|
||||||
pw.println(" Account: " + req.mAccountId + ", Attachment: " + req.mAttachmentId);
|
pw.println(" Account: " + req.mAccountId + ", Attachment: " + req.mAttachmentId);
|
||||||
pw.println(" Priority: " + req.mPriority + ", Time: " + req.mTime +
|
pw.println(" Priority: " + req.mPriority + ", Time: " + req.mTime +
|
||||||
(req.mInProgress ? " [In progress]" : ""));
|
(req.mInProgress ? " [In progress]" : ""));
|
||||||
Attachment att = Attachment.restoreAttachmentWithId(this, req.mAttachmentId);
|
final 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) {
|
||||||
String fileName = att.mFileName;
|
final String fileName = att.mFileName;
|
||||||
String suffix = "[none]";
|
final String suffix;
|
||||||
int lastDot = fileName.lastIndexOf('.');
|
final int lastDot = fileName.lastIndexOf('.');
|
||||||
if (lastDot >= 0) {
|
if (lastDot >= 0) {
|
||||||
suffix = fileName.substring(lastDot);
|
suffix = fileName.substring(lastDot);
|
||||||
|
} else {
|
||||||
|
suffix = "[none]";
|
||||||
}
|
}
|
||||||
pw.print(" Suffix: " + suffix);
|
pw.print(" Suffix: " + suffix);
|
||||||
if (att.getContentUri() != null) {
|
if (att.getContentUri() != null) {
|
||||||
@ -1305,10 +1222,10 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For Testing
|
// For Testing
|
||||||
/*package*/ AccountManagerStub mAccountManagerStub;
|
AccountManagerStub mAccountManagerStub;
|
||||||
private final HashMap<Long, Intent> mAccountServiceMap = new HashMap<Long, Intent>();
|
private final HashMap<Long, Intent> mAccountServiceMap = new HashMap<Long, Intent>();
|
||||||
|
|
||||||
/*package*/ void addServiceIntentForTest(long accountId, Intent intent) {
|
void addServiceIntentForTest(final long accountId, final Intent intent) {
|
||||||
mAccountServiceMap.put(accountId, intent);
|
mAccountServiceMap.put(accountId, intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1316,11 +1233,11 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
* We only use the getAccounts() call from AccountManager, so this class wraps that call and
|
* 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
|
* allows us to build a mock account manager stub in the unit tests
|
||||||
*/
|
*/
|
||||||
/*package*/ static class AccountManagerStub {
|
static class AccountManagerStub {
|
||||||
private int mNumberOfAccounts;
|
private int mNumberOfAccounts;
|
||||||
private final AccountManager mAccountManager;
|
private final AccountManager mAccountManager;
|
||||||
|
|
||||||
AccountManagerStub(Context context) {
|
AccountManagerStub(final Context context) {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
mAccountManager = AccountManager.get(context);
|
mAccountManager = AccountManager.get(context);
|
||||||
} else {
|
} else {
|
||||||
@ -1328,7 +1245,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ int getNumberOfAccounts() {
|
int getNumberOfAccounts() {
|
||||||
if (mAccountManager != null) {
|
if (mAccountManager != null) {
|
||||||
return mAccountManager.getAccounts().length;
|
return mAccountManager.getAccounts().length;
|
||||||
} else {
|
} else {
|
||||||
@ -1336,7 +1253,7 @@ public class AttachmentService extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ void setNumberOfAccounts(int numberOfAccounts) {
|
void setNumberOfAccounts(final int numberOfAccounts) {
|
||||||
mNumberOfAccounts = numberOfAccounts;
|
mNumberOfAccounts = numberOfAccounts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user