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