diff --git a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java index 66304faff..b5a72ba29 100755 --- a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java +++ b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java @@ -28,9 +28,12 @@ import android.database.ContentObservable; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; +import android.os.Bundle; import android.os.Environment; import android.os.Looper; import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.os.Parcelable; import android.os.RemoteException; import android.provider.BaseColumns; @@ -43,7 +46,11 @@ import com.android.mail.providers.UIProvider; import com.android.mail.utils.LogUtils; import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.io.IOUtils; + import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -471,6 +478,16 @@ public abstract class EmailContent { public static final int CONTENT_INTRO_TEXT_COLUMN = 7; public static final int CONTENT_QUOTED_TEXT_START_POS_COLUMN = 8; + /** + * Following values are for EmailMessageCursor + */ + // Value is an int specifying the row to get + public static final String RESPOND_COMMAND_GET_HTML_PIPE = "EmailMessageCursor.getHtmlPipe"; + public static final String RESPOND_COMMAND_GET_TEXT_PIPE = "EmailMessageCursor.getTextPipe"; + // Value returned is a ParcelFileDescriptor pipe, or null if no content is present + public static final String RESPOND_RESULT_HTML_PIPE_KEY = "EmailMessageCursor.htmlPipe"; + public static final String RESPOND_RESULT_TEXT_PIPE_KEY = "EmailMessageCursor.textPipe"; + public static final String[] CONTENT_PROJECTION = new String[] { BodyColumns._ID, BodyColumns.MESSAGE_KEY, @@ -651,12 +668,48 @@ public abstract class EmailContent { return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO); } + private static String readBodyFromPipe(ParcelFileDescriptor d) { + final AutoCloseInputStream htmlInput = new AutoCloseInputStream(d); + String content = null; + try { + content = IOUtils.toString(htmlInput, "utf8"); + } catch (final IOException e) { + LogUtils.e(LogUtils.TAG, e, "IOError while reading message body"); + content = null; + } finally { + try { + htmlInput.close(); + } catch (final IOException e) { + LogUtils.e(LogUtils.TAG, e, "IOError while closing message body"); + } + } + return content; + } + @Override public void restore(Cursor cursor) { mBaseUri = EmailContent.Body.CONTENT_URI; mMessageKey = cursor.getLong(CONTENT_MESSAGE_KEY_COLUMN); + // These get overwritten below if we find a file descriptor in the respond() call, + // but we'll keep this here in case we want to construct a matrix cursor or something + // to build a Body object from. mHtmlContent = cursor.getString(CONTENT_HTML_CONTENT_COLUMN); mTextContent = cursor.getString(CONTENT_TEXT_CONTENT_COLUMN); + final int rowId = cursor.getPosition(); + final Bundle command = new Bundle(2); + command.putInt(RESPOND_COMMAND_GET_HTML_PIPE, rowId); + command.putInt(RESPOND_COMMAND_GET_TEXT_PIPE, rowId); + final Bundle response = cursor.respond(command); + final ParcelFileDescriptor htmlDescriptor = + response.getParcelable(RESPOND_RESULT_HTML_PIPE_KEY); + if (htmlDescriptor != null) { + mHtmlContent = readBodyFromPipe(htmlDescriptor); + } + final ParcelFileDescriptor textDescriptor = + response.getParcelable(RESPOND_RESULT_TEXT_PIPE_KEY); + if (textDescriptor != null) { + mTextContent = readBodyFromPipe(textDescriptor); + } mHtmlReply = cursor.getString(CONTENT_HTML_REPLY_COLUMN); mTextReply = cursor.getString(CONTENT_TEXT_REPLY_COLUMN); mSourceKey = cursor.getLong(CONTENT_SOURCE_KEY_COLUMN); diff --git a/src/com/android/email/provider/EmailMessageCursor.java b/src/com/android/email/provider/EmailMessageCursor.java index 150f5ed57..3ebfdbc0a 100644 --- a/src/com/android/email/provider/EmailMessageCursor.java +++ b/src/com/android/email/provider/EmailMessageCursor.java @@ -21,13 +21,20 @@ import android.database.CursorWrapper; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDoneException; import android.database.sqlite.SQLiteStatement; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; import android.provider.BaseColumns; +import android.text.TextUtils; import android.util.SparseArray; import com.android.emailcommon.provider.EmailContent.Body; import com.android.emailcommon.provider.EmailContent.BodyColumns; import com.android.mail.utils.LogUtils; +import java.io.IOException; + /** * This class wraps a cursor for the purpose of bypassing the CursorWindow object for the * potentially over-sized body content fields. The CursorWindow has a hard limit of 2MB and so a @@ -50,10 +57,12 @@ public class EmailMessageCursor extends CursorWrapper { private final SparseArray mHtmlParts; private final int mTextColumnIndex; private final int mHtmlColumnIndex; + private final boolean mDeliverColumnsInline; public EmailMessageCursor(final Cursor cursor, final SQLiteDatabase db, final String htmlColumn, - final String textColumn) { + final String textColumn, final boolean deliverColumnsInline) { super(cursor); + mDeliverColumnsInline = deliverColumnsInline; mHtmlColumnIndex = cursor.getColumnIndex(htmlColumn); mTextColumnIndex = cursor.getColumnIndex(textColumn); final int cursorSize = cursor.getCount(); @@ -94,10 +103,12 @@ public class EmailMessageCursor extends CursorWrapper { @Override public String getString(final int columnIndex) { - if (columnIndex == mHtmlColumnIndex) { - return mHtmlParts.get(getPosition()); - } else if (columnIndex == mTextColumnIndex) { - return mTextParts.get(getPosition()); + if (mDeliverColumnsInline) { + if (columnIndex == mHtmlColumnIndex) { + return mHtmlParts.get(getPosition()); + } else if (columnIndex == mTextColumnIndex) { + return mTextParts.get(getPosition()); + } } return super.getString(columnIndex); } @@ -112,4 +123,53 @@ public class EmailMessageCursor extends CursorWrapper { return super.getType(columnIndex); } } + + private static ParcelFileDescriptor createPipeAndFillAsync(final String contents) { + try { + final ParcelFileDescriptor descriptors[] = ParcelFileDescriptor.createPipe(); + final ParcelFileDescriptor readDescriptor = descriptors[0]; + final ParcelFileDescriptor writeDescriptor = descriptors[1]; + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + final AutoCloseOutputStream outStream = + new AutoCloseOutputStream(writeDescriptor); + try { + outStream.write(contents.getBytes("utf8")); + } catch (final IOException e) { + LogUtils.e(LogUtils.TAG, e, "IOException while writing to body pipe"); + } finally { + try { + outStream.close(); + } catch (final IOException e) { + LogUtils.e(LogUtils.TAG, e, "IOException while closing body pipe"); + } + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return readDescriptor; + } catch (final IOException e) { + LogUtils.e(LogUtils.TAG, e, "IOException while creating body pipe"); + return null; + } + } + + @Override + public Bundle respond(Bundle extras) { + final int htmlRow = extras.getInt(Body.RESPOND_COMMAND_GET_HTML_PIPE, -1); + final int textRow = extras.getInt(Body.RESPOND_COMMAND_GET_TEXT_PIPE, -1); + + final Bundle b = new Bundle(2); + + if (htmlRow >= 0 && !TextUtils.isEmpty(mHtmlParts.get(htmlRow))) { + b.putParcelable(Body.RESPOND_RESULT_HTML_PIPE_KEY, + createPipeAndFillAsync(mHtmlParts.get(htmlRow))); + } + if (textRow >= 0 && !TextUtils.isEmpty(mTextParts.get(textRow))) { + b.putParcelable(Body.RESPOND_RESULT_TEXT_PIPE_KEY, + createPipeAndFillAsync(mTextParts.get(textRow))); + } + return b; + } } diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java index c36109470..d45c3ec57 100644 --- a/src/com/android/email/provider/EmailProvider.java +++ b/src/com/android/email/provider/EmailProvider.java @@ -1312,8 +1312,11 @@ public class EmailProvider extends ContentProvider { } c = db.rawQuery(sb.toString(), selectionArgs); if (c != null) { + // We don't want to deliver the body contents inline here because we might + // be sending this cursor to the Exchange process, and we'll blow out the + // CursorWindow if there's a large message body. c = new EmailMessageCursor(c, db, BodyColumns.HTML_CONTENT, - BodyColumns.TEXT_CONTENT); + BodyColumns.TEXT_CONTENT, false); } break; } @@ -2383,8 +2386,8 @@ public class EmailProvider extends ContentProvider { .add(UIProvider.MessageColumns.BCC, MessageColumns.BCC_LIST) .add(UIProvider.MessageColumns.REPLY_TO, MessageColumns.REPLY_TO_LIST) .add(UIProvider.MessageColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP) - .add(UIProvider.MessageColumns.BODY_HTML, "") // Loaded in EmailMessageCursor - .add(UIProvider.MessageColumns.BODY_TEXT, "") // Loaded in EmailMessageCursor + .add(UIProvider.MessageColumns.BODY_HTML, null) // Loaded in EmailMessageCursor + .add(UIProvider.MessageColumns.BODY_TEXT, null) // Loaded in EmailMessageCursor .add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0") .add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING) .add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0") @@ -4296,7 +4299,7 @@ public class EmailProvider extends ContentProvider { } if (c != null) { c = new EmailMessageCursor(c, db, UIProvider.MessageColumns.BODY_HTML, - UIProvider.MessageColumns.BODY_TEXT); + UIProvider.MessageColumns.BODY_TEXT, true /* deliverColumnsInline */); } notifyUri = UIPROVIDER_MESSAGE_NOTIFIER.buildUpon().appendPath(id).build(); break;