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 1975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blankimport android.accounts.AccountManager; 203bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport android.app.AlarmManager; 213bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport android.app.PendingIntent; 2209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.app.Service; 233bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport android.content.BroadcastReceiver; 2409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentValues; 2509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.Context; 2609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.Intent; 2709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.database.Cursor; 28973702b30e8c2fb2f622f4ef37b42b3bdbd3ef17Marc Blankimport android.net.ConnectivityManager; 2975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blankimport android.net.Uri; 3009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.IBinder; 3109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.RemoteException; 327c03217316b21d59a00cf1abed093470e8004144Martin Hibdonimport android.os.SystemClock; 3309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.text.format.DateUtils; 3409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 35b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.AttachmentInfo; 36b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.EmailConnectivityManager; 37b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.NotificationController; 38f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.email2.ui.MailActivityEmail; 39b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.provider.Account; 40b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.provider.EmailContent; 41b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.provider.EmailContent.Attachment; 42f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.emailcommon.provider.EmailContent.AttachmentColumns; 43b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.provider.EmailContent.Message; 44b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.service.EmailServiceProxy; 45b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.service.EmailServiceStatus; 46b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.service.IEmailServiceCallback; 47b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.utility.AttachmentUtilities; 48b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.utility.Utility; 498f474dc02f01d7805716422625ef7b50fa0e4aefMark Weiimport com.android.mail.providers.UIProvider.AttachmentState; 50560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils; 51b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank 5209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.io.File; 53a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.FileDescriptor; 54a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.PrintWriter; 55f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Comparator; 5609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.util.HashMap; 57f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Iterator; 58f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.TreeSet; 593bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport java.util.concurrent.ConcurrentHashMap; 6009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 6109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankpublic class AttachmentDownloadService extends Service implements Runnable { 625ed194434f875df551ebcaf673f5b9e8c385d652Martin Hibdon public static final String TAG = LogUtils.TAG; 6309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 647c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // Minimum wait time before retrying a download that failed due to connection error 657c03217316b21d59a00cf1abed093470e8004144Martin Hibdon private static final long CONNECTION_ERROR_RETRY_MILLIS = 10 * DateUtils.SECOND_IN_MILLIS; 667c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // Number of retries before we start delaying between 677c03217316b21d59a00cf1abed093470e8004144Martin Hibdon private static final long CONNECTION_ERROR_DELAY_THRESHOLD = 5; 687c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // Maximum time to retry for connection errors. 697c03217316b21d59a00cf1abed093470e8004144Martin Hibdon private static final long CONNECTION_ERROR_MAX_RETRIES = 10; 707c03217316b21d59a00cf1abed093470e8004144Martin Hibdon 7109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Our idle time, waiting for notifications; this is something of a failsafe 7209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int PROCESS_QUEUE_WAIT_TIME = 30 * ((int)DateUtils.MINUTE_IN_MILLIS); 733bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // How often our watchdog checks for callback timeouts 74f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank private static final int WATCHDOG_CHECK_INTERVAL = 20 * ((int)DateUtils.SECOND_IN_MILLIS); 753bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // How long we'll wait for a callback before canceling a download and retrying 763bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private static final int CALLBACK_TIMEOUT = 30 * ((int)DateUtils.SECOND_IN_MILLIS); 77819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Try to download an attachment in the background this many times before giving up 78819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy private static final int MAX_DOWNLOAD_RETRIES = 5; 79f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static final int PRIORITY_NONE = -1; 8009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @SuppressWarnings("unused") 81f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Low priority will be used for opportunistic downloads 82f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_BACKGROUND = 0; 83f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Normal priority is for forwarded downloads in outgoing mail 84f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_SEND_MAIL = 1; 85f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // High priority is for user requests 86f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_FOREGROUND = 2; 8709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 8875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Minimum free storage in order to perform prefetch (25% of total memory) 8975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F; 9075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Maximum prefetch storage (also 25% of total memory) 9175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private static final float PREFETCH_MAXIMUM_ATTACHMENT_STORAGE = 0.25F; 9275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 9309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // We can try various values here; I think 2 is completely reasonable as a first pass 9409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2; 9509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Limit on the number of simultaneous downloads per account 9609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Note that a limit of 1 is currently enforced by both Services (MailService and Controller) 9709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT = 1; 98475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Limit on the number of attachments we'll check for background download 99475c20d3a883737853aa7301cc649736a36387c5Marc Blank private static final int MAX_ATTACHMENTS_TO_CHECK = 25; 10075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 101b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank private static final String EXTRA_ATTACHMENT = 102b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank "com.android.email.AttachmentDownloadService.attachment"; 103b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank 104ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // sRunningService is only set in the UI thread; it's visibility elsewhere is guaranteed 105ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // by the use of "volatile" 106ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank /*package*/ static volatile AttachmentDownloadService sRunningService = null; 10709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 108f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ Context mContext; 109ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank /*package*/ EmailConnectivityManager mConnectivityManager; 1103ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank 111f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ final DownloadSet mDownloadSet = new DownloadSet(new DownloadComparator()); 1123bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 1133a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private final HashMap<Long, Intent> mAccountServiceMap = new HashMap<Long, Intent>(); 11475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // A map of attachment storage used per account 11575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // NOTE: This map is not kept current in terms of deletions (i.e. it stores the last calculated 11675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // amount plus the size of any new attachments laoded). If and when we reach the per-account 11775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // limit, we recalculate the actual usage 11875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ final HashMap<Long, Long> mAttachmentStorageMap = new HashMap<Long, Long>(); 119819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // A map of attachment ids to the number of failed attempts to download the attachment 120819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // NOTE: We do not want to persist this. This allows us to retry background downloading 121819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // if any transient network errors are fixed & and the app is restarted 122819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy /* package */ final HashMap<Long, Integer> mAttachmentFailureMap = new HashMap<Long, Integer>(); 12309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final ServiceCallback mServiceCallback = new ServiceCallback(); 12475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 12509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final Object mLock = new Object(); 12609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private volatile boolean mStop = false; 12709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 12875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ AccountManagerStub mAccountManagerStub; 12975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 13075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /** 13175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * We only use the getAccounts() call from AccountManager, so this class wraps that call and 13275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * allows us to build a mock account manager stub in the unit tests 13375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank */ 13475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ static class AccountManagerStub { 13575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private int mNumberOfAccounts; 13675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private final AccountManager mAccountManager; 13775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 13875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank AccountManagerStub(Context context) { 13975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (context != null) { 14075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManager = AccountManager.get(context); 14175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 14275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManager = null; 14375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 14475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 14575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 14675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ int getNumberOfAccounts() { 14775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (mAccountManager != null) { 14875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return mAccountManager.getAccounts().length; 14975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 15075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return mNumberOfAccounts; 15175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 15275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 15375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 15475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ void setNumberOfAccounts(int numberOfAccounts) { 15575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mNumberOfAccounts = numberOfAccounts; 15675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 15775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 1583bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 1593bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /** 1603bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * Watchdog alarm receiver; responsible for making sure that downloads in progress are not 1613bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * stalled, as determined by the timing of the most recent service callback 1623bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank */ 1633bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public static class Watchdog extends BroadcastReceiver { 1643bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank @Override 1653bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public void onReceive(final Context context, Intent intent) { 1663bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank new Thread(new Runnable() { 167f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank @Override 1683bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public void run() { 1693bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank watchdogAlarm(); 1703bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1713bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank }, "AttachmentDownloadService Watchdog").start(); 1723bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1733bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1743bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 175f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public static class DownloadRequest { 176f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final int priority; 177f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long time; 178f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long attachmentId; 179f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long messageId; 180f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long accountId; 18109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean inProgress = false; 182a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastStatusCode; 183a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastProgress; 184a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long lastCallbackTime; 185a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long startTime; 1867c03217316b21d59a00cf1abed093470e8004144Martin Hibdon long retryCount; 1877c03217316b21d59a00cf1abed093470e8004144Martin Hibdon long retryStartTime; 18809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 18909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private DownloadRequest(Context context, Attachment attachment) { 19009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachmentId = attachment.mId; 19109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey); 19209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (msg != null) { 19309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank accountId = msg.mAccountKey; 19409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank messageId = msg.mId; 195f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 196f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank accountId = messageId = -1; 197f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 198f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank priority = getPriority(attachment); 199272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon time = SystemClock.elapsedRealtime(); 200f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 201f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 202272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon private DownloadRequest(DownloadRequest orig, long newTime) { 203272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon priority = orig.priority; 204272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon attachmentId = orig.attachmentId; 205272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon messageId = orig.messageId; 206272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon accountId = orig.accountId; 207272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon time = newTime; 208272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon inProgress = orig.inProgress; 209272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon lastStatusCode = orig.lastStatusCode; 210272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon lastProgress = orig.lastProgress; 211272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon lastCallbackTime = orig.lastCallbackTime; 212272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon startTime = orig.startTime; 213272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon retryCount = orig.retryCount; 214272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon retryStartTime = orig.retryStartTime; 215272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon } 216272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon 217272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon 218f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 219f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int hashCode() { 220f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return (int)attachmentId; 221f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 222f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 223f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 224f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Two download requests are equals if their attachment id's are equals 225f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 226f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 227f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public boolean equals(Object object) { 228f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (!(object instanceof DownloadRequest)) return false; 229f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = (DownloadRequest)object; 230f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req.attachmentId == attachmentId; 231f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 232f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 233f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 234f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 235f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Comparator class for the download set; we first compare by priority. Requests with equal 236f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * priority are compared by the time the request was created (older requests come first) 237f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 238f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*protected*/ static class DownloadComparator implements Comparator<DownloadRequest> { 239f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 240f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int compare(DownloadRequest req1, DownloadRequest req2) { 241f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int res; 242f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.priority != req2.priority) { 243f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.priority < req2.priority) ? -1 : 1; 244f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 245f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.time == req2.time) { 246f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = 0; 247f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 248f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.time > req2.time) ? -1 : 1; 249f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 25009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 251f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return res; 25209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 25309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 25409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 25509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 256f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * The DownloadSet is a TreeSet sorted by priority class (e.g. low, high, etc.) and the 257f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * time of the request. Higher priority requests 25809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * are always processed first; among equals, the oldest request is processed first. The 25909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * priority key represents this ordering. Note: All methods that change the attachment map are 26009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * synchronized on the map itself 26109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 262f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ class DownloadSet extends TreeSet<DownloadRequest> { 26309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final long serialVersionUID = 1L; 2643bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private PendingIntent mWatchdogPendingIntent; 26509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 266f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ DownloadSet(Comparator<? super DownloadRequest> comparator) { 267f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank super(comparator); 268f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 26909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 27009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 271f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Maps attachment id to DownloadRequest 27209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 2733bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /*package*/ final ConcurrentHashMap<Long, DownloadRequest> mDownloadsInProgress = 2743bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank new ConcurrentHashMap<Long, DownloadRequest>(); 27509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 27609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 27709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * onChange is called by the AttachmentReceiver upon receipt of a valid notification from 27809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * EmailProvider that an attachment has been inserted or modified. It's not strictly 27909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * necessary that we detect a deleted attachment, as the code always checks for the 28009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * existence of an attachment before acting on it. 28109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 282ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank public synchronized void onChange(Context context, Attachment att) { 283f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = findDownloadRequest(att.mId); 284f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank long priority = getPriority(att); 285f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (priority == PRIORITY_NONE) { 2869e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 287560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "== Attachment changed: " + att.mId); 28809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 28909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // In this case, there is no download priority for this attachment 290f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 291f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If it exists in the map, remove it 29209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // NOTE: We don't yet support deleting downloads in progress 2939e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 294560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "== Attachment " + att.mId + " was in queue, removing"); 29509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 296f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank remove(req); 29709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 29809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } else { 29909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Ignore changes that occur during download 30009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (mDownloadsInProgress.containsKey(att.mId)) return; 301f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is new, add the request to the queue 30209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req == null) { 303ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank req = new DownloadRequest(context, att); 304f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank add(req); 30509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 30609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If the request already existed, we'll update the priority (so that the time is 30709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // up-to-date); otherwise, we create a new request 3089e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 309560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "== Download queued for attachment " + att.mId + ", class " + 310f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank req.priority + ", priority time " + req.time); 31109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 31209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 31309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue if we're in a wait 31409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 31509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 31609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 31709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 318f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Find a queued DownloadRequest, given the attachment's id 31909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the id of the attachment 320f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the DownloadRequest for that attachment (or null, if none) 32109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 322f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized DownloadRequest findDownloadRequest(long id) { 323f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = iterator(); 324f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while(iterator.hasNext()) { 325f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 326f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req.attachmentId == id) { 327f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req; 32809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 32909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 330f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return null; 33109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 33209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 33332bed4bb8e23d7322ab338773d135845f392d3cfBen Komalo @Override 334b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank public synchronized boolean isEmpty() { 335b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank return super.isEmpty() && mDownloadsInProgress.isEmpty(); 336b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 337b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank 33809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 33909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing 34009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * the limit on maximum downloads 34109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 342f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void processQueue() { 3439e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 344560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "== Checking attachment queue, " + mDownloadSet.size() 345560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy + " entries"); 34609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 3473ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank 348f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 34909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // First, start up any required downloads, in priority order 350f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while (iterator.hasNext() && 351f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) { 352f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 35375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Enforce per-account limit here 35475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (downloadsForAccount(req.accountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) { 3559e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 356560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "== Skip #" + req.attachmentId + "; maxed for acct #" + 35775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank req.accountId); 35875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 35975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank continue; 360f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } else if (Attachment.restoreAttachmentWithId(mContext, req.attachmentId) == null) { 361f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank continue; 36275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 36309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (!req.inProgress) { 3647c03217316b21d59a00cf1abed093470e8004144Martin Hibdon final long currentTime = SystemClock.elapsedRealtime(); 3657c03217316b21d59a00cf1abed093470e8004144Martin Hibdon if (req.retryCount > 0 && req.retryStartTime > currentTime) { 3667c03217316b21d59a00cf1abed093470e8004144Martin Hibdon LogUtils.d(TAG, "== waiting to retry attachment %d", req.attachmentId); 3677c03217316b21d59a00cf1abed093470e8004144Martin Hibdon setWatchdogAlarm(CONNECTION_ERROR_RETRY_MILLIS); 3687c03217316b21d59a00cf1abed093470e8004144Martin Hibdon continue; 3697c03217316b21d59a00cf1abed093470e8004144Martin Hibdon } 370f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.tryStartDownload(req); 37109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 37209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 3737fbcefff7d5a745335d1ec562e783a59087cc0b1Marc Blank 3743ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank // Don't prefetch if background downloading is disallowed 375433b0ed9fef3e424563df70f22164508c970e4d5Marc Blank EmailConnectivityManager ecm = mConnectivityManager; 376433b0ed9fef3e424563df70f22164508c970e4d5Marc Blank if (ecm == null) return; 377faf9ecc992c34de53969335f9fb403d2b17f3163Marc Blank if (!ecm.isAutoSyncAllowed()) return; 378973702b30e8c2fb2f622f4ef37b42b3bdbd3ef17Marc Blank // Don't prefetch unless we're on a WiFi network 379433b0ed9fef3e424563df70f22164508c970e4d5Marc Blank if (ecm.getActiveNetworkType() != ConnectivityManager.TYPE_WIFI) { 380973702b30e8c2fb2f622f4ef37b42b3bdbd3ef17Marc Blank return; 381973702b30e8c2fb2f622f4ef37b42b3bdbd3ef17Marc Blank } 38209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Then, try opportunistic download of appropriate attachments 38309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size(); 38475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Always leave one slot for user requested download 38575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (backgroundDownloads > (MAX_SIMULTANEOUS_DOWNLOADS - 1)) { 386475c20d3a883737853aa7301cc649736a36387c5Marc Blank // We'll load up the newest 25 attachments that aren't loaded or queued 387475c20d3a883737853aa7301cc649736a36387c5Marc Blank Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI, 388475c20d3a883737853aa7301cc649736a36387c5Marc Blank MAX_ATTACHMENTS_TO_CHECK); 389f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Cursor c = mContext.getContentResolver().query(lookupUri, 390f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Attachment.CONTENT_PROJECTION, 3912f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank EmailContent.Attachment.PRECACHE_INBOX_SELECTION, 392475c20d3a883737853aa7301cc649736a36387c5Marc Blank null, Attachment.RECORD_ID + " DESC"); 393475c20d3a883737853aa7301cc649736a36387c5Marc Blank File cacheDir = mContext.getCacheDir(); 394475c20d3a883737853aa7301cc649736a36387c5Marc Blank try { 395475c20d3a883737853aa7301cc649736a36387c5Marc Blank while (c.moveToNext()) { 396f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Attachment att = new Attachment(); 397f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank att.restore(c); 398f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Account account = Account.restoreAccountWithId(mContext, att.mAccountKey); 3993a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (account == null) { 400475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Clean up this orphaned attachment; there's no point in keeping it 401475c20d3a883737853aa7301cc649736a36387c5Marc Blank // around; then try to find another one 402f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, att.mId); 403f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } else { 404475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Check that the attachment meets system requirements for download 405f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank AttachmentInfo info = new AttachmentInfo(mContext, att); 406475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (info.isEligibleForDownload()) { 407f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // Either the account must be able to prefetch or this must be 408f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // an inline attachment 409f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (att.mContentId != null || 410f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank (canPrefetchForAccount(account, cacheDir))) { 411819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy Integer tryCount; 412819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy tryCount = mAttachmentFailureMap.get(att.mId); 413819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (tryCount != null && tryCount > MAX_DOWNLOAD_RETRIES) { 414819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // move onto the next attachment 415819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy continue; 416819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 417475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Start this download and we're done 418475c20d3a883737853aa7301cc649736a36387c5Marc Blank DownloadRequest req = new DownloadRequest(mContext, att); 419475c20d3a883737853aa7301cc649736a36387c5Marc Blank mDownloadSet.tryStartDownload(req); 420475c20d3a883737853aa7301cc649736a36387c5Marc Blank break; 421475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 422475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 423475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 42475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 425475c20d3a883737853aa7301cc649736a36387c5Marc Blank } finally { 426475c20d3a883737853aa7301cc649736a36387c5Marc Blank c.close(); 42709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 42809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 42909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 43009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 43109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 43209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Count the number of running downloads in progress for this account 43309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param accountId the id of the account 43409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return the count of running downloads 43509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 436f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized int downloadsForAccount(long accountId) { 43709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int count = 0; 43809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank for (DownloadRequest req: mDownloadsInProgress.values()) { 43909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req.accountId == accountId) { 44009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank count++; 44109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 44209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 44309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return count; 44409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 44509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 446cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank /** 447cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank * Watchdog for downloads; we use this in case we are hanging on a download, which might 448cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank * have failed silently (the connection dropped, for example) 449cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank */ 4503bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private void onWatchdogAlarm() { 451f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // If our service instance is gone, just leave 452ecaa97741332e506afade647402896993ba64fbaMarc Blank if (mStop) { 453ecaa97741332e506afade647402896993ba64fbaMarc Blank return; 454ecaa97741332e506afade647402896993ba64fbaMarc Blank } 4553bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank long now = System.currentTimeMillis(); 4563bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank for (DownloadRequest req: mDownloadsInProgress.values()) { 4573bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Check how long it's been since receiving a callback 4583bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank long timeSinceCallback = now - req.lastCallbackTime; 4593bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (timeSinceCallback > CALLBACK_TIMEOUT) { 4609e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 461560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "== Download of " + req.attachmentId + " timed out"); 4623bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4637c03217316b21d59a00cf1abed093470e8004144Martin Hibdon cancelDownload(req); 4643bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4653bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4663bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Check whether we can start new downloads... 46741c63a05eb04e5e1c6e12676768fd6e40dcbe913Marc Blank if (mConnectivityManager != null && mConnectivityManager.hasConnectivity()) { 46881273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank processQueue(); 46981273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank } 470f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // If there are downloads in progress, reset alarm 471f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (!mDownloadsInProgress.isEmpty()) { 4729e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 473560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "Reschedule watchdog..."); 474f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 475f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank setWatchdogAlarm(); 476f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 4773bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4783bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 4793bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /** 4803a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * Attempt to execute the DownloadRequest, enforcing the maximum downloads per account 4813a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * parameter 4823a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @param req the DownloadRequest 4833a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @return whether or not the download was started 4843a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank */ 4853a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /*package*/ synchronized boolean tryStartDownload(DownloadRequest req) { 486f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank EmailServiceProxy service = EmailServiceUtils.getServiceForAccount( 4872075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu AttachmentDownloadService.this, req.accountId); 4883a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4893a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // Do not download the same attachment multiple times 4903a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank boolean alreadyInProgress = mDownloadsInProgress.get(req.attachmentId) != null; 4913a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (alreadyInProgress) return false; 4923a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4933a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank try { 4949e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 495560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, ">> Starting download for attachment #" + req.attachmentId); 4963a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 497f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank startDownload(service, req); 4983a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } catch (RemoteException e) { 4993a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // TODO: Consider whether we need to do more in this case... 5003a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // For now, fix up our data to reflect the failure 5013a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank cancelDownload(req); 5023a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 5033a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return true; 5043a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 5053a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 5063a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private synchronized DownloadRequest getDownloadInProgress(long attachmentId) { 5073a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return mDownloadsInProgress.get(attachmentId); 5083a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 5093a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 5107c03217316b21d59a00cf1abed093470e8004144Martin Hibdon private void setWatchdogAlarm(final long delay) { 511f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // Lazily initialize the pending intent 512f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (mWatchdogPendingIntent == null) { 513f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Intent intent = new Intent(mContext, Watchdog.class); 514f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank mWatchdogPendingIntent = 515f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank PendingIntent.getBroadcast(mContext, 0, intent, 0); 516f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 517f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // Set the alarm 518f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank AlarmManager am = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); 5197c03217316b21d59a00cf1abed093470e8004144Martin Hibdon am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, 520f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank mWatchdogPendingIntent); 521f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 522f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank 5237c03217316b21d59a00cf1abed093470e8004144Martin Hibdon private void setWatchdogAlarm() { 5247c03217316b21d59a00cf1abed093470e8004144Martin Hibdon setWatchdogAlarm(WATCHDOG_CHECK_INTERVAL); 5257c03217316b21d59a00cf1abed093470e8004144Martin Hibdon } 5267c03217316b21d59a00cf1abed093470e8004144Martin Hibdon 5273a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /** 5283bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * Do the work of starting an attachment download using the EmailService interface, and 5293bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * set our watchdog alarm 5303bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * 531d5acf0bbc00cbe9a2c075e1bd4825ccbd9851d8dYu Ping Hu * @param service the service handling the download 5323bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @param req the DownloadRequest 5333bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @throws RemoteException 5343bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank */ 535f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank private void startDownload(EmailServiceProxy service, DownloadRequest req) 5363bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank throws RemoteException { 5373bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank req.startTime = System.currentTimeMillis(); 5383bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank req.inProgress = true; 5393bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mDownloadsInProgress.put(req.attachmentId, req); 5400132856e673d5fd79a33b3bbc6fa8c06ffc679eaAnthony Lee service.loadAttachment(mServiceCallback, req.accountId, req.attachmentId, 541d5acf0bbc00cbe9a2c075e1bd4825ccbd9851d8dYu Ping Hu req.priority != PRIORITY_FOREGROUND); 542f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank setWatchdogAlarm(); 543ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy } 5442f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank 54569fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank private void cancelDownload(DownloadRequest req) { 546272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon LogUtils.d(TAG, "cancelDownload #%d", req.attachmentId); 54769fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank req.inProgress = false; 548272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon mDownloadsInProgress.remove(req.attachmentId); 549272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon // Remove the download from our queue, and then decide whether or not to add it back. 550272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon remove(req); 551272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon req.retryCount++; 552272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon if (req.retryCount > CONNECTION_ERROR_MAX_RETRIES) { 553272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon LogUtils.d(TAG, "too many failures, giving up"); 554272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon } else { 555272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon LogUtils.d(TAG, "moving to end of queue, will retry"); 556272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon // The time field of DownloadRequest is final, because it's unsafe to change it 557272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon // as long as the DownloadRequest is in the DownloadSet. It's needed for the 558272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon // comparator, so changing time would make the request unfindable. 559272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon // Instead, we'll create a new DownloadRequest with an updated time. 560272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon // This will sort at the end of the set. 561272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon req = new DownloadRequest(req, SystemClock.elapsedRealtime()); 562272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon add(req); 563272b317f3d021e2ce385820ac2660909ff939ab5Martin Hibdon } 56469fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank } 56569fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank 56609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 56709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called when a download is finished; we get notified of this via our EmailServiceCallback 56809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the attachment whose download is finished 56909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param statusCode the EmailServiceStatus code returned by the Service 57009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 571f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void endDownload(long attachmentId, int statusCode) { 57209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Say we're no longer downloading this 57309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mDownloadsInProgress.remove(attachmentId); 574819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 575819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // TODO: This code is conservative and treats connection issues as failures. 576819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Since we have no mechanism to throttle reconnection attempts, it makes 577819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // sense to be cautious here. Once logic is in place to prevent connecting 578819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // in a tight loop, we can exclude counting connection issues as "failures". 579819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 580819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Update the attachment failure list if needed 581819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy Integer downloadCount; 582819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount = mAttachmentFailureMap.remove(attachmentId); 583819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (statusCode != EmailServiceStatus.SUCCESS) { 584819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (downloadCount == null) { 585819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount = 0; 586819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 587819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount += 1; 588819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy mAttachmentFailureMap.put(attachmentId, downloadCount); 589819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 590819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 591f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 59209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.CONNECTION_ERROR) { 59309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If this needs to be retried, just process the queue again 59409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req != null) { 5957c03217316b21d59a00cf1abed093470e8004144Martin Hibdon req.retryCount++; 5967c03217316b21d59a00cf1abed093470e8004144Martin Hibdon if (req.retryCount > CONNECTION_ERROR_MAX_RETRIES) { 5977c03217316b21d59a00cf1abed093470e8004144Martin Hibdon LogUtils.d(TAG, "Connection Error #%d, giving up", attachmentId); 5987c03217316b21d59a00cf1abed093470e8004144Martin Hibdon remove(req); 5997c03217316b21d59a00cf1abed093470e8004144Martin Hibdon } else if (req.retryCount > CONNECTION_ERROR_DELAY_THRESHOLD) { 6007c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // TODO: I'm not sure this is a great retry/backoff policy, but we're 6017c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // afraid of changing behavior too much in case something relies upon it. 6027c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // So now, for the first five errors, we'll retry immediately. For the next 6037c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // five tries, we'll add a ten second delay between each. After that, we'll 6047c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // give up. 6057c03217316b21d59a00cf1abed093470e8004144Martin Hibdon LogUtils.d(TAG, "ConnectionError #%d, retried %d times, adding delay", 6067c03217316b21d59a00cf1abed093470e8004144Martin Hibdon attachmentId, req.retryCount); 6077c03217316b21d59a00cf1abed093470e8004144Martin Hibdon req.inProgress = false; 6087c03217316b21d59a00cf1abed093470e8004144Martin Hibdon req.retryStartTime = SystemClock.elapsedRealtime() + 6097c03217316b21d59a00cf1abed093470e8004144Martin Hibdon CONNECTION_ERROR_RETRY_MILLIS; 6107c03217316b21d59a00cf1abed093470e8004144Martin Hibdon setWatchdogAlarm(CONNECTION_ERROR_RETRY_MILLIS); 6117c03217316b21d59a00cf1abed093470e8004144Martin Hibdon } else { 6127c03217316b21d59a00cf1abed093470e8004144Martin Hibdon LogUtils.d(TAG, "ConnectionError #%d, retried %d times, adding delay", 6137c03217316b21d59a00cf1abed093470e8004144Martin Hibdon attachmentId, req.retryCount); 6147c03217316b21d59a00cf1abed093470e8004144Martin Hibdon req.inProgress = false; 6157c03217316b21d59a00cf1abed093470e8004144Martin Hibdon req.retryStartTime = 0; 6167c03217316b21d59a00cf1abed093470e8004144Martin Hibdon kick(); 6177c03217316b21d59a00cf1abed093470e8004144Martin Hibdon } 61809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return; 62009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 621b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 622782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank // If the request is still in the queue, remove it 623782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank if (req != null) { 624782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank remove(req); 625782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank } 6269e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 627b961c78ff4bd250d4a25497158162cb230a55057Marc Blank long secs = 0; 628b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if (req != null) { 629b961c78ff4bd250d4a25497158162cb230a55057Marc Blank secs = (System.currentTimeMillis() - req.time) / 1000; 630b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 63109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" : 63209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank "Error " + statusCode; 633560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "<< Download finished for attachment #" + attachmentId + "; " + secs 634560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy + " seconds from request, status: " + status); 63509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 636b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 63709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); 63809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 63975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long accountId = attachment.mAccountKey; 64075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Update our attachment storage for this account 64175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Long currentStorage = mAttachmentStorageMap.get(accountId); 64275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (currentStorage == null) { 64375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank currentStorage = 0L; 64475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 64575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAttachmentStorageMap.put(accountId, currentStorage + attachment.mSize); 64609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean deleted = false; 64709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if ((attachment.mFlags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 64809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.ATTACHMENT_NOT_FOUND) { 649f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is a forwarding download, and the attachment doesn't exist (or 65009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // can't be downloaded) delete it from the outgoing message, lest that 65109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // message never get sent 65209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId); 65309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // TODO: Talk to UX about whether this is even worth doing 654d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank NotificationController nc = NotificationController.getInstance(mContext); 655d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank nc.showDownloadForwardFailedNotification(attachment); 65609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank deleted = true; 65709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 65809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If we're an attachment on forwarded mail, and if we're not still blocked, 65909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // try to send pending mail now (as mediated by MailService) 66036cc9c18edb7ee897d0d758788b6e338cb2ef126Makoto Onuki if ((req != null) && 661b961c78ff4bd250d4a25497158162cb230a55057Marc Blank !Utility.hasUnloadedAttachments(mContext, attachment.mMessageKey)) { 6629e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 663560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "== Downloads finished for outgoing msg #" 664560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy + req.messageId); 66509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 666f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank EmailServiceProxy service = EmailServiceUtils.getServiceForAccount( 6672075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu mContext, accountId); 668f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank try { 669f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank service.sendMail(accountId); 670f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } catch (RemoteException e) { 671f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // We tried 672f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 67309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 67409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 6752ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank if (statusCode == EmailServiceStatus.MESSAGE_NOT_FOUND) { 6762f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank Message msg = Message.restoreMessageWithId(mContext, attachment.mMessageKey); 6772f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank if (msg == null) { 6782f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank // If there's no associated message, delete the attachment 6792f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId); 6802f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank } else { 6812f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank // If there really is a message, retry 6827c03217316b21d59a00cf1abed093470e8004144Martin Hibdon // TODO: How will this get retried? It's still marked as inProgress? 6832f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank kick(); 6842f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank return; 6852f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank } 6862ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank } else if (!deleted) { 68709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Clear the download flags, since we're done for now. Note that this happens 68809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // only for non-recoverable errors. When these occur for forwarded mail, we can 68909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // ignore it and continue; otherwise, it was either 1) a user request, in which 69009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the user can retry manually or 2) an opportunistic download, in which 69109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the download wasn't critical 69209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank ContentValues cv = new ContentValues(); 69309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int flags = 69409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 69509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank cv.put(Attachment.FLAGS, attachment.mFlags &= ~flags); 6968f474dc02f01d7805716422625ef7b50fa0e4aefMark Wei cv.put(Attachment.UI_STATE, AttachmentState.SAVED); 69709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.update(mContext, cv); 69809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 69909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 70009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue 70109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 70209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 70309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 70409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 705f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 706f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Calculate the download priority of an Attachment. A priority of zero means that the 707f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * attachment is not marked for download. 708f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @param att the Attachment 709f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the priority key of the Attachment 710f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 711f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static int getPriority(Attachment att) { 712f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int priorityClass = PRIORITY_NONE; 713f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int flags = att.mFlags; 714f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 715f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy priorityClass = PRIORITY_SEND_MAIL; 716f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) { 717f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy priorityClass = PRIORITY_FOREGROUND; 718f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 719f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return priorityClass; 72009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 72109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 72209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private void kick() { 72309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 72409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.notify(); 72509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 72609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 72709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 72809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 72909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * We use an EmailServiceCallback to keep track of the progress of downloads. These callbacks 73064b64cca01c1a827c1b3824f099fd638cfb15826Marc Blank * come from either Controller (IMAP) or ExchangeService (EAS). Note that we only implement the 73109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * single callback that's defined by the EmailServiceCallback interface. 73209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 73309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private class ServiceCallback extends IEmailServiceCallback.Stub { 734f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank @Override 73509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, 73609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int progress) { 737a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // Record status and progress 73875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank DownloadRequest req = mDownloadSet.getDownloadInProgress(attachmentId); 739a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req != null) { 7409e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 74175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank String code; 74275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank switch(statusCode) { 74375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank case EmailServiceStatus.SUCCESS: code = "Success"; break; 74475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank case EmailServiceStatus.IN_PROGRESS: code = "In progress"; break; 74575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank default: code = Integer.toString(statusCode); break; 74675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 74775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (statusCode != EmailServiceStatus.IN_PROGRESS) { 7481ad443612d70dc4f0c59b67e1b4a4626fc7ad854Martin Hibdon LogUtils.d(TAG, ">> Attachment status " + attachmentId + ": " + code); 7495ed194434f875df551ebcaf673f5b9e8c385d652Martin Hibdon } else if (progress >= (req.lastProgress + 10)) { 7501ad443612d70dc4f0c59b67e1b4a4626fc7ad854Martin Hibdon LogUtils.d(TAG, ">> Attachment progress %d: %d%%", attachmentId, progress); 75175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 75275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 753a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastStatusCode = statusCode; 754a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress = progress; 755a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime = System.currentTimeMillis(); 756f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); 757f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (attachment != null && statusCode == EmailServiceStatus.IN_PROGRESS) { 758f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank ContentValues values = new ContentValues(); 759f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank values.put(AttachmentColumns.UI_DOWNLOADED_SIZE, 760f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank attachment.mSize * progress / 100); 761f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // Update UIProvider with updated download size 762f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // Individual services will set contentUri and state when finished 763f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank attachment.update(mContext, values); 764f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } 765a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 76609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank switch (statusCode) { 76709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank case EmailServiceStatus.IN_PROGRESS: 76809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 76909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank default: 770f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.endDownload(attachmentId, statusCode); 77109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 77209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 77309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 77409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 77509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 7763a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /*package*/ void addServiceIntentForTest(long accountId, Intent intent) { 7773a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAccountServiceMap.put(accountId, intent); 77809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 77909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 780f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ void onChange(Attachment att) { 781ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mDownloadSet.onChange(this, att); 78209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 78309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 784f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean isQueued(long attachmentId) { 785f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return mDownloadSet.findDownloadRequest(attachmentId) != null; 786f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 787f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 788fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /*package*/ int getSize() { 789fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return mDownloadSet.size(); 790fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 791fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 792f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean dequeue(long attachmentId) { 793f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 794f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 7959e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 796560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "Dequeued attachmentId: " + attachmentId); 797b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 798f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.remove(req); 799f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return true; 800f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 801f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return false; 80209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 80309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 80409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 805fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * Ask the service for the number of items in the download queue 806fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * @return the number of items queued for download 807fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank */ 808fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank public static int getQueueSize() { 809ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 810ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 811ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.getSize(); 812fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 813fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return 0; 814fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 815fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 816fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /** 81709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service whether a particular attachment is queued for download 81809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 81909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment is queued for download 82009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 82109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean isAttachmentQueued(long attachmentId) { 822ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 823ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 824ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.isQueued(attachmentId); 82509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 82609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 82709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 82809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 82909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 83009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service to remove an attachment from the download queue 83109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 83209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment was removed from the queue 83309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 83409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean cancelQueuedAttachment(long attachmentId) { 835ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 836ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 837ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.dequeue(attachmentId); 83809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 83909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 84009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 84109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 8423bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public static void watchdogAlarm() { 843ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 844ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 845ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank service.mDownloadSet.onWatchdogAlarm(); 8463bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 8473bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 8483bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 84909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 85009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called directly by EmailProvider whenever an attachment is inserted or changed 851b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank * @param context the caller's context 85209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the attachment's id 85309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param flags the new flags for the attachment 85409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 855b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank public static void attachmentChanged(final Context context, final long id, final int flags) { 85609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Utility.runAsync(new Runnable() { 857f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank @Override 85809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 859b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId(context, id); 86009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 86109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Store the flags we got from EmailProvider; given that all of this 86209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // activity is asynchronous, we need to use the newest data from 86309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // EmailProvider 86409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.mFlags = flags; 865b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank Intent intent = new Intent(context, AttachmentDownloadService.class); 866b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank intent.putExtra(EXTRA_ATTACHMENT, attachment); 867b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank context.startService(intent); 86809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 86909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank }}); 87009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 87109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 87275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /** 87375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * Determine whether an attachment can be prefetched for the given account 87475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * @return true if download is allowed, false otherwise 87575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank */ 8763a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank public boolean canPrefetchForAccount(Account account, File dir) { 877475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Check account, just in case 878475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (account == null) return false; 879475c20d3a883737853aa7301cc649736a36387c5Marc Blank // First, check preference and quickly return if prefetch isn't allowed 880475c20d3a883737853aa7301cc649736a36387c5Marc Blank if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) return false; 881475c20d3a883737853aa7301cc649736a36387c5Marc Blank 88275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long totalStorage = dir.getTotalSpace(); 88375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long usableStorage = dir.getUsableSpace(); 88475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE); 88575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 88675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // If there's not enough overall storage available, stop now 88775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (usableStorage < minAvailable) { 88875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return false; 88975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 89075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 89175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts(); 89275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long perAccountMaxStorage = 89375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank (long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts); 89475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 89575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Retrieve our idea of currently used attachment storage; since we don't track deletions, 89675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // this number is the "worst case". If the number is greater than what's allowed per 89775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // account, we walk the directory to determine the actual number 8983a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Long accountStorage = mAttachmentStorageMap.get(account.mId); 89975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (accountStorage == null || (accountStorage > perAccountMaxStorage)) { 90075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Calculate the exact figure for attachment storage for this account 90175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage = 0L; 90275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank File[] files = dir.listFiles(); 90375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (files != null) { 90475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank for (File file : files) { 90575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage += file.length(); 90675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 90775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 90875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Cache the value 9093a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAttachmentStorageMap.put(account.mId, accountStorage); 91075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 91175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 91275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Return true if we're using less than the maximum per account 91375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (accountStorage < perAccountMaxStorage) { 91475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return true; 91575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 9169e608aa5a1c53355d15fe23e0c1c32091b124b4dMartin Hibdon if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) { 917560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, ">> Prefetch not allowed for account " + account.mId + "; used " + 91875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage + ", limit " + perAccountMaxStorage); 91975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 92075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return false; 92175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 92275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 92375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 924f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank @Override 92509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 926ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // These fields are only used within the service thread 92709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mContext = this; 928ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mConnectivityManager = new EmailConnectivityManager(this, TAG); 92975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManagerStub = new AccountManagerStub(this); 9307fbcefff7d5a745335d1ec562e783a59087cc0b1Marc Blank 93109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Run through all attachments in the database that require download and add them to 93209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // the queue 93309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 93409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Cursor c = getContentResolver().query(Attachment.CONTENT_URI, 93509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.ID_PROJECTION, "(" + Attachment.FLAGS + " & ?) != 0", 93609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new String[] {Integer.toString(mask)}, null); 93709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 938560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "Count: " + c.getCount()); 93909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (c.moveToNext()) { 94009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId( 94109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank this, c.getLong(EmailContent.ID_PROJECTION_COLUMN)); 94209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 943ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mDownloadSet.onChange(this, attachment); 94409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 94509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 94609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (Exception e) { 94709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank e.printStackTrace(); 94809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 94909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank finally { 95009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank c.close(); 95109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 95209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 95309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Loop until stopped, with a 30 minute wait loop 95409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (!mStop) { 95509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Here's where we run our attachment loading logic... 956efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler // Make a local copy of the variable so we don't null-crash on service shutdown 957efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler final EmailConnectivityManager ecm = mConnectivityManager; 958efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler if (ecm != null) { 959efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler ecm.waitForConnectivity(); 960efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler } 961efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler if (mStop) { 962efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler // We might be bailing out here due to the service shutting down 963efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler break; 964efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler } 965f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.processQueue(); 966b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank if (mDownloadSet.isEmpty()) { 967560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(TAG, "*** All done; shutting down service"); 968b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank stopSelf(); 969b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank break; 970b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 97109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 97209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 97309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.wait(PROCESS_QUEUE_WAIT_TIME); 97409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (InterruptedException e) { 97509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // That's ok; we'll just keep looping 97609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 97709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 97809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 979ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank 980ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // Unregister now that we're done 981efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler // Make a local copy of the variable so we don't null-crash on service shutdown 982efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler final EmailConnectivityManager ecm = mConnectivityManager; 983efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler if (ecm != null) { 984efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler ecm.unregister(); 985b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 98609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 98709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 98809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 98909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public int onStartCommand(Intent intent, int flags, int startId) { 990b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank if (sRunningService == null) { 991b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank sRunningService = this; 992b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 99390a48115517c49a954eaa1b79b72a23b4486107aMarc Blank if (intent != null && intent.hasExtra(EXTRA_ATTACHMENT)) { 994b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank Attachment att = (Attachment)intent.getParcelableExtra(EXTRA_ATTACHMENT); 995b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank onChange(att); 996b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 99709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return Service.START_STICKY; 99809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 99909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 100009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 100109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onCreate() { 100209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Start up our service thread 100309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new Thread(this, "AttachmentDownloadService").start(); 100409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 100509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 100609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public IBinder onBind(Intent intent) { 100709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return null; 100809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 100909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 101009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 101109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onDestroy() { 1012cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank // Mark this instance of the service as stopped 1013cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank mStop = true; 101409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (sRunningService != null) { 101509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 1016b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank sRunningService = null; 1017b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 1018b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank if (mConnectivityManager != null) { 1019b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank mConnectivityManager.unregister(); 1020efd835aceaa58ceefa29057b21644653bff70ae3Tony Mantler mConnectivityManager.stopWait(); 1021b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank mConnectivityManager = null; 102209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 102309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 1024a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank 1025a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank @Override 1026a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1027a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println("AttachmentDownloadService"); 1028a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long time = System.currentTimeMillis(); 1029a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank synchronized(mDownloadSet) { 1030a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Queue, " + mDownloadSet.size() + " entries"); 1031a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 1032a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // First, start up any required downloads, in priority order 1033a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank while (iterator.hasNext()) { 1034a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank DownloadRequest req = iterator.next(); 1035a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Account: " + req.accountId + ", Attachment: " + req.attachmentId); 1036a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Priority: " + req.priority + ", Time: " + req.time + 1037a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank (req.inProgress ? " [In progress]" : "")); 1038ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank Attachment att = Attachment.restoreAttachmentWithId(this, req.attachmentId); 1039a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att == null) { 1040a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Attachment not in database?"); 1041eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank } else if (att.mFileName != null) { 1042a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String fileName = att.mFileName; 1043a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String suffix = "[none]"; 1044a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastDot = fileName.lastIndexOf('.'); 1045a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (lastDot >= 0) { 1046a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank suffix = fileName.substring(lastDot); 1047a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1048a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Suffix: " + suffix); 10496e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank if (att.getContentUri() != null) { 10506e5bccf2c984039da5ae1dc08cffa665b73b6474Marc Blank pw.print(" ContentUri: " + att.getContentUri()); 1051a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1052a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Mime: "); 1053a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att.mMimeType != null) { 1054a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(att.mMimeType); 1055a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } else { 10568a574694606f0e5d781334d0d426fc379c51f3edMarc Blank pw.print(AttachmentUtilities.inferMimeType(fileName, null)); 1057eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank pw.print(" [inferred]"); 1058a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1059a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Size: " + att.mSize); 1060a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1061a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.inProgress) { 1062a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Status: " + req.lastStatusCode + ", Progress: " + 1063a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress); 1064a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Started: " + req.startTime + ", Callback: " + 1065a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime); 1066a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Elapsed: " + ((time - req.startTime) / 1000L) + "s"); 1067a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.lastCallbackTime > 0) { 1068a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" CB: " + ((time - req.lastCallbackTime) / 1000L) + "s"); 1069a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1070a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1071a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1072a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1073a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 107409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank} 1075