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; 37cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.email.NotificationController; 38cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.Account; 39cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent; 40cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent.Attachment; 41cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent.AttachmentColumns; 42cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.provider.EmailContent.Message; 43cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.service.EmailServiceProxy; 44cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.service.EmailServiceStatus; 45cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.service.IEmailServiceCallback; 46cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.utility.AttachmentUtilities; 47cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.emailcommon.utility.Utility; 48cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.mail.providers.UIProvider.AttachmentState; 49cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport com.android.mail.utils.LogUtils; 500e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Leeimport com.google.common.annotations.VisibleForTesting; 51cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 52cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.io.File; 53cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.io.FileDescriptor; 54cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.io.PrintWriter; 55a72a12241f413f113d61d318757a49b7e33c2af6Anthony Leeimport java.util.Collection; 56cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.Comparator; 57cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.HashMap; 580e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Leeimport java.util.PriorityQueue; 59cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.Queue; 60cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.concurrent.ConcurrentHashMap; 61cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leeimport java.util.concurrent.ConcurrentLinkedQueue; 62cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 63cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Leepublic class AttachmentService extends Service implements Runnable { 640e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // For logging. 650e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee public static final String LOG_TAG = "AttachmentService"; 66cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 67ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // STOPSHIP Set this to 0 before shipping. 68ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee private static final int ENABLE_ATTACHMENT_SERVICE_DEBUG = 0; 69ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 70cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Minimum wait time before retrying a download that failed due to connection error 71cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final long CONNECTION_ERROR_RETRY_MILLIS = 10 * DateUtils.SECOND_IN_MILLIS; 72cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Number of retries before we start delaying between 73cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final long CONNECTION_ERROR_DELAY_THRESHOLD = 5; 74cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Maximum time to retry for connection errors. 75cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final long CONNECTION_ERROR_MAX_RETRIES = 10; 76cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 77cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Our idle time, waiting for notifications; this is something of a failsafe 78cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final int PROCESS_QUEUE_WAIT_TIME = 30 * ((int)DateUtils.MINUTE_IN_MILLIS); 79cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // How long we'll wait for a callback before canceling a download and retrying 80cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final int CALLBACK_TIMEOUT = 30 * ((int)DateUtils.SECOND_IN_MILLIS); 81cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Try to download an attachment in the background this many times before giving up 82cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final int MAX_DOWNLOAD_RETRIES = 5; 830e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 84e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static final int PRIORITY_NONE = -1; 85cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // High priority is for user requests 86e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static final int PRIORITY_FOREGROUND = 0; 87e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static final int PRIORITY_HIGHEST = PRIORITY_FOREGROUND; 880e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // Normal priority is for forwarded downloads in outgoing mail 89e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static final int PRIORITY_SEND_MAIL = 1; 900e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // Low priority will be used for opportunistic downloads 91e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static final int PRIORITY_BACKGROUND = 2; 92e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static final int PRIORITY_LOWEST = PRIORITY_BACKGROUND; 93cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 94cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Minimum free storage in order to perform prefetch (25% of total memory) 95cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final float PREFETCH_MINIMUM_STORAGE_AVAILABLE = 0.25F; 96cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Maximum prefetch storage (also 25% of total memory) 97cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final float PREFETCH_MAXIMUM_ATTACHMENT_STORAGE = 0.25F; 98cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 99cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // We can try various values here; I think 2 is completely reasonable as a first pass 100cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2; 101cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Limit on the number of simultaneous downloads per account 102cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Note that a limit of 1 is currently enforced by both Services (MailService and Controller) 103cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final int MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT = 1; 104cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Limit on the number of attachments we'll check for background download 105cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee private static final int MAX_ATTACHMENTS_TO_CHECK = 25; 106cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 107ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee private static final String EXTRA_ATTACHMENT_ID = 108ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee "com.android.email.AttachmentService.attachment_id"; 109ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee private static final String EXTRA_ATTACHMENT_FLAGS = 110ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee "com.android.email.AttachmentService.attachment_flags"; 111a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 112e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // This callback is invoked by the various service implementations to give us download progress 113a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // since those modules are responsible for the actual download. 11439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final ServiceCallback mServiceCallback = new ServiceCallback(); 115cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 116cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // sRunningService is only set in the UI thread; it's visibility elsewhere is guaranteed 117cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // by the use of "volatile" 118e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static volatile AttachmentService sRunningService = null; 119cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 120a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // Signify that we are being shut down & destroyed. 121a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee private volatile boolean mStop = false; 122a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 123e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee EmailConnectivityManager mConnectivityManager; 12439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 12539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Helper class that keeps track of in progress downloads to make sure that they 12639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // are progressing well. 127e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final AttachmentWatchdog mWatchdog = new AttachmentWatchdog(); 128cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 129a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee private final Object mLock = new Object(); 130cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 131a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // A map of attachment storage used per account as we have account based maximums to follow. 132cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // NOTE: This map is not kept current in terms of deletions (i.e. it stores the last calculated 133a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // amount plus the size of any new attachments loaded). If and when we reach the per-account 134cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // limit, we recalculate the actual usage 135e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final ConcurrentHashMap<Long, Long> mAttachmentStorageMap = new ConcurrentHashMap<Long, Long>(); 136a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 137cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // A map of attachment ids to the number of failed attempts to download the attachment 138cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // NOTE: We do not want to persist this. This allows us to retry background downloading 139cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // if any transient network errors are fixed & and the app is restarted 140e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final ConcurrentHashMap<Long, Integer> mAttachmentFailureMap = new ConcurrentHashMap<Long, Integer>(); 141cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 142a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // Keeps tracks of downloads in progress based on an attachment ID to DownloadRequest mapping. 143e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final ConcurrentHashMap<Long, DownloadRequest> mDownloadsInProgress = 144a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee new ConcurrentHashMap<Long, DownloadRequest>(); 145cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 146e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final DownloadQueue mDownloadQueue = new DownloadQueue(); 147cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 14839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // The queue entries here are entries of the form {id, flags}, with the values passed in to 149ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // attachmentChanged(). Entries in the queue are picked off in processQueue(). 15039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee private static final Queue<long[]> sAttachmentChangedQueue = 15139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee new ConcurrentLinkedQueue<long[]>(); 15239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 153ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // Extra layer of control over debug logging that should only be enabled when 154ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // we need to take an extra deep dive at debugging the workflow in this class. 155ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee static private void debugTrace(final String format, final Object... args) { 156ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (ENABLE_ATTACHMENT_SERVICE_DEBUG > 0) { 157ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, String.format(format, args)); 158ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 159ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 16039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 1610e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee /** 1620e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * This class is used to contain the details and state of a particular request to download 1630e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * an attachment. These objects are constructed and either placed in the {@link DownloadQueue} 1640e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * or in the in-progress map used to keep track of downloads that are currently happening 1650e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * in the system 1660e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee */ 167e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static class DownloadRequest { 1680e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // Details of the request. 1690e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final int mPriority; 170ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final long mCreatedTime; 1710e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final long mAttachmentId; 1720e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final long mMessageId; 1730e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final long mAccountId; 1740e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 1750e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // Status of the request. 1760e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee boolean mInProgress = false; 1770e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee int mLastStatusCode; 1780e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee int mLastProgress; 1790e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee long mLastCallbackTime; 1800e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee long mStartTime; 1810e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee long mRetryCount; 1820e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee long mRetryStartTime; 1830e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 1840e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee /** 1850e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * This constructor is mainly used for tests 1860e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @param attPriority The priority of this attachment 1870e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @param attId The id of the row in the attachment table. 1880e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee */ 1890e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee @VisibleForTesting 1900e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee DownloadRequest(final int attPriority, final long attId) { 1910e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // This constructor should only be used for unit tests. 192ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee mCreatedTime = SystemClock.elapsedRealtime(); 1930e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mPriority = attPriority; 1940e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mAttachmentId = attId; 1950e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mAccountId = -1; 1960e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mMessageId = -1; 1970e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 1980e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 1990e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee private DownloadRequest(final Context context, final Attachment attachment) { 2000e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mAttachmentId = attachment.mId; 2010e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey); 202cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (msg != null) { 2030e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mAccountId = msg.mAccountKey; 2040e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mMessageId = msg.mId; 205cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } else { 2060e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mAccountId = mMessageId = -1; 207cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 20839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mPriority = getAttachmentPriority(attachment); 209ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee mCreatedTime = SystemClock.elapsedRealtime(); 210cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 211cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 2120e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee private DownloadRequest(final DownloadRequest orig, final long newTime) { 2130e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mPriority = orig.mPriority; 2140e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mAttachmentId = orig.mAttachmentId; 2150e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mMessageId = orig.mMessageId; 2160e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mAccountId = orig.mAccountId; 217ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee mCreatedTime = newTime; 2180e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mInProgress = orig.mInProgress; 2190e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mLastStatusCode = orig.mLastStatusCode; 2200e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mLastProgress = orig.mLastProgress; 2210e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mLastCallbackTime = orig.mLastCallbackTime; 2220e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mStartTime = orig.mStartTime; 2230e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mRetryCount = orig.mRetryCount; 2240e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mRetryStartTime = orig.mRetryStartTime; 225cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 226cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 227cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee @Override 228cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee public int hashCode() { 2290e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return (int)mAttachmentId; 230cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 231cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 232cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee /** 233cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * Two download requests are equals if their attachment id's are equals 234cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee */ 235cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee @Override 2360e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee public boolean equals(final Object object) { 237cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (!(object instanceof DownloadRequest)) return false; 2380e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final DownloadRequest req = (DownloadRequest)object; 2390e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return req.mAttachmentId == mAttachmentId; 240cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 241cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 242cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 243cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee /** 2440e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * This class is used to organize the various download requests that are pending. 2450e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * We need a class that allows us to prioritize a collection of {@link DownloadRequest} objects 2460e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * while being able to pull off request with the highest priority but we also need 2470e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * to be able to find a particular {@link DownloadRequest} by id or by reference for retrieval. 2480e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * Bonus points for an implementation that does not require an iterator to accomplish its tasks 2490e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * as we can avoid pesky ConcurrentModificationException when one thread has the iterator 2500e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * and another thread modifies the collection. 2510e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee */ 252e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static class DownloadQueue { 2530e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee private final int DEFAULT_SIZE = 10; 2540e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 2550e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // For synchronization 2560e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee private final Object mLock = new Object(); 2570e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 258e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee /** 259e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * Comparator class for the download set; we first compare by priority. Requests with equal 260e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * priority are compared by the time the request was created (older requests come first) 261e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee */ 262e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee private static class DownloadComparator implements Comparator<DownloadRequest> { 263e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee @Override 264e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee public int compare(DownloadRequest req1, DownloadRequest req2) { 265e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee int res; 266e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req1.mPriority != req2.mPriority) { 267e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee res = (req1.mPriority < req2.mPriority) ? -1 : 1; 268e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 269ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (req1.mCreatedTime == req2.mCreatedTime) { 270e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee res = 0; 271e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 272ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee res = (req1.mCreatedTime < req2.mCreatedTime) ? -1 : 1; 273e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 274e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 275e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return res; 276e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 277e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 278e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee 2790e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // For prioritization of DownloadRequests. 280e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final PriorityQueue<DownloadRequest> mRequestQueue = 281a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee new PriorityQueue<DownloadRequest>(DEFAULT_SIZE, new DownloadComparator()); 2820e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 2830e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // Secondary collection to quickly find objects w/o the help of an iterator. 2840e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // This class should be kept in lock step with the priority queue. 285ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final ConcurrentHashMap<Long, DownloadRequest> mRequestMap = 286ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee new ConcurrentHashMap<Long, DownloadRequest>(); 2870e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 2880e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee /** 2890e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * This function will add the request to our collections if it does not already 2900e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * exist. If it does exist, the function will silently succeed. 2910e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @param request The {@link DownloadRequest} that should be added to our queue 2920e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @return true if it was added (or already exists), false otherwise 2930e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee */ 294e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee public boolean addRequest(final DownloadRequest request) 295a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee throws NullPointerException { 2960e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // It is key to keep the map and queue in lock step 2970e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (request == null) { 298a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // We can't add a null entry into the queue so let's throw what the underlying 299a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // data structure would throw. 300a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee throw new NullPointerException(); 3010e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3020e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final long requestId = request.mAttachmentId; 3030e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (requestId < 0) { 3040e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // Invalid request 305ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Not adding a DownloadRequest with an invalid attachment id"); 3060e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return false; 3070e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 308ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Queuing DownloadRequest #%d", requestId); 3090e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee synchronized (mLock) { 3100e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // Check to see if this request is is already in the queue 3110e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final boolean exists = mRequestMap.containsKey(requestId); 3120e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (!exists) { 3130e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mRequestQueue.offer(request); 3140e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mRequestMap.put(requestId, request); 315ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } else { 316ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("DownloadRequest #%d was already in the queue"); 3170e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3180e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3190e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return true; 3200e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3210e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 3220e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee /** 3230e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * This function will remove the specified request from the internal collections. 3240e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @param request The {@link DownloadRequest} that should be removed from our queue 3250e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @return true if it was removed or the request was invalid (meaning that the request 3260e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * is not in our queue), false otherwise. 3270e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee */ 328e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee public boolean removeRequest(final DownloadRequest request) { 3290e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (request == null) { 3300e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // If it is invalid, its not in the queue. 3310e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return true; 3320e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 333ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Removing DownloadRequest #%d", request.mAttachmentId); 3340e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final boolean result; 3350e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee synchronized (mLock) { 3360e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // It is key to keep the map and queue in lock step 3370e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee result = mRequestQueue.remove(request); 3380e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (result) { 3390e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mRequestMap.remove(request.mAttachmentId); 3400e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3410e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return result; 3420e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3430e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3440e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 3450e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee /** 3460e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * Return the next request from our queue. 3470e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @return The next {@link DownloadRequest} object or null if the queue is empty 3480e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee */ 349e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee public DownloadRequest getNextRequest() { 3500e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee // It is key to keep the map and queue in lock step 3510e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final DownloadRequest returnRequest; 3520e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee synchronized (mLock) { 3530e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee returnRequest = mRequestQueue.poll(); 3540e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (returnRequest != null) { 3550e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee final long requestId = returnRequest.mAttachmentId; 3560e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee mRequestMap.remove(requestId); 3570e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3580e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 359ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (returnRequest != null) { 360ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Retrieved DownloadRequest #%d", returnRequest.mAttachmentId); 361ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 3620e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return returnRequest; 3630e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3640e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 3650e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee /** 3660e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * Return the {@link DownloadRequest} with the given ID (attachment ID) 3670e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @param requestId The ID of the request in question 3680e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee * @return The associated {@link DownloadRequest} object or null if it does not exist 3690e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee */ 3700e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee public DownloadRequest findRequestById(final long requestId) { 3710e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (requestId < 0) { 3720e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee return null; 3730e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 374e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized (mLock) { 375e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return mRequestMap.get(requestId); 376e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 3770e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3780e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 3790e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee public int getSize() { 380e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized (mLock) { 381e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return mRequestMap.size(); 382e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 3830e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3840e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 3850e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee public boolean isEmpty() { 386e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized (mLock) { 387e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return mRequestMap.isEmpty(); 388e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 3890e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3900e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee } 3910e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee 392a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee /** 393a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee * Watchdog alarm receiver; responsible for making sure that downloads in progress are not 394a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee * stalled, as determined by the timing of the most recent service callback 395a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee */ 396a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee public static class AttachmentWatchdog extends BroadcastReceiver { 397a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // How often our watchdog checks for callback timeouts 398a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee private static final int WATCHDOG_CHECK_INTERVAL = 20 * ((int)DateUtils.SECOND_IN_MILLIS); 399a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee public static final String EXTRA_CALLBACK_TIMEOUT = "callback_timeout"; 400a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee private PendingIntent mWatchdogPendingIntent; 401a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 402a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee public void setWatchdogAlarm(final Context context, final long delay, 403a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee final int callbackTimeout) { 404a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // Lazily initialize the pending intent 405a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee if (mWatchdogPendingIntent == null) { 406a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee Intent intent = new Intent(context, AttachmentWatchdog.class); 407a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee intent.putExtra(EXTRA_CALLBACK_TIMEOUT, callbackTimeout); 408a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee mWatchdogPendingIntent = 409a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee PendingIntent.getBroadcast(context, 0, intent, 0); 410a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 411a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // Set the alarm 412a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee final AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 413a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, 414a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee mWatchdogPendingIntent); 415ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Set up a watchdog for %d millis in the future", delay); 416a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 417a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 418a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee public void setWatchdogAlarm(final Context context) { 419a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // Call the real function with default values. 420a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee setWatchdogAlarm(context, WATCHDOG_CHECK_INTERVAL, CALLBACK_TIMEOUT); 421a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 422a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 423a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee @Override 424a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee public void onReceive(final Context context, final Intent intent) { 425a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee final int callbackTimeout = intent.getIntExtra(EXTRA_CALLBACK_TIMEOUT, 426a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee CALLBACK_TIMEOUT); 427a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee new Thread(new Runnable() { 428a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee @Override 429a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee public void run() { 43039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // TODO: Really don't like hard coding the AttachmentService reference here 43139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // as it makes testing harder if we are trying to mock out the service 43239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // We should change this with some sort of getter that returns the 43339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // static (or test) AttachmentService instance to use. 434a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee final AttachmentService service = AttachmentService.sRunningService; 435a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee if (service != null) { 436a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // If our service instance is gone, just leave 437a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee if (service.mStop) { 438a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee return; 439a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 440a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // Get the timeout time from the intent. 441a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee watchdogAlarm(service, callbackTimeout); 442a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 443a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 444a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee }, "AttachmentService AttachmentWatchdog").start(); 445a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 446a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 44739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee boolean validateDownloadRequest(final DownloadRequest dr, final int callbackTimeout, 44839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final long now) { 44939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Check how long it's been since receiving a callback 45039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final long timeSinceCallback = now - dr.mLastCallbackTime; 45139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (timeSinceCallback > callbackTimeout) { 452ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Timeout for DownloadRequest #%d ", dr.mAttachmentId); 45339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return true; 45439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 45539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return false; 45639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 45739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 458a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee /** 459a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee * Watchdog for downloads; we use this in case we are hanging on a download, which might 460a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee * have failed silently (the connection dropped, for example) 461a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee */ 462e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee void watchdogAlarm(final AttachmentService service, final int callbackTimeout) { 463ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Received a timer callback in the watchdog"); 464ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 465a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // We want to iterate on each of the downloads that are currently in progress and 466a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // cancel the ones that seem to be taking too long. 46739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final Collection<DownloadRequest> inProgressRequests = 46839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee service.mDownloadsInProgress.values(); 469a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee for (DownloadRequest req: inProgressRequests) { 470ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Checking in-progress request with id: %d", req.mAttachmentId); 47139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final boolean shouldCancelDownload = validateDownloadRequest(req, callbackTimeout, 47239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee System.currentTimeMillis()); 47339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (shouldCancelDownload) { 474ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Cancelling DownloadRequest #%d", req.mAttachmentId); 475a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee service.cancelDownload(req); 476e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: Should we also mark the attachment as failed at this point in time? 477a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 478a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 479a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // Check whether we can start new downloads... 480a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee if (service.isConnected()) { 481a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee service.processQueue(); 482a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 48339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee issueNextWatchdogAlarm(service); 48439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 48539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 48639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee void issueNextWatchdogAlarm(final AttachmentService service) { 48739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (!service.mDownloadsInProgress.isEmpty()) { 488ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Rescheduling watchdog..."); 489a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee setWatchdogAlarm(service); 490a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 491a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 492a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 493a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 49439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /** 49539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * We use an EmailServiceCallback to keep track of the progress of downloads. These callbacks 496ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee * come from either Controller (IMAP/POP) or ExchangeService (EAS). Note that we only 497ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee * implement the single callback that's defined by the EmailServiceCallback interface. 49839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 49939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee class ServiceCallback extends IEmailServiceCallback.Stub { 50039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 50139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /** 50239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * Simple routine to generate updated status values for the Attachment based on the 50339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * service callback. Right now it is very simple but factoring out this code allows us 50439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * to test easier and very easy to expand in the future. 50539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 50639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee ContentValues getAttachmentUpdateValues(final Attachment attachment, 50739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final int statusCode, final int progress) { 50839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final ContentValues values = new ContentValues(); 50939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (attachment != null) { 51039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (statusCode == EmailServiceStatus.IN_PROGRESS) { 51139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // TODO: What else do we want to expose about this in-progress download through 51239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // the provider? If there is more, make sure that the service implementation 51339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // reports it and make sure that we add it here. 51439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee values.put(AttachmentColumns.UI_STATE, AttachmentState.DOWNLOADING); 51539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee values.put(AttachmentColumns.UI_DOWNLOADED_SIZE, 51639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee attachment.mSize * progress / 100); 51739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 51839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 51939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return values; 520a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 52139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 52239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee @Override 52339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee public void loadAttachmentStatus(final long messageId, final long attachmentId, 52439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final int statusCode, final int progress) { 525ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace(LOG_TAG, "ServiceCallback for attachment #%d", attachmentId); 526ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 52739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Record status and progress 52839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final DownloadRequest req = mDownloadsInProgress.get(attachmentId); 52939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (req != null) { 530ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final long now = System.currentTimeMillis(); 531ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("ServiceCallback: status code changing from %d to %d", 532ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee req.mLastStatusCode, statusCode); 533ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("ServiceCallback: progress changing from %d to %d", 534ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee req.mLastProgress,progress); 535ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("ServiceCallback: last callback time changing from %d to %d", 536ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee req.mLastCallbackTime, now); 53739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 53839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Update some state to keep track of the progress of the download 53939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee req.mLastStatusCode = statusCode; 54039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee req.mLastProgress = progress; 541ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee req.mLastCallbackTime = now; 54239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 54339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Update the attachment status in the provider. 54439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final Attachment attachment = 54539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee Attachment.restoreAttachmentWithId(AttachmentService.this, attachmentId); 54639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final ContentValues values = getAttachmentUpdateValues(attachment, statusCode, 54739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee progress); 54839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (values.size() > 0) { 54939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee attachment.update(AttachmentService.this, values); 55039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 55139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 55239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee switch (statusCode) { 55339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee case EmailServiceStatus.IN_PROGRESS: 55439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee break; 55539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee default: 55639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // It is assumed that any other error is either a success or an error 55739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Either way, the final updates to the DownloadRequest and attachment 55839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // objects will be handed there. 559ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Attachment #%d is done", attachmentId); 56039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee endDownload(attachmentId, statusCode); 56139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee break; 56239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 56339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } else { 56439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // The only way that we can get a callback from the service implementation for 56539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // an attachment that doesn't exist is if it was cancelled due to the 56639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // AttachmentWatchdog. This is a valid scenario and the Watchdog should have already 56739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // marked this attachment as failed/cancelled. 56839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 56939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 57039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 57139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 57239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /** 57339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * Called directly by EmailProvider whenever an attachment is inserted or changed. Since this 57439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * call is being invoked on the UI thread, we need to make sure that the downloads are 57539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * happening in the background. 57639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * @param context the caller's context 57739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * @param id the attachment's id 57839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * @param flags the new flags for the attachment 57939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 58039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee public static void attachmentChanged(final Context context, final long id, final int flags) { 581ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Attachment with id: %d will potentially be queued for download", id); 582ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // Throw this info into an intent and send it to the attachment service. 583ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final Intent intent = new Intent(context, AttachmentService.class); 584ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Calling startService with extras %d & %d", id, flags); 585ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee intent.putExtra(EXTRA_ATTACHMENT_ID, id); 586ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee intent.putExtra(EXTRA_ATTACHMENT_FLAGS, flags); 587ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee context.startService(intent); 58839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 58939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 59039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /** 59139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * The main entry point for this service, the attachment to download can be identified 59239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * by the EXTRA_ATTACHMENT extra in the intent. 59339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 59439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee @Override 59539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee public int onStartCommand(final Intent intent, final int flags, final int startId) { 59639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (sRunningService == null) { 59739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee sRunningService = this; 59839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 599ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (intent != null) { 600ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // Let's add this id/flags combo to the list of potential attachments to process. 601ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final long attachment_id = intent.getLongExtra(EXTRA_ATTACHMENT_ID, -1); 602ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final int attachment_flags = intent.getIntExtra(EXTRA_ATTACHMENT_FLAGS, -1); 603ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if ((attachment_id >= 0) && (attachment_flags >= 0)) { 604ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee sAttachmentChangedQueue.add(new long[]{attachment_id, attachment_flags}); 605ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // Process the queue if we're in a wait 606ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee kick(); 607ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } else { 608ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Received an invalid intent w/o the required extras %d & %d", 609ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee attachment_id, attachment_flags); 610ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 61139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } else { 612ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Received a null intent in onStartCommand"); 61339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 61439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return Service.START_STICKY; 615a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 616a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 61739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /** 61839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * Most of the leg work is done by our service thread that is created when this 61939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * service is created. 62039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 62139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee @Override 62239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee public void onCreate() { 62339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Start up our service thread. 62439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee new Thread(this, "AttachmentService").start(); 625a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 626a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 62739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee @Override 62839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee public IBinder onBind(final Intent intent) { 62939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return null; 63039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 63139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 63239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee @Override 63339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee public void onDestroy() { 634ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Destroying AttachmentService object"); 635ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee dumpInProgressDownloads(); 636ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 63739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Mark this instance of the service as stopped. Our main loop for the AttachmentService 63839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // checks for this flag along with the AttachmentWatchdog. 63939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mStop = true; 64039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (sRunningService != null) { 64139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Kick it awake to get it to realize that we are stopping. 64239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee kick(); 64339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee sRunningService = null; 64439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 64539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (mConnectivityManager != null) { 64639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mConnectivityManager.unregister(); 64739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mConnectivityManager.stopWait(); 64839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mConnectivityManager = null; 64939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 650a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 651a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 652a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee /** 65339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * The main routine for our AttachmentService service thread. 654cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee */ 65539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee @Override 65639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee public void run() { 65739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // These fields are only used within the service thread 65839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mConnectivityManager = new EmailConnectivityManager(this, LOG_TAG); 65939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mAccountManagerStub = new AccountManagerStub(this); 66039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 66139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Run through all attachments in the database that require download and add them to 66239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // the queue. This is the case where a previous AttachmentService may have been notified 66339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // to stop before processing everything in its queue. 66439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 66539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final Cursor c = getContentResolver().query(Attachment.CONTENT_URI, 66639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee EmailContent.ID_PROJECTION, "(" + AttachmentColumns.FLAGS + " & ?) != 0", 66739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee new String[] {Integer.toString(mask)}, null); 66839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee try { 669ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, 670ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee "Count of previous downloads to resume (from db): %d", c.getCount()); 67139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee while (c.moveToNext()) { 67239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final Attachment attachment = Attachment.restoreAttachmentWithId( 67339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee this, c.getLong(EmailContent.ID_PROJECTION_COLUMN)); 67439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (attachment != null) { 675ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Attempting to download attachment #%d again.", attachment.mId); 67639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee onChange(this, attachment); 67739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 67839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 67939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } catch (Exception e) { 68039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee e.printStackTrace(); 68139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } finally { 68239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee c.close(); 68339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 68439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 68539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Loop until stopped, with a 30 minute wait loop 68639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee while (!mStop) { 68739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Here's where we run our attachment loading logic... 68839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Make a local copy of the variable so we don't null-crash on service shutdown 68939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final EmailConnectivityManager ecm = mConnectivityManager; 69039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (ecm != null) { 69139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee ecm.waitForConnectivity(); 69239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 69339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (mStop) { 69439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // We might be bailing out here due to the service shutting down 695ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "AttachmentService has been instructed to stop"); 69639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee break; 69739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 698ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 699ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // In advanced debug mode, let's look at the state of all in-progress downloads 700ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // after processQueue() runs. 701ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Downloads Map before processQueue"); 702ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee dumpInProgressDownloads(); 70339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee processQueue(); 704ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Downloads Map after processQueue"); 705ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee dumpInProgressDownloads(); 706ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 707ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (mDownloadQueue.isEmpty() && (mDownloadsInProgress.size() < 1)) { 708ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Shutting down service. No in-progress or pending downloads."); 70939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee stopSelf(); 71039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee break; 71139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 712ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Run() wait for mLock"); 71339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee synchronized(mLock) { 71439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee try { 71539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mLock.wait(PROCESS_QUEUE_WAIT_TIME); 71639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } catch (InterruptedException e) { 71739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // That's ok; we'll just keep looping 71839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 71939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 720ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Run() got mLock"); 72139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 72239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 72339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Unregister now that we're done 72439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee // Make a local copy of the variable so we don't null-crash on service shutdown 72539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final EmailConnectivityManager ecm = mConnectivityManager; 72639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (ecm != null) { 72739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee ecm.unregister(); 72839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 72939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 73039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 73139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /* 73239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * Function that kicks the service into action as it may be waiting for this object 73339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * as it processed the last round of attachments. 73439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 73539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee private void kick() { 73639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee synchronized(mLock) { 73739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mLock.notify(); 73839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 739e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 740cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 741e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee /** 742e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * onChange is called by the AttachmentReceiver upon receipt of a valid notification from 743e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * EmailProvider that an attachment has been inserted or modified. It's not strictly 744e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * necessary that we detect a deleted attachment, as the code always checks for the 745e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * existence of an attachment before acting on it. 746e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee */ 747e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee public synchronized void onChange(final Context context, final Attachment att) { 748ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("onChange() for Attachment: #%d", att.mId); 749e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee DownloadRequest req = mDownloadQueue.findRequestById(att.mId); 75039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final long priority = getAttachmentPriority(att); 751e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (priority == PRIORITY_NONE) { 752ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Attachment #%d has no priority and will not be downloaded", 753ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee att.mId); 754e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // In this case, there is no download priority for this attachment 755e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req != null) { 756e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If it exists in the map, remove it 757e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // NOTE: We don't yet support deleting downloads in progress 758e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadQueue.removeRequest(req); 759e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 760e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 761e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Ignore changes that occur during download 762ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (mDownloadsInProgress.containsKey(att.mId)) { 763ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Attachment #%d was already in the queue", att.mId); 764ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee return; 765ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 766e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If this is new, add the request to the queue 767e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req == null) { 768ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Attachment #%d is a new download request", att.mId); 769e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req = new DownloadRequest(context, att); 770e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final AttachmentInfo attachInfo = new AttachmentInfo(context, att); 771e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (!attachInfo.isEligibleForDownload()) { 772ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Attachment #%d is not eligible for download", att.mId); 773e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // We can't download this file due to policy, depending on what type 774e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // of request we received, we handle the response differently. 775e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (((att.mFlags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) || 776e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee ((att.mFlags & Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD) != 0)) { 777ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Attachment #%d cannot be downloaded ever", att.mId); 778e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // There are a couple of situations where we will not even allow this 779e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // request to go in the queue because we can already process it as a 780e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // failure. 781ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // 1. The user explicitly wants to download this attachment from the 782e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // email view but they should not be able to...either because there is 783e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // no app to view it or because its been marked as a policy violation. 784e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // 2. The user is forwarding an email and the attachment has been 785e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // marked as a policy violation. If the attachment is non viewable 786e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // that is OK for forwarding a message so we'll let it pass through 787e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee markAttachmentAsFailed(att); 788e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return; 789cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 790e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If we get this far it a forward of an attachment that is only 791e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // ineligible because we can't view it or process it. Not because we 792e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // can't download it for policy reasons. Let's let this go through because 793e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // the final recipient of this forward email might be able to process it. 794cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 795e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadQueue.addRequest(req); 796cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 797ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // TODO: If the request already existed, we'll update the priority (so that the time is 798ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // up-to-date); otherwise, create a new request 799ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, 800ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee "Attachment #%d queued for download, priority: %d, created time: %d", 801ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee att.mId, req.mPriority, req.mCreatedTime); 802cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 803e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Process the queue if we're in a wait 804e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee kick(); 805e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 806cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 807e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee /** 80839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * Set the bits in the provider to mark this download as failed. 80939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * @param att The attachment that failed to download. 81039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 81139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee void markAttachmentAsFailed(final Attachment att) { 81239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final ContentValues cv = new ContentValues(); 81339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final int flags = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 81439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee cv.put(AttachmentColumns.FLAGS, att.mFlags &= ~flags); 81539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee cv.put(AttachmentColumns.UI_STATE, AttachmentState.FAILED); 81639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee att.update(this, cv); 81739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 81839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 81939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /** 820ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee * Set the bits in the provider to mark this download as completed. 821ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee * @param att The attachment that was downloaded. 822ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee */ 823ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee void markAttachmentAsCompleted(final Attachment att) { 824ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final ContentValues cv = new ContentValues(); 825ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final int flags = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 826ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee cv.put(AttachmentColumns.FLAGS, att.mFlags &= ~flags); 827ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee cv.put(AttachmentColumns.UI_STATE, AttachmentState.SAVED); 828ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee att.update(this, cv); 829ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 830ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 831ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee /** 832e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing 833e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * the limit on maximum downloads 834e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee */ 835e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized void processQueue() { 836ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Processing changed queue, num entries: %d", sAttachmentChangedQueue.size()); 837ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 838ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // First thing we need to do is process the list of "potential downloads" that we 839ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // added to sAttachmentChangedQueue 840ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee long[] change = sAttachmentChangedQueue.poll(); 841ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee while (change != null) { 842ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // Process this change 843ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final long id = change[0]; 844ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final long flags = change[1]; 845ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final Attachment attachment = Attachment.restoreAttachmentWithId(this, id); 846ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (attachment == null) { 847ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Could not restore attachment #%d", id); 848ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee continue; 849ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 850ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee attachment.mFlags = (int) flags; 851ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee onChange(this, attachment); 852ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee change = sAttachmentChangedQueue.poll(); 853cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 854cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 855ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Processing download queue, num entries: %d", mDownloadQueue.getSize()); 856ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 857e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee while (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS) { 858e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final DownloadRequest req = mDownloadQueue.getNextRequest(); 859e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req == null) { 860e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // No more queued requests? We are done for now. 861e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee break; 862cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 863ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // Enforce per-account limit here 86439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (getDownloadsForAccount(req.mAccountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) { 865ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Skipping #%d; maxed for acct %d", 866ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee req.mAttachmentId, req.mAccountId); 867e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee continue; 868ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 869ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (Attachment.restoreAttachmentWithId(this, req.mAttachmentId) == null) { 870ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.e(LOG_TAG, "Could not load attachment: #%d", req.mAttachmentId); 871e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee continue; 872e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 873e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (!req.mInProgress) { 874e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final long currentTime = SystemClock.elapsedRealtime(); 875e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req.mRetryCount > 0 && req.mRetryStartTime > currentTime) { 876ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Need to wait before retrying attachment #%d", req.mAttachmentId); 87739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mWatchdog.setWatchdogAlarm(this, CONNECTION_ERROR_RETRY_MILLIS, 878e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee CALLBACK_TIMEOUT); 879e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee continue; 880cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 881e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: We try to gate ineligible downloads from entering the queue but its 882e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // always possible that they made it in here regardless in the future. In a 883e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // perfect world, we would make it bullet proof with a check for eligibility 884e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // here instead/also. 885e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee tryStartDownload(req); 886cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 887e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 888cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 889e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Check our ability to be opportunistic regarding background downloads. 890e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final EmailConnectivityManager ecm = mConnectivityManager; 891e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if ((ecm == null) || !ecm.isAutoSyncAllowed() || 892e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee (ecm.getActiveNetworkType() != ConnectivityManager.TYPE_WIFI)) { 893e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Only prefetch if it if connectivity is available, prefetch is enabled 894e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // and we are on WIFI 895ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Skipping opportunistic downloads since WIFI is not available"); 896e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return; 897e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 898e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee 899e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Then, try opportunistic download of appropriate attachments 900ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee final int availableBackgroundThreads = 901ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size() - 1; 902ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (availableBackgroundThreads < 1) { 903e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // We want to leave one spot open for a user requested download that we haven't 904e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // started processing yet. 905ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Skipping opportunistic downloads, %d threads available", 906ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee availableBackgroundThreads); 907e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return; 908e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 909e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee 910ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Launching up to %d opportunistic downloads", availableBackgroundThreads); 911ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 912e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // We'll load up the newest 25 attachments that aren't loaded or queued 913e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: We are always looking for MAX_ATTACHMENTS_TO_CHECK, shouldn't this be 914e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // backgroundDownloads instead? We should fix and test this. 915e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final Uri lookupUri = EmailContent.uriWithLimit(Attachment.CONTENT_URI, 916e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee MAX_ATTACHMENTS_TO_CHECK); 91739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final Cursor c = this.getContentResolver().query(lookupUri, 918e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee Attachment.CONTENT_PROJECTION, 919e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee EmailContent.Attachment.PRECACHE_INBOX_SELECTION, 920e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee null, AttachmentColumns._ID + " DESC"); 92139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee File cacheDir = this.getCacheDir(); 922e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee try { 923e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee while (c.moveToNext()) { 924e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final Attachment att = new Attachment(); 925e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee att.restore(c); 92639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final Account account = Account.restoreAccountWithId(this, att.mAccountKey); 927e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (account == null) { 928e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Clean up this orphaned attachment; there's no point in keeping it 929e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // around; then try to find another one 930ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Found orphaned attachment #%d", att.mId); 93139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee EmailContent.delete(this, Attachment.CONTENT_URI, att.mId); 932e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 933e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Check that the attachment meets system requirements for download 934e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Note that there couple be policy that does not allow this attachment 935e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // to be downloaded. 93639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final AttachmentInfo info = new AttachmentInfo(this, att); 937e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (info.isEligibleForDownload()) { 938e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Either the account must be able to prefetch or this must be 939e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // an inline attachment. 940ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (att.mContentId != null || canPrefetchForAccount(account, cacheDir)) { 941e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final Integer tryCount = mAttachmentFailureMap.get(att.mId); 942e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (tryCount != null && tryCount > MAX_DOWNLOAD_RETRIES) { 943e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // move onto the next attachment 944ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, 945ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee "Too many failed attempts for attachment #%d ", att.mId); 946e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee continue; 947cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 948e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Start this download and we're done 94939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final DownloadRequest req = new DownloadRequest(this, att); 950e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee tryStartDownload(req); 951e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee break; 952cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 953e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 954e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If this attachment was ineligible for download 955e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // because of policy related issues, its flags would be set to 956e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // FLAG_POLICY_DISALLOWS_DOWNLOAD and would not show up in the 957e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // query results. We are most likely here for other reasons such 958e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // as the inability to view the attachment. In that case, let's just 959e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // skip it for now. 960ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Skipping attachment #%d, it is ineligible", att.mId); 961cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 962cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 963cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 964e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } finally { 965e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee c.close(); 966cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 967e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 968cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 969e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee /** 970e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * Attempt to execute the DownloadRequest, enforcing the maximum downloads per account 971e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * parameter 972e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * @param req the DownloadRequest 973e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * @return whether or not the download was started 974e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee */ 975e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized boolean tryStartDownload(final DownloadRequest req) { 976e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final EmailServiceProxy service = EmailServiceUtils.getServiceForAccount( 977e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee AttachmentService.this, req.mAccountId); 978cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 979e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Do not download the same attachment multiple times 980e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee boolean alreadyInProgress = mDownloadsInProgress.get(req.mAttachmentId) != null; 981ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (alreadyInProgress) { 982ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("This attachment #%d is already in progress", req.mAttachmentId); 983ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee return false; 984ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 985cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 986e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee try { 987e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee startDownload(service, req); 988e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } catch (RemoteException e) { 989e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: Consider whether we need to do more in this case... 990e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // For now, fix up our data to reflect the failure 991e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee cancelDownload(req); 992cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 993e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return true; 994e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 995cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 996e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee /** 997e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * Do the work of starting an attachment download using the EmailService interface, and 998e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * set our watchdog alarm 999e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * 1000e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * @param service the service handling the download 1001e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * @param req the DownloadRequest 1002e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * @throws RemoteException 1003e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee */ 1004e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee private void startDownload(final EmailServiceProxy service, final DownloadRequest req) 1005e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee throws RemoteException { 1006ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Starting download for Attachment #%d", req.mAttachmentId); 1007e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mStartTime = System.currentTimeMillis(); 1008e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mInProgress = true; 1009e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadsInProgress.put(req.mAttachmentId, req); 1010e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee service.loadAttachment(mServiceCallback, req.mAccountId, req.mAttachmentId, 1011e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mPriority != PRIORITY_FOREGROUND); 101239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mWatchdog.setWatchdogAlarm(this); 1013e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 1014cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1015e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized void cancelDownload(final DownloadRequest req) { 1016ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Cancelling download for Attachment #%d", req.mAttachmentId); 1017e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mInProgress = false; 1018e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadsInProgress.remove(req.mAttachmentId); 1019e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Remove the download from our queue, and then decide whether or not to add it back. 1020e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadQueue.removeRequest(req); 1021e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mRetryCount++; 1022e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) { 1023ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Too many failures giving up on Attachment #%d", req.mAttachmentId); 1024e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 1025ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Moving to end of queue, will retry #%d", req.mAttachmentId); 1026e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // The time field of DownloadRequest is final, because it's unsafe to change it 1027e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // as long as the DownloadRequest is in the DownloadSet. It's needed for the 1028e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // comparator, so changing time would make the request unfindable. 1029e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Instead, we'll create a new DownloadRequest with an updated time. 1030e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // This will sort at the end of the set. 1031e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final DownloadRequest newReq = new DownloadRequest(req, SystemClock.elapsedRealtime()); 1032e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadQueue.addRequest(newReq); 1033cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1034e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 1035cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1036e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee /** 1037e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * Called when a download is finished; we get notified of this via our EmailServiceCallback 1038e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * @param attachmentId the id of the attachment whose download is finished 1039e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * @param statusCode the EmailServiceStatus code returned by the Service 1040e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee */ 1041e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized void endDownload(final long attachmentId, final int statusCode) { 1042ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Finishing download #%d", attachmentId); 1043ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 1044e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Say we're no longer downloading this 1045e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadsInProgress.remove(attachmentId); 1046e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee 1047e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: This code is conservative and treats connection issues as failures. 1048e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Since we have no mechanism to throttle reconnection attempts, it makes 1049e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // sense to be cautious here. Once logic is in place to prevent connecting 1050e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // in a tight loop, we can exclude counting connection issues as "failures". 1051e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee 1052e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Update the attachment failure list if needed 1053e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee Integer downloadCount; 1054e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee downloadCount = mAttachmentFailureMap.remove(attachmentId); 1055e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (statusCode != EmailServiceStatus.SUCCESS) { 1056e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (downloadCount == null) { 1057e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee downloadCount = 0; 1058cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1059e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee downloadCount += 1; 1060ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "This attachment failed, adding #%d to failure map", attachmentId); 1061e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mAttachmentFailureMap.put(attachmentId, downloadCount); 1062cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1063cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1064e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final DownloadRequest req = mDownloadQueue.findRequestById(attachmentId); 1065e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (statusCode == EmailServiceStatus.CONNECTION_ERROR) { 1066e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If this needs to be retried, just process the queue again 1067e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req != null) { 1068e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mRetryCount++; 1069e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req.mRetryCount > CONNECTION_ERROR_MAX_RETRIES) { 1070e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // We are done, we maxed out our total number of tries. 1071ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // Not that we do not flag this attachment with any special flags so the 1072ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // AttachmentService will try to download this attachment again the next time 1073ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee // that it starts up. 1074ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Too many tried for connection errors, giving up #%d", 1075ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee attachmentId); 1076e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadQueue.removeRequest(req); 1077e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Note that we are not doing anything with the attachment right now 1078e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // We will annotate it later in this function if needed. 1079e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else if (req.mRetryCount > CONNECTION_ERROR_DELAY_THRESHOLD) { 1080e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: I'm not sure this is a great retry/backoff policy, but we're 1081e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // afraid of changing behavior too much in case something relies upon it. 1082e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // So now, for the first five errors, we'll retry immediately. For the next 1083e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // five tries, we'll add a ten second delay between each. After that, we'll 1084e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // give up. 1085ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "ConnectionError #%d, retried %d times, adding delay", 1086e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee attachmentId, req.mRetryCount); 1087e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mInProgress = false; 1088e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mRetryStartTime = SystemClock.elapsedRealtime() + 1089e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee CONNECTION_ERROR_RETRY_MILLIS; 109039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee mWatchdog.setWatchdogAlarm(this, CONNECTION_ERROR_RETRY_MILLIS, 1091e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee CALLBACK_TIMEOUT); 1092e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 1093ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "ConnectionError for #%d, retried %d times, adding delay", 1094e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee attachmentId, req.mRetryCount); 1095e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mInProgress = false; 1096e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee req.mRetryStartTime = 0; 1097e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee kick(); 1098cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1099cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1100e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return; 1101e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 1102cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1103e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If the request is still in the queue, remove it 1104e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (req != null) { 1105e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mDownloadQueue.removeRequest(req); 1106e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 1107cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1108ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (ENABLE_ATTACHMENT_SERVICE_DEBUG > 0) { 1109e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee long secs = 0; 1110cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (req != null) { 1111ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee secs = (System.currentTimeMillis() - req.mCreatedTime) / 1000; 1112cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1113e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" : 1114e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee "Error " + statusCode; 1115ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Download finished for attachment #%d; %d seconds from request, status: %s", 1116ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee attachmentId, secs, status); 1117e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 1118cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 111939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee final Attachment attachment = Attachment.restoreAttachmentWithId(this, attachmentId); 1120e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (attachment != null) { 1121e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final long accountId = attachment.mAccountKey; 1122e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Update our attachment storage for this account 1123e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee Long currentStorage = mAttachmentStorageMap.get(accountId); 1124e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (currentStorage == null) { 1125e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee currentStorage = 0L; 1126e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 1127e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee mAttachmentStorageMap.put(accountId, currentStorage + attachment.mSize); 1128e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee boolean deleted = false; 1129e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if ((attachment.mFlags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 1130e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (statusCode == EmailServiceStatus.ATTACHMENT_NOT_FOUND) { 1131e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If this is a forwarding download, and the attachment doesn't exist (or 1132e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // can't be downloaded) delete it from the outgoing message, lest that 1133e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // message never get sent 113439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee EmailContent.delete(this, Attachment.CONTENT_URI, attachment.mId); 1135e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: Talk to UX about whether this is even worth doing 113639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee NotificationController nc = NotificationController.getInstance(this); 11378c03e2af9f439c6e0c6abb38b0c371da7ccdb72aTony Mantler nc.showDownloadForwardFailedNotificationSynchronous(attachment); 1138e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee deleted = true; 1139ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Deleting forwarded attachment #%d for message #%d", 1140ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee attachmentId, attachment.mMessageKey); 1141cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1142e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If we're an attachment on forwarded mail, and if we're not still blocked, 1143e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // try to send pending mail now (as mediated by MailService) 1144e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if ((req != null) && 114539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee !Utility.hasUnloadedAttachments(this, attachment.mMessageKey)) { 1146ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Downloads finished for outgoing msg #%d", req.mMessageId); 1147e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee EmailServiceProxy service = EmailServiceUtils.getServiceForAccount( 114839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee this, accountId); 1149e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee try { 1150e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee service.sendMail(accountId); 1151e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } catch (RemoteException e) { 1152ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.e(LOG_TAG, "RemoteException while trying to send message: #%d, %s", 1153ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee req.mMessageId, e.toString()); 1154cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1155cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1156e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } 1157e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (statusCode == EmailServiceStatus.MESSAGE_NOT_FOUND) { 115839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee Message msg = Message.restoreMessageWithId(this, attachment.mMessageKey); 1159e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (msg == null) { 1160ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Deleting attachment #%d with no associated message #%d", 1161ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee attachment.mId, attachment.mMessageKey); 1162e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If there's no associated message, delete the attachment 116339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee EmailContent.delete(this, Attachment.CONTENT_URI, attachment.mId); 1164e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 1165e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If there really is a message, retry 1166e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // TODO: How will this get retried? It's still marked as inProgress? 1167ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.w(LOG_TAG, "Retrying attachment #%d with associated message #%d", 1168ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee attachment.mId, attachment.mMessageKey); 1169e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee kick(); 1170e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return; 1171cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1172e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else if (!deleted) { 1173e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Clear the download flags, since we're done for now. Note that this happens 1174e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // only for non-recoverable errors. When these occur for forwarded mail, we can 1175e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // ignore it and continue; otherwise, it was either 1) a user request, in which 1176e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // case the user can retry manually or 2) an opportunistic download, in which 1177e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // case the download wasn't critical 1178ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Attachment #%d successfully downloaded!", attachment.mId); 1179ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee markAttachmentAsCompleted(attachment); 1180cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1181cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1182e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Process the queue 1183e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee kick(); 1184cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1185cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1186cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee /** 118739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * Count the number of running downloads in progress for this account 118839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * @param accountId the id of the account 118939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee * @return the count of running downloads 119039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee */ 119139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee synchronized int getDownloadsForAccount(final long accountId) { 119239e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee int count = 0; 119339e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee for (final DownloadRequest req: mDownloadsInProgress.values()) { 119439e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee if (req.mAccountId == accountId) { 119539e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee count++; 119639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 119739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 119839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return count; 119939e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee } 120039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee 120139e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee /** 1202cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * Calculate the download priority of an Attachment. A priority of zero means that the 1203cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * attachment is not marked for download. 1204cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * @param att the Attachment 1205cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * @return the priority key of the Attachment 1206cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee */ 120739e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee private static int getAttachmentPriority(final Attachment att) { 1208cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee int priorityClass = PRIORITY_NONE; 1209e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final int flags = att.mFlags; 1210cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 1211cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee priorityClass = PRIORITY_SEND_MAIL; 1212cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) { 1213cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee priorityClass = PRIORITY_FOREGROUND; 1214cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1215cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee return priorityClass; 1216cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1217cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1218cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee /** 1219e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * Determine whether an attachment can be prefetched for the given account based on 1220e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee * total download size restrictions tied to the account. 1221cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee * @return true if download is allowed, false otherwise 1222cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee */ 1223e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee public boolean canPrefetchForAccount(final Account account, final File dir) { 1224cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Check account, just in case 1225cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (account == null) return false; 1226e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee 1227cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // First, check preference and quickly return if prefetch isn't allowed 1228ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if ((account.mFlags & Account.FLAGS_BACKGROUND_ATTACHMENTS) == 0) { 1229ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Prefetch is not allowed for this account: %d", account.getId()); 1230ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee return false; 1231ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 1232cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1233e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final long totalStorage = dir.getTotalSpace(); 1234e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final long usableStorage = dir.getUsableSpace(); 1235e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final long minAvailable = (long)(totalStorage * PREFETCH_MINIMUM_STORAGE_AVAILABLE); 1236cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1237cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // If there's not enough overall storage available, stop now 1238ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (usableStorage < minAvailable) { 1239ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Not enough physical storage for prefetch"); 1240ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee return false; 1241ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 1242cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1243e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final int numberOfAccounts = mAccountManagerStub.getNumberOfAccounts(); 1244e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Calculate an even per-account storage although it would make a lot of sense to not 1245e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // do this as you may assign more storage to your corporate account rather than a personal 1246e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // account. 1247e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final long perAccountMaxStorage = 124839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee (long)(totalStorage * PREFETCH_MAXIMUM_ATTACHMENT_STORAGE / numberOfAccounts); 1249cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1250cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Retrieve our idea of currently used attachment storage; since we don't track deletions, 1251cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // this number is the "worst case". If the number is greater than what's allowed per 1252e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // account, we walk the directory to determine the actual number. 1253cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee Long accountStorage = mAttachmentStorageMap.get(account.mId); 1254cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (accountStorage == null || (accountStorage > perAccountMaxStorage)) { 1255cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Calculate the exact figure for attachment storage for this account 1256cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee accountStorage = 0L; 1257cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee File[] files = dir.listFiles(); 1258cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (files != null) { 1259cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee for (File file : files) { 1260cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee accountStorage += file.length(); 1261cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1262cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1263e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // Cache the value. No locking here since this is a concurrent collection object. 1264cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee mAttachmentStorageMap.put(account.mId, accountStorage); 1265cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1266cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1267cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee // Return true if we're using less than the maximum per account 1268e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee if (accountStorage >= perAccountMaxStorage) { 1269ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee debugTrace("Prefetch not allowed for account %d; used: %d, limit %d", 1270ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee account.mId, accountStorage, perAccountMaxStorage); 1271cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee return false; 1272cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1273e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee return true; 1274cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1275cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 127639e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee boolean isConnected() { 1277cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (mConnectivityManager != null) { 127839e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return mConnectivityManager.hasConnectivity(); 1279cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 128039e3bd63b59e3f562e7cbdc4b6b070b0d08c639aAnthony Lee return false; 1281cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1282cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee 1283a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // For Debugging. 1284ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee synchronized public void dumpInProgressDownloads() { 1285ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (ENABLE_ATTACHMENT_SERVICE_DEBUG < 1) { 1286ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Advanced logging not configured."); 1287ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 1288ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee for (final DownloadRequest req : mDownloadsInProgress.values()) { 1289ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "--BEGIN DownloadRequest DUMP--"); 1290ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Account: #%d", req.mAccountId); 1291ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Message: #%d", req.mMessageId); 1292ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Attachment: #%d", req.mAttachmentId); 1293ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Created Time: %d", req.mCreatedTime); 1294ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Priority: %d", req.mPriority); 1295ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee if (req.mInProgress == true) { 1296ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "This download is in progress"); 1297ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } else { 1298ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "This download is not in progress"); 1299ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 1300ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Start Time: %d", req.mStartTime); 1301ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Retry Count: %d", req.mRetryCount); 1302ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Retry Start Tiome: %d", req.mRetryStartTime); 1303ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Last Status Code: %d", req.mLastStatusCode); 1304ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Last Progress: %d", req.mLastProgress); 1305ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "Last Callback Time: %d", req.mLastCallbackTime); 1306ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee LogUtils.d(LOG_TAG, "------------------------------"); 1307ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 1308ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee } 1309ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 1310ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee 1311cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee @Override 1312e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee public void dump(final FileDescriptor fd, final PrintWriter pw, final String[] args) { 1313cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.println("AttachmentService"); 1314e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final long time = System.currentTimeMillis(); 1315e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee synchronized(mDownloadQueue) { 1316e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee pw.println(" Queue, " + mDownloadQueue.getSize() + " entries"); 1317e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If you iterate over the queue either via iterator or collection, they are not 1318e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // returned in any particular order. With all things being equal its better to go with 1319e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // a collection to avoid any potential ConcurrentModificationExceptions. 1320e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // If we really want this sorted, we can sort it manually since performance isn't a big 1321e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee // concern with this debug method. 1322e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee for (final DownloadRequest req : mDownloadQueue.mRequestMap.values()) { 13230e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee pw.println(" Account: " + req.mAccountId + ", Attachment: " + req.mAttachmentId); 1324ff52eef8e691645e657f02a21c9d51968c2bdcfdAnthony Lee pw.println(" Priority: " + req.mPriority + ", Time: " + req.mCreatedTime + 13250e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee (req.mInProgress ? " [In progress]" : "")); 1326e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final Attachment att = Attachment.restoreAttachmentWithId(this, req.mAttachmentId); 1327cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (att == null) { 1328cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.println(" Attachment not in database?"); 1329cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } else if (att.mFileName != null) { 1330e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final String fileName = att.mFileName; 1331e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final String suffix; 1332e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee final int lastDot = fileName.lastIndexOf('.'); 1333cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (lastDot >= 0) { 1334cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee suffix = fileName.substring(lastDot); 1335e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee } else { 1336e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee suffix = "[none]"; 1337cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1338cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.print(" Suffix: " + suffix); 1339cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (att.getContentUri() != null) { 1340cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.print(" ContentUri: " + att.getContentUri()); 1341cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1342cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.print(" Mime: "); 1343cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee if (att.mMimeType != null) { 1344cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.print(att.mMimeType); 1345cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } else { 1346cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.print(AttachmentUtilities.inferMimeType(fileName, null)); 1347cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.print(" [inferred]"); 1348cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1349cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee pw.println(" Size: " + att.mSize); 1350cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 13510e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (req.mInProgress) { 13520e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee pw.println(" Status: " + req.mLastStatusCode + ", Progress: " + 13530e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee req.mLastProgress); 13540e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee pw.println(" Started: " + req.mStartTime + ", Callback: " + 13550e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee req.mLastCallbackTime); 13560e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee pw.println(" Elapsed: " + ((time - req.mStartTime) / 1000L) + "s"); 13570e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee if (req.mLastCallbackTime > 0) { 13580e49862711852775acc91aa8d2dee2b6b70ea1d0Anthony Lee pw.println(" CB: " + ((time - req.mLastCallbackTime) / 1000L) + "s"); 1359cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1360cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1361cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1362cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1363cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee } 1364a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 1365a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee // For Testing 1366e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee AccountManagerStub mAccountManagerStub; 1367a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee private final HashMap<Long, Intent> mAccountServiceMap = new HashMap<Long, Intent>(); 1368a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 1369e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee void addServiceIntentForTest(final long accountId, final Intent intent) { 1370a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee mAccountServiceMap.put(accountId, intent); 1371a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 1372a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 1373a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee /** 1374a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee * We only use the getAccounts() call from AccountManager, so this class wraps that call and 1375a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee * allows us to build a mock account manager stub in the unit tests 1376a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee */ 1377e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee static class AccountManagerStub { 1378a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee private int mNumberOfAccounts; 1379a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee private final AccountManager mAccountManager; 1380a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 1381e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee AccountManagerStub(final Context context) { 1382a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee if (context != null) { 1383a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee mAccountManager = AccountManager.get(context); 1384a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } else { 1385a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee mAccountManager = null; 1386a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 1387a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 1388a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 1389e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee int getNumberOfAccounts() { 1390a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee if (mAccountManager != null) { 1391a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee return mAccountManager.getAccounts().length; 1392a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } else { 1393a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee return mNumberOfAccounts; 1394a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 1395a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 1396a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee 1397e33511699a9b091a6e216d29e4f3e7e756ee6ecaAnthony Lee void setNumberOfAccounts(final int numberOfAccounts) { 1398a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee mNumberOfAccounts = numberOfAccounts; 1399a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 1400a72a12241f413f113d61d318757a49b7e33c2af6Anthony Lee } 1401cd2495ebddf97109f3820bb4f92db753d4777cdcAnthony Lee} 1402