Bypass the cursor window for email bodies
b/11787468 Change-Id: Iba5faa5b825357144d07ec4dfcf010c6af50496d
This commit is contained in:
parent
845db97119
commit
f678a18b69
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.email.provider;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.CursorWrapper;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteDoneException;
|
||||||
|
import android.database.sqlite.SQLiteStatement;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Body;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.BodyColumns;
|
||||||
|
import com.android.mail.utils.LogUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* large email message can exceed that limit and cause the cursor to fail to load.
|
||||||
|
*
|
||||||
|
* To get around this, we load null values in those columns, and then in this wrapper we directly
|
||||||
|
* load the content from the DB, skipping the cursor window.
|
||||||
|
*
|
||||||
|
* This will still potentially blow up if this cursor gets wrapped in a CrossProcessCursorWrapper
|
||||||
|
* which uses a CursorWindow to shuffle results between processes. This is currently only done in
|
||||||
|
* Exchange, and only for outgoing mail, so hopefully users never type more than 2MB of email on
|
||||||
|
* their device.
|
||||||
|
*
|
||||||
|
* If we want to address that issue fully, we need to return the body through a
|
||||||
|
* ParcelFileDescriptor or some other mechanism that doesn't involve passing the data through a
|
||||||
|
* CursorWindow.
|
||||||
|
*/
|
||||||
|
public class EmailMessageCursor extends CursorWrapper {
|
||||||
|
private final SparseArray<String> mTextParts;
|
||||||
|
private final SparseArray<String> mHtmlParts;
|
||||||
|
private final int mTextColumnIndex;
|
||||||
|
private final int mHtmlColumnIndex;
|
||||||
|
|
||||||
|
public EmailMessageCursor(final Cursor cursor, final SQLiteDatabase db, final String htmlColumn,
|
||||||
|
final String textColumn) {
|
||||||
|
super(cursor);
|
||||||
|
mHtmlColumnIndex = cursor.getColumnIndex(htmlColumn);
|
||||||
|
mTextColumnIndex = cursor.getColumnIndex(textColumn);
|
||||||
|
final int cursorSize = cursor.getCount();
|
||||||
|
mHtmlParts = new SparseArray<String>(cursorSize);
|
||||||
|
mTextParts = new SparseArray<String>(cursorSize);
|
||||||
|
|
||||||
|
final SQLiteStatement htmlSql = db.compileStatement(
|
||||||
|
"SELECT " + BodyColumns.HTML_CONTENT +
|
||||||
|
" FROM " + Body.TABLE_NAME +
|
||||||
|
" WHERE " + BodyColumns.MESSAGE_KEY + "=?"
|
||||||
|
);
|
||||||
|
final SQLiteStatement textSql = db.compileStatement(
|
||||||
|
"SELECT " + BodyColumns.TEXT_CONTENT +
|
||||||
|
" FROM " + Body.TABLE_NAME +
|
||||||
|
" WHERE " + BodyColumns.MESSAGE_KEY + "=?"
|
||||||
|
);
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
final int position = cursor.getPosition();
|
||||||
|
final long rowId = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
|
||||||
|
htmlSql.bindLong(1, rowId);
|
||||||
|
textSql.bindLong(1, rowId);
|
||||||
|
try {
|
||||||
|
if (mHtmlColumnIndex != -1) {
|
||||||
|
final String underlyingHtmlString = htmlSql.simpleQueryForString();
|
||||||
|
mHtmlParts.put(position, underlyingHtmlString);
|
||||||
|
}
|
||||||
|
if (mTextColumnIndex != -1) {
|
||||||
|
final String underlyingTextString = textSql.simpleQueryForString();
|
||||||
|
mTextParts.put(position, underlyingTextString);
|
||||||
|
}
|
||||||
|
} catch (final SQLiteDoneException e) {
|
||||||
|
LogUtils.d(LogUtils.TAG, e, "Done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.moveToPosition(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(final int columnIndex) {
|
||||||
|
if (columnIndex == mHtmlColumnIndex) {
|
||||||
|
return mHtmlParts.get(getPosition());
|
||||||
|
} else if (columnIndex == mTextColumnIndex) {
|
||||||
|
return mTextParts.get(getPosition());
|
||||||
|
}
|
||||||
|
return super.getString(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getType(int columnIndex) {
|
||||||
|
if (columnIndex == mHtmlColumnIndex || columnIndex == mTextColumnIndex) {
|
||||||
|
// Need to force this, otherwise we might fall through to some other get*() method
|
||||||
|
// instead of getString() if the underlying cursor has other ideas about this content
|
||||||
|
return FIELD_TYPE_STRING;
|
||||||
|
} else {
|
||||||
|
return super.getType(columnIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1273,7 +1273,6 @@ public class EmailProvider extends ContentProvider {
|
||||||
case MESSAGE_STATE_CHANGE:
|
case MESSAGE_STATE_CHANGE:
|
||||||
return db.query(MessageStateChange.TABLE_NAME, projection, selection,
|
return db.query(MessageStateChange.TABLE_NAME, projection, selection,
|
||||||
selectionArgs, null, null, sortOrder, limit);
|
selectionArgs, null, null, sortOrder, limit);
|
||||||
case BODY:
|
|
||||||
case MESSAGE:
|
case MESSAGE:
|
||||||
case UPDATED_MESSAGE:
|
case UPDATED_MESSAGE:
|
||||||
case DELETED_MESSAGE:
|
case DELETED_MESSAGE:
|
||||||
|
@ -1289,7 +1288,35 @@ public class EmailProvider extends ContentProvider {
|
||||||
case QUICK_RESPONSE:
|
case QUICK_RESPONSE:
|
||||||
c = uiQuickResponse(projection);
|
c = uiQuickResponse(projection);
|
||||||
break;
|
break;
|
||||||
case BODY_ID:
|
case BODY:
|
||||||
|
case BODY_ID: {
|
||||||
|
final ProjectionMap map = new ProjectionMap.Builder()
|
||||||
|
.addAll(projection)
|
||||||
|
.build();
|
||||||
|
final ContentValues cv = new ContentValues(2);
|
||||||
|
cv.put(BodyColumns.HTML_CONTENT, ""); // Loaded in EmailMessageCursor
|
||||||
|
cv.put(BodyColumns.TEXT_CONTENT, ""); // Loaded in EmailMessageCursor
|
||||||
|
final StringBuilder sb = genSelect(map, projection, cv);
|
||||||
|
sb.append(" FROM ").append(Body.TABLE_NAME);
|
||||||
|
if (match == BODY_ID) {
|
||||||
|
id = uri.getPathSegments().get(1);
|
||||||
|
sb.append(" WHERE ").append(whereWithId(id, selection));
|
||||||
|
} else if (!TextUtils.isEmpty(selection)) {
|
||||||
|
sb.append(" WHERE ").append(selection);
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(sortOrder)) {
|
||||||
|
sb.append(" ORDER BY ").append(sortOrder);
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(limit)) {
|
||||||
|
sb.append(" LIMIT ").append(limit);
|
||||||
|
}
|
||||||
|
c = db.rawQuery(sb.toString(), selectionArgs);
|
||||||
|
if (c != null) {
|
||||||
|
c = new EmailMessageCursor(c, db, BodyColumns.HTML_CONTENT,
|
||||||
|
BodyColumns.TEXT_CONTENT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case MESSAGE_ID:
|
case MESSAGE_ID:
|
||||||
case DELETED_MESSAGE_ID:
|
case DELETED_MESSAGE_ID:
|
||||||
case UPDATED_MESSAGE_ID:
|
case UPDATED_MESSAGE_ID:
|
||||||
|
@ -2356,8 +2383,8 @@ public class EmailProvider extends ContentProvider {
|
||||||
.add(UIProvider.MessageColumns.BCC, MessageColumns.BCC_LIST)
|
.add(UIProvider.MessageColumns.BCC, MessageColumns.BCC_LIST)
|
||||||
.add(UIProvider.MessageColumns.REPLY_TO, MessageColumns.REPLY_TO_LIST)
|
.add(UIProvider.MessageColumns.REPLY_TO, MessageColumns.REPLY_TO_LIST)
|
||||||
.add(UIProvider.MessageColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
|
.add(UIProvider.MessageColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
|
||||||
.add(UIProvider.MessageColumns.BODY_HTML, BodyColumns.HTML_CONTENT)
|
.add(UIProvider.MessageColumns.BODY_HTML, "") // Loaded in EmailMessageCursor
|
||||||
.add(UIProvider.MessageColumns.BODY_TEXT, BodyColumns.TEXT_CONTENT)
|
.add(UIProvider.MessageColumns.BODY_TEXT, "") // Loaded in EmailMessageCursor
|
||||||
.add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
|
.add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
|
||||||
.add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
|
.add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
|
||||||
.add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
|
.add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
|
||||||
|
@ -4267,6 +4294,10 @@ public class EmailProvider extends ContentProvider {
|
||||||
} else {
|
} else {
|
||||||
c = db.rawQuery(sql, new String[] {id});
|
c = db.rawQuery(sql, new String[] {id});
|
||||||
}
|
}
|
||||||
|
if (c != null) {
|
||||||
|
c = new EmailMessageCursor(c, db, UIProvider.MessageColumns.BODY_HTML,
|
||||||
|
UIProvider.MessageColumns.BODY_TEXT);
|
||||||
|
}
|
||||||
notifyUri = UIPROVIDER_MESSAGE_NOTIFIER.buildUpon().appendPath(id).build();
|
notifyUri = UIPROVIDER_MESSAGE_NOTIFIER.buildUpon().appendPath(id).build();
|
||||||
break;
|
break;
|
||||||
case UI_ATTACHMENTS:
|
case UI_ATTACHMENTS:
|
||||||
|
|
Loading…
Reference in New Issue