AttachmentDownloadService.java revision 81273dfcee3b075451860f60ee15f2aa06ba81ec
109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank/* 209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Copyright (C) 2010 The Android Open Source Project 309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * 409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Licensed under the Apache License, Version 2.0 (the "License"); 509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * you may not use this file except in compliance with the License. 609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * You may obtain a copy of the License at 709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * 809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * http://www.apache.org/licenses/LICENSE-2.0 909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * 1009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Unless required by applicable law or agreed to in writing, software 1109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * distributed under the License is distributed on an "AS IS" BASIS, 1209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * See the License for the specific language governing permissions and 1409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * limitations under the License. 1509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 1609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 1709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankpackage com.android.email.service; 1809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 19475c20d3a883737853aa7301cc649736a36387c5Marc Blankimport com.android.email.AttachmentInfo; 203a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blankimport com.android.email.Controller.ControllerService; 2109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.Email; 223ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blankimport com.android.email.EmailConnectivityManager; 23899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport com.android.email.NotificationController; 24a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent; 25a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Account; 26a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment; 27a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Message; 280d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport com.android.emailcommon.service.EmailServiceProxy; 290d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport com.android.emailcommon.service.EmailServiceStatus; 300d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport com.android.emailcommon.service.IEmailServiceCallback; 318a574694606f0e5d781334d0d426fc379c51f3edMarc Blankimport com.android.emailcommon.utility.AttachmentUtilities; 3231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.utility.Utility; 3309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 3475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blankimport android.accounts.AccountManager; 353bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport android.app.AlarmManager; 363bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport android.app.PendingIntent; 3709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.app.Service; 383bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport android.content.BroadcastReceiver; 3909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentValues; 4009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.Context; 4109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.Intent; 4209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.database.Cursor; 4375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blankimport android.net.Uri; 4409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.IBinder; 4509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.RemoteException; 4609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.text.format.DateUtils; 4709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.util.Log; 4809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 4909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.io.File; 50a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.FileDescriptor; 51a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.PrintWriter; 52f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Comparator; 5309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.util.HashMap; 54f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Iterator; 55f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.TreeSet; 563bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport java.util.concurrent.ConcurrentHashMap; 5709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 5809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankpublic class AttachmentDownloadService extends Service implements Runnable { 5909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static final String TAG = "AttachmentService"; 6009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 6109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Our idle time, waiting for notifications; this is something of a failsafe 6209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int PROCESS_QUEUE_WAIT_TIME = 30 * ((int)DateUtils.MINUTE_IN_MILLIS); 633bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // How often our watchdog checks for callback timeouts 643bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private static final int WATCHDOG_CHECK_INTERVAL = 15 * ((int)DateUtils.SECOND_IN_MILLIS); 653bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // How long we'll wait for a callback before canceling a download and retrying 663bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private static final int CALLBACK_TIMEOUT = 30 * ((int)DateUtils.SECOND_IN_MILLIS); 67819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Try to download an attachment in the background this many times before giving up 68819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy private static final int MAX_DOWNLOAD_RETRIES = 5; 69f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static final int PRIORITY_NONE = -1; 7009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @SuppressWarnings("unused") 71f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Low priority will be used for opportunistic downloads 72f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_BACKGROUND = 0; 73f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Normal priority is for forwarded downloads in outgoing mail 74f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_SEND_MAIL = 1; 75f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // High priority is for user requests 76f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_FOREGROUND = 2; 7709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 7875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Minimum free storage in order to perform prefetch (25% of total memory) 7975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F; 8075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Maximum prefetch storage (also 25% of total memory) 8175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private static final float PREFETCH_MAXIMUM_ATTACHMENT_STORAGE = 0.25F; 8275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 8309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // We can try various values here; I think 2 is completely reasonable as a first pass 8409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2; 8509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Limit on the number of simultaneous downloads per account 8609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Note that a limit of 1 is currently enforced by both Services (MailService and Controller) 8709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT = 1; 88475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Limit on the number of attachments we'll check for background download 89475c20d3a883737853aa7301cc649736a36387c5Marc Blank private static final int MAX_ATTACHMENTS_TO_CHECK = 25; 9075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 91ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // sRunningService is only set in the UI thread; it's visibility elsewhere is guaranteed 92ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // by the use of "volatile" 93ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank /*package*/ static volatile AttachmentDownloadService sRunningService = null; 9409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 95f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ Context mContext; 96ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank /*package*/ EmailConnectivityManager mConnectivityManager; 973ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank 98f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ final DownloadSet mDownloadSet = new DownloadSet(new DownloadComparator()); 993bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 1003a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private final HashMap<Long, Intent> mAccountServiceMap = new HashMap<Long, Intent>(); 10175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // A map of attachment storage used per account 10275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // NOTE: This map is not kept current in terms of deletions (i.e. it stores the last calculated 10375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // amount plus the size of any new attachments laoded). If and when we reach the per-account 10475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // limit, we recalculate the actual usage 10575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ final HashMap<Long, Long> mAttachmentStorageMap = new HashMap<Long, Long>(); 106819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // A map of attachment ids to the number of failed attempts to download the attachment 107819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // NOTE: We do not want to persist this. This allows us to retry background downloading 108819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // if any transient network errors are fixed & and the app is restarted 109819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy /* package */ final HashMap<Long, Integer> mAttachmentFailureMap = new HashMap<Long, Integer>(); 11009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final ServiceCallback mServiceCallback = new ServiceCallback(); 11175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 11209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final Object mLock = new Object(); 11309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private volatile boolean mStop = false; 11409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 11575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ AccountManagerStub mAccountManagerStub; 11675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 11775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /** 11875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * We only use the getAccounts() call from AccountManager, so this class wraps that call and 11975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * allows us to build a mock account manager stub in the unit tests 12075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank */ 12175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ static class AccountManagerStub { 12275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private int mNumberOfAccounts; 12375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private final AccountManager mAccountManager; 12475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 12575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank AccountManagerStub(Context context) { 12675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (context != null) { 12775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManager = AccountManager.get(context); 12875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 12975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManager = null; 13075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 13175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 13275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 13375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ int getNumberOfAccounts() { 13475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (mAccountManager != null) { 13575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return mAccountManager.getAccounts().length; 13675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 13775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return mNumberOfAccounts; 13875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 13975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 14075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 14175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ void setNumberOfAccounts(int numberOfAccounts) { 14275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mNumberOfAccounts = numberOfAccounts; 14375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 14475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 1453bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 1463bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /** 1473bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * Watchdog alarm receiver; responsible for making sure that downloads in progress are not 1483bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * stalled, as determined by the timing of the most recent service callback 1493bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank */ 1503bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public static class Watchdog extends BroadcastReceiver { 1513bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank @Override 1523bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public void onReceive(final Context context, Intent intent) { 1533bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank new Thread(new Runnable() { 1543bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public void run() { 1553bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank watchdogAlarm(); 1563bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1573bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank }, "AttachmentDownloadService Watchdog").start(); 1583bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1593bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1603bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 161f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public static class DownloadRequest { 162f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final int priority; 163f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long time; 164f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long attachmentId; 165f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long messageId; 166f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long accountId; 16709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean inProgress = false; 168a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastStatusCode; 169a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastProgress; 170a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long lastCallbackTime; 171a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long startTime; 17209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 17309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private DownloadRequest(Context context, Attachment attachment) { 17409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachmentId = attachment.mId; 17509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey); 17609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (msg != null) { 17709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank accountId = msg.mAccountKey; 17809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank messageId = msg.mId; 179f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 180f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank accountId = messageId = -1; 181f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 182f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank priority = getPriority(attachment); 183f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank time = System.currentTimeMillis(); 184f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 185f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 186f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 187f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int hashCode() { 188f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return (int)attachmentId; 189f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 190f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 191f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 192f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Two download requests are equals if their attachment id's are equals 193f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 194f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 195f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public boolean equals(Object object) { 196f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (!(object instanceof DownloadRequest)) return false; 197f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = (DownloadRequest)object; 198f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req.attachmentId == attachmentId; 199f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 200f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 201f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 202f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 203f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Comparator class for the download set; we first compare by priority. Requests with equal 204f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * priority are compared by the time the request was created (older requests come first) 205f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 206f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*protected*/ static class DownloadComparator implements Comparator<DownloadRequest> { 207f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 208f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int compare(DownloadRequest req1, DownloadRequest req2) { 209f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int res; 210f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.priority != req2.priority) { 211f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.priority < req2.priority) ? -1 : 1; 212f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 213f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.time == req2.time) { 214f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = 0; 215f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 216f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.time > req2.time) ? -1 : 1; 217f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 21809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 219f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return res; 22009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 22109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 22209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 22309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 224f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * The DownloadSet is a TreeSet sorted by priority class (e.g. low, high, etc.) and the 225f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * time of the request. Higher priority requests 22609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * are always processed first; among equals, the oldest request is processed first. The 22709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * priority key represents this ordering. Note: All methods that change the attachment map are 22809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * synchronized on the map itself 22909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 230f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ class DownloadSet extends TreeSet<DownloadRequest> { 23109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final long serialVersionUID = 1L; 2323bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private PendingIntent mWatchdogPendingIntent; 2333bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private AlarmManager mAlarmManager; 23409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 235f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ DownloadSet(Comparator<? super DownloadRequest> comparator) { 236f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank super(comparator); 237f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 23809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 23909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 240f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Maps attachment id to DownloadRequest 24109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 2423bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /*package*/ final ConcurrentHashMap<Long, DownloadRequest> mDownloadsInProgress = 2433bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank new ConcurrentHashMap<Long, DownloadRequest>(); 24409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 24509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 24609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * onChange is called by the AttachmentReceiver upon receipt of a valid notification from 24709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * EmailProvider that an attachment has been inserted or modified. It's not strictly 24809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * necessary that we detect a deleted attachment, as the code always checks for the 24909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * existence of an attachment before acting on it. 25009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 251ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank public synchronized void onChange(Context context, Attachment att) { 252f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = findDownloadRequest(att.mId); 253f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank long priority = getPriority(att); 254f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (priority == PRIORITY_NONE) { 25509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 25609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Attachment changed: " + att.mId); 25709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 25809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // In this case, there is no download priority for this attachment 259f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 260f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If it exists in the map, remove it 26109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // NOTE: We don't yet support deleting downloads in progress 26209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 26309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Attachment " + att.mId + " was in queue, removing"); 26409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 265f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank remove(req); 26609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 26709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } else { 26809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Ignore changes that occur during download 26909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (mDownloadsInProgress.containsKey(att.mId)) return; 270f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is new, add the request to the queue 27109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req == null) { 272ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank req = new DownloadRequest(context, att); 273f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank add(req); 27409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 27509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If the request already existed, we'll update the priority (so that the time is 27609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // up-to-date); otherwise, we create a new request 27709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 27809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Download queued for attachment " + att.mId + ", class " + 279f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank req.priority + ", priority time " + req.time); 28009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 28109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 28209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue if we're in a wait 28309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 28409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 28509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 28609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 287f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Find a queued DownloadRequest, given the attachment's id 28809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the id of the attachment 289f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the DownloadRequest for that attachment (or null, if none) 29009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 291f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized DownloadRequest findDownloadRequest(long id) { 292f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = iterator(); 293f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while(iterator.hasNext()) { 294f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 295f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req.attachmentId == id) { 296f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req; 29709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 29809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 299f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return null; 30009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 30109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 30209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 30309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing 30409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * the limit on maximum downloads 30509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 306f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void processQueue() { 30709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 308f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Log.d(TAG, "== Checking attachment queue, " + mDownloadSet.size() + " entries"); 30909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 3103ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank 311f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 31209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // First, start up any required downloads, in priority order 313f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while (iterator.hasNext() && 314f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) { 315f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 31675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Enforce per-account limit here 31775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (downloadsForAccount(req.accountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) { 31875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (Email.DEBUG) { 31975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, "== Skip #" + req.attachmentId + "; maxed for acct #" + 32075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank req.accountId); 32175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 32275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank continue; 32375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 32475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 32509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (!req.inProgress) { 326f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.tryStartDownload(req); 32709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 32809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 3297fbcefff7d5a745335d1ec562e783a59087cc0b1Marc Blank 3303ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank // Don't prefetch if background downloading is disallowed 3313ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank if (!mConnectivityManager.isBackgroundDataAllowed()) return; 33209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Then, try opportunistic download of appropriate attachments 33309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size(); 33475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Always leave one slot for user requested download 33575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (backgroundDownloads > (MAX_SIMULTANEOUS_DOWNLOADS - 1)) { 336475c20d3a883737853aa7301cc649736a36387c5Marc Blank // We'll load up the newest 25 attachments that aren't loaded or queued 337475c20d3a883737853aa7301cc649736a36387c5Marc Blank Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI, 338475c20d3a883737853aa7301cc649736a36387c5Marc Blank MAX_ATTACHMENTS_TO_CHECK); 339475c20d3a883737853aa7301cc649736a36387c5Marc Blank Cursor c = mContext.getContentResolver().query(lookupUri, AttachmentInfo.PROJECTION, 340751e4b2d9509b63bcdd3c8780afa82b67b99d7d4Todd Kennedy EmailContent.Attachment.EMPTY_URI_INBOX_SELECTION, 341475c20d3a883737853aa7301cc649736a36387c5Marc Blank null, Attachment.RECORD_ID + " DESC"); 342475c20d3a883737853aa7301cc649736a36387c5Marc Blank File cacheDir = mContext.getCacheDir(); 343475c20d3a883737853aa7301cc649736a36387c5Marc Blank try { 344475c20d3a883737853aa7301cc649736a36387c5Marc Blank while (c.moveToNext()) { 345475c20d3a883737853aa7301cc649736a36387c5Marc Blank long accountKey = c.getLong(AttachmentInfo.COLUMN_ACCOUNT_KEY); 346475c20d3a883737853aa7301cc649736a36387c5Marc Blank long id = c.getLong(AttachmentInfo.COLUMN_ID); 3473a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Account account = Account.restoreAccountWithId(mContext, accountKey); 3483a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (account == null) { 349475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Clean up this orphaned attachment; there's no point in keeping it 350475c20d3a883737853aa7301cc649736a36387c5Marc Blank // around; then try to find another one 351475c20d3a883737853aa7301cc649736a36387c5Marc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, id); 3523a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } else if (canPrefetchForAccount(account, cacheDir)) { 353475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Check that the attachment meets system requirements for download 354475c20d3a883737853aa7301cc649736a36387c5Marc Blank AttachmentInfo info = new AttachmentInfo(mContext, c); 355475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (info.isEligibleForDownload()) { 356475c20d3a883737853aa7301cc649736a36387c5Marc Blank Attachment att = Attachment.restoreAttachmentWithId(mContext, id); 357475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (att != null) { 358819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy Integer tryCount; 359819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy tryCount = mAttachmentFailureMap.get(att.mId); 360819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (tryCount != null && tryCount > MAX_DOWNLOAD_RETRIES) { 361819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // move onto the next attachment 362819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy continue; 363819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 364475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Start this download and we're done 365475c20d3a883737853aa7301cc649736a36387c5Marc Blank DownloadRequest req = new DownloadRequest(mContext, att); 366475c20d3a883737853aa7301cc649736a36387c5Marc Blank mDownloadSet.tryStartDownload(req); 367475c20d3a883737853aa7301cc649736a36387c5Marc Blank break; 368475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 369475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 370475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 37175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 372475c20d3a883737853aa7301cc649736a36387c5Marc Blank } finally { 373475c20d3a883737853aa7301cc649736a36387c5Marc Blank c.close(); 37409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 37509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 37609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 37709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 37809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 37909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Count the number of running downloads in progress for this account 38009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param accountId the id of the account 38109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return the count of running downloads 38209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 383f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized int downloadsForAccount(long accountId) { 38409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int count = 0; 38509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank for (DownloadRequest req: mDownloadsInProgress.values()) { 38609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req.accountId == accountId) { 38709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank count++; 38809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 38909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 39009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return count; 39109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 39209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 3933bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private void onWatchdogAlarm() { 3943bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank long now = System.currentTimeMillis(); 3953bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank for (DownloadRequest req: mDownloadsInProgress.values()) { 3963bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Check how long it's been since receiving a callback 3973bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank long timeSinceCallback = now - req.lastCallbackTime; 3983bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (timeSinceCallback > CALLBACK_TIMEOUT) { 3993bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (Email.DEBUG) { 40075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, "== Download of " + req.attachmentId + " timed out"); 4013bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 40269fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank cancelDownload(req); 4033bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4043bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4053bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // If there are downloads in progress, reset alarm 4063bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (mDownloadsInProgress.isEmpty()) { 4073bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (mAlarmManager != null && mWatchdogPendingIntent != null) { 4083bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mAlarmManager.cancel(mWatchdogPendingIntent); 4093bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4103bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4113bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Check whether we can start new downloads... 41281273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank if (mConnectivityManager.hasConnectivity()) { 41381273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank processQueue(); 41481273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank } 4153bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4163bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 4173bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /** 4183a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * Attempt to execute the DownloadRequest, enforcing the maximum downloads per account 4193a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * parameter 4203a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @param req the DownloadRequest 4213a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @return whether or not the download was started 4223a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank */ 4233a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /*package*/ synchronized boolean tryStartDownload(DownloadRequest req) { 4243a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Intent intent = getServiceIntentForAccount(req.accountId); 4253a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (intent == null) return false; 4263a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4273a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // Do not download the same attachment multiple times 4283a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank boolean alreadyInProgress = mDownloadsInProgress.get(req.attachmentId) != null; 4293a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (alreadyInProgress) return false; 4303a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4313a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank try { 4323a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (Email.DEBUG) { 4333a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Log.d(TAG, ">> Starting download for attachment #" + req.attachmentId); 4343a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4353a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank startDownload(intent, req); 4363a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } catch (RemoteException e) { 4373a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // TODO: Consider whether we need to do more in this case... 4383a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // For now, fix up our data to reflect the failure 4393a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank cancelDownload(req); 4403a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4413a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return true; 4423a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4433a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4443a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private synchronized DownloadRequest getDownloadInProgress(long attachmentId) { 4453a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return mDownloadsInProgress.get(attachmentId); 4463a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4473a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4483a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /** 4493bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * Do the work of starting an attachment download using the EmailService interface, and 4503bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * set our watchdog alarm 4513bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * 4523bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @param serviceClass the class that will attempt the download 4533bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @param req the DownloadRequest 4543bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @throws RemoteException 4553bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank */ 4563a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private void startDownload(Intent intent, DownloadRequest req) 4573bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank throws RemoteException { 4583bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank req.startTime = System.currentTimeMillis(); 4593bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank req.inProgress = true; 4603bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mDownloadsInProgress.put(req.attachmentId, req); 4613bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank EmailServiceProxy proxy = 4623a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank new EmailServiceProxy(mContext, intent, mServiceCallback); 463dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank proxy.loadAttachment(req.attachmentId, req.priority != PRIORITY_FOREGROUND); 4643bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Lazily initialize our (reusable) pending intent 4653bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (mWatchdogPendingIntent == null) { 466ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy createWatchdogPendingIntent(mContext); 4673bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4683bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Set the alarm 4693bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, 4703bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank System.currentTimeMillis() + WATCHDOG_CHECK_INTERVAL, WATCHDOG_CHECK_INTERVAL, 4713bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mWatchdogPendingIntent); 4723bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 47381273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank 474ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy /*package*/ void createWatchdogPendingIntent(Context context) { 475ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy Intent alarmIntent = new Intent(context, Watchdog.class); 476ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy mWatchdogPendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0); 477ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy mAlarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 478ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy } 47969fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank private void cancelDownload(DownloadRequest req) { 48069fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank mDownloadsInProgress.remove(req.attachmentId); 48169fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank req.inProgress = false; 48269fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank } 48369fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank 48409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 48509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called when a download is finished; we get notified of this via our EmailServiceCallback 48609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the attachment whose download is finished 48709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param statusCode the EmailServiceStatus code returned by the Service 48809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 489f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void endDownload(long attachmentId, int statusCode) { 49009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Say we're no longer downloading this 49109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mDownloadsInProgress.remove(attachmentId); 492819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 493819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // TODO: This code is conservative and treats connection issues as failures. 494819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Since we have no mechanism to throttle reconnection attempts, it makes 495819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // sense to be cautious here. Once logic is in place to prevent connecting 496819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // in a tight loop, we can exclude counting connection issues as "failures". 497819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 498819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Update the attachment failure list if needed 499819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy Integer downloadCount; 500819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount = mAttachmentFailureMap.remove(attachmentId); 501819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (statusCode != EmailServiceStatus.SUCCESS) { 502819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (downloadCount == null) { 503819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount = 0; 504819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 505819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount += 1; 506819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy mAttachmentFailureMap.put(attachmentId, downloadCount); 507819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 508819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 509f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 51009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.CONNECTION_ERROR) { 51109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If this needs to be retried, just process the queue again 51209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 51309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== The download for attachment #" + attachmentId + 51409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank " will be retried"); 51509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 51609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req != null) { 51709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req.inProgress = false; 51809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 51909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 52009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return; 52109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 522b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 523782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank // If the request is still in the queue, remove it 524782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank if (req != null) { 525782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank remove(req); 526782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank } 52709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 528b961c78ff4bd250d4a25497158162cb230a55057Marc Blank long secs = 0; 529b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if (req != null) { 530b961c78ff4bd250d4a25497158162cb230a55057Marc Blank secs = (System.currentTimeMillis() - req.time) / 1000; 531b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 53209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" : 53309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank "Error " + statusCode; 53409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "<< Download finished for attachment #" + attachmentId + "; " + secs + 53509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank " seconds from request, status: " + status); 53609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 537b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 53809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); 53909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 54075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long accountId = attachment.mAccountKey; 54175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Update our attachment storage for this account 54275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Long currentStorage = mAttachmentStorageMap.get(accountId); 54375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (currentStorage == null) { 54475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank currentStorage = 0L; 54575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 54675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAttachmentStorageMap.put(accountId, currentStorage + attachment.mSize); 54709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean deleted = false; 54809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if ((attachment.mFlags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 54909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.ATTACHMENT_NOT_FOUND) { 550f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is a forwarding download, and the attachment doesn't exist (or 55109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // can't be downloaded) delete it from the outgoing message, lest that 55209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // message never get sent 55309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId); 55409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // TODO: Talk to UX about whether this is even worth doing 555d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank NotificationController nc = NotificationController.getInstance(mContext); 556d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank nc.showDownloadForwardFailedNotification(attachment); 55709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank deleted = true; 55809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 55909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If we're an attachment on forwarded mail, and if we're not still blocked, 56009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // try to send pending mail now (as mediated by MailService) 56136cc9c18edb7ee897d0d758788b6e338cb2ef126Makoto Onuki if ((req != null) && 562b961c78ff4bd250d4a25497158162cb230a55057Marc Blank !Utility.hasUnloadedAttachments(mContext, attachment.mMessageKey)) { 56309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 56409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Downloads finished for outgoing msg #" + req.messageId); 56509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 56609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank MailService.actionSendPendingMail(mContext, req.accountId); 56709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 56809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 5692ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank if (statusCode == EmailServiceStatus.MESSAGE_NOT_FOUND) { 5702ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank // If there's no associated message, delete the attachment 5712ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId); 5722ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank } else if (!deleted) { 57309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Clear the download flags, since we're done for now. Note that this happens 57409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // only for non-recoverable errors. When these occur for forwarded mail, we can 57509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // ignore it and continue; otherwise, it was either 1) a user request, in which 57609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the user can retry manually or 2) an opportunistic download, in which 57709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the download wasn't critical 57809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank ContentValues cv = new ContentValues(); 57909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int flags = 58009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 58109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank cv.put(Attachment.FLAGS, attachment.mFlags &= ~flags); 58209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.update(mContext, cv); 58309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue 58609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 58709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 590f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 591f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Calculate the download priority of an Attachment. A priority of zero means that the 592f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * attachment is not marked for download. 593f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @param att the Attachment 594f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the priority key of the Attachment 595f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 596f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static int getPriority(Attachment att) { 597f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int priorityClass = PRIORITY_NONE; 598f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int flags = att.mFlags; 599f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 600f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy priorityClass = PRIORITY_SEND_MAIL; 601f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) { 602f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy priorityClass = PRIORITY_FOREGROUND; 603f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 604f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return priorityClass; 60509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 60609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 60709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private void kick() { 60809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 60909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.notify(); 61009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 61309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 61409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * We use an EmailServiceCallback to keep track of the progress of downloads. These callbacks 61564b64cca01c1a827c1b3824f099fd638cfb15826Marc Blank * come from either Controller (IMAP) or ExchangeService (EAS). Note that we only implement the 61609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * single callback that's defined by the EmailServiceCallback interface. 61709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 61809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private class ServiceCallback extends IEmailServiceCallback.Stub { 61909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, 62009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int progress) { 621a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // Record status and progress 62275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank DownloadRequest req = mDownloadSet.getDownloadInProgress(attachmentId); 623a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req != null) { 62475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (Email.DEBUG) { 62575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank String code; 62675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank switch(statusCode) { 62775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank case EmailServiceStatus.SUCCESS: code = "Success"; break; 62875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank case EmailServiceStatus.IN_PROGRESS: code = "In progress"; break; 62975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank default: code = Integer.toString(statusCode); break; 63075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 63175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (statusCode != EmailServiceStatus.IN_PROGRESS) { 63275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, ">> Attachment " + attachmentId + ": " + code); 63375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else if (progress >= (req.lastProgress + 15)) { 63475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, ">> Attachment " + attachmentId + ": " + progress + "%"); 63575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 63675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 637a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastStatusCode = statusCode; 638a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress = progress; 639a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime = System.currentTimeMillis(); 640a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 64109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank switch (statusCode) { 64209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank case EmailServiceStatus.IN_PROGRESS: 64309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 64409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank default: 645f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.endDownload(attachmentId, statusCode); 64609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 64709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 64809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 64909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 65009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 65109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void sendMessageStatus(long messageId, String subject, int statusCode, int progress) 65209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 65309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 65409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 65509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 65609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void syncMailboxListStatus(long accountId, int statusCode, int progress) 65709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 65809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 65909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 66009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 66109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void syncMailboxStatus(long mailboxId, int statusCode, int progress) 66209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 66309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 66409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 66509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 66609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 6673a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * Return an Intent to be used used based on the account type of the provided account id. We 66809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * cache the results to avoid repeated database access 66909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param accountId the id of the account 6703a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @return the Intent to be used for the account or null (if the account no longer exists) 67109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 6723a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private synchronized Intent getServiceIntentForAccount(long accountId) { 6733a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // TODO: We should have some more data-driven way of determining the service intent. 6743a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Intent serviceIntent = mAccountServiceMap.get(accountId); 6753a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (serviceIntent == null) { 67609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String protocol = Account.getProtocol(mContext, accountId); 677edb05ca5ee21a9f410416b427141be59be01f5d2Marc Blank if (protocol == null) return null; 6783a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank serviceIntent = new Intent(mContext, ControllerService.class); 67909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (protocol.equals("eas")) { 6803a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank serviceIntent = new Intent(EmailServiceProxy.EXCHANGE_INTENT); 68109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 6823a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAccountServiceMap.put(accountId, serviceIntent); 68309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 6843a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return serviceIntent; 68509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 68609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 6873a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /*package*/ void addServiceIntentForTest(long accountId, Intent intent) { 6883a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAccountServiceMap.put(accountId, intent); 68909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 69009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 691f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ void onChange(Attachment att) { 692ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mDownloadSet.onChange(this, att); 69309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 69409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 695f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean isQueued(long attachmentId) { 696f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return mDownloadSet.findDownloadRequest(attachmentId) != null; 697f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 698f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 699fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /*package*/ int getSize() { 700fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return mDownloadSet.size(); 701fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 702fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 703f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean dequeue(long attachmentId) { 704f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 705f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 706b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if (Email.DEBUG) { 707b961c78ff4bd250d4a25497158162cb230a55057Marc Blank Log.d(TAG, "Dequeued attachmentId: " + attachmentId); 708b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 709f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.remove(req); 710f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return true; 711f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 712f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return false; 71309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 71409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 71509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 716fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * Ask the service for the number of items in the download queue 717fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * @return the number of items queued for download 718fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank */ 719fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank public static int getQueueSize() { 720ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 721ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 722ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.getSize(); 723fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 724fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return 0; 725fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 726fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 727fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /** 72809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service whether a particular attachment is queued for download 72909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 73009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment is queued for download 73109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 73209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean isAttachmentQueued(long attachmentId) { 733ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 734ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 735ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.isQueued(attachmentId); 73609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 73709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 73809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 73909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 74009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 74109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service to remove an attachment from the download queue 74209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 74309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment was removed from the queue 74409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 74509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean cancelQueuedAttachment(long attachmentId) { 746ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 747ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 748ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.dequeue(attachmentId); 74909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 75009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 75109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 75209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 7533bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public static void watchdogAlarm() { 754ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 755ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 756ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank service.mDownloadSet.onWatchdogAlarm(); 7573bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 7583bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 7593bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 76009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 76109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called directly by EmailProvider whenever an attachment is inserted or changed 76209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the attachment's id 76309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param flags the new flags for the attachment 76409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 76509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static void attachmentChanged(final long id, final int flags) { 766ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank final AttachmentDownloadService service = sRunningService; 767ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service == null) return; 76809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Utility.runAsync(new Runnable() { 76909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 77009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank final Attachment attachment = 771ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank Attachment.restoreAttachmentWithId(service, id); 77209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 77309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Store the flags we got from EmailProvider; given that all of this 77409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // activity is asynchronous, we need to use the newest data from 77509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // EmailProvider 77609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.mFlags = flags; 777ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank service.onChange(attachment); 77809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 77909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank }}); 78009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 78109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 78275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /** 78375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * Determine whether an attachment can be prefetched for the given account 78475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * @return true if download is allowed, false otherwise 78575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank */ 7863a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank public boolean canPrefetchForAccount(Account account, File dir) { 787475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Check account, just in case 788475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (account == null) return false; 789475c20d3a883737853aa7301cc649736a36387c5Marc Blank // First, check preference and quickly return if prefetch isn't allowed 790475c20d3a883737853aa7301cc649736a36387c5Marc Blank if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) return false; 791475c20d3a883737853aa7301cc649736a36387c5Marc Blank 79275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long totalStorage = dir.getTotalSpace(); 79375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long usableStorage = dir.getUsableSpace(); 79475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE); 79575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 79675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // If there's not enough overall storage available, stop now 79775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (usableStorage < minAvailable) { 79875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return false; 79975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 80075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 80175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts(); 80275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long perAccountMaxStorage = 80375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank (long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts); 80475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 80575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Retrieve our idea of currently used attachment storage; since we don't track deletions, 80675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // this number is the "worst case". If the number is greater than what's allowed per 80775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // account, we walk the directory to determine the actual number 8083a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Long accountStorage = mAttachmentStorageMap.get(account.mId); 80975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (accountStorage == null || (accountStorage > perAccountMaxStorage)) { 81075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Calculate the exact figure for attachment storage for this account 81175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage = 0L; 81275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank File[] files = dir.listFiles(); 81375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (files != null) { 81475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank for (File file : files) { 81575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage += file.length(); 81675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 81775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 81875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Cache the value 8193a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAttachmentStorageMap.put(account.mId, accountStorage); 82075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 82175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 82275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Return true if we're using less than the maximum per account 82375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (accountStorage < perAccountMaxStorage) { 82475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return true; 82575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 82675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (Email.DEBUG) { 8273a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Log.d(TAG, ">> Prefetch not allowed for account " + account.mId + "; used " + 82875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage + ", limit " + perAccountMaxStorage); 82975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 83075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return false; 83175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 83275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 83375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 83409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 835ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // These fields are only used within the service thread 83609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mContext = this; 837ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mConnectivityManager = new EmailConnectivityManager(this, TAG); 83875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManagerStub = new AccountManagerStub(this); 8397fbcefff7d5a745335d1ec562e783a59087cc0b1Marc Blank 84009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Run through all attachments in the database that require download and add them to 84109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // the queue 84209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 84309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Cursor c = getContentResolver().query(Attachment.CONTENT_URI, 84409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.ID_PROJECTION, "(" + Attachment.FLAGS + " & ?) != 0", 84509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new String[] {Integer.toString(mask)}, null); 84609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 84709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "Count: " + c.getCount()); 84809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (c.moveToNext()) { 84909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId( 85009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank this, c.getLong(EmailContent.ID_PROJECTION_COLUMN)); 85109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 852ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mDownloadSet.onChange(this, attachment); 85309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 85409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 85509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (Exception e) { 85609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank e.printStackTrace(); 85709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 85809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank finally { 85909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank c.close(); 86009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 86109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 86209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Loop until stopped, with a 30 minute wait loop 86309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (!mStop) { 86409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Here's where we run our attachment loading logic... 86581273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank mConnectivityManager.waitForConnectivity(); 866f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.processQueue(); 86709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 86809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 86909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.wait(PROCESS_QUEUE_WAIT_TIME); 87009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (InterruptedException e) { 87109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // That's ok; we'll just keep looping 87209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 87309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 87409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 875ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank 876ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // Unregister now that we're done 877ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mConnectivityManager.unregister(); 87809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 87909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 88009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 88109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public int onStartCommand(Intent intent, int flags, int startId) { 88209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank sRunningService = this; 88309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return Service.START_STICKY; 88409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 88509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 88609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 88709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * The lifecycle of this service is managed by Email.setServicesEnabled(), which is called 88809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * throughout the code, in particular 1) after boot and 2) after accounts are added or removed 88909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * The goal is that this service should be running at all times when there's at least one 89009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * email account present. 89109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 89209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 89309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onCreate() { 89409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Start up our service thread 89509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new Thread(this, "AttachmentDownloadService").start(); 89609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 89709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 89809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public IBinder onBind(Intent intent) { 89909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return null; 90009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 90109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 90209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 90309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onDestroy() { 90409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "**** ON DESTROY!"); 90509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (sRunningService != null) { 90609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mStop = true; 90709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 90809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 90909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank sRunningService = null; 91009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 911a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank 912a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank @Override 913a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 914a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println("AttachmentDownloadService"); 915a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long time = System.currentTimeMillis(); 916a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank synchronized(mDownloadSet) { 917a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Queue, " + mDownloadSet.size() + " entries"); 918a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 919a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // First, start up any required downloads, in priority order 920a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank while (iterator.hasNext()) { 921a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank DownloadRequest req = iterator.next(); 922a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Account: " + req.accountId + ", Attachment: " + req.attachmentId); 923a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Priority: " + req.priority + ", Time: " + req.time + 924a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank (req.inProgress ? " [In progress]" : "")); 925ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank Attachment att = Attachment.restoreAttachmentWithId(this, req.attachmentId); 926a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att == null) { 927a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Attachment not in database?"); 928eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank } else if (att.mFileName != null) { 929a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String fileName = att.mFileName; 930a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String suffix = "[none]"; 931a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastDot = fileName.lastIndexOf('.'); 932a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (lastDot >= 0) { 933a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank suffix = fileName.substring(lastDot); 934a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 935a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Suffix: " + suffix); 936a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att.mContentUri != null) { 937a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" ContentUri: " + att.mContentUri); 938a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 939a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Mime: "); 940a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att.mMimeType != null) { 941a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(att.mMimeType); 942a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } else { 9438a574694606f0e5d781334d0d426fc379c51f3edMarc Blank pw.print(AttachmentUtilities.inferMimeType(fileName, null)); 944eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank pw.print(" [inferred]"); 945a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 946a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Size: " + att.mSize); 947a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 948a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.inProgress) { 949a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Status: " + req.lastStatusCode + ", Progress: " + 950a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress); 951a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Started: " + req.startTime + ", Callback: " + 952a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime); 953a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Elapsed: " + ((time - req.startTime) / 1000L) + "s"); 954a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.lastCallbackTime > 0) { 955a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" CB: " + ((time - req.lastCallbackTime) / 1000L) + "s"); 956a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 957a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 958a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 959a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 960a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 96109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank} 962