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