AttachmentDownloadService.java revision fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92e
109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank/* 209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Copyright (C) 2010 The Android Open Source Project 309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * 409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Licensed under the Apache License, Version 2.0 (the "License"); 509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * you may not use this file except in compliance with the License. 609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * You may obtain a copy of the License at 709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * 809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * http://www.apache.org/licenses/LICENSE-2.0 909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * 1009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Unless required by applicable law or agreed to in writing, software 1109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * distributed under the License is distributed on an "AS IS" BASIS, 1209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * See the License for the specific language governing permissions and 1409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * limitations under the License. 1509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 1609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 1709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankpackage com.android.email.service; 1809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 1909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.Email; 20899c5b866192a4c4a12413446d10e5d98dbf94faMakoto Onukiimport com.android.email.NotificationController; 2109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.Utility; 22a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport com.android.email.Controller.ControllerService; 23a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport com.android.email.ExchangeUtils.NullEmailService; 2409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.provider.AttachmentProvider; 2509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.provider.EmailContent; 2609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.provider.EmailContent.Account; 2709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.provider.EmailContent.Attachment; 2809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport com.android.email.provider.EmailContent.Message; 2964b64cca01c1a827c1b3824f099fd638cfb15826Marc Blankimport com.android.exchange.ExchangeService; 3009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 3109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.app.Service; 3209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentValues; 3309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.Context; 3409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.Intent; 3509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.database.Cursor; 3609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.IBinder; 3709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.RemoteException; 3809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.text.format.DateUtils; 3909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.util.Log; 4009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 4109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.io.File; 42a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.FileDescriptor; 43a162668f2350f681258fb0949a7fa228f2021ed8Marc Blankimport java.io.PrintWriter; 44f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Comparator; 4509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport java.util.HashMap; 46f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.Iterator; 47f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blankimport java.util.TreeSet; 4809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 4909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankpublic class AttachmentDownloadService extends Service implements Runnable { 5009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static final String TAG = "AttachmentService"; 5109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 5209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Our idle time, waiting for notifications; this is something of a failsafe 5309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int PROCESS_QUEUE_WAIT_TIME = 30 * ((int)DateUtils.MINUTE_IN_MILLIS); 5409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 55f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static final int PRIORITY_NONE = -1; 5609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @SuppressWarnings("unused") 57f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Low priority will be used for opportunistic downloads 58f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static final int PRIORITY_LOW = 0; 59f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Normal priority is for forwarded downloads in outgoing mail 60f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static final int PRIORITY_NORMAL = 1; 61f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // High priority is for user requests 62f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static final int PRIORITY_HIGH = 2; 6309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 6409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // We can try various values here; I think 2 is completely reasonable as a first pass 6509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2; 6609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Limit on the number of simultaneous downloads per account 6709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Note that a limit of 1 is currently enforced by both Services (MailService and Controller) 6809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final int MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT = 1; 6909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 70f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ static AttachmentDownloadService sRunningService = null; 7109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 72f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ Context mContext; 73f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ final DownloadSet mDownloadSet = new DownloadSet(new DownloadComparator()); 7409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final HashMap<Long, Class<? extends Service>> mAccountServiceMap = 7509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new HashMap<Long, Class<? extends Service>>(); 7609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final ServiceCallback mServiceCallback = new ServiceCallback(); 7709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private final Object mLock = new Object(); 7809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private volatile boolean mStop = false; 7909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 80f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public static class DownloadRequest { 81f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final int priority; 82f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long time; 83f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long attachmentId; 84f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long messageId; 85f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank final long accountId; 8609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean inProgress = false; 87a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastStatusCode; 88a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastProgress; 89a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long lastCallbackTime; 90a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long startTime; 9109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 9209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private DownloadRequest(Context context, Attachment attachment) { 9309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachmentId = attachment.mId; 9409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Message msg = Message.restoreMessageWithId(context, attachment.mMessageKey); 9509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (msg != null) { 9609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank accountId = msg.mAccountKey; 9709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank messageId = msg.mId; 98f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 99f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank accountId = messageId = -1; 100f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 101f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank priority = getPriority(attachment); 102f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank time = System.currentTimeMillis(); 103f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 104f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 105f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 106f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int hashCode() { 107f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return (int)attachmentId; 108f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 109f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 110f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 111f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Two download requests are equals if their attachment id's are equals 112f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 113f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 114f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public boolean equals(Object object) { 115f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (!(object instanceof DownloadRequest)) return false; 116f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = (DownloadRequest)object; 117f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req.attachmentId == attachmentId; 118f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 119f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 120f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 121f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 122f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Comparator class for the download set; we first compare by priority. Requests with equal 123f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * priority are compared by the time the request was created (older requests come first) 124f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 125f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*protected*/ static class DownloadComparator implements Comparator<DownloadRequest> { 126f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank @Override 127f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank public int compare(DownloadRequest req1, DownloadRequest req2) { 128f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int res; 129f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.priority != req2.priority) { 130f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.priority < req2.priority) ? -1 : 1; 131f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 132f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req1.time == req2.time) { 133f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = 0; 134f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else { 135f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank res = (req1.time > req2.time) ? -1 : 1; 136f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 13709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 138f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank //Log.d(TAG, "Compare " + req1.attachmentId + " to " + req2.attachmentId + " = " + res); 139f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return res; 14009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 14109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 14209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 14309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 144f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * The DownloadSet is a TreeSet sorted by priority class (e.g. low, high, etc.) and the 145f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * time of the request. Higher priority requests 14609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * are always processed first; among equals, the oldest request is processed first. The 14709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * priority key represents this ordering. Note: All methods that change the attachment map are 14809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * synchronized on the map itself 14909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 150f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ class DownloadSet extends TreeSet<DownloadRequest> { 15109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private static final long serialVersionUID = 1L; 15209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 153f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ DownloadSet(Comparator<? super DownloadRequest> comparator) { 154f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank super(comparator); 155f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 15609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 15709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 158f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Maps attachment id to DownloadRequest 15909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 160f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ final HashMap<Long, DownloadRequest> mDownloadsInProgress = 161f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank new HashMap<Long, DownloadRequest>(); 16209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 16309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 16409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * onChange is called by the AttachmentReceiver upon receipt of a valid notification from 16509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * EmailProvider that an attachment has been inserted or modified. It's not strictly 16609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * necessary that we detect a deleted attachment, as the code always checks for the 16709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * existence of an attachment before acting on it. 16809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 16909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public synchronized void onChange(Attachment att) { 170f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = findDownloadRequest(att.mId); 171f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank long priority = getPriority(att); 172f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (priority == PRIORITY_NONE) { 17309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 17409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Attachment changed: " + att.mId); 17509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 17609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // In this case, there is no download priority for this attachment 177f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 178f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If it exists in the map, remove it 17909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // NOTE: We don't yet support deleting downloads in progress 18009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 18109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Attachment " + att.mId + " was in queue, removing"); 18209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 183f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank remove(req); 18409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 18509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } else { 18609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Ignore changes that occur during download 18709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (mDownloadsInProgress.containsKey(att.mId)) return; 188f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is new, add the request to the queue 18909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req == null) { 19009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req = new DownloadRequest(mContext, att); 191f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank add(req); 19209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 19309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If the request already existed, we'll update the priority (so that the time is 19409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // up-to-date); otherwise, we create a new request 19509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 19609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Download queued for attachment " + att.mId + ", class " + 197f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank req.priority + ", priority time " + req.time); 19809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 19909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 20009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue if we're in a wait 20109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 20209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 20309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 20409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 205f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Find a queued DownloadRequest, given the attachment's id 20609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the id of the attachment 207f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the DownloadRequest for that attachment (or null, if none) 20809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 209f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized DownloadRequest findDownloadRequest(long id) { 210f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = iterator(); 211f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while(iterator.hasNext()) { 212f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 213f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req.attachmentId == id) { 214f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return req; 21509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 21609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 217f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return null; 21809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 21909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 22009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 22109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Run through the AttachmentMap and find DownloadRequests that can be executed, enforcing 22209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * the limit on maximum downloads 22309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 224f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void processQueue() { 22509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 226f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Log.d(TAG, "== Checking attachment queue, " + mDownloadSet.size() + " entries"); 22709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 228f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 22909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // First, start up any required downloads, in priority order 230f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank while (iterator.hasNext() && 231f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank (mDownloadsInProgress.size() < MAX_SIMULTANEOUS_DOWNLOADS)) { 232f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = iterator.next(); 23309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (!req.inProgress) { 234f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.tryStartDownload(req); 23509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 23609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 23709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Then, try opportunistic download of appropriate attachments 23809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int backgroundDownloads = MAX_SIMULTANEOUS_DOWNLOADS - mDownloadsInProgress.size(); 23909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (backgroundDownloads > 0) { 24009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // TODO Code for background downloads here 24109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 24209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== We'd look for up to " + backgroundDownloads + 24309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank " background download(s) now..."); 24409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 24509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 24609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 24709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 24809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 24909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Count the number of running downloads in progress for this account 25009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param accountId the id of the account 25109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return the count of running downloads 25209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 253f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized int downloadsForAccount(long accountId) { 25409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int count = 0; 25509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank for (DownloadRequest req: mDownloadsInProgress.values()) { 25609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req.accountId == accountId) { 25709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank count++; 25809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 25909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 26009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return count; 26109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 26209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 26309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 26409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Attempt to execute the DownloadRequest, enforcing the maximum downloads per account 26509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * parameter 26609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param req the DownloadRequest 26709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the download was started 26809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 269f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized boolean tryStartDownload(DownloadRequest req) { 27009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Enforce per-account limit 27109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (downloadsForAccount(req.accountId) >= MAX_SIMULTANEOUS_DOWNLOADS_PER_ACCOUNT) { 27209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 27309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Skip #" + req.attachmentId + "; maxed for acct #" + 27409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req.accountId); 27509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 27609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 27709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 27809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Class<? extends Service> serviceClass = getServiceClassForAccount(req.accountId); 27909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (serviceClass == null) return false; 28009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailServiceProxy proxy = 28109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new EmailServiceProxy(mContext, serviceClass, mServiceCallback); 28209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 28309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank File file = AttachmentProvider.getAttachmentFilename(mContext, req.accountId, 28409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req.attachmentId); 28509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 28609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, ">> Starting download for attachment #" + req.attachmentId); 28709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 288f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Don't actually run the load if this is the NullEmailService (used in unit tests) 289f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (!serviceClass.equals(NullEmailService.class)) { 290a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.startTime = System.currentTimeMillis(); 291f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank proxy.loadAttachment(req.attachmentId, file.getAbsolutePath(), 292f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank AttachmentProvider.getAttachmentUri(req.accountId, req.attachmentId) 29309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank .toString()); 294f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 29509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mDownloadsInProgress.put(req.attachmentId, req); 29609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req.inProgress = true; 29709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (RemoteException e) { 29809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // TODO: Consider whether we need to do more in this case... 29909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // For now, fix up our data to reflect the failure 30009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mDownloadsInProgress.remove(req.attachmentId); 30109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req.inProgress = false; 30209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 30309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return true; 30409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 30509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 30609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 30709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called when a download is finished; we get notified of this via our EmailServiceCallback 30809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the attachment whose download is finished 30909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param statusCode the EmailServiceStatus code returned by the Service 31009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 311f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ synchronized void endDownload(long attachmentId, int statusCode) { 31209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Say we're no longer downloading this 31309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mDownloadsInProgress.remove(attachmentId); 314f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 31509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.CONNECTION_ERROR) { 31609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If this needs to be retried, just process the queue again 31709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 31809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== The download for attachment #" + attachmentId + 31909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank " will be retried"); 32009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 32109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (req != null) { 32209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank req.inProgress = false; 32309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 32409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 32509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return; 32609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 327b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 328f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // Remove the request from the queue 329f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank remove(req); 33009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 331b961c78ff4bd250d4a25497158162cb230a55057Marc Blank long secs = 0; 332b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if (req != null) { 333b961c78ff4bd250d4a25497158162cb230a55057Marc Blank secs = (System.currentTimeMillis() - req.time) / 1000; 334b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 33509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String status = (statusCode == EmailServiceStatus.SUCCESS) ? "Success" : 33609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank "Error " + statusCode; 33709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "<< Download finished for attachment #" + attachmentId + "; " + secs + 33809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank " seconds from request, status: " + status); 33909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 340b961c78ff4bd250d4a25497158162cb230a55057Marc Blank 34109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); 34209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 34309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank boolean deleted = false; 34409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if ((attachment.mFlags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 34509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (statusCode == EmailServiceStatus.ATTACHMENT_NOT_FOUND) { 346f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank // If this is a forwarding download, and the attachment doesn't exist (or 34709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // can't be downloaded) delete it from the outgoing message, lest that 34809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // message never get sent 34909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId); 35009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // TODO: Talk to UX about whether this is even worth doing 351d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank NotificationController nc = NotificationController.getInstance(mContext); 352d3e4f3ca7e43fb7ebaa140f93a44a1fb96a0577eMarc Blank nc.showDownloadForwardFailedNotification(attachment); 35309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank deleted = true; 35409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 35509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If we're an attachment on forwarded mail, and if we're not still blocked, 35609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // try to send pending mail now (as mediated by MailService) 357b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if ((req != null) && 358b961c78ff4bd250d4a25497158162cb230a55057Marc Blank !Utility.hasUnloadedAttachments(mContext, attachment.mMessageKey)) { 35909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 36009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "== Downloads finished for outgoing msg #" + req.messageId); 36109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 36209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank MailService.actionSendPendingMail(mContext, req.accountId); 36309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 36409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 36509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (!deleted) { 36609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Clear the download flags, since we're done for now. Note that this happens 36709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // only for non-recoverable errors. When these occur for forwarded mail, we can 36809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // ignore it and continue; otherwise, it was either 1) a user request, in which 36909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the user can retry manually or 2) an opportunistic download, in which 37009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // case the download wasn't critical 37109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank ContentValues cv = new ContentValues(); 37209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int flags = 37309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 37409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank cv.put(Attachment.FLAGS, attachment.mFlags &= ~flags); 37509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.update(mContext, cv); 37609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 37709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 37809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Process the queue 37909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 38009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 38109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 38209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 383f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /** 384f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * Calculate the download priority of an Attachment. A priority of zero means that the 385f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * attachment is not marked for download. 386f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @param att the Attachment 387f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank * @return the priority key of the Attachment 388f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank */ 389f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank private static int getPriority(Attachment att) { 390f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int priorityClass = PRIORITY_NONE; 391f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank int flags = att.mFlags; 392f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if ((flags & Attachment.FLAG_DOWNLOAD_FORWARD) != 0) { 393f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank priorityClass = PRIORITY_NORMAL; 394f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } else if ((flags & Attachment.FLAG_DOWNLOAD_USER_REQUEST) != 0) { 395f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank priorityClass = PRIORITY_HIGH; 396f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 397f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return priorityClass; 39809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 39909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 40009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private void kick() { 40109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 40209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.notify(); 40309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 40409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 40509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 40609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 40709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * We use an EmailServiceCallback to keep track of the progress of downloads. These callbacks 40864b64cca01c1a827c1b3824f099fd638cfb15826Marc Blank * come from either Controller (IMAP) or ExchangeService (EAS). Note that we only implement the 40909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * single callback that's defined by the EmailServiceCallback interface. 41009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 41109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private class ServiceCallback extends IEmailServiceCallback.Stub { 41209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode, 41309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int progress) { 41409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (Email.DEBUG) { 41509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String code; 41609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank switch(statusCode) { 41709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank case EmailServiceStatus.SUCCESS: 41809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank code = "Success"; 41909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 42009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank case EmailServiceStatus.IN_PROGRESS: 42109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank code = "In progress"; 42209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 42309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank default: 42409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank code = Integer.toString(statusCode); 42509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 42609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "loadAttachmentStatus, id = " + attachmentId + " code = "+ code + 42709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank ", " + progress + "%"); 42809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 429a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // Record status and progress 430a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 431a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req != null) { 432a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastStatusCode = statusCode; 433a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress = progress; 434a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime = System.currentTimeMillis(); 435a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 43609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank switch (statusCode) { 43709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank case EmailServiceStatus.IN_PROGRESS: 43809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 43909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank default: 440f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.endDownload(attachmentId, statusCode); 44109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank break; 44209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 44309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 44409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 44509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 44609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void sendMessageStatus(long messageId, String subject, int statusCode, int progress) 44709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 44809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 44909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 45009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 45109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void syncMailboxListStatus(long accountId, int statusCode, int progress) 45209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 45309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 45409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 45509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 45609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void syncMailboxStatus(long mailboxId, int statusCode, int progress) 45709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank throws RemoteException { 45809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 45909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 46009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 46109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 46209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Return the class of the service used by the account type of the provided account id. We 46309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * cache the results to avoid repeated database access 46409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param accountId the id of the account 46509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return the service class for the account 46609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 46709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank private synchronized Class<? extends Service> getServiceClassForAccount(long accountId) { 46809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // TODO: We should have some more data-driven way of determining the service class. I'd 46909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // suggest adding an attribute in the stores.xml file 47009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Class<? extends Service> serviceClass = mAccountServiceMap.get(accountId); 47109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (serviceClass == null) { 47209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String protocol = Account.getProtocol(mContext, accountId); 47309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (protocol.equals("eas")) { 47464b64cca01c1a827c1b3824f099fd638cfb15826Marc Blank serviceClass = ExchangeService.class; 47509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } else { 47645f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki serviceClass = ControllerService.class; 47709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 47809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mAccountServiceMap.put(accountId, serviceClass); 47909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 48009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return serviceClass; 48109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 48209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 483f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*protected*/ void addServiceClass(long accountId, Class<? extends Service> serviceClass) { 484f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mAccountServiceMap.put(accountId, serviceClass); 48509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 48609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 487f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ void onChange(Attachment att) { 488f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.onChange(att); 48909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 49009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 491f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean isQueued(long attachmentId) { 492f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return mDownloadSet.findDownloadRequest(attachmentId) != null; 493f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 494f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank 495fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /*package*/ int getSize() { 496fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return mDownloadSet.size(); 497fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 498fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 499f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank /*package*/ boolean dequeue(long attachmentId) { 500f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank DownloadRequest req = mDownloadSet.findDownloadRequest(attachmentId); 501f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (req != null) { 502b961c78ff4bd250d4a25497158162cb230a55057Marc Blank if (Email.DEBUG) { 503b961c78ff4bd250d4a25497158162cb230a55057Marc Blank Log.d(TAG, "Dequeued attachmentId: " + attachmentId); 504b961c78ff4bd250d4a25497158162cb230a55057Marc Blank } 505f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.remove(req); 506f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return true; 507f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank } 508f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank return false; 50909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 51009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 51109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 512fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * Ask the service for the number of items in the download queue 513fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank * @return the number of items queued for download 514fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank */ 515fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank public static int getQueueSize() { 516fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank if (sRunningService != null) { 517fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return sRunningService.getSize(); 518fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 519fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank return 0; 520fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank } 521fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank 522fdc41d4ffc29d29c9c6d8e0b81db98aaf7afa92eMarc Blank /** 52309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service whether a particular attachment is queued for download 52409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 52509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment is queued for download 52609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 52709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean isAttachmentQueued(long attachmentId) { 52809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (sRunningService != null) { 52909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return sRunningService.isQueued(attachmentId); 53009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 53109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 53209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 53309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 53409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 53509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Ask the service to remove an attachment from the download queue 53609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param attachmentId the id of the Attachment (as stored by EmailProvider) 53709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the attachment was removed from the queue 53809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 53909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean cancelQueuedAttachment(long attachmentId) { 54009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (sRunningService != null) { 54109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return sRunningService.dequeue(attachmentId); 54209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 54309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 54409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 54509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 54609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 54709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Called directly by EmailProvider whenever an attachment is inserted or changed 54809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param id the attachment's id 54909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param flags the new flags for the attachment 55009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 55109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static void attachmentChanged(final long id, final int flags) { 552f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank if (sRunningService == null) return; 55309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Utility.runAsync(new Runnable() { 55409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 55509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank final Attachment attachment = 55609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.restoreAttachmentWithId(sRunningService, id); 55709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 55809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Store the flags we got from EmailProvider; given that all of this 55909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // activity is asynchronous, we need to use the newest data from 56009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // EmailProvider 56109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank attachment.mFlags = flags; 56209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank sRunningService.onChange(attachment); 56309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 56409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank }}); 56509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 56609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 56709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void run() { 56809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mContext = this; 56909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Run through all attachments in the database that require download and add them to 57009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // the queue 57109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank int mask = Attachment.FLAG_DOWNLOAD_FORWARD | Attachment.FLAG_DOWNLOAD_USER_REQUEST; 57209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Cursor c = getContentResolver().query(Attachment.CONTENT_URI, 57309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank EmailContent.ID_PROJECTION, "(" + Attachment.FLAGS + " & ?) != 0", 57409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new String[] {Integer.toString(mask)}, null); 57509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 57609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "Count: " + c.getCount()); 57709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (c.moveToNext()) { 57809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment attachment = Attachment.restoreAttachmentWithId( 57909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank this, c.getLong(EmailContent.ID_PROJECTION_COLUMN)); 58009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (attachment != null) { 581f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.onChange(attachment); 58209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (Exception e) { 58509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank e.printStackTrace(); 58609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 58709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank finally { 58809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank c.close(); 58909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 59009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 59109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Loop until stopped, with a 30 minute wait loop 59209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank while (!mStop) { 59309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Here's where we run our attachment loading logic... 594f19f9cf4d3e5229715da77fe05a1a2bbd8da3f41Marc Blank mDownloadSet.processQueue(); 59509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank synchronized(mLock) { 59609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 59709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mLock.wait(PROCESS_QUEUE_WAIT_TIME); 59809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } catch (InterruptedException e) { 59909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // That's ok; we'll just keep looping 60009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 60109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 60209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 60309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 60409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 60509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 60609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public int onStartCommand(Intent intent, int flags, int startId) { 60709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank sRunningService = this; 60809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return Service.START_STICKY; 60909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 61009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 61109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 61209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * The lifecycle of this service is managed by Email.setServicesEnabled(), which is called 61309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * throughout the code, in particular 1) after boot and 2) after accounts are added or removed 61409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * The goal is that this service should be running at all times when there's at least one 61509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * email account present. 61609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 61709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 61809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onCreate() { 61909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // Start up our service thread 62009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank new Thread(this, "AttachmentDownloadService").start(); 62109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 62209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 62309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public IBinder onBind(Intent intent) { 62409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return null; 62509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 62609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 62709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank @Override 62809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public void onDestroy() { 62909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Log.d(TAG, "**** ON DESTROY!"); 63009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (sRunningService != null) { 63109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank mStop = true; 63209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank kick(); 63309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 63409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank sRunningService = null; 63509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 636a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank 637a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank @Override 638a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 639a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println("AttachmentDownloadService"); 640a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank long time = System.currentTimeMillis(); 641a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank synchronized(mDownloadSet) { 642a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Queue, " + mDownloadSet.size() + " entries"); 643a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator(); 644a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank // First, start up any required downloads, in priority order 645a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank while (iterator.hasNext()) { 646a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank DownloadRequest req = iterator.next(); 647a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Account: " + req.accountId + ", Attachment: " + req.attachmentId); 648a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Priority: " + req.priority + ", Time: " + req.time + 649a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank (req.inProgress ? " [In progress]" : "")); 650a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank Attachment att = Attachment.restoreAttachmentWithId(mContext, req.attachmentId); 651a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att == null) { 652a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Attachment not in database?"); 653eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank } else if (att.mFileName != null) { 654a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String fileName = att.mFileName; 655a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank String suffix = "[none]"; 656a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank int lastDot = fileName.lastIndexOf('.'); 657a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (lastDot >= 0) { 658a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank suffix = fileName.substring(lastDot); 659a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 660a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Suffix: " + suffix); 661a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att.mContentUri != null) { 662a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" ContentUri: " + att.mContentUri); 663a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 664a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(" Mime: "); 665a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (att.mMimeType != null) { 666a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(att.mMimeType); 667a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } else { 668a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.print(AttachmentProvider.inferMimeType(fileName, null)); 669eed42e83ab1808a523e614cb6096e169d8122b95Marc Blank pw.print(" [inferred]"); 670a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 671a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Size: " + att.mSize); 672a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 673a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.inProgress) { 674a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Status: " + req.lastStatusCode + ", Progress: " + 675a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastProgress); 676a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Started: " + req.startTime + ", Callback: " + 677a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank req.lastCallbackTime); 678a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" Elapsed: " + ((time - req.startTime) / 1000L) + "s"); 679a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank if (req.lastCallbackTime > 0) { 680a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank pw.println(" CB: " + ((time - req.lastCallbackTime) / 1000L) + "s"); 681a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 682a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 683a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 684a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 685a162668f2350f681258fb0949a7fa228f2021ed8Marc Blank } 68609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank} 687