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