AttachmentDownloadService.java revision 32bed4bb8e23d7322ab338773d135845f392d3cf
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; 3209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.text.format.DateUtils; 3309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.util.Log; 3409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 35b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.AttachmentInfo; 36b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.Controller.ControllerService; 37b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.Email; 38b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.EmailConnectivityManager; 39b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.email.NotificationController; 40b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.provider.Account; 41b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.provider.EmailContent; 42b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blankimport com.android.emailcommon.provider.EmailContent.Attachment; 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; 49b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank 5009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.io.File; 51a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.FileDescriptor; 52a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.PrintWriter; 53f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Comparator; 5409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.util.HashMap; 55f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Iterator; 56f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.TreeSet; 573bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blankimport java.util.concurrent.ConcurrentHashMap; 5809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 5909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankpublic class AttachmentDownloadService extends Service implements Runnable { 6009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static final String TAG = "AttachmentService"; 6109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 6209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Our idle time, waiting for notifications; this is something of a failsafe 6309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int PROCESS_QUEUE_WAIT_TIME = 30 * ((int)DateUtils.MINUTE_IN_MILLIS); 643bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // How often our watchdog checks for callback timeouts 653bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private static final int WATCHDOG_CHECK_INTERVAL = 15 * ((int)DateUtils.SECOND_IN_MILLIS); 663bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // How long we'll wait for a callback before canceling a download and retrying 673bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private static final int CALLBACK_TIMEOUT = 30 * ((int)DateUtils.SECOND_IN_MILLIS); 68819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Try to download an attachment in the background this many times before giving up 69819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy private static final int MAX_DOWNLOAD_RETRIES = 5; 70f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static final int PRIORITY_NONE = -1; 7109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @SuppressWarnings("unused") 72f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Low priority will be used for opportunistic downloads 73f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_BACKGROUND = 0; 74f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Normal priority is for forwarded downloads in outgoing mail 75f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_SEND_MAIL = 1; 76f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // High priority is for user requests 77f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy private static final int PRIORITY_FOREGROUND = 2; 7809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 7975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Minimum free storage in order to perform prefetch (25% of total memory) 8075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F; 8175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Maximum prefetch storage (also 25% of total memory) 8275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private static final float PREFETCH_MAXIMUM_ATTACHMENT_STORAGE = 0.25F; 8375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 8409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // We can try various values here; I think 2 is completely reasonable as a first pass 8509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2; 8609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Limit on the number of simultaneous downloads per account 8709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Note that a limit of 1 is currently enforced by both Services (MailService and Controller) 8809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT = 1; 89475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Limit on the number of attachments we'll check for background download 90475c20d3a883737853aa7301cc649736a36387c5Marc Blank private static final int MAX_ATTACHMENTS_TO_CHECK = 25; 9175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 92b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank private static final String EXTRA_ATTACHMENT = 93b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank "com.android.email.AttachmentDownloadService.attachment"; 94b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank 95ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // sRunningService is only set in the UI thread; it's visibility elsewhere is guaranteed 96ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // by the use of "volatile" 97ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank /*package*/ static volatile AttachmentDownloadService sRunningService = null; 9809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 99f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ Context mContext; 100ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank /*package*/ EmailConnectivityManager mConnectivityManager; 1013ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank 102f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ final DownloadSet mDownloadSet = new DownloadSet(new DownloadComparator()); 1033bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 1043a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private final HashMap<Long, Intent> mAccountServiceMap = new HashMap<Long, Intent>(); 10575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // A map of attachment storage used per account 10675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // NOTE: This map is not kept current in terms of deletions (i.e. it stores the last calculated 10775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // amount plus the size of any new attachments laoded). If and when we reach the per-account 10875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // limit, we recalculate the actual usage 10975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ final HashMap<Long, Long> mAttachmentStorageMap = new HashMap<Long, Long>(); 110819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // A map of attachment ids to the number of failed attempts to download the attachment 111819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // NOTE: We do not want to persist this. This allows us to retry background downloading 112819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // if any transient network errors are fixed & and the app is restarted 113819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy /* package */ final HashMap<Long, Integer> mAttachmentFailureMap = new HashMap<Long, Integer>(); 11409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final ServiceCallback mServiceCallback = new ServiceCallback(); 11575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 11609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final Object mLock = new Object(); 11709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private volatile boolean mStop = false; 11809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 11975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ AccountManagerStub mAccountManagerStub; 12075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 12175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /** 12275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * We only use the getAccounts() call from AccountManager, so this class wraps that call and 12375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * allows us to build a mock account manager stub in the unit tests 12475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank */ 12575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ static class AccountManagerStub { 12675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private int mNumberOfAccounts; 12775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank private final AccountManager mAccountManager; 12875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 12975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank AccountManagerStub(Context context) { 13075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (context != null) { 13175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManager = AccountManager.get(context); 13275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 13375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManager = null; 13475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 13575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 13675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 13775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ int getNumberOfAccounts() { 13875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (mAccountManager != null) { 13975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return mAccountManager.getAccounts().length; 14075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 14175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return mNumberOfAccounts; 14275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 14375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 14475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 14575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /*package*/ void setNumberOfAccounts(int numberOfAccounts) { 14675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mNumberOfAccounts = numberOfAccounts; 14775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 14875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 1493bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 1503bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /** 1513bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * Watchdog alarm receiver; responsible for making sure that downloads in progress are not 1523bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * stalled, as determined by the timing of the most recent service callback 1533bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank */ 1543bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public static class Watchdog extends BroadcastReceiver { 1553bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank @Override 1563bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public void onReceive(final Context context, Intent intent) { 1573bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank new Thread(new Runnable() { 1583bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public void run() { 1593bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank watchdogAlarm(); 1603bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1613bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank }, "AttachmentDownloadService Watchdog").start(); 1623bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1633bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 1643bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 165f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public static class DownloadRequest { 166f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final int priority; 167f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long time; 168f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long attachmentId; 169f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long messageId; 170f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long accountId; 17109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean inProgress = false; 172a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastStatusCode; 173a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastProgress; 174a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long lastCallbackTime; 175a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long startTime; 17609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 17709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private DownloadRequest(Context context, Attachment attachment) { 17809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachmentId = attachment.mId; 17909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey); 18009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (msg != null) { 18109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank accountId = msg.mAccountKey; 18209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank messageId = msg.mId; 183f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 184f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank accountId = messageId = -1; 185f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 186f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank priority = getPriority(attachment); 187f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank time = System.currentTimeMillis(); 188f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 189f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 190f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 191f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int hashCode() { 192f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return (int)attachmentId; 193f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 194f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 195f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 196f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Two download requests are equals if their attachment id's are equals 197f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 198f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 199f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public boolean equals(Object object) { 200f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (!(object instanceof DownloadRequest)) return false; 201f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = (DownloadRequest)object; 202f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req.attachmentId == attachmentId; 203f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 204f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 205f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 206f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 207f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Comparator class for the download set; we first compare by priority. Requests with equal 208f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * priority are compared by the time the request was created (older requests come first) 209f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 210f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*protected*/ static class DownloadComparator implements Comparator<DownloadRequest> { 211f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 212f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int compare(DownloadRequest req1, DownloadRequest req2) { 213f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int res; 214f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.priority != req2.priority) { 215f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.priority < req2.priority) ? -1 : 1; 216f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 217f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.time == req2.time) { 218f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = 0; 219f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 220f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.time > req2.time) ? -1 : 1; 221f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 22209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 223f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return res; 22409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 22509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 22609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 22709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 228f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * The DownloadSet is a TreeSet sorted by priority class (e.g. low, high, etc.) and the 229f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * time of the request. Higher priority requests 23009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * are always processed first; among equals, the oldest request is processed first. The 23109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * priority key represents this ordering. Note: All methods that change the attachment map are 23209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * synchronized on the map itself 23309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 234f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ class DownloadSet extends TreeSet<DownloadRequest> { 23509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final long serialVersionUID = 1L; 2363bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private PendingIntent mWatchdogPendingIntent; 2373bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private AlarmManager mAlarmManager; 23809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 239f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ DownloadSet(Comparator<? super DownloadRequest> comparator) { 240f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank super(comparator); 241f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 24209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 24309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 244f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Maps attachment id to DownloadRequest 24509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 2463bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /*package*/ final ConcurrentHashMap<Long, DownloadRequest> mDownloadsInProgress = 2473bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank new ConcurrentHashMap<Long, DownloadRequest>(); 24809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 24909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 25009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * onChange is called by the AttachmentReceiver upon receipt of a valid notification from 25109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * EmailProvider that an attachment has been inserted or modified. It's not strictly 25209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * necessary that we detect a deleted attachment, as the code always checks for the 25309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * existence of an attachment before acting on it. 25409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 255ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank public synchronized void onChange(Context context, Attachment att) { 256f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = findDownloadRequest(att.mId); 257f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank long priority = getPriority(att); 258f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (priority == PRIORITY_NONE) { 25909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 26009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Attachment changed: " + att.mId); 26109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 26209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // In this case, there is no download priority for this attachment 263f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 264f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If it exists in the map, remove it 26509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // NOTE: We don't yet support deleting downloads in progress 26609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 26709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Attachment " + att.mId + " was in queue, removing"); 26809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 269f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank remove(req); 27009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 27109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } else { 27209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Ignore changes that occur during download 27309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (mDownloadsInProgress.containsKey(att.mId)) return; 274f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is new, add the request to the queue 27509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req == null) { 276ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank req = new DownloadRequest(context, att); 277f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank add(req); 27809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 27909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If the request already existed, we'll update the priority (so that the time is 28009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // up-to-date); otherwise, we create a new request 28109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 28209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Download queued for attachment " + att.mId + ", class " + 283f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank req.priority + ", priority time " + req.time); 28409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 28509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 28609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue if we're in a wait 28709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 28809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 28909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 29009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 291f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Find a queued DownloadRequest, given the attachment's id 29209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the id of the attachment 293f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the DownloadRequest for that attachment (or null, if none) 29409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 295f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized DownloadRequest findDownloadRequest(long id) { 296f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = iterator(); 297f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while(iterator.hasNext()) { 298f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 299f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req.attachmentId == id) { 300f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req; 30109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 30209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 303f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return null; 30409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 30509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 30632bed4bb8e23d7322ab338773d135845f392d3cfBen Komalo @Override 307b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank public synchronized boolean isEmpty() { 308b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank return super.isEmpty() && mDownloadsInProgress.isEmpty(); 309b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 310b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank 31109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 31209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing 31309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * the limit on maximum downloads 31409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 315f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void processQueue() { 31609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 317f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Log.d(TAG, "== Checking attachment queue, " + mDownloadSet.size() + " entries"); 31809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 3193ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank 320f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 32109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // First, start up any required downloads, in priority order 322f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while (iterator.hasNext() && 323f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) { 324f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 32575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Enforce per-account limit here 32675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (downloadsForAccount(req.accountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) { 32775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (Email.DEBUG) { 32875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, "== Skip #" + req.attachmentId + "; maxed for acct #" + 32975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank req.accountId); 33075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 33175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank continue; 33275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 33375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 33409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (!req.inProgress) { 335f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.tryStartDownload(req); 33609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 33709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 3387fbcefff7d5a745335d1ec562e783a59087cc0b1Marc Blank 3393ef8f54bae6a3e02919cfd7add7ed6bf7fdda901Marc Blank // Don't prefetch if background downloading is disallowed 340433b0ed9fef3e424563df70f22164508c970e4d5Marc Blank EmailConnectivityManager ecm = mConnectivityManager; 341433b0ed9fef3e424563df70f22164508c970e4d5Marc Blank if (ecm == null) return; 342433b0ed9fef3e424563df70f22164508c970e4d5Marc Blank if (!ecm.isBackgroundDataAllowed()) return; 343973702b30e8c2fb2f622f4ef37b42b3bdbd3ef17Marc Blank // Don't prefetch unless we're on a WiFi network 344433b0ed9fef3e424563df70f22164508c970e4d5Marc Blank if (ecm.getActiveNetworkType() != ConnectivityManager.TYPE_WIFI) { 345973702b30e8c2fb2f622f4ef37b42b3bdbd3ef17Marc Blank return; 346973702b30e8c2fb2f622f4ef37b42b3bdbd3ef17Marc Blank } 34709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Then, try opportunistic download of appropriate attachments 34809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size(); 34975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Always leave one slot for user requested download 35075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (backgroundDownloads > (MAX_SIMULTANEOUS_DOWNLOADS - 1)) { 351475c20d3a883737853aa7301cc649736a36387c5Marc Blank // We'll load up the newest 25 attachments that aren't loaded or queued 352475c20d3a883737853aa7301cc649736a36387c5Marc Blank Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI, 353475c20d3a883737853aa7301cc649736a36387c5Marc Blank MAX_ATTACHMENTS_TO_CHECK); 354475c20d3a883737853aa7301cc649736a36387c5Marc Blank Cursor c = mContext.getContentResolver().query(lookupUri, AttachmentInfo.PROJECTION, 3552f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank EmailContent.Attachment.PRECACHE_INBOX_SELECTION, 356475c20d3a883737853aa7301cc649736a36387c5Marc Blank null, Attachment.RECORD_ID + " DESC"); 357475c20d3a883737853aa7301cc649736a36387c5Marc Blank File cacheDir = mContext.getCacheDir(); 358475c20d3a883737853aa7301cc649736a36387c5Marc Blank try { 359475c20d3a883737853aa7301cc649736a36387c5Marc Blank while (c.moveToNext()) { 360475c20d3a883737853aa7301cc649736a36387c5Marc Blank long accountKey = c.getLong(AttachmentInfo.COLUMN_ACCOUNT_KEY); 361475c20d3a883737853aa7301cc649736a36387c5Marc Blank long id = c.getLong(AttachmentInfo.COLUMN_ID); 3623a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Account account = Account.restoreAccountWithId(mContext, accountKey); 3633a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (account == null) { 364475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Clean up this orphaned attachment; there's no point in keeping it 365475c20d3a883737853aa7301cc649736a36387c5Marc Blank // around; then try to find another one 366475c20d3a883737853aa7301cc649736a36387c5Marc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, id); 3673a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } else if (canPrefetchForAccount(account, cacheDir)) { 368475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Check that the attachment meets system requirements for download 369475c20d3a883737853aa7301cc649736a36387c5Marc Blank AttachmentInfo info = new AttachmentInfo(mContext, c); 370475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (info.isEligibleForDownload()) { 371475c20d3a883737853aa7301cc649736a36387c5Marc Blank Attachment att = Attachment.restoreAttachmentWithId(mContext, id); 372475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (att != null) { 373819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy Integer tryCount; 374819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy tryCount = mAttachmentFailureMap.get(att.mId); 375819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (tryCount != null && tryCount > MAX_DOWNLOAD_RETRIES) { 376819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // move onto the next attachment 377819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy continue; 378819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 379475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Start this download and we're done 380475c20d3a883737853aa7301cc649736a36387c5Marc Blank DownloadRequest req = new DownloadRequest(mContext, att); 381475c20d3a883737853aa7301cc649736a36387c5Marc Blank mDownloadSet.tryStartDownload(req); 382475c20d3a883737853aa7301cc649736a36387c5Marc Blank break; 383475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 384475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 385475c20d3a883737853aa7301cc649736a36387c5Marc Blank } 38675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 387475c20d3a883737853aa7301cc649736a36387c5Marc Blank } finally { 388475c20d3a883737853aa7301cc649736a36387c5Marc Blank c.close(); 38909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 39009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 39109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 39209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 39309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 39409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Count the number of running downloads in progress for this account 39509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param accountId the id of the account 39609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return the count of running downloads 39709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 398f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized int downloadsForAccount(long accountId) { 39909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int count = 0; 40009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank for (DownloadRequest req: mDownloadsInProgress.values()) { 40109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req.accountId == accountId) { 40209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank count++; 40309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 40409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 40509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return count; 40609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 40709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 408cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank /** 409cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank * Watchdog for downloads; we use this in case we are hanging on a download, which might 410cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank * have failed silently (the connection dropped, for example) 411cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank */ 4123bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank private void onWatchdogAlarm() { 413cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank // If our service instance is gone, just leave... 414cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank if (mStop) return; 4153bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank long now = System.currentTimeMillis(); 4163bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank for (DownloadRequest req: mDownloadsInProgress.values()) { 4173bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Check how long it's been since receiving a callback 4183bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank long timeSinceCallback = now - req.lastCallbackTime; 4193bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (timeSinceCallback > CALLBACK_TIMEOUT) { 4203bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (Email.DEBUG) { 42175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, "== Download of " + req.attachmentId + " timed out"); 4223bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 42369fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank cancelDownload(req); 4243bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4253bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4263bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // If there are downloads in progress, reset alarm 4273bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (mDownloadsInProgress.isEmpty()) { 4283bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (mAlarmManager != null && mWatchdogPendingIntent != null) { 4293bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mAlarmManager.cancel(mWatchdogPendingIntent); 4303bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4313bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4323bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Check whether we can start new downloads... 43341c63a05eb04e5e1c6e12676768fd6e40dcbe913Marc Blank if (mConnectivityManager != null && mConnectivityManager.hasConnectivity()) { 43481273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank processQueue(); 43581273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank } 4363bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4373bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 4383bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank /** 4393a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * Attempt to execute the DownloadRequest, enforcing the maximum downloads per account 4403a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * parameter 4413a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @param req the DownloadRequest 4423a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @return whether or not the download was started 4433a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank */ 4443a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /*package*/ synchronized boolean tryStartDownload(DownloadRequest req) { 4453a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Intent intent = getServiceIntentForAccount(req.accountId); 4463a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (intent == null) return false; 4473a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4483a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // Do not download the same attachment multiple times 4493a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank boolean alreadyInProgress = mDownloadsInProgress.get(req.attachmentId) != null; 4503a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (alreadyInProgress) return false; 4513a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4523a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank try { 4533a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (Email.DEBUG) { 4543a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Log.d(TAG, ">> Starting download for attachment #" + req.attachmentId); 4553a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4563a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank startDownload(intent, req); 4573a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } catch (RemoteException e) { 4583a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // TODO: Consider whether we need to do more in this case... 4593a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // For now, fix up our data to reflect the failure 4603a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank cancelDownload(req); 4613a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4623a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return true; 4633a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4643a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4653a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private synchronized DownloadRequest getDownloadInProgress(long attachmentId) { 4663a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return mDownloadsInProgress.get(attachmentId); 4673a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank } 4683a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank 4693a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /** 4703bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * Do the work of starting an attachment download using the EmailService interface, and 4713bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * set our watchdog alarm 4723bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * 4733bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @param serviceClass the class that will attempt the download 4743bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @param req the DownloadRequest 4753bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank * @throws RemoteException 4763bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank */ 4773a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private void startDownload(Intent intent, DownloadRequest req) 4783bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank throws RemoteException { 4793bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank req.startTime = System.currentTimeMillis(); 4803bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank req.inProgress = true; 4813bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mDownloadsInProgress.put(req.attachmentId, req); 4823bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank EmailServiceProxy proxy = 4833a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank new EmailServiceProxy(mContext, intent, mServiceCallback); 484dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank proxy.loadAttachment(req.attachmentId, req.priority != PRIORITY_FOREGROUND); 4853bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Lazily initialize our (reusable) pending intent 4863bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank if (mWatchdogPendingIntent == null) { 487ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy createWatchdogPendingIntent(mContext); 4883bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 4893bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank // Set the alarm 4903bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, 4913bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank System.currentTimeMillis() + WATCHDOG_CHECK_INTERVAL, WATCHDOG_CHECK_INTERVAL, 4923bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank mWatchdogPendingIntent); 4933bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 49481273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank 495ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy /*package*/ void createWatchdogPendingIntent(Context context) { 496ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy Intent alarmIntent = new Intent(context, Watchdog.class); 497ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy mWatchdogPendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0); 498ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy mAlarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 499ffe6ef342a6d9ea958aaee11b57db54e52252ddeTodd Kennedy } 5002f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank 50169fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank private void cancelDownload(DownloadRequest req) { 50269fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank mDownloadsInProgress.remove(req.attachmentId); 50369fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank req.inProgress = false; 50469fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank } 50569fc25244ba1b30856426c77c2e4be3964eb50daMarc Blank 50609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 50709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called when a download is finished; we get notified of this via our EmailServiceCallback 50809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the attachment whose download is finished 50909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param statusCode the EmailServiceStatus code returned by the Service 51009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 511f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void endDownload(long attachmentId, int statusCode) { 51209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Say we're no longer downloading this 51309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mDownloadsInProgress.remove(attachmentId); 514819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 515819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // TODO: This code is conservative and treats connection issues as failures. 516819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Since we have no mechanism to throttle reconnection attempts, it makes 517819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // sense to be cautious here. Once logic is in place to prevent connecting 518819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // in a tight loop, we can exclude counting connection issues as "failures". 519819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 520819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy // Update the attachment failure list if needed 521819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy Integer downloadCount; 522819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount = mAttachmentFailureMap.remove(attachmentId); 523819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (statusCode != EmailServiceStatus.SUCCESS) { 524819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy if (downloadCount == null) { 525819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount = 0; 526819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 527819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy downloadCount += 1; 528819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy mAttachmentFailureMap.put(attachmentId, downloadCount); 529819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy } 530819db01eadc3a95b02b2f820b483056b68c631f0Todd Kennedy 531f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 53209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.CONNECTION_ERROR) { 53309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If this needs to be retried, just process the queue again 53409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 53509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== The download for attachment #" + attachmentId + 53609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank " will be retried"); 53709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 53809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req != null) { 53909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req.inProgress = false; 54009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 54109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 54209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return; 54309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 544b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 545782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank // If the request is still in the queue, remove it 546782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank if (req != null) { 547782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank remove(req); 548782c21e953f3a157b2771cb15e097ed812e27ab1Marc Blank } 54909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 550b961c78ff4bd250d4a25497158162cb230a55057Marc Blank long secs = 0; 551b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if (req != null) { 552b961c78ff4bd250d4a25497158162cb230a55057Marc Blank secs = (System.currentTimeMillis() - req.time) / 1000; 553b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 55409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" : 55509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank "Error " + statusCode; 55609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "<< Download finished for attachment #" + attachmentId + "; " + secs + 55709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank " seconds from request, status: " + status); 55809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 559b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 56009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); 56109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 56275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long accountId = attachment.mAccountKey; 56375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Update our attachment storage for this account 56475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Long currentStorage = mAttachmentStorageMap.get(accountId); 56575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (currentStorage == null) { 56675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank currentStorage = 0L; 56775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 56875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAttachmentStorageMap.put(accountId, currentStorage + attachment.mSize); 56909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean deleted = false; 57009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if ((attachment.mFlags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 57109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.ATTACHMENT_NOT_FOUND) { 572f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is a forwarding download, and the attachment doesn't exist (or 57309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // can't be downloaded) delete it from the outgoing message, lest that 57409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // message never get sent 57509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId); 57609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // TODO: Talk to UX about whether this is even worth doing 577d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank NotificationController nc = NotificationController.getInstance(mContext); 578d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank nc.showDownloadForwardFailedNotification(attachment); 57909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank deleted = true; 58009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If we're an attachment on forwarded mail, and if we're not still blocked, 58209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // try to send pending mail now (as mediated by MailService) 58336cc9c18edb7ee897d0d758788b6e338cb2ef126Makoto Onuki if ((req != null) && 584b961c78ff4bd250d4a25497158162cb230a55057Marc Blank !Utility.hasUnloadedAttachments(mContext, attachment.mMessageKey)) { 58509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 58609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Downloads finished for outgoing msg #" + req.messageId); 58709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank MailService.actionSendPendingMail(mContext, req.accountId); 58909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 59009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 5912ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank if (statusCode == EmailServiceStatus.MESSAGE_NOT_FOUND) { 5922f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank Message msg = Message.restoreMessageWithId(mContext, attachment.mMessageKey); 5932f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank if (msg == null) { 5942f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank // If there's no associated message, delete the attachment 5952f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId); 5962f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank } else { 5972f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank // If there really is a message, retry 5982f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank kick(); 5992f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank return; 6002f6cbb021cd97e2450c29b72a27236ba4ef20823Marc Blank } 6012ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank } else if (!deleted) { 60209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Clear the download flags, since we're done for now. Note that this happens 60309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // only for non-recoverable errors. When these occur for forwarded mail, we can 60409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // ignore it and continue; otherwise, it was either 1) a user request, in which 60509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the user can retry manually or 2) an opportunistic download, in which 60609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the download wasn't critical 60709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank ContentValues cv = new ContentValues(); 60809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int flags = 60909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 61009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank cv.put(Attachment.FLAGS, attachment.mFlags &= ~flags); 61109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.update(mContext, cv); 61209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue 61509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 61609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 619f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 620f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Calculate the download priority of an Attachment. A priority of zero means that the 621f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * attachment is not marked for download. 622f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @param att the Attachment 623f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the priority key of the Attachment 624f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 625f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static int getPriority(Attachment att) { 626f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int priorityClass = PRIORITY_NONE; 627f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int flags = att.mFlags; 628f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 629f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy priorityClass = PRIORITY_SEND_MAIL; 630f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) { 631f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy priorityClass = PRIORITY_FOREGROUND; 632f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 633f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return priorityClass; 63409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 63509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 63609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private void kick() { 63709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 63809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.notify(); 63909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 64009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 64109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 64209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 64309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * We use an EmailServiceCallback to keep track of the progress of downloads. These callbacks 64464b64cca01c1a827c1b3824f099fd638cfb15826Marc Blank * come from either Controller (IMAP) or ExchangeService (EAS). Note that we only implement the 64509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * single callback that's defined by the EmailServiceCallback interface. 64609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 64709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private class ServiceCallback extends IEmailServiceCallback.Stub { 64809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, 64909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int progress) { 650a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // Record status and progress 65175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank DownloadRequest req = mDownloadSet.getDownloadInProgress(attachmentId); 652a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req != null) { 65375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (Email.DEBUG) { 65475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank String code; 65575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank switch(statusCode) { 65675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank case EmailServiceStatus.SUCCESS: code = "Success"; break; 65775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank case EmailServiceStatus.IN_PROGRESS: code = "In progress"; break; 65875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank default: code = Integer.toString(statusCode); break; 65975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 66075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (statusCode != EmailServiceStatus.IN_PROGRESS) { 66175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, ">> Attachment " + attachmentId + ": " + code); 66275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else if (progress >= (req.lastProgress + 15)) { 66375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank Log.d(TAG, ">> Attachment " + attachmentId + ": " + progress + "%"); 66475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 66575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 666a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastStatusCode = statusCode; 667a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress = progress; 668a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime = System.currentTimeMillis(); 669a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 67009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank switch (statusCode) { 67109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank case EmailServiceStatus.IN_PROGRESS: 67209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 67309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank default: 674f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.endDownload(attachmentId, statusCode); 67509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 67609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 67709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 67809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 67909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 68009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void sendMessageStatus(long messageId, String subject, int statusCode, int progress) 68109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 68209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 68309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 68409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 68509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void syncMailboxListStatus(long accountId, int statusCode, int progress) 68609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 68709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 68809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 68909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 69009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void syncMailboxStatus(long mailboxId, int statusCode, int progress) 69109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 69209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 69309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 69409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 69509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 6963a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * Return an Intent to be used used based on the account type of the provided account id. We 69709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * cache the results to avoid repeated database access 69809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param accountId the id of the account 6993a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank * @return the Intent to be used for the account or null (if the account no longer exists) 70009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 7013a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank private synchronized Intent getServiceIntentForAccount(long accountId) { 7023a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank // TODO: We should have some more data-driven way of determining the service intent. 7033a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Intent serviceIntent = mAccountServiceMap.get(accountId); 7043a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank if (serviceIntent == null) { 70509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String protocol = Account.getProtocol(mContext, accountId); 706edb05ca5ee21a9f410416b427141be59be01f5d2Marc Blank if (protocol == null) return null; 7073a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank serviceIntent = new Intent(mContext, ControllerService.class); 70809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (protocol.equals("eas")) { 7093a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank serviceIntent = new Intent(EmailServiceProxy.EXCHANGE_INTENT); 71009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 7113a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAccountServiceMap.put(accountId, serviceIntent); 71209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 7133a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank return serviceIntent; 71409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 71509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 7163a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank /*package*/ void addServiceIntentForTest(long accountId, Intent intent) { 7173a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAccountServiceMap.put(accountId, intent); 71809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 71909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 720f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ void onChange(Attachment att) { 721ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mDownloadSet.onChange(this, att); 72209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 72309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 724f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean isQueued(long attachmentId) { 725f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return mDownloadSet.findDownloadRequest(attachmentId) != null; 726f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 727f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 728fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /*package*/ int getSize() { 729fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return mDownloadSet.size(); 730fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 731fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 732f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean dequeue(long attachmentId) { 733f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 734f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 735b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if (Email.DEBUG) { 736b961c78ff4bd250d4a25497158162cb230a55057Marc Blank Log.d(TAG, "Dequeued attachmentId: " + attachmentId); 737b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 738f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.remove(req); 739f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return true; 740f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 741f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return false; 74209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 74309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 74409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 745fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * Ask the service for the number of items in the download queue 746fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * @return the number of items queued for download 747fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank */ 748fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank public static int getQueueSize() { 749ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 750ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 751ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.getSize(); 752fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 753fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return 0; 754fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 755fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 756fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /** 75709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service whether a particular attachment is queued for download 75809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 75909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment is queued for download 76009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 76109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean isAttachmentQueued(long attachmentId) { 762ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 763ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 764ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.isQueued(attachmentId); 76509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 76609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 76709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 76809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 76909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 77009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service to remove an attachment from the download queue 77109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 77209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment was removed from the queue 77309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 77409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean cancelQueuedAttachment(long attachmentId) { 775ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 776ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 777ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank return service.dequeue(attachmentId); 77809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 77909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 78009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 78109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 7823bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank public static void watchdogAlarm() { 783ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank AttachmentDownloadService service = sRunningService; 784ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank if (service != null) { 785ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank service.mDownloadSet.onWatchdogAlarm(); 7863bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 7873bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank } 7883bbc690600d5fe697f02b739d4fe18f0f9f03314Marc Blank 78909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 79009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called directly by EmailProvider whenever an attachment is inserted or changed 791b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank * @param context the caller's context 79209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the attachment's id 79309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param flags the new flags for the attachment 79409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 795b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank public static void attachmentChanged(final Context context, final long id, final int flags) { 79609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Utility.runAsync(new Runnable() { 79709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 798b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId(context, id); 79909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 80009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Store the flags we got from EmailProvider; given that all of this 80109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // activity is asynchronous, we need to use the newest data from 80209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // EmailProvider 80309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.mFlags = flags; 804b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank Intent intent = new Intent(context, AttachmentDownloadService.class); 805b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank intent.putExtra(EXTRA_ATTACHMENT, attachment); 806b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank context.startService(intent); 80709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 80809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank }}); 80909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 81009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 81175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank /** 81275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * Determine whether an attachment can be prefetched for the given account 81375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank * @return true if download is allowed, false otherwise 81475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank */ 8153a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank public boolean canPrefetchForAccount(Account account, File dir) { 816475c20d3a883737853aa7301cc649736a36387c5Marc Blank // Check account, just in case 817475c20d3a883737853aa7301cc649736a36387c5Marc Blank if (account == null) return false; 818475c20d3a883737853aa7301cc649736a36387c5Marc Blank // First, check preference and quickly return if prefetch isn't allowed 819475c20d3a883737853aa7301cc649736a36387c5Marc Blank if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) return false; 820475c20d3a883737853aa7301cc649736a36387c5Marc Blank 82175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long totalStorage = dir.getTotalSpace(); 82275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long usableStorage = dir.getUsableSpace(); 82375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE); 82475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 82575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // If there's not enough overall storage available, stop now 82675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (usableStorage < minAvailable) { 82775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return false; 82875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 82975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 83075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts(); 83175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank long perAccountMaxStorage = 83275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank (long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts); 83375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 83475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Retrieve our idea of currently used attachment storage; since we don't track deletions, 83575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // this number is the "worst case". If the number is greater than what's allowed per 83675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // account, we walk the directory to determine the actual number 8373a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Long accountStorage = mAttachmentStorageMap.get(account.mId); 83875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (accountStorage == null || (accountStorage > perAccountMaxStorage)) { 83975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Calculate the exact figure for attachment storage for this account 84075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage = 0L; 84175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank File[] files = dir.listFiles(); 84275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (files != null) { 84375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank for (File file : files) { 84475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage += file.length(); 84575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 84675a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 84775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Cache the value 8483a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank mAttachmentStorageMap.put(account.mId, accountStorage); 84975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 85075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 85175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank // Return true if we're using less than the maximum per account 85275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (accountStorage < perAccountMaxStorage) { 85375a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return true; 85475a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } else { 85575a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank if (Email.DEBUG) { 8563a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank Log.d(TAG, ">> Prefetch not allowed for account " + account.mId + "; used " + 85775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank accountStorage + ", limit " + perAccountMaxStorage); 85875a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 85975a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank return false; 86075a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 86175a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank } 86275a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank 86309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 864ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // These fields are only used within the service thread 86509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mContext = this; 866ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mConnectivityManager = new EmailConnectivityManager(this, TAG); 86775a873be8420e50f0aeb5a77716358ee0ca66b01Marc Blank mAccountManagerStub = new AccountManagerStub(this); 8687fbcefff7d5a745335d1ec562e783a59087cc0b1Marc Blank 86909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Run through all attachments in the database that require download and add them to 87009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // the queue 87109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 87209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Cursor c = getContentResolver().query(Attachment.CONTENT_URI, 87309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.ID_PROJECTION, "(" + Attachment.FLAGS + " & ?) != 0", 87409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new String[] {Integer.toString(mask)}, null); 87509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 87609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "Count: " + c.getCount()); 87709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (c.moveToNext()) { 87809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId( 87909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank this, c.getLong(EmailContent.ID_PROJECTION_COLUMN)); 88009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 881ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank mDownloadSet.onChange(this, attachment); 88209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 88309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 88409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (Exception e) { 88509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank e.printStackTrace(); 88609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 88709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank finally { 88809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank c.close(); 88909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 89009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 89109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Loop until stopped, with a 30 minute wait loop 89209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (!mStop) { 89309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Here's where we run our attachment loading logic... 89481273dfcee3b075451860f60ee15f2aa06ba81ecMarc Blank mConnectivityManager.waitForConnectivity(); 895f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.processQueue(); 896b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank if (mDownloadSet.isEmpty()) { 897b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank Log.d(TAG, "*** All done; shutting down service"); 898b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank stopSelf(); 899b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank break; 900b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 90109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 90209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 90309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.wait(PROCESS_QUEUE_WAIT_TIME); 90409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (InterruptedException e) { 90509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // That's ok; we'll just keep looping 90609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 90709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 90809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 909ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank 910ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank // Unregister now that we're done 911b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank if (mConnectivityManager != null) { 912b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank mConnectivityManager.unregister(); 913b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 91409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 91509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 91609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 91709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public int onStartCommand(Intent intent, int flags, int startId) { 918b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank if (sRunningService == null) { 919b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank sRunningService = this; 920b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 92190a48115517c49a954eaa1b79b72a23b4486107aMarc Blank if (intent != null && intent.hasExtra(EXTRA_ATTACHMENT)) { 922b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank Attachment att = (Attachment)intent.getParcelableExtra(EXTRA_ATTACHMENT); 923b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank onChange(att); 924b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 92509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return Service.START_STICKY; 92609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 92709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 92809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 92909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onCreate() { 93009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Start up our service thread 93109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new Thread(this, "AttachmentDownloadService").start(); 93209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 93309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 93409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public IBinder onBind(Intent intent) { 93509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return null; 93609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 93709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 93809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 93909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onDestroy() { 940cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank // Mark this instance of the service as stopped 941cb2f0a8bb9ff6728a111b1f790c12555ac7086fbMarc Blank mStop = true; 94209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (sRunningService != null) { 94309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 944b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank sRunningService = null; 945b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank } 946b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank if (mConnectivityManager != null) { 947b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank mConnectivityManager.unregister(); 948b2a8c2ce4c617d59cb4876951cb952fc97e8382bMarc Blank mConnectivityManager = null; 94909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 95009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 951a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank 952a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank @Override 953a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 954a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println("AttachmentDownloadService"); 955a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long time = System.currentTimeMillis(); 956a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank synchronized(mDownloadSet) { 957a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Queue, " + mDownloadSet.size() + " entries"); 958a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 959a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // First, start up any required downloads, in priority order 960a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank while (iterator.hasNext()) { 961a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank DownloadRequest req = iterator.next(); 962a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Account: " + req.accountId + ", Attachment: " + req.attachmentId); 963a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Priority: " + req.priority + ", Time: " + req.time + 964a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank (req.inProgress ? " [In progress]" : "")); 965ed9938cd9caf4937eadb3e35333e2fe2b157bde5Marc Blank Attachment att = Attachment.restoreAttachmentWithId(this, req.attachmentId); 966a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att == null) { 967a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Attachment not in database?"); 968eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank } else if (att.mFileName != null) { 969a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String fileName = att.mFileName; 970a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String suffix = "[none]"; 971a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastDot = fileName.lastIndexOf('.'); 972a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (lastDot >= 0) { 973a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank suffix = fileName.substring(lastDot); 974a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 975a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Suffix: " + suffix); 976a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att.mContentUri != null) { 977a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" ContentUri: " + att.mContentUri); 978a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 979a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Mime: "); 980a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att.mMimeType != null) { 981a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(att.mMimeType); 982a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } else { 9838a574694606f0e5d781334d0d426fc379c51f3edMarc Blank pw.print(AttachmentUtilities.inferMimeType(fileName, null)); 984eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank pw.print(" [inferred]"); 985a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 986a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Size: " + att.mSize); 987a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 988a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.inProgress) { 989a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Status: " + req.lastStatusCode + ", Progress: " + 990a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress); 991a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Started: " + req.startTime + ", Callback: " + 992a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime); 993a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Elapsed: " + ((time - req.startTime) / 1000L) + "s"); 994a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.lastCallbackTime > 0) { 995a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" CB: " + ((time - req.lastCallbackTime) / 1000L) + "s"); 996a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 997a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 998a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 999a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 1000a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 100109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank} 1002