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