Bypass the cursor completely when passing message within EmailContent contexts

We should investigate using PFDs when passing to the UI layer too, for consistency

Change-Id: I21ad1987926b93af20287ae8e49bfc7a4ad99570
This commit is contained in:
Tony Mantler 2014-04-16 14:21:35 -07:00
parent 1b8cb007ba
commit 9caaebb142
3 changed files with 125 additions and 9 deletions

View File

@ -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);

View File

@ -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<String> 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<Void, Void, Void>() {
@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;
}
}

View File

@ -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;