1576b58578e9b42910c91fc9d838188e2673140abMarc Blank/*
2576b58578e9b42910c91fc9d838188e2673140abMarc Blank * Copyright (C) 2008-2009 Marc Blank
3576b58578e9b42910c91fc9d838188e2673140abMarc Blank * Licensed to The Android Open Source Project.
4576b58578e9b42910c91fc9d838188e2673140abMarc Blank *
5576b58578e9b42910c91fc9d838188e2673140abMarc Blank * Licensed under the Apache License, Version 2.0 (the "License");
6576b58578e9b42910c91fc9d838188e2673140abMarc Blank * you may not use this file except in compliance with the License.
7576b58578e9b42910c91fc9d838188e2673140abMarc Blank * You may obtain a copy of the License at
8576b58578e9b42910c91fc9d838188e2673140abMarc Blank *
9576b58578e9b42910c91fc9d838188e2673140abMarc Blank *      http://www.apache.org/licenses/LICENSE-2.0
10576b58578e9b42910c91fc9d838188e2673140abMarc Blank *
11576b58578e9b42910c91fc9d838188e2673140abMarc Blank * Unless required by applicable law or agreed to in writing, software
12576b58578e9b42910c91fc9d838188e2673140abMarc Blank * distributed under the License is distributed on an "AS IS" BASIS,
13576b58578e9b42910c91fc9d838188e2673140abMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14576b58578e9b42910c91fc9d838188e2673140abMarc Blank * See the License for the specific language governing permissions and
15576b58578e9b42910c91fc9d838188e2673140abMarc Blank * limitations under the License.
16576b58578e9b42910c91fc9d838188e2673140abMarc Blank */
17576b58578e9b42910c91fc9d838188e2673140abMarc Blank
187c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankpackage com.android.exchange;
19576b58578e9b42910c91fc9d838188e2673140abMarc Blank
20abf1a363797fa4863b8a82b8579242c27ca61649Marc Blankimport android.content.ContentUris;
21abf1a363797fa4863b8a82b8579242c27ca61649Marc Blankimport android.content.ContentValues;
22abf1a363797fa4863b8a82b8579242c27ca61649Marc Blankimport android.content.Context;
23abf1a363797fa4863b8a82b8579242c27ca61649Marc Blankimport android.database.Cursor;
24c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blankimport android.net.TrafficStats;
25abf1a363797fa4863b8a82b8579242c27ca61649Marc Blankimport android.net.Uri;
26abf1a363797fa4863b8a82b8579242c27ca61649Marc Blankimport android.os.RemoteException;
27b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrookimport android.text.TextUtils;
28abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank
29c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blankimport com.android.emailcommon.TrafficFlags;
30c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.internet.Rfc822Output;
31220686b40eb4cdeed35b1035dc3ad79842e4ce6eMarc Blankimport com.android.emailcommon.mail.MessagingException;
327372782488977df778a33d990401ce9e397f646bMarc Blankimport com.android.emailcommon.provider.Account;
33c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Body;
34c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.BodyColumns;
35c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns;
36c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
37c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.MessageColumns;
38c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.SyncColumns;
394d8774462ace9a45154b2df418b9f2fe7a9c685dBen Komaloimport com.android.emailcommon.provider.Mailbox;
4058dac270fd3a2fb1ff6cb5287ef9b61ea5080e41Marc Blankimport com.android.emailcommon.service.EmailServiceStatus;
41c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.utility.Utility;
42c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport com.android.exchange.CommandStatusException.CommandStatus;
43c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport com.android.exchange.adapter.Parser;
44c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport com.android.exchange.adapter.Parser.EmptyStreamException;
45c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport com.android.exchange.adapter.Serializer;
46c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport com.android.exchange.adapter.Tags;
47576b58578e9b42910c91fc9d838188e2673140abMarc Blank
48c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport org.apache.http.HttpEntity;
491b06024587a4499bcf3f9005337e8f7cae5ffa26Marc Blankimport org.apache.http.HttpStatus;
509e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blankimport org.apache.http.entity.InputStreamEntity;
518047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank
52013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blankimport java.io.ByteArrayOutputStream;
539e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blankimport java.io.File;
549e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blankimport java.io.FileInputStream;
559e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blankimport java.io.FileOutputStream;
5667698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport java.io.IOException;
57c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport java.io.InputStream;
58c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blankimport java.io.OutputStream;
5967698e240187c902bed123bf18d342ff25ec75c7Marc Blank
60ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpublic class EasOutboxService extends EasSyncService {
61576b58578e9b42910c91fc9d838188e2673140abMarc Blank
629e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    public static final int SEND_FAILED = 1;
639e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    public static final String MAILBOX_KEY_AND_NOT_SEND_FAILED =
648ae559267e89badc9b82b8f947878ae036c5b6a8Marc Blank        MessageColumns.MAILBOX_KEY + "=? and (" + SyncColumns.SERVER_ID + " is null or " +
65c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        SyncColumns.SERVER_ID + "!=" + SEND_FAILED + ')';
6685f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank    public static final String[] BODY_SOURCE_PROJECTION =
6785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        new String[] {BodyColumns.SOURCE_MESSAGE_KEY};
6885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank    public static final String WHERE_MESSAGE_KEY = Body.MESSAGE_KEY + "=?";
699e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank
70c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    // This is a normal email (i.e. not one of the other types)
71c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    public static final int MODE_NORMAL = 0;
72c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    // This is a smart reply email
73c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    public static final int MODE_SMART_REPLY = 1;
74c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    // This is a smart forward email
75c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    public static final int MODE_SMART_FORWARD = 2;
76c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
77e4957f03ab45eeaaa895455de2f544c06852356fMarc Blank    // This needs to be long enough to send the longest reasonable message, without being so long
78e4957f03ab45eeaaa895455de2f544c06852356fMarc Blank    // as to effectively "hang" sending of mail.  The standard 30 second timeout isn't long enough
79e4957f03ab45eeaaa895455de2f544c06852356fMarc Blank    // for pictures and the like.  For now, we'll use 15 minutes, in the knowledge that any socket
80e4957f03ab45eeaaa895455de2f544c06852356fMarc Blank    // failure would probably generate an Exception before timing out anyway
81e4957f03ab45eeaaa895455de2f544c06852356fMarc Blank    public static final int SEND_MAIL_TIMEOUT = 15*MINUTES;
82e4957f03ab45eeaaa895455de2f544c06852356fMarc Blank
83e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    protected EasOutboxService(Context _context, Mailbox _mailbox) {
84576b58578e9b42910c91fc9d838188e2673140abMarc Blank        super(_context, _mailbox);
859e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    }
869e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank
87c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    /**
88c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * Our own HttpEntity subclass that is able to insert opaque data (in this case the MIME
89c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * representation of the message body as stored in a temporary file) into the serializer stream
90c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     */
91c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    private static class SendMailEntity extends InputStreamEntity {
92c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private final Context mContext;
93c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private final FileInputStream mFileStream;
94c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private final long mFileLength;
95c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private final int mSendTag;
96c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private final Message mMessage;
97c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
98c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private static final int[] MODE_TAGS =  new int[] {Tags.COMPOSE_SEND_MAIL,
99c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            Tags.COMPOSE_SMART_REPLY, Tags.COMPOSE_SMART_FORWARD};
100c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
101c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        public SendMailEntity(Context context, FileInputStream instream, long length, int tag,
102c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                Message message) {
103c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            super(instream, length);
104c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mContext = context;
105c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mFileStream = instream;
106c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mFileLength = length;
107c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mSendTag = tag;
108c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mMessage = message;
109c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
110c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
111c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        /**
112c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank         * We always return -1 because we don't know the actual length of the POST data (this
113c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank         * causes HttpClient to send the data in "chunked" mode)
114c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank         */
115c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        @Override
116c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        public long getContentLength() {
117013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            ByteArrayOutputStream baos = new ByteArrayOutputStream();
118013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            try {
119013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                // Calculate the overhead for the WBXML data
120013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                writeTo(baos, false);
121013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                // Return the actual size that will be sent
122013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                return baos.size() + mFileLength;
123013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            } catch (IOException e) {
124013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                // Just return -1 (unknown)
125013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            } finally {
126013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                try {
127013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                    baos.close();
128013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                } catch (IOException e) {
129013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                    // Ignore
130013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                }
131013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            }
132c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            return -1;
133c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
134c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
135c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        @Override
136c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        public void writeTo(OutputStream outstream) throws IOException {
137013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            writeTo(outstream, true);
138013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank        }
139013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank
140013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank        /**
141013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank         * Write the message to the output stream
142013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank         * @param outstream the output stream to write
143013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank         * @param withData whether or not the actual data is to be written; true when sending
144013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank         *   mail; false when calculating size only
145013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank         * @throws IOException
146013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank         */
147013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank        public void writeTo(OutputStream outstream, boolean withData) throws IOException {
148c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Not sure if this is possible; the check is taken from the superclass
149c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            if (outstream == null) {
150c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                throw new IllegalArgumentException("Output stream may not be null");
151c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            }
152c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
153c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // We'll serialize directly into the output stream
154c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            Serializer s = new Serializer(outstream);
155c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Send the appropriate initial tag
156c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            s.start(mSendTag);
157c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // The Message-Id for this message (note that we cannot use the messageId stored in
158c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // the message, as EAS 14 limits the length to 40 chars and we use 70+)
159c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            s.data(Tags.COMPOSE_CLIENT_ID, "SendMail-" + System.nanoTime());
160c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // We always save sent mail
161c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            s.tag(Tags.COMPOSE_SAVE_IN_SENT_ITEMS);
162c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
163c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // If we're using smart reply/forward, we need info about the original message
164c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            if (mSendTag != Tags.COMPOSE_SEND_MAIL) {
165c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                OriginalMessageInfo info = getOriginalMessageInfo(mContext, mMessage.mId);
166c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                if (info != null) {
167c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                    s.start(Tags.COMPOSE_SOURCE);
168abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                    // For search results, use the long id (stored in mProtocolSearchInfo); else,
169abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                    // use folder id/item id combo
170abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                    if (mMessage.mProtocolSearchInfo != null) {
171abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                        s.data(Tags.COMPOSE_LONG_ID, mMessage.mProtocolSearchInfo);
172abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                    } else {
173abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                        s.data(Tags.COMPOSE_ITEM_ID, info.mItemId);
174abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                        s.data(Tags.COMPOSE_FOLDER_ID, info.mCollectionId);
175abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                    }
176c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                    s.end();  // Tags.COMPOSE_SOURCE
177c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                }
178c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            }
179c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
180c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Start the MIME tag; this is followed by "opaque" data (byte array)
181c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            s.start(Tags.COMPOSE_MIME);
182c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Send opaque data from the file stream
183013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            if (withData) {
184013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                s.opaque(mFileStream, (int)mFileLength);
185013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            } else {
186013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank                s.opaqueWithoutData((int)mFileLength);
187013236b008a6b0c98cc2c556c520c5c1c08c9f23Marc Blank            }
188c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // And we're done
189c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            s.end().end().done();
190c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
191c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    }
192c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
193c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    private static class SendMailParser extends Parser {
194c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private final int mStartTag;
195c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        private int mStatus;
196c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
197c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        public SendMailParser(InputStream in, int startTag) throws IOException {
198c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            super(in);
199c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mStartTag = startTag;
200c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
201c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
202c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        public int getStatus() {
203c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            return mStatus;
204c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
205c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
206c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        /**
207c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank         * The only useful info in the SendMail response is the status; we capture and save it
208c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank         */
209c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        @Override
210c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        public boolean parse() throws IOException {
211c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            if (nextTag(START_DOCUMENT) != mStartTag) {
212c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                throw new IOException();
213c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            }
214c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
215c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                if (tag == Tags.COMPOSE_STATUS) {
216c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                    mStatus = getValueInt();
217c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                } else {
218c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                    skipTag();
219c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                }
220c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            }
221c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            return true;
222c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
223c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    }
224c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
225c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    /**
226c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * For OriginalMessageInfo, we use the terminology of EAS for the serverId and mailboxId of the
227c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * original message
228c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     */
229c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    protected static class OriginalMessageInfo {
230c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        final String mItemId;
231c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        final String mCollectionId;
232b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        final String mLongId;
233c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
234b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        OriginalMessageInfo(String itemId, String collectionId, String longId) {
235c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mItemId = itemId;
236c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            mCollectionId = collectionId;
237b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            mLongId = longId;
238c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
239c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    }
240c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
241f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank    private void sendCallback(long msgId, String subject, int status) {
242f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank        try {
243385a0be662509754e687bcfa9813208b050bf951Marc Blank            ExchangeService.callback().sendMessageStatus(msgId, subject, status, 0);
244f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank        } catch (RemoteException e) {
245f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank            // It's all good
246f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank        }
247f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank    }
248f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank
249abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank    /*package*/ String generateSmartSendCmd(boolean reply, OriginalMessageInfo info) {
250abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank        StringBuilder sb = new StringBuilder();
251abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank        sb.append(reply ? "SmartReply" : "SmartForward");
252b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        if (!TextUtils.isEmpty(info.mLongId)) {
253b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            sb.append("&LongId=");
254b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            sb.append(Uri.encode(info.mLongId, ":"));
255b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        } else {
256b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            sb.append("&ItemId=");
257b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            sb.append(Uri.encode(info.mItemId, ":"));
258b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            sb.append("&CollectionId=");
259b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            sb.append(Uri.encode(info.mCollectionId, ":"));
260b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        }
261abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank        return sb.toString();
2628eeede0d74ba2b139ce96d883541f12a562cc17eMarc Blank    }
2638eeede0d74ba2b139ce96d883541f12a562cc17eMarc Blank
2649e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank    /**
265c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * Get information about the original message that is referenced by the message to be sent; this
266c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * information will exist for replies and forwards
267c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     *
268c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * @param context the caller's context
269c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * @param msgId the id of the message we're sending
270c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * @return a data structure with the serverId and mailboxId of the original message, or null if
271c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     * either or both of those pieces of information can't be found
272c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank     */
273c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    private static OriginalMessageInfo getOriginalMessageInfo(Context context, long msgId) {
274c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        // Note: itemId and collectionId are the terms used by EAS to refer to the serverId and
275c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        // mailboxId of a Message
276c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        String itemId = null;
277c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        String collectionId = null;
278b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        String longId = null;
279c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
280c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        // First, we need to get the id of the reply/forward message
281c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        String[] cols = Utility.getRowColumns(context, Body.CONTENT_URI,
282c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                BODY_SOURCE_PROJECTION, WHERE_MESSAGE_KEY,
283c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                new String[] {Long.toString(msgId)});
284c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        if (cols != null) {
285b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            long refId = Long.parseLong(cols[0]);
286c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Then, we need the serverId and mailboxKey of the message
287c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            cols = Utility.getRowColumns(context, Message.CONTENT_URI, refId,
288abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                    SyncColumns.SERVER_ID, MessageColumns.MAILBOX_KEY,
289abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank                    MessageColumns.PROTOCOL_SEARCH_INFO);
290c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            if (cols != null) {
291c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                itemId = cols[0];
292c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                long boxId = Long.parseLong(cols[1]);
293c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                // Then, we need the serverId of the mailbox
294c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                cols = Utility.getRowColumns(context, Mailbox.CONTENT_URI, boxId,
295c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                        MailboxColumns.SERVER_ID);
296c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                if (cols != null) {
297c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                    collectionId = cols[0];
298c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                }
299c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            }
300c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
301abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank        // We need either a longId or both itemId (serverId) and collectionId (mailboxId) to process
302abf1a363797fa4863b8a82b8579242c27ca61649Marc Blank        // a smart reply or a smart forward
303b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook        if (longId != null || (itemId != null && collectionId != null)){
304b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            return new OriginalMessageInfo(itemId, collectionId, longId);
305c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        }
306c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        return null;
307c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    }
308c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
309c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    private void sendFailed(long msgId, int result) {
310c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        ContentValues cv = new ContentValues();
311c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        cv.put(SyncColumns.SERVER_ID, SEND_FAILED);
312c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        Message.update(mContext, Message.CONTENT_URI, msgId, cv);
313c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        sendCallback(msgId, null, result);
314c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank    }
315c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
3167e62548f1f79d03ad55d98815fe6a9c59ea80ab0Marc Blank    /**
3179e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank     * Send a single message via EAS
3189e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank     * Note that we mark messages SEND_FAILED when there is a permanent failure, rather than an
319385a0be662509754e687bcfa9813208b050bf951Marc Blank     * IOException, which is handled by ExchangeService with retries, backoffs, etc.
3209e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank     *
3219e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank     * @param cacheDir the cache directory for this context
3229e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank     * @param msgId the _id of the message to send
3239e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank     * @throws IOException
3249e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank     */
325b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank    int sendMessage(File cacheDir, long msgId) throws IOException, MessagingException {
3266534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank        // We always return SUCCESS unless the sending error is account-specific (security or
3276534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank        // authentication) rather than message-specific; returning anything else will terminate
3286534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank        // the Outbox sync! Message-specific errors are marked in the messages themselves.
3296534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank        int result = EmailServiceStatus.SUCCESS;
330c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        // Say we're starting to send this message
331f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank        sendCallback(msgId, null, EmailServiceStatus.IN_PROGRESS);
332c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank        // Create a temporary file (this will hold the outgoing message in RFC822 (MIME) format)
3339e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        File tmpFile = File.createTempFile("eas_", "tmp", cacheDir);
3349e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        try {
335c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Get the message and fail quickly if not found
336c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            Message msg = Message.restoreMessageWithId(mContext, msgId);
337c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            if (msg == null) return EmailServiceStatus.MESSAGE_NOT_FOUND;
33885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
339c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // See what kind of outgoing messge this is
340c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            int flags = msg.mFlags;
34185f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            boolean reply = (flags & Message.FLAG_TYPE_REPLY) != 0;
34285f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            boolean forward = (flags & Message.FLAG_TYPE_FORWARD) != 0;
34383352b933def4cff612ba40895e86c971043e1fbMarc Blank            boolean includeQuotedText = (flags & Message.FLAG_NOT_INCLUDE_QUOTED_TEXT) == 0;
344c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
34585f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            // The reference message and mailbox are called item and collection in EAS
346c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            OriginalMessageInfo referenceInfo = null;
34783352b933def4cff612ba40895e86c971043e1fbMarc Blank            // Respect the sense of the include quoted text flag
34883352b933def4cff612ba40895e86c971043e1fbMarc Blank            if (includeQuotedText && (reply || forward)) {
349c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                referenceInfo = getOriginalMessageInfo(mContext, msgId);
35085f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            }
3516f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank            // Generally, we use SmartReply/SmartForward if we've got a good reference
352c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            boolean smartSend = referenceInfo != null;
3536f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank            // But we won't use SmartForward if the account isn't set up for it (currently, we only
3546f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank            // use SmartForward for EAS 12.0 or later to avoid creating eml files that are
3556f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank            // potentially difficult for the recipient to handle)
3566f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank            if (forward && ((mAccount.mFlags & Account.FLAGS_SUPPORTS_SMART_FORWARD) == 0)) {
3576f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank                smartSend = false;
3586f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank            }
35985f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
360c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Write the message to the temporary file
361c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            FileOutputStream fileOutputStream = new FileOutputStream(tmpFile);
362b931f82fa44c2e26e2645c0d5fde9eef3e666efdPaul Westbrook            Rfc822Output.writeTo(mContext, msgId, fileOutputStream, smartSend, true);
363c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            fileOutputStream.close();
36485f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
365c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Sending via EAS14 is a whole 'nother kettle of fish
366c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            boolean isEas14 = (Double.parseDouble(mAccount.mProtocolVersion) >=
367c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE);
36885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank
3696534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank            while (true) {
370701e2ab3a2d105e6588fdfd01fc2162527f5c69aMarc Blank                // Get an input stream to our temporary file and create an entity with it
371701e2ab3a2d105e6588fdfd01fc2162527f5c69aMarc Blank                FileInputStream fileStream = new FileInputStream(tmpFile);
372701e2ab3a2d105e6588fdfd01fc2162527f5c69aMarc Blank                long fileLength = tmpFile.length();
373701e2ab3a2d105e6588fdfd01fc2162527f5c69aMarc Blank
3746534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                // The type of entity depends on whether we're using EAS 14
3756534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                HttpEntity inputEntity;
3766534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                // For EAS 14, we need to save the wbxml tag we're using
3776534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                int modeTag = 0;
378c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                if (isEas14) {
3796534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    int mode =
3806534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        !smartSend ? MODE_NORMAL : reply ? MODE_SMART_REPLY : MODE_SMART_FORWARD;
3816534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    modeTag = SendMailEntity.MODE_TAGS[mode];
3826534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    inputEntity =
3836534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        new SendMailEntity(mContext, fileStream, fileLength, modeTag, msg);
384c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                } else {
3856534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    inputEntity = new InputStreamEntity(fileStream, fileLength);
386c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                }
3876534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                // Create the appropriate command and POST it to the server
3886534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                String cmd = "SendMail";
3896534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                if (smartSend) {
3906534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    // In EAS 14, we don't send itemId and collectionId in the command
391c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                    if (isEas14) {
3926534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        cmd = reply ? "SmartReply" : "SmartForward";
3936534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    } else {
3946534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        cmd = generateSmartSendCmd(reply, referenceInfo);
3956534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    }
3966534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                }
3976534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank
3986534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                // If we're not EAS 14, add our save-in-sent setting here
3996534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                if (!isEas14) {
4006534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    cmd += "&SaveInSent=T";
4016534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                }
4026534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                userLog("Send cmd: " + cmd);
4036534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank
4046534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                // Finally, post SendMail to the server
4056534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                EasResponse resp = sendHttpClientPost(cmd, inputEntity, SEND_MAIL_TIMEOUT);
4066534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                try {
4076534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    fileStream.close();
4086534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    int code = resp.getStatus();
4096534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    if (code == HttpStatus.SC_OK) {
4106534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        // HTTP OK before EAS 14 is a thumbs up; in EAS 14, we've got to parse
4116534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        // the reply
4126534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        if (isEas14) {
4136534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                            try {
4146534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                // Try to parse the result
4156534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                SendMailParser p =
4166534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                    new SendMailParser(resp.getInputStream(), modeTag);
4176534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                // If we get here, the SendMail failed; go figure
4186534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                p.parse();
4196534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                // The parser holds the status
4206534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                int status = p.getStatus();
4216534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                userLog("SendMail error, status: " + status);
4226534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                if (CommandStatus.isNeedsProvisioning(status)) {
4236534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                    result = EmailServiceStatus.SECURITY_FAILURE;
4246534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                } else if (status == CommandStatus.ITEM_NOT_FOUND && smartSend) {
4256534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                    // This is the retry case for EAS 14; we'll send without "smart"
4266534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                    // commands next time
4276534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                    resp.close();
4286534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                    smartSend = false;
4296534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                    continue;
4306534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                }
4316534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                sendFailed(msgId, result);
4326534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                return result;
4336534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                            } catch (EmptyStreamException e) {
4346534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                // This is actually fine; an empty stream means SendMail succeeded
435c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                            }
436c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                        }
437c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank
4386534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        // If we're here, the SendMail command succeeded
4396534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        userLog("Deleting message...");
4406534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        // Delete the message from the Outbox and send callback
4416534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        mContentResolver.delete(
4426534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                                ContentUris.withAppendedId(Message.CONTENT_URI, msgId), null, null);
4436534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        sendCallback(-1, msg.mSubject, EmailServiceStatus.SUCCESS);
4446534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        break;
4456534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    } else if (code == EasSyncService.INTERNAL_SERVER_ERROR_CODE && smartSend) {
4466534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        // This is the retry case for EAS 12.1 and below; we'll send without "smart"
4476534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        // commands next time
4486534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        resp.close();
4496534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        smartSend = false;
450bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    } else {
4516534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        userLog("Message sending failed, code: " + code);
4526534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        if (EasResponse.isAuthError(code)) {
4536534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                            result = EmailServiceStatus.LOGIN_FAILED;
4546534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        } else if (EasResponse.isProvisionError(code)) {
4556534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                            result = EmailServiceStatus.SECURITY_FAILURE;
4566534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        }
4576534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        sendFailed(msgId, result);
4586534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                        break;
459bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                    }
4606534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                } finally {
4616534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank                    resp.close();
462f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank                }
4639e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank            }
464f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank        } catch (IOException e) {
465f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank            // We catch this just to send the callback
466f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank            sendCallback(msgId, null, EmailServiceStatus.CONNECTION_ERROR);
467f3ae2f9ee2ced1afc5cac4ebad125161726b6c0bMarc Blank            throw e;
4689e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        } finally {
4699e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank            // Clean up the temporary file
4709e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank            if (tmpFile.exists()) {
4719e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank                tmpFile.delete();
4729e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank            }
4739e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        }
474b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank        return result;
475576b58578e9b42910c91fc9d838188e2673140abMarc Blank    }
476576b58578e9b42910c91fc9d838188e2673140abMarc Blank
4778047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank    @Override
478ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void run() {
4797ad79c00d01a68cc0874b5fcae7c487c88b39748Marc Blank        setupService();
480c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank        // Use SMTP flags for sending mail
481c0dce2284e50f2b638bad8c1fa6e2028e46ea5d9Marc Blank        TrafficStats.setThreadStatsTag(TrafficFlags.getSmtpFlags(mContext, mAccount));
4829e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank        File cacheDir = mContext.getCacheDir();
483576b58578e9b42910c91fc9d838188e2673140abMarc Blank        try {
484220686b40eb4cdeed35b1035dc3ad79842e4ce6eMarc Blank            mDeviceId = ExchangeService.getDeviceId(mContext);
485c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            // Get a cursor to Outbox messages
4868047ef058e41c164c2c8ab230ae8d123f042c167Marc Blank            Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
4879e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank                    Message.ID_COLUMN_PROJECTION, MAILBOX_KEY_AND_NOT_SEND_FAILED,
4889e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank                    new String[] {Long.toString(mMailbox.mId)}, null);
489c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank            try {
490c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                // Loop through the messages, sending each one
491ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                while (c.moveToNext()) {
492c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                    long msgId = c.getLong(Message.ID_COLUMNS_ID_COLUMN);
4939e93e3403ad25add433cb9d2cb0f8cb9154e57eeMarc Blank                    if (msgId != 0) {
4946f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank                        if (Utility.hasUnloadedAttachments(mContext, msgId)) {
4956f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank                            // We'll just have to wait on this...
4966f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank                            continue;
4976f61d1668fe8cae1bf01488ea4752a51d48fbb58Marc Blank                        }
498b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                        int result = sendMessage(cacheDir, msgId);
499b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                        // If there's an error, it should stop the service; we will distinguish
500b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                        // at least between login failures and everything else
501b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                        if (result == EmailServiceStatus.LOGIN_FAILED) {
502b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                            mExitStatus = EXIT_LOGIN_FAILURE;
503b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                            return;
504c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                        } else if (result == EmailServiceStatus.SECURITY_FAILURE) {
505c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                            mExitStatus = EXIT_SECURITY_FAILURE;
506c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                            return;
507b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                        } else if (result == EmailServiceStatus.REMOTE_EXCEPTION) {
508b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                            mExitStatus = EXIT_EXCEPTION;
509b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                            return;
510b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank                        }
511576b58578e9b42910c91fc9d838188e2673140abMarc Blank                    }
512576b58578e9b42910c91fc9d838188e2673140abMarc Blank                }
513576b58578e9b42910c91fc9d838188e2673140abMarc Blank            } finally {
514c171a2362e6db78385463e3b7b1bc66585fdcdfcMarc Blank                c.close();
515576b58578e9b42910c91fc9d838188e2673140abMarc Blank            }
516b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank            mExitStatus = EXIT_DONE;
51785f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank        } catch (IOException e) {
51885f44a524693fb6ede88b3b1e683ca7b493d5983Marc Blank            mExitStatus = EXIT_IO_ERROR;
519ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (Exception e) {
52009d35e5d64d106a77accadcffe39b8e039a38175Marc Blank            userLog("Exception caught in EasOutboxService", e);
521ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mExitStatus = EXIT_EXCEPTION;
522ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
5230a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank            userLog(mMailbox.mDisplayName, ": sync finished");
524b2d97333a542c68951a5d64732c6fd56511643f6Marc Blank            userLog("Outbox exited with status ", mExitStatus);
525385a0be662509754e687bcfa9813208b050bf951Marc Blank            ExchangeService.done(this);
526576b58578e9b42910c91fc9d838188e2673140abMarc Blank        }
527576b58578e9b42910c91fc9d838188e2673140abMarc Blank    }
528c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank
529c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank    /**
530c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank     * Convenience method for adding a Message to an account's outbox
531c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank     * @param context the context of the caller
532c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank     * @param accountId the accountId for the sending account
533c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank     * @param msg the message to send
534c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank     */
535c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank    public static void sendMessage(Context context, long accountId, Message msg) {
536c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank        Mailbox mailbox = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_OUTBOX);
537c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank        if (mailbox != null) {
538c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank            msg.mMailboxKey = mailbox.mId;
539c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank            msg.mAccountKey = accountId;
540c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank            msg.save(context);
541c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank        }
542c8dc8009bcbb9dbf781f0028f07b2bbca600aeebMarc Blank    }
543576b58578e9b42910c91fc9d838188e2673140abMarc Blank}