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