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}