1cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee/*
2cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * Copyright (C) 2014 The Android Open Source Project
3cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee *
4cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * Licensed under the Apache License, Version 2.0 (the "License");
5cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * you may not use this file except in compliance with the License.
6cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * You may obtain a copy of the License at
7cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee *
8cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee *      http://www.apache.org/licenses/LICENSE-2.0
9cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee *
10cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * Unless required by applicable law or agreed to in writing, software
11cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * distributed under the License is distributed on an "AS IS" BASIS,
12cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * See the License for the specific language governing permissions and
14cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * limitations under the License.
15cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee */
16cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
17cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leepackage com.android.email.service;
18cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
19cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.accounts.AccountManager;
20cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.app.AlarmManager;
21cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.app.PendingIntent;
22cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.app.Service;
23cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.content.BroadcastReceiver;
24cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.content.ContentValues;
25cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.content.Context;
26cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.content.Intent;
27cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.database.Cursor;
28cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.net.ConnectivityManager;
29cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.net.Uri;
30cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.os.IBinder;
31cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.os.RemoteException;
32cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.os.SystemClock;
33cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport android.text.format.DateUtils;
34cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
35cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.email.AttachmentInfo;
36cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.email.EmailConnectivityManager;
37bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrookimport com.android.email.NotificationControllerCreatorHolder;
38cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.email.NotificationController;
39cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.Account;
40cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent;
41cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent.Attachment;
42cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent.AttachmentColumns;
43cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent.Message;
44cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.service.EmailServiceProxy;
45cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.service.EmailServiceStatus;
46cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.service.IEmailServiceCallback;
47cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.utility.AttachmentUtilities;
48cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.utility.Utility;
49cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.mail.providers.UIProvider.AttachmentState;
50cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.mail.utils.LogUtils;
510e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Leeimport com.google.common.annotations.VisibleForTesting;
52cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
53cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.io.File;
54cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.io.FileDescriptor;
55cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.io.PrintWriter;
56a72a12241f413f113d61d318757a49b7e33c2af6Anthony Leeimport java.util.Collection;
57cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.Comparator;
58cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.HashMap;
590e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Leeimport java.util.PriorityQueue;
60cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.Queue;
61cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.concurrent.ConcurrentHashMap;
62cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.concurrent.ConcurrentLinkedQueue;
63cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
64cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leepublic class AttachmentService extends Service implements Runnable {
650e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee    // For logging.
660e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee    public static final String LOG_TAG = "AttachmentService";
67cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
68ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    // STOPSHIP Set this to 0 before shipping.
69ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    private static final int ENABLE_ATTACHMENT_SERVICE_DEBUG = 0;
70ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
71cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Minimum wait time before retrying a download that failed due to connection error
72cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final long CONNECTION_ERROR_RETRY_MILLIS = 10 * DateUtils.SECOND_IN_MILLIS;
73cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Number of retries before we start delaying between
74cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final long CONNECTION_ERROR_DELAY_THRESHOLD = 5;
75cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Maximum time to retry for connection errors.
76cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final long CONNECTION_ERROR_MAX_RETRIES = 10;
77cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
78cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Our idle time, waiting for notifications; this is something of a failsafe
79cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final int PROCESS_QUEUE_WAIT_TIME = 30 * ((int)DateUtils.MINUTE_IN_MILLIS);
80cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // How long we'll wait for a callback before canceling a download and retrying
81cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final int CALLBACK_TIMEOUT = 30 * ((int)DateUtils.SECOND_IN_MILLIS);
82cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Try to download an attachment in the background this many times before giving up
83cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final int MAX_DOWNLOAD_RETRIES = 5;
840e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
85e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static final int PRIORITY_NONE = -1;
86cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // High priority is for user requests
87e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static final int PRIORITY_FOREGROUND = 0;
88e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static final int PRIORITY_HIGHEST = PRIORITY_FOREGROUND;
890e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee    // Normal priority is for forwarded downloads in outgoing mail
90e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static final int PRIORITY_SEND_MAIL = 1;
910e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee    // Low priority will be used for opportunistic downloads
92e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static final int PRIORITY_BACKGROUND = 2;
93e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static final int PRIORITY_LOWEST = PRIORITY_BACKGROUND;
94cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
95cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Minimum free storage in order to perform prefetch (25% of total memory)
96cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F;
97cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Maximum prefetch storage (also 25% of total memory)
98cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final float PREFETCH_MAXIMUM_ATTACHMENT_STORAGE = 0.25F;
99cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
100cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // We can try various values here; I think 2 is completely reasonable as a first pass
101cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2;
102cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Limit on the number of simultaneous downloads per account
103cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Note that a limit of 1 is currently enforced by both Services (MailService and Controller)
104cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final int MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT = 1;
105cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // Limit on the number of attachments we'll check for background download
106cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    private static final int MAX_ATTACHMENTS_TO_CHECK = 25;
107cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
108ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    private static final String EXTRA_ATTACHMENT_ID =
109ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            "com.android.email.AttachmentService.attachment_id";
110ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    private static final String EXTRA_ATTACHMENT_FLAGS =
111ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            "com.android.email.AttachmentService.attachment_flags";
112a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
113e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    // This callback is invoked by the various service implementations to give us download progress
114a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    // since those modules are responsible for the actual download.
11539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    final ServiceCallback mServiceCallback = new ServiceCallback();
116cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
117cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // sRunningService is only set in the UI thread; it's visibility elsewhere is guaranteed
118cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // by the use of "volatile"
119e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static volatile AttachmentService sRunningService = null;
120cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
121a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    // Signify that we are being shut down & destroyed.
122a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    private volatile boolean mStop = false;
123a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
124e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    EmailConnectivityManager mConnectivityManager;
12539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
12639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    // Helper class that keeps track of in progress downloads to make sure that they
12739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    // are progressing well.
128e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    final AttachmentWatchdog mWatchdog = new AttachmentWatchdog();
129cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
130a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    private final Object mLock = new Object();
131cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
132a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    // A map of attachment storage used per account as we have account based maximums to follow.
133cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // NOTE: This map is not kept current in terms of deletions (i.e. it stores the last calculated
134a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    // amount plus the size of any new attachments loaded).  If and when we reach the per-account
135cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // limit, we recalculate the actual usage
136e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    final ConcurrentHashMap<Long, Long> mAttachmentStorageMap = new ConcurrentHashMap<Long, Long>();
137a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
138cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // A map of attachment ids to the number of failed attempts to download the attachment
139cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    // NOTE: We do not want to persist this. This allows us to retry background downloading
140824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee    // if any transient network errors are fixed and the app is restarted
141824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee    final ConcurrentHashMap<Long, Integer> mAttachmentFailureMap =
142824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee            new ConcurrentHashMap<Long, Integer>();
143cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
144a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    // Keeps tracks of downloads in progress based on an attachment ID to DownloadRequest mapping.
145e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    final ConcurrentHashMap<Long, DownloadRequest> mDownloadsInProgress =
146a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            new ConcurrentHashMap<Long, DownloadRequest>();
147cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
148e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    final DownloadQueue mDownloadQueue = new DownloadQueue();
149cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
15039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    // The queue entries here are entries of the form {id, flags}, with the values passed in to
151ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    // attachmentChanged(). Entries in the queue are picked off in processQueue().
15239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    private static final Queue<long[]> sAttachmentChangedQueue =
15339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            new ConcurrentLinkedQueue<long[]>();
15439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
155ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    // Extra layer of control over debug logging that should only be enabled when
156ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    // we need to take an extra deep dive at debugging the workflow in this class.
157ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    static private void debugTrace(final String format, final Object... args) {
158ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if (ENABLE_ATTACHMENT_SERVICE_DEBUG > 0) {
159ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, String.format(format, args));
160ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        }
161ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    }
16239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
1630e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee    /**
1640e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * This class is used to contain the details and state of a particular request to download
1650e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * an attachment. These objects are constructed and either placed in the {@link DownloadQueue}
1660e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * or in the in-progress map used to keep track of downloads that are currently happening
1670e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * in the system
1680e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     */
169e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static class DownloadRequest {
1700e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        // Details of the request.
1710e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        final int mPriority;
172ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        final long mCreatedTime;
1730e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        final long mAttachmentId;
1740e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        final long mMessageId;
1750e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        final long mAccountId;
1760e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
1770e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        // Status of the request.
1780e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        boolean mInProgress = false;
1790e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        int mLastStatusCode;
1800e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        int mLastProgress;
1810e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        long mLastCallbackTime;
1820e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        long mStartTime;
1830e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        long mRetryCount;
1840e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        long mRetryStartTime;
1850e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
1860e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        /**
1870e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * This constructor is mainly used for tests
1880e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @param attPriority The priority of this attachment
1890e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @param attId The id of the row in the attachment table.
1900e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         */
1910e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        @VisibleForTesting
1920e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        DownloadRequest(final int attPriority, final long attId) {
1930e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            // This constructor should only be used for unit tests.
194ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            mCreatedTime = SystemClock.elapsedRealtime();
1950e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mPriority = attPriority;
1960e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mAttachmentId = attId;
1970e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mAccountId = -1;
1980e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mMessageId = -1;
1990e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        }
2000e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
2010e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        private DownloadRequest(final Context context, final Attachment attachment) {
2020e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mAttachmentId = attachment.mId;
2030e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            final Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey);
204cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            if (msg != null) {
2050e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                mAccountId = msg.mAccountKey;
2060e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                mMessageId = msg.mId;
207cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            } else {
2080e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                mAccountId = mMessageId = -1;
209cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
21039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            mPriority = getAttachmentPriority(attachment);
211ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            mCreatedTime = SystemClock.elapsedRealtime();
212cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
213cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
2140e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        private DownloadRequest(final DownloadRequest orig, final long newTime) {
2150e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mPriority = orig.mPriority;
2160e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mAttachmentId = orig.mAttachmentId;
2170e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mMessageId = orig.mMessageId;
2180e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mAccountId = orig.mAccountId;
219ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            mCreatedTime = newTime;
2200e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mInProgress = orig.mInProgress;
2210e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mLastStatusCode = orig.mLastStatusCode;
2220e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mLastProgress = orig.mLastProgress;
2230e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mLastCallbackTime = orig.mLastCallbackTime;
2240e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mStartTime = orig.mStartTime;
2250e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mRetryCount = orig.mRetryCount;
2260e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            mRetryStartTime = orig.mRetryStartTime;
227cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
228cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
229cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        @Override
230cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        public int hashCode() {
2310e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            return (int)mAttachmentId;
232cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
233cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
234cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        /**
235cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee         * Two download requests are equals if their attachment id's are equals
236cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee         */
237cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        @Override
2380e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        public boolean equals(final Object object) {
239cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            if (!(object instanceof DownloadRequest)) return false;
2400e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            final DownloadRequest req = (DownloadRequest)object;
2410e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            return req.mAttachmentId == mAttachmentId;
242cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
243cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    }
244cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
245cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    /**
2460e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * This class is used to organize the various download requests that are pending.
2470e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * We need a class that allows us to prioritize a collection of {@link DownloadRequest} objects
2480e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * while being able to pull off request with the highest priority but we also need
2490e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * to be able to find a particular {@link DownloadRequest} by id or by reference for retrieval.
2500e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * Bonus points for an implementation that does not require an iterator to accomplish its tasks
2510e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * as we can avoid pesky ConcurrentModificationException when one thread has the iterator
2520e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     * and another thread modifies the collection.
2530e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee     */
254e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static class DownloadQueue {
2550e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        private final int DEFAULT_SIZE = 10;
2560e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
2570e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        // For synchronization
2580e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        private final Object mLock = new Object();
2590e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
260e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        /**
261e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee         * Comparator class for the download set; we first compare by priority.  Requests with equal
262e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee         * priority are compared by the time the request was created (older requests come first)
263e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee         */
264e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        private static class DownloadComparator implements Comparator<DownloadRequest> {
265e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            @Override
266e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            public int compare(DownloadRequest req1, DownloadRequest req2) {
267e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                int res;
268e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if (req1.mPriority != req2.mPriority) {
269e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    res = (req1.mPriority < req2.mPriority) ? -1 : 1;
270e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                } else {
271ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    if (req1.mCreatedTime == req2.mCreatedTime) {
272e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        res = 0;
273e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    } else {
274ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        res = (req1.mCreatedTime < req2.mCreatedTime) ? -1 : 1;
275e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    }
276e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                }
277e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                return res;
278e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
279e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        }
280e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee
2810e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        // For prioritization of DownloadRequests.
282e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final PriorityQueue<DownloadRequest> mRequestQueue =
283a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                new PriorityQueue<DownloadRequest>(DEFAULT_SIZE, new DownloadComparator());
2840e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
2850e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        // Secondary collection to quickly find objects w/o the help of an iterator.
2860e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        // This class should be kept in lock step with the priority queue.
287ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        final ConcurrentHashMap<Long, DownloadRequest> mRequestMap =
288ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                new ConcurrentHashMap<Long, DownloadRequest>();
2890e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
2900e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        /**
2910e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * This function will add the request to our collections if it does not already
2920e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * exist. If it does exist, the function will silently succeed.
2930e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @param request The {@link DownloadRequest} that should be added to our queue
2940e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @return true if it was added (or already exists), false otherwise
2950e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         */
296e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        public boolean addRequest(final DownloadRequest request)
297a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                throws NullPointerException {
2980e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            // It is key to keep the map and queue in lock step
2990e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            if (request == null) {
300a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                // We can't add a null entry into the queue so let's throw what the underlying
301a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                // data structure would throw.
302a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                throw new NullPointerException();
3030e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            }
3040e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            final long requestId = request.mAttachmentId;
3050e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            if (requestId < 0) {
3060e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                // Invalid request
307ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "Not adding a DownloadRequest with an invalid attachment id");
3080e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                return false;
3090e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            }
310ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Queuing DownloadRequest #%d", requestId);
3110e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            synchronized (mLock) {
3120e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                // Check to see if this request is is already in the queue
3130e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                final boolean exists = mRequestMap.containsKey(requestId);
3140e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                if (!exists) {
3150e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    mRequestQueue.offer(request);
3160e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    mRequestMap.put(requestId, request);
317ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                } else {
318ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    debugTrace("DownloadRequest #%d was already in the queue");
3190e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                }
3200e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            }
3210e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            return true;
3220e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        }
3230e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
3240e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        /**
3250e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * This function will remove the specified request from the internal collections.
3260e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @param request The {@link DownloadRequest} that should be removed from our queue
3270e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @return true if it was removed or the request was invalid (meaning that the request
3280e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * is not in our queue), false otherwise.
3290e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         */
330e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        public boolean removeRequest(final DownloadRequest request) {
3310e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            if (request == null) {
3320e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                // If it is invalid, its not in the queue.
3330e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                return true;
3340e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            }
335ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Removing DownloadRequest #%d", request.mAttachmentId);
3360e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            final boolean result;
3370e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            synchronized (mLock) {
3380e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                // It is key to keep the map and queue in lock step
3390e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                result = mRequestQueue.remove(request);
3400e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                if (result) {
3410e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    mRequestMap.remove(request.mAttachmentId);
3420e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                }
3430e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                return result;
3440e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            }
3450e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        }
3460e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
3470e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        /**
3480e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * Return the next request from our queue.
3490e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @return The next {@link DownloadRequest} object or null if the queue is empty
3500e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         */
351e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        public DownloadRequest getNextRequest() {
3520e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            // It is key to keep the map and queue in lock step
3530e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            final DownloadRequest returnRequest;
3540e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            synchronized (mLock) {
3550e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                returnRequest = mRequestQueue.poll();
3560e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                if (returnRequest != null) {
3570e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    final long requestId = returnRequest.mAttachmentId;
3580e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    mRequestMap.remove(requestId);
3590e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                }
3600e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            }
361ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            if (returnRequest != null) {
362ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("Retrieved DownloadRequest #%d", returnRequest.mAttachmentId);
363ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            }
3640e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            return returnRequest;
3650e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        }
3660e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
3670e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        /**
3680e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * Return the {@link DownloadRequest} with the given ID (attachment ID)
3690e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @param requestId The ID of the request in question
3700e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         * @return The associated {@link DownloadRequest} object or null if it does not exist
3710e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee         */
3720e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        public DownloadRequest findRequestById(final long requestId) {
3730e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            if (requestId < 0) {
3740e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                return null;
3750e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee            }
376e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            synchronized (mLock) {
377e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                return mRequestMap.get(requestId);
378e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
3790e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        }
3800e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
3810e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        public int getSize() {
382e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            synchronized (mLock) {
383e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                return mRequestMap.size();
384e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
3850e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        }
3860e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
3870e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        public boolean isEmpty() {
388e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            synchronized (mLock) {
389e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                return mRequestMap.isEmpty();
390e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
3910e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee        }
3920e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee    }
3930e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee
394a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    /**
395a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee     * Watchdog alarm receiver; responsible for making sure that downloads in progress are not
396a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee     * stalled, as determined by the timing of the most recent service callback
397a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee     */
398a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    public static class AttachmentWatchdog extends BroadcastReceiver {
399a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        // How often our watchdog checks for callback timeouts
400a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        private static final int WATCHDOG_CHECK_INTERVAL = 20 * ((int)DateUtils.SECOND_IN_MILLIS);
401a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        public static final String EXTRA_CALLBACK_TIMEOUT = "callback_timeout";
402a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        private PendingIntent mWatchdogPendingIntent;
403a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
404a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        public void setWatchdogAlarm(final Context context, final long delay,
405a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                final int callbackTimeout) {
406a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            // Lazily initialize the pending intent
407a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            if (mWatchdogPendingIntent == null) {
408a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                Intent intent = new Intent(context, AttachmentWatchdog.class);
409a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                intent.putExtra(EXTRA_CALLBACK_TIMEOUT, callbackTimeout);
410a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                mWatchdogPendingIntent =
411a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                        PendingIntent.getBroadcast(context, 0, intent, 0);
412a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            }
413a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            // Set the alarm
414a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            final AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
415a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay,
416a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                    mWatchdogPendingIntent);
417ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Set up a watchdog for %d millis in the future", delay);
418a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
419a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
420a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        public void setWatchdogAlarm(final Context context) {
421a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            // Call the real function with default values.
422a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            setWatchdogAlarm(context, WATCHDOG_CHECK_INTERVAL, CALLBACK_TIMEOUT);
423a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
424a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
425a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        @Override
426a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        public void onReceive(final Context context, final Intent intent) {
427a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            final int callbackTimeout = intent.getIntExtra(EXTRA_CALLBACK_TIMEOUT,
428a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                    CALLBACK_TIMEOUT);
429a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            new Thread(new Runnable() {
430a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                @Override
431a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                public void run() {
43239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // TODO: Really don't like hard coding the AttachmentService reference here
43339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // as it makes testing harder if we are trying to mock out the service
43439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // We should change this with some sort of getter that returns the
43539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // static (or test) AttachmentService instance to use.
436a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                    final AttachmentService service = AttachmentService.sRunningService;
437a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                    if (service != null) {
438a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                        // If our service instance is gone, just leave
439a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                        if (service.mStop) {
440a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                            return;
441a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                        }
442a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                        // Get the timeout time from the intent.
443a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                        watchdogAlarm(service, callbackTimeout);
444a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                    }
445a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                }
446a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            }, "AttachmentService AttachmentWatchdog").start();
447a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
448a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
44939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        boolean validateDownloadRequest(final DownloadRequest dr, final int callbackTimeout,
45039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final long now) {
45139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            // Check how long it's been since receiving a callback
45239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            final long timeSinceCallback = now - dr.mLastCallbackTime;
45339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (timeSinceCallback > callbackTimeout) {
454ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "Timeout for DownloadRequest #%d ", dr.mAttachmentId);
45539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                return true;
45639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
45739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            return false;
45839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
45939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
460a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        /**
461a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee         * Watchdog for downloads; we use this in case we are hanging on a download, which might
462a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee         * have failed silently (the connection dropped, for example)
463a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee         */
464e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        void watchdogAlarm(final AttachmentService service, final int callbackTimeout) {
465ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Received a timer callback in the watchdog");
466ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
467a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            // We want to iterate on each of the downloads that are currently in progress and
468a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            // cancel the ones that seem to be taking too long.
46939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            final Collection<DownloadRequest> inProgressRequests =
47039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    service.mDownloadsInProgress.values();
471a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            for (DownloadRequest req: inProgressRequests) {
472ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("Checking in-progress request with id: %d", req.mAttachmentId);
47339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final boolean shouldCancelDownload = validateDownloadRequest(req, callbackTimeout,
47439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        System.currentTimeMillis());
47539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                if (shouldCancelDownload) {
476ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "Cancelling DownloadRequest #%d", req.mAttachmentId);
477a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                    service.cancelDownload(req);
478e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // TODO: Should we also mark the attachment as failed at this point in time?
479a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                }
480a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            }
481a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            // Check whether we can start new downloads...
482a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            if (service.isConnected()) {
483a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                service.processQueue();
484a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            }
48539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            issueNextWatchdogAlarm(service);
48639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
48739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
48839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        void issueNextWatchdogAlarm(final AttachmentService service) {
48939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (!service.mDownloadsInProgress.isEmpty()) {
490ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("Rescheduling watchdog...");
491a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                setWatchdogAlarm(service);
492a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            }
493a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
494a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    }
495a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
49639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    /**
49739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * We use an EmailServiceCallback to keep track of the progress of downloads.  These callbacks
498ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee     * come from either Controller (IMAP/POP) or ExchangeService (EAS).  Note that we only
499ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee     * implement the single callback that's defined by the EmailServiceCallback interface.
50039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     */
50139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    class ServiceCallback extends IEmailServiceCallback.Stub {
50239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
50339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        /**
50439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee         * Simple routine to generate updated status values for the Attachment based on the
50539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee         * service callback. Right now it is very simple but factoring out this code allows us
50639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee         * to test easier and very easy to expand in the future.
50739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee         */
50839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        ContentValues getAttachmentUpdateValues(final Attachment attachment,
50939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final int statusCode, final int progress) {
51039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            final ContentValues values = new ContentValues();
51139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (attachment != null) {
51239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                if (statusCode == EmailServiceStatus.IN_PROGRESS) {
51339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // TODO: What else do we want to expose about this in-progress download through
51439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // the provider?  If there is more, make sure that the service implementation
51539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // reports it and make sure that we add it here.
51639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    values.put(AttachmentColumns.UI_STATE, AttachmentState.DOWNLOADING);
51739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    values.put(AttachmentColumns.UI_DOWNLOADED_SIZE,
51839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                            attachment.mSize * progress / 100);
51939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                }
52039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
52139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            return values;
522a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
52339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
52439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        @Override
52539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        public void loadAttachmentStatus(final long messageId, final long attachmentId,
52639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final int statusCode, final int progress) {
527ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace(LOG_TAG, "ServiceCallback for attachment #%d", attachmentId);
528ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
52939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            // Record status and progress
53039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            final DownloadRequest req = mDownloadsInProgress.get(attachmentId);
53139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (req != null) {
532ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                final long now = System.currentTimeMillis();
533ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("ServiceCallback: status code changing from %d to %d",
534ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        req.mLastStatusCode, statusCode);
535ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("ServiceCallback: progress changing from %d to %d",
536ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        req.mLastProgress,progress);
537ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("ServiceCallback: last callback time changing from %d to %d",
538ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        req.mLastCallbackTime, now);
53939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
54039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                // Update some state to keep track of the progress of the download
54139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                req.mLastStatusCode = statusCode;
54239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                req.mLastProgress = progress;
543ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                req.mLastCallbackTime = now;
54439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
54539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                // Update the attachment status in the provider.
54639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final Attachment attachment =
54739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        Attachment.restoreAttachmentWithId(AttachmentService.this, attachmentId);
54839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final ContentValues values = getAttachmentUpdateValues(attachment, statusCode,
54939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        progress);
55039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                if (values.size() > 0) {
55139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    attachment.update(AttachmentService.this, values);
55239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                }
55339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
55439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                switch (statusCode) {
55539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    case EmailServiceStatus.IN_PROGRESS:
55639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        break;
55739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    default:
55839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        // It is assumed that any other error is either a success or an error
55939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        // Either way, the final updates to the DownloadRequest and attachment
56039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        // objects will be handed there.
561ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        LogUtils.d(LOG_TAG, "Attachment #%d is done", attachmentId);
56239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        endDownload(attachmentId, statusCode);
56339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        break;
56439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                }
56539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            } else {
56639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                // The only way that we can get a callback from the service implementation for
56739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                // an attachment that doesn't exist is if it was cancelled due to the
56839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                // AttachmentWatchdog. This is a valid scenario and the Watchdog should have already
56939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                // marked this attachment as failed/cancelled.
57039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
57139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
57239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    }
57339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
57439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    /**
57539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * Called directly by EmailProvider whenever an attachment is inserted or changed. Since this
57639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * call is being invoked on the UI thread, we need to make sure that the downloads are
57739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * happening in the background.
57839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * @param context the caller's context
57939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * @param id the attachment's id
58039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * @param flags the new flags for the attachment
58139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     */
58239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    public static void attachmentChanged(final Context context, final long id, final int flags) {
583ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        LogUtils.d(LOG_TAG, "Attachment with id: %d will potentially be queued for download", id);
584ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        // Throw this info into an intent and send it to the attachment service.
585ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        final Intent intent = new Intent(context, AttachmentService.class);
586ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        debugTrace("Calling startService with extras %d & %d", id, flags);
587ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        intent.putExtra(EXTRA_ATTACHMENT_ID, id);
588ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        intent.putExtra(EXTRA_ATTACHMENT_FLAGS, flags);
589ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        context.startService(intent);
59039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    }
59139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
59239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    /**
59339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * The main entry point for this service, the attachment to download can be identified
59439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * by the EXTRA_ATTACHMENT extra in the intent.
59539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     */
59639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    @Override
59739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    public int onStartCommand(final Intent intent, final int flags, final int startId) {
59839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        if (sRunningService == null) {
59939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            sRunningService = this;
60039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
601ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if (intent != null) {
602ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            // Let's add this id/flags combo to the list of potential attachments to process.
603ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            final long attachment_id = intent.getLongExtra(EXTRA_ATTACHMENT_ID, -1);
604ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            final int attachment_flags = intent.getIntExtra(EXTRA_ATTACHMENT_FLAGS, -1);
605ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            if ((attachment_id >= 0) && (attachment_flags >= 0)) {
606ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                sAttachmentChangedQueue.add(new long[]{attachment_id, attachment_flags});
607ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                // Process the queue if we're in a wait
608ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                kick();
609ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            } else {
610ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("Received an invalid intent w/o the required extras %d & %d",
611ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        attachment_id, attachment_flags);
612ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            }
61339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        } else {
614ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Received a null intent in onStartCommand");
61539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
61639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        return Service.START_STICKY;
617a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    }
618a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
61939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    /**
62039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * Most of the leg work is done by our service thread that is created when this
62139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * service is created.
62239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     */
62339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    @Override
62439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    public void onCreate() {
62539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // Start up our service thread.
62639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        new Thread(this, "AttachmentService").start();
627a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    }
628a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
62939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    @Override
63039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    public IBinder onBind(final Intent intent) {
63139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        return null;
63239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    }
63339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
63439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    @Override
63539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    public void onDestroy() {
636ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        debugTrace("Destroying AttachmentService object");
637ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        dumpInProgressDownloads();
638ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
63939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // Mark this instance of the service as stopped. Our main loop for the AttachmentService
64039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // checks for this flag along with the AttachmentWatchdog.
64139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        mStop = true;
64239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        if (sRunningService != null) {
64339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            // Kick it awake to get it to realize that we are stopping.
64439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            kick();
64539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            sRunningService = null;
64639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
64739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        if (mConnectivityManager != null) {
64839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            mConnectivityManager.unregister();
64939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            mConnectivityManager.stopWait();
65039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            mConnectivityManager = null;
65139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
652a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    }
653a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
654a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    /**
65539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * The main routine for our AttachmentService service thread.
656cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     */
65739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    @Override
65839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    public void run() {
65939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // These fields are only used within the service thread
66039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        mConnectivityManager = new EmailConnectivityManager(this, LOG_TAG);
66139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        mAccountManagerStub = new AccountManagerStub(this);
66239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
66339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // Run through all attachments in the database that require download and add them to
66439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // the queue. This is the case where a previous AttachmentService may have been notified
66539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // to stop before processing everything in its queue.
66639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
66739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final Cursor c = getContentResolver().query(Attachment.CONTENT_URI,
66839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                EmailContent.ID_PROJECTION, "(" + AttachmentColumns.FLAGS + " & ?) != 0",
66939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                new String[] {Integer.toString(mask)}, null);
67039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        try {
671ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG,
672ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    "Count of previous downloads to resume (from db): %d", c.getCount());
67339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            while (c.moveToNext()) {
67439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final Attachment attachment = Attachment.restoreAttachmentWithId(
67539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        this, c.getLong(EmailContent.ID_PROJECTION_COLUMN));
67639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                if (attachment != null) {
677ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    debugTrace("Attempting to download attachment #%d again.", attachment.mId);
67839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    onChange(this, attachment);
67939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                }
68039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
68139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        } catch (Exception e) {
68239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            e.printStackTrace();
68339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        } finally {
68439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            c.close();
68539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
68639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
68739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // Loop until stopped, with a 30 minute wait loop
68839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        while (!mStop) {
68939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            // Here's where we run our attachment loading logic...
69039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            // Make a local copy of the variable so we don't null-crash on service shutdown
69139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            final EmailConnectivityManager ecm = mConnectivityManager;
69239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (ecm != null) {
69339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                ecm.waitForConnectivity();
69439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
69539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (mStop) {
69639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                // We might be bailing out here due to the service shutting down
697ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "AttachmentService has been instructed to stop");
69839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                break;
69939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
700ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
701ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            // In advanced debug mode, let's look at the state of all in-progress downloads
702ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            // after processQueue() runs.
703824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee            debugTrace("In progress downloads before processQueue");
704ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            dumpInProgressDownloads();
70539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            processQueue();
706824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee            debugTrace("In progress downloads after processQueue");
707ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            dumpInProgressDownloads();
708ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
709ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            if (mDownloadQueue.isEmpty() && (mDownloadsInProgress.size() < 1)) {
710ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "Shutting down service. No in-progress or pending downloads.");
71139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                stopSelf();
71239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                break;
71339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
714824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee            debugTrace("Run() idle, wait for mLock (something to do)");
71539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            synchronized(mLock) {
71639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                try {
71739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    mLock.wait(PROCESS_QUEUE_WAIT_TIME);
71839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                } catch (InterruptedException e) {
71939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    // That's ok; we'll just keep looping
72039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                }
72139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
722824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee            debugTrace("Run() got mLock (there is work to do or we timed out)");
72339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
72439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
72539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // Unregister now that we're done
72639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        // Make a local copy of the variable so we don't null-crash on service shutdown
72739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final EmailConnectivityManager ecm = mConnectivityManager;
72839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        if (ecm != null) {
72939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            ecm.unregister();
73039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
73139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    }
73239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
73339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    /*
73439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * Function that kicks the service into action as it may be waiting for this object
73539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * as it processed the last round of attachments.
73639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     */
73739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    private void kick() {
73839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        synchronized(mLock) {
73939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            mLock.notify();
74039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
741e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    }
742cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
743e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    /**
744e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * onChange is called by the AttachmentReceiver upon receipt of a valid notification from
745e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * EmailProvider that an attachment has been inserted or modified.  It's not strictly
746e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * necessary that we detect a deleted attachment, as the code always checks for the
747e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * existence of an attachment before acting on it.
748e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     */
749e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    public synchronized void onChange(final Context context, final Attachment att) {
750ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        debugTrace("onChange() for Attachment: #%d", att.mId);
751e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        DownloadRequest req = mDownloadQueue.findRequestById(att.mId);
75239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final long priority = getAttachmentPriority(att);
753e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if (priority == PRIORITY_NONE) {
754ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Attachment #%d has no priority and will not be downloaded",
755ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    att.mId);
756e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // In this case, there is no download priority for this attachment
757e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (req != null) {
758e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // If it exists in the map, remove it
759e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // NOTE: We don't yet support deleting downloads in progress
760e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                mDownloadQueue.removeRequest(req);
761e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
762e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        } else {
763e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // Ignore changes that occur during download
764ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            if (mDownloadsInProgress.containsKey(att.mId)) {
765ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                debugTrace("Attachment #%d was already in the queue", att.mId);
766ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                return;
767ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            }
768e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // If this is new, add the request to the queue
769e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (req == null) {
770ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "Attachment #%d is a new download request", att.mId);
771e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                req = new DownloadRequest(context, att);
772e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                final AttachmentInfo attachInfo = new AttachmentInfo(context, att);
773e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if (!attachInfo.isEligibleForDownload()) {
774ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "Attachment #%d is not eligible for download", att.mId);
775e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // We can't download this file due to policy, depending on what type
776e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // of request we received, we handle the response differently.
777e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    if (((att.mFlags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) ||
778e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            ((att.mFlags & Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD) != 0)) {
779ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        LogUtils.w(LOG_TAG, "Attachment #%d cannot be downloaded ever", att.mId);
780e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // There are a couple of situations where we will not even allow this
781e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // request to go in the queue because we can already process it as a
782e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // failure.
783ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        // 1. The user explicitly wants to download this attachment from the
784e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // email view but they should not be able to...either because there is
785e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // no app to view it or because its been marked as a policy violation.
786e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // 2. The user is forwarding an email and the attachment has been
787e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // marked as a policy violation. If the attachment is non viewable
788e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // that is OK for forwarding a message so we'll let it pass through
789e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        markAttachmentAsFailed(att);
790e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        return;
791cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    }
792e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // If we get this far it a forward of an attachment that is only
793e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // ineligible because we can't view it or process it. Not because we
794e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // can't download it for policy reasons. Let's let this go through because
795e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // the final recipient of this forward email might be able to process it.
796cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
797e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                mDownloadQueue.addRequest(req);
798cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
799ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            // TODO: If the request already existed, we'll update the priority (so that the time is
800ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            // up-to-date); otherwise, create a new request
801ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG,
802ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    "Attachment #%d queued for download, priority: %d, created time: %d",
803ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    att.mId, req.mPriority, req.mCreatedTime);
804cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
805e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Process the queue if we're in a wait
806e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        kick();
807e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    }
808cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
809e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    /**
81039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * Set the bits in the provider to mark this download as failed.
81139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * @param att The attachment that failed to download.
81239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     */
81339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    void markAttachmentAsFailed(final Attachment att) {
81439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final ContentValues cv = new ContentValues();
81539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final int flags = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
81639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        cv.put(AttachmentColumns.FLAGS, att.mFlags &= ~flags);
81739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        cv.put(AttachmentColumns.UI_STATE, AttachmentState.FAILED);
81839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        att.update(this, cv);
81939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    }
82039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
82139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    /**
822ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee     * Set the bits in the provider to mark this download as completed.
823ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee     * @param att The attachment that was downloaded.
824ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee     */
825ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    void markAttachmentAsCompleted(final Attachment att) {
826ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        final ContentValues cv = new ContentValues();
827ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        final int flags = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST;
828ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        cv.put(AttachmentColumns.FLAGS, att.mFlags &= ~flags);
829ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        cv.put(AttachmentColumns.UI_STATE, AttachmentState.SAVED);
830ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        att.update(this, cv);
831ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    }
832ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
833ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    /**
834e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing
835e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * the limit on maximum downloads
836e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     */
837e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    synchronized void processQueue() {
838ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        debugTrace("Processing changed queue, num entries: %d", sAttachmentChangedQueue.size());
839ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
840ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        // First thing we need to do is process the list of "potential downloads" that we
841ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        // added to sAttachmentChangedQueue
842ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        long[] change = sAttachmentChangedQueue.poll();
843ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        while (change != null) {
844ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            // Process this change
845ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            final long id = change[0];
846ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            final long flags = change[1];
847ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            final Attachment attachment = Attachment.restoreAttachmentWithId(this, id);
848ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            if (attachment == null) {
849ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.w(LOG_TAG, "Could not restore attachment #%d", id);
850ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                continue;
851ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            }
852ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            attachment.mFlags = (int) flags;
853ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            onChange(this, attachment);
854ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            change = sAttachmentChangedQueue.poll();
855cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
856cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
857ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        debugTrace("Processing download queue, num entries: %d", mDownloadQueue.getSize());
858ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
859e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        while (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS) {
860e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            final DownloadRequest req = mDownloadQueue.getNextRequest();
861e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (req == null) {
862e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // No more queued requests?  We are done for now.
863e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                break;
864cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
865ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            // Enforce per-account limit here
86639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (getDownloadsForAccount(req.mAccountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) {
867ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.w(LOG_TAG, "Skipping #%d; maxed for acct %d",
868ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        req.mAttachmentId, req.mAccountId);
869e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                continue;
870ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            }
871ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            if (Attachment.restoreAttachmentWithId(this, req.mAttachmentId) == null) {
872ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.e(LOG_TAG, "Could not load attachment: #%d", req.mAttachmentId);
873e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                continue;
874e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
875e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (!req.mInProgress) {
876e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                final long currentTime = SystemClock.elapsedRealtime();
877e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if (req.mRetryCount > 0 && req.mRetryStartTime > currentTime) {
878ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    debugTrace("Need to wait before retrying attachment #%d", req.mAttachmentId);
87939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    mWatchdog.setWatchdogAlarm(this, CONNECTION_ERROR_RETRY_MILLIS,
880e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            CALLBACK_TIMEOUT);
881e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    continue;
882cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
883e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // TODO: We try to gate ineligible downloads from entering the queue but its
884e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // always possible that they made it in here regardless in the future.  In a
885e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // perfect world, we would make it bullet proof with a check for eligibility
886e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // here instead/also.
887e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                tryStartDownload(req);
888cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
889e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        }
890cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
891e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Check our ability to be opportunistic regarding background downloads.
892e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final EmailConnectivityManager ecm = mConnectivityManager;
893e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if ((ecm == null) || !ecm.isAutoSyncAllowed() ||
894e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                (ecm.getActiveNetworkType() != ConnectivityManager.TYPE_WIFI)) {
895e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // Only prefetch if it if connectivity is available, prefetch is enabled
896e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // and we are on WIFI
897ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Skipping opportunistic downloads since WIFI is not available");
898e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            return;
899e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        }
900e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee
901e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Then, try opportunistic download of appropriate attachments
902ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        final int availableBackgroundThreads =
903824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee                MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size();
904ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if (availableBackgroundThreads < 1) {
905e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // We want to leave one spot open for a user requested download that we haven't
906e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // started processing yet.
907ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Skipping opportunistic downloads, %d threads available",
908ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    availableBackgroundThreads);
909824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee            dumpInProgressDownloads();
910e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            return;
911e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        }
912e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee
913ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        debugTrace("Launching up to %d opportunistic downloads", availableBackgroundThreads);
914ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
915e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // We'll load up the newest 25 attachments that aren't loaded or queued
916e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // TODO: We are always looking for MAX_ATTACHMENTS_TO_CHECK, shouldn't this be
917e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // backgroundDownloads instead?  We should fix and test this.
918e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI,
919e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                MAX_ATTACHMENTS_TO_CHECK);
92039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final Cursor c = this.getContentResolver().query(lookupUri,
921e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                Attachment.CONTENT_PROJECTION,
922e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                EmailContent.Attachment.PRECACHE_INBOX_SELECTION,
923e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                null, AttachmentColumns._ID + " DESC");
92439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        File cacheDir = this.getCacheDir();
925e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        try {
926e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            while (c.moveToNext()) {
927e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                final Attachment att = new Attachment();
928e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                att.restore(c);
92939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                final Account account = Account.restoreAccountWithId(this, att.mAccountKey);
930e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if (account == null) {
931e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // Clean up this orphaned attachment; there's no point in keeping it
932e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // around; then try to find another one
933ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    debugTrace("Found orphaned attachment #%d", att.mId);
93439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    EmailContent.delete(this, Attachment.CONTENT_URI, att.mId);
935e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                } else {
936e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // Check that the attachment meets system requirements for download
937e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // Note that there couple be policy that does not allow this attachment
938e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // to be downloaded.
93939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    final AttachmentInfo info = new AttachmentInfo(this, att);
940e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    if (info.isEligibleForDownload()) {
941e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // Either the account must be able to prefetch or this must be
942e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // an inline attachment.
943ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        if (att.mContentId != null || canPrefetchForAccount(account, cacheDir)) {
944e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            final Integer tryCount = mAttachmentFailureMap.get(att.mId);
945e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            if (tryCount != null && tryCount > MAX_DOWNLOAD_RETRIES) {
946e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                                // move onto the next attachment
947ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                                LogUtils.w(LOG_TAG,
948ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                                        "Too many failed attempts for attachment #%d ", att.mId);
949e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                                continue;
950cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                            }
951e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            // Start this download and we're done
95239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                            final DownloadRequest req = new DownloadRequest(this, att);
953e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            tryStartDownload(req);
954e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            break;
955cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                        }
956e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    } else {
957e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // If this attachment was ineligible for download
958e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // because of policy related issues, its flags would be set to
959e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // FLAG_POLICY_DISALLOWS_DOWNLOAD and would not show up in the
960e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // query results. We are most likely here for other reasons such
961e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // as the inability to view the attachment. In that case, let's just
962e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        // skip it for now.
963ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        LogUtils.w(LOG_TAG, "Skipping attachment #%d, it is ineligible", att.mId);
964cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    }
965cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
966cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
967e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        } finally {
968e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            c.close();
969cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
970e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    }
971cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
972e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    /**
973e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * Attempt to execute the DownloadRequest, enforcing the maximum downloads per account
974e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * parameter
975e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * @param req the DownloadRequest
976e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * @return whether or not the download was started
977e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     */
978e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    synchronized boolean tryStartDownload(final DownloadRequest req) {
979e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(
980e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                AttachmentService.this, req.mAccountId);
981cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
982e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Do not download the same attachment multiple times
983e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        boolean alreadyInProgress = mDownloadsInProgress.get(req.mAttachmentId) != null;
984ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if (alreadyInProgress) {
985ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("This attachment #%d is already in progress", req.mAttachmentId);
986ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            return false;
987ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        }
988cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
989e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        try {
990e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            startDownload(service, req);
991e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        } catch (RemoteException e) {
992e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // TODO: Consider whether we need to do more in this case...
993e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // For now, fix up our data to reflect the failure
994e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            cancelDownload(req);
995cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
996e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        return true;
997e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    }
998cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
999e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    /**
1000e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * Do the work of starting an attachment download using the EmailService interface, and
1001e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * set our watchdog alarm
1002e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     *
1003e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * @param service the service handling the download
1004e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * @param req the DownloadRequest
1005e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * @throws RemoteException
1006e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     */
1007e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    private void startDownload(final EmailServiceProxy service, final DownloadRequest req)
1008e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            throws RemoteException {
1009ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        LogUtils.d(LOG_TAG, "Starting download for Attachment #%d", req.mAttachmentId);
1010e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        req.mStartTime = System.currentTimeMillis();
1011e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        req.mInProgress = true;
1012e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        mDownloadsInProgress.put(req.mAttachmentId, req);
1013e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        service.loadAttachment(mServiceCallback, req.mAccountId, req.mAttachmentId,
1014e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                req.mPriority != PRIORITY_FOREGROUND);
101539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        mWatchdog.setWatchdogAlarm(this);
1016e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    }
1017cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1018e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    synchronized void cancelDownload(final DownloadRequest req) {
1019ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        LogUtils.d(LOG_TAG, "Cancelling download for Attachment #%d", req.mAttachmentId);
1020e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        req.mInProgress = false;
1021e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        mDownloadsInProgress.remove(req.mAttachmentId);
1022e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Remove the download from our queue, and then decide whether or not to add it back.
1023e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        mDownloadQueue.removeRequest(req);
1024e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        req.mRetryCount++;
1025e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
1026ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.w(LOG_TAG, "Too many failures giving up on Attachment #%d", req.mAttachmentId);
1027e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        } else {
1028ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Moving to end of queue, will retry #%d", req.mAttachmentId);
1029e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // The time field of DownloadRequest is final, because it's unsafe to change it
1030e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // as long as the DownloadRequest is in the DownloadSet. It's needed for the
1031e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // comparator, so changing time would make the request unfindable.
1032e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // Instead, we'll create a new DownloadRequest with an updated time.
1033e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // This will sort at the end of the set.
1034e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            final DownloadRequest newReq = new DownloadRequest(req, SystemClock.elapsedRealtime());
1035e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            mDownloadQueue.addRequest(newReq);
1036cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
1037e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    }
1038cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1039e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    /**
1040e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * Called when a download is finished; we get notified of this via our EmailServiceCallback
1041e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * @param attachmentId the id of the attachment whose download is finished
1042e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * @param statusCode the EmailServiceStatus code returned by the Service
1043e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     */
1044e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    synchronized void endDownload(final long attachmentId, final int statusCode) {
1045ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        LogUtils.d(LOG_TAG, "Finishing download #%d", attachmentId);
1046ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
1047e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Say we're no longer downloading this
1048e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        mDownloadsInProgress.remove(attachmentId);
1049e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee
1050e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // TODO: This code is conservative and treats connection issues as failures.
1051e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Since we have no mechanism to throttle reconnection attempts, it makes
1052e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // sense to be cautious here. Once logic is in place to prevent connecting
1053e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // in a tight loop, we can exclude counting connection issues as "failures".
1054e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee
1055e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Update the attachment failure list if needed
1056e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        Integer downloadCount;
1057e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        downloadCount = mAttachmentFailureMap.remove(attachmentId);
1058e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if (statusCode != EmailServiceStatus.SUCCESS) {
1059e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (downloadCount == null) {
1060e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                downloadCount = 0;
1061cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
1062e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            downloadCount += 1;
1063ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.w(LOG_TAG, "This attachment failed, adding #%d to failure map", attachmentId);
1064e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            mAttachmentFailureMap.put(attachmentId, downloadCount);
1065cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
1066cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1067e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final DownloadRequest req = mDownloadQueue.findRequestById(attachmentId);
1068e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if (statusCode == EmailServiceStatus.CONNECTION_ERROR) {
1069e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // If this needs to be retried, just process the queue again
1070e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (req != null) {
1071e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                req.mRetryCount++;
1072e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) {
1073e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // We are done, we maxed out our total number of tries.
1074ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    // Not that we do not flag this attachment with any special flags so the
1075ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    // AttachmentService will try to download this attachment again the next time
1076ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    // that it starts up.
1077ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "Too many tried for connection errors, giving up #%d",
1078ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                            attachmentId);
1079e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    mDownloadQueue.removeRequest(req);
1080e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // Note that we are not doing anything with the attachment right now
1081e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // We will annotate it later in this function if needed.
1082e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                } else if (req.mRetryCount > CONNECTION_ERROR_DELAY_THRESHOLD) {
1083e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // TODO: I'm not sure this is a great retry/backoff policy, but we're
1084e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // afraid of changing behavior too much in case something relies upon it.
1085e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // So now, for the first five errors, we'll retry immediately. For the next
1086e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // five tries, we'll add a ten second delay between each. After that, we'll
1087e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // give up.
1088ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "ConnectionError #%d, retried %d times, adding delay",
1089e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            attachmentId, req.mRetryCount);
1090e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    req.mInProgress = false;
1091e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    req.mRetryStartTime = SystemClock.elapsedRealtime() +
1092e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            CONNECTION_ERROR_RETRY_MILLIS;
109339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    mWatchdog.setWatchdogAlarm(this, CONNECTION_ERROR_RETRY_MILLIS,
1094e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            CALLBACK_TIMEOUT);
1095e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                } else {
1096ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "ConnectionError for #%d, retried %d times, adding delay",
1097e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                            attachmentId, req.mRetryCount);
1098e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    req.mInProgress = false;
1099e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    req.mRetryStartTime = 0;
1100e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    kick();
1101cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
1102cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
1103e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            return;
1104e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        }
1105cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1106e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // If the request is still in the queue, remove it
1107e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if (req != null) {
1108e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            mDownloadQueue.removeRequest(req);
1109e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        }
1110cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1111ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if (ENABLE_ATTACHMENT_SERVICE_DEBUG > 0) {
1112e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            long secs = 0;
1113cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            if (req != null) {
1114ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                secs = (System.currentTimeMillis() - req.mCreatedTime) / 1000;
1115cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
1116e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            final String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" :
1117e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                "Error " + statusCode;
1118ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Download finished for attachment #%d; %d seconds from request, status: %s",
1119ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    attachmentId, secs, status);
1120e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        }
1121cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
112239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        final Attachment attachment = Attachment.restoreAttachmentWithId(this, attachmentId);
1123e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if (attachment != null) {
1124e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            final long accountId = attachment.mAccountKey;
1125e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // Update our attachment storage for this account
1126e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            Long currentStorage = mAttachmentStorageMap.get(accountId);
1127e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (currentStorage == null) {
1128e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                currentStorage = 0L;
1129e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
1130e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            mAttachmentStorageMap.put(accountId, currentStorage + attachment.mSize);
1131e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            boolean deleted = false;
1132e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if ((attachment.mFlags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) {
1133e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if (statusCode == EmailServiceStatus.ATTACHMENT_NOT_FOUND) {
1134e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // If this is a forwarding download, and the attachment doesn't exist (or
1135e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // can't be downloaded) delete it from the outgoing message, lest that
1136e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // message never get sent
113739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    EmailContent.delete(this, Attachment.CONTENT_URI, attachment.mId);
1138e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // TODO: Talk to UX about whether this is even worth doing
1139bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                    final NotificationController nc =
1140bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                            NotificationControllerCreatorHolder.getInstance(this);
1141bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                    if (nc != null) {
1142bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                        nc.showDownloadForwardFailedNotificationSynchronous(attachment);
1143bb68c13afa630cae058eb40d3ce68644f3f3c8b9Paul Westbrook                    }
1144e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    deleted = true;
1145ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "Deleting forwarded attachment #%d for message #%d",
1146ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        attachmentId, attachment.mMessageKey);
1147cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
1148e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // If we're an attachment on forwarded mail, and if we're not still blocked,
1149e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // try to send pending mail now (as mediated by MailService)
1150e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if ((req != null) &&
115139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                        !Utility.hasUnloadedAttachments(this, attachment.mMessageKey)) {
1152ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    debugTrace("Downloads finished for outgoing msg #%d", req.mMessageId);
1153e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(
115439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                            this, accountId);
1155e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    try {
1156e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        service.sendMail(accountId);
1157e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    } catch (RemoteException e) {
1158ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                        LogUtils.e(LOG_TAG, "RemoteException while trying to send message: #%d, %s",
1159ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                                req.mMessageId, e.toString());
1160cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    }
1161cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
1162e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            }
1163e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            if (statusCode == EmailServiceStatus.MESSAGE_NOT_FOUND) {
116439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                Message msg = Message.restoreMessageWithId(this, attachment.mMessageKey);
1165e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                if (msg == null) {
1166ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "Deleting attachment #%d with no associated message #%d",
1167ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                            attachment.mId, attachment.mMessageKey);
1168e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // If there's no associated message, delete the attachment
116939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                    EmailContent.delete(this, Attachment.CONTENT_URI, attachment.mId);
1170e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                } else {
1171e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // If there really is a message, retry
1172e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    // TODO: How will this get retried? It's still marked as inProgress?
1173ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    LogUtils.w(LOG_TAG, "Retrying attachment #%d with associated message #%d",
1174ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                            attachment.mId, attachment.mMessageKey);
1175e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    kick();
1176e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    return;
1177cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
1178e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            } else if (!deleted) {
1179e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // Clear the download flags, since we're done for now.  Note that this happens
1180e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // only for non-recoverable errors.  When these occur for forwarded mail, we can
1181e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // ignore it and continue; otherwise, it was either 1) a user request, in which
1182e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // case the user can retry manually or 2) an opportunistic download, in which
1183e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                // case the download wasn't critical
1184ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "Attachment #%d successfully downloaded!", attachment.mId);
1185ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                markAttachmentAsCompleted(attachment);
1186cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
1187cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
1188e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Process the queue
1189e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        kick();
1190cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    }
1191cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1192cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    /**
119339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * Count the number of running downloads in progress for this account
119439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * @param accountId the id of the account
119539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     * @return the count of running downloads
119639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee     */
119739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    synchronized int getDownloadsForAccount(final long accountId) {
119839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        int count = 0;
119939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        for (final DownloadRequest req: mDownloadsInProgress.values()) {
120039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            if (req.mAccountId == accountId) {
120139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                count++;
120239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            }
120339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        }
120439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        return count;
120539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    }
120639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee
120739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    /**
1208cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     * Calculate the download priority of an Attachment.  A priority of zero means that the
1209cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     * attachment is not marked for download.
1210cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     * @param att the Attachment
1211cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     * @return the priority key of the Attachment
1212cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     */
121339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    private static int getAttachmentPriority(final Attachment att) {
1214cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        int priorityClass = PRIORITY_NONE;
1215e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final int flags = att.mFlags;
1216cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) {
1217cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            priorityClass = PRIORITY_SEND_MAIL;
1218cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        } else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) {
1219cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            priorityClass = PRIORITY_FOREGROUND;
1220cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
1221cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        return priorityClass;
1222cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    }
1223cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1224cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    /**
1225e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * Determine whether an attachment can be prefetched for the given account based on
1226e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee     * total download size restrictions tied to the account.
1227cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     * @return true if download is allowed, false otherwise
1228cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee     */
1229e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    public boolean canPrefetchForAccount(final Account account, final File dir) {
1230cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        // Check account, just in case
1231cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        if (account == null) return false;
1232e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee
1233cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        // First, check preference and quickly return if prefetch isn't allowed
1234ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) {
1235ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Prefetch is not allowed for this account: %d", account.getId());
1236ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            return false;
1237ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        }
1238cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1239e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final long totalStorage = dir.getTotalSpace();
1240e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final long usableStorage = dir.getUsableSpace();
1241e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE);
1242cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1243cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        // If there's not enough overall storage available, stop now
1244ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if (usableStorage < minAvailable) {
1245ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Not enough physical storage for prefetch");
1246ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            return false;
1247ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        }
1248cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1249e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts();
1250e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // Calculate an even per-account storage although it would make a lot of sense to not
1251e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // do this as you may assign more storage to your corporate account rather than a personal
1252e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // account.
1253e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final long perAccountMaxStorage =
125439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee                (long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts);
1255cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1256cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        // Retrieve our idea of currently used attachment storage; since we don't track deletions,
1257cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        // this number is the "worst case".  If the number is greater than what's allowed per
1258e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        // account, we walk the directory to determine the actual number.
1259cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        Long accountStorage = mAttachmentStorageMap.get(account.mId);
1260cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        if (accountStorage == null || (accountStorage > perAccountMaxStorage)) {
1261cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            // Calculate the exact figure for attachment storage for this account
1262cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            accountStorage = 0L;
1263cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            File[] files = dir.listFiles();
1264cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            if (files != null) {
1265cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                for (File file : files) {
1266cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    accountStorage += file.length();
1267cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
1268cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
1269e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // Cache the value. No locking here since this is a concurrent collection object.
1270cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            mAttachmentStorageMap.put(account.mId, accountStorage);
1271cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
1272cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1273cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        // Return true if we're using less than the maximum per account
1274e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        if (accountStorage >= perAccountMaxStorage) {
1275ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            debugTrace("Prefetch not allowed for account %d; used: %d, limit %d",
1276ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                    account.mId, accountStorage, perAccountMaxStorage);
1277cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            return false;
1278cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
1279e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        return true;
1280cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    }
1281cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
128239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee    boolean isConnected() {
1283cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        if (mConnectivityManager != null) {
128439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee            return mConnectivityManager.hasConnectivity();
1285cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
128639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee        return false;
1287cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    }
1288cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee
1289a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    // For Debugging.
1290ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    synchronized public void dumpInProgressDownloads() {
1291ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        if (ENABLE_ATTACHMENT_SERVICE_DEBUG < 1) {
1292ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Advanced logging not configured.");
1293ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        }
1294824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee        LogUtils.d(LOG_TAG, "Here are the in-progress downloads...");
1295ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        for (final DownloadRequest req : mDownloadsInProgress.values()) {
1296ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "--BEGIN DownloadRequest DUMP--");
1297ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Account: #%d", req.mAccountId);
1298ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Message: #%d", req.mMessageId);
1299ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Attachment: #%d", req.mAttachmentId);
1300ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Created Time: %d", req.mCreatedTime);
1301ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Priority: %d", req.mPriority);
1302ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            if (req.mInProgress == true) {
1303ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "This download is in progress");
1304ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            } else {
1305ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                LogUtils.d(LOG_TAG, "This download is not in progress");
1306ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            }
1307ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Start Time: %d", req.mStartTime);
1308ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Retry Count: %d", req.mRetryCount);
1309ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Retry Start Tiome: %d", req.mRetryStartTime);
1310ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Last Status Code: %d", req.mLastStatusCode);
1311ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Last Progress: %d", req.mLastProgress);
1312ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "Last Callback Time: %d", req.mLastCallbackTime);
1313ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee            LogUtils.d(LOG_TAG, "------------------------------");
1314ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee        }
1315824a80491cb1be22bef67483aa266a3ccb0fc557Anthony Lee        LogUtils.d(LOG_TAG, "Done reporting in-progress downloads...");
1316ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee    }
1317ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
1318ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee
1319cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    @Override
1320e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    public void dump(final FileDescriptor fd, final PrintWriter pw, final String[] args) {
1321cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        pw.println("AttachmentService");
1322e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        final long time = System.currentTimeMillis();
1323e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        synchronized(mDownloadQueue) {
1324e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            pw.println("  Queue, " + mDownloadQueue.getSize() + " entries");
1325e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // If you iterate over the queue either via iterator or collection, they are not
1326e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // returned in any particular order. With all things being equal its better to go with
1327e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // a collection to avoid any potential ConcurrentModificationExceptions.
1328e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // If we really want this sorted, we can sort it manually since performance isn't a big
1329e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            // concern with this debug method.
1330e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee            for (final DownloadRequest req : mDownloadQueue.mRequestMap.values()) {
13310e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                pw.println("    Account: " + req.mAccountId + ", Attachment: " + req.mAttachmentId);
1332ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee                pw.println("      Priority: " + req.mPriority + ", Time: " + req.mCreatedTime +
13330e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                        (req.mInProgress ? " [In progress]" : ""));
1334e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                final Attachment att = Attachment.restoreAttachmentWithId(this, req.mAttachmentId);
1335cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                if (att == null) {
1336cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    pw.println("      Attachment not in database?");
1337cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                } else if (att.mFileName != null) {
1338e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    final String fileName = att.mFileName;
1339e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    final String suffix;
1340e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    final int lastDot = fileName.lastIndexOf('.');
1341cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    if (lastDot >= 0) {
1342cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                        suffix = fileName.substring(lastDot);
1343e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                    } else {
1344e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee                        suffix = "[none]";
1345cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    }
1346cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    pw.print("      Suffix: " + suffix);
1347cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    if (att.getContentUri() != null) {
1348cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                        pw.print(" ContentUri: " + att.getContentUri());
1349cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    }
1350cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    pw.print(" Mime: ");
1351cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    if (att.mMimeType != null) {
1352cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                        pw.print(att.mMimeType);
1353cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    } else {
1354cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                        pw.print(AttachmentUtilities.inferMimeType(fileName, null));
1355cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                        pw.print(" [inferred]");
1356cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    }
1357cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    pw.println(" Size: " + att.mSize);
1358cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
13590e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                if (req.mInProgress) {
13600e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    pw.println("      Status: " + req.mLastStatusCode + ", Progress: " +
13610e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                            req.mLastProgress);
13620e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    pw.println("      Started: " + req.mStartTime + ", Callback: " +
13630e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                            req.mLastCallbackTime);
13640e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    pw.println("      Elapsed: " + ((time - req.mStartTime) / 1000L) + "s");
13650e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                    if (req.mLastCallbackTime > 0) {
13660e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee                        pw.println("      CB: " + ((time - req.mLastCallbackTime) / 1000L) + "s");
1367cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                    }
1368cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee                }
1369cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee            }
1370cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee        }
1371cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee    }
1372a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
1373a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    // For Testing
1374e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    AccountManagerStub mAccountManagerStub;
1375a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    private final HashMap<Long, Intent> mAccountServiceMap = new HashMap<Long, Intent>();
1376a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
1377e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    void addServiceIntentForTest(final long accountId, final Intent intent) {
1378a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        mAccountServiceMap.put(accountId, intent);
1379a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    }
1380a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
1381a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    /**
1382a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee     * We only use the getAccounts() call from AccountManager, so this class wraps that call and
1383a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee     * allows us to build a mock account manager stub in the unit tests
1384a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee     */
1385e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee    static class AccountManagerStub {
1386a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        private int mNumberOfAccounts;
1387a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        private final AccountManager mAccountManager;
1388a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
1389e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        AccountManagerStub(final Context context) {
1390a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            if (context != null) {
1391a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                mAccountManager = AccountManager.get(context);
1392a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            } else {
1393a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                mAccountManager = null;
1394a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            }
1395a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
1396a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
1397e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        int getNumberOfAccounts() {
1398a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            if (mAccountManager != null) {
1399a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                return mAccountManager.getAccounts().length;
1400a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            } else {
1401a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee                return mNumberOfAccounts;
1402a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            }
1403a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
1404a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee
1405e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee        void setNumberOfAccounts(final int numberOfAccounts) {
1406a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee            mNumberOfAccounts = numberOfAccounts;
1407a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee        }
1408a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee    }
1409cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee}
1410