summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTony Mantler <nicoya@google.com>2014-05-08 13:07:54 -0700
committerTony Mantler <nicoya@google.com>2014-05-08 15:50:57 -0700
commit2f288864b621cfb5aee44eda27df463460d33dd3 (patch)
tree6bcee481806c2e5a890c7fbd0051c203dfda5623 /src
parent7366516bb08c59739c84b1c9b4effd14c1a561fc (diff)
downloadandroid_packages_apps_Email-2f288864b621cfb5aee44eda27df463460d33dd3.tar.gz
android_packages_apps_Email-2f288864b621cfb5aee44eda27df463460d33dd3.tar.bz2
android_packages_apps_Email-2f288864b621cfb5aee44eda27df463460d33dd3.zip
Fetch email bodies using ContentResolver#openInputStream
This brings us further along the path to storing email bodies outside of the database. Change-Id: I96296114ade0d561df724878ed92999306bcd176
Diffstat (limited to 'src')
-rw-r--r--src/com/android/email/provider/EmailMessageCursor.java121
-rw-r--r--src/com/android/email/provider/EmailProvider.java132
-rw-r--r--src/com/android/email/provider/Utilities.java2
-rw-r--r--src/com/android/email/service/ImapService.java4
4 files changed, 132 insertions, 127 deletions
diff --git a/src/com/android/email/provider/EmailMessageCursor.java b/src/com/android/email/provider/EmailMessageCursor.java
index d2a1a6cb6..7e455edab 100644
--- a/src/com/android/email/provider/EmailMessageCursor.java
+++ b/src/com/android/email/provider/EmailMessageCursor.java
@@ -21,27 +21,13 @@ 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;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
/**
* 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
@@ -58,74 +44,34 @@ import java.util.concurrent.atomic.AtomicInteger;
* 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.
- *
- * The fromUiQuery param indicates that this EmailMessageCursor object was created from uiQuery().
- * This is significant because we know that the body content fields will be retrieved within
- * the same process as the provider so we can proceed w/o having to worry about any cross
- * process marshalling issues. Secondly, if the request is made from a uiQuery, the _id column
- * of the cursor will be a Message._id. If this call is made outside if the uiQuery(), than the
- * _id column is actually Body._id so we need to proceed accordingly.
*/
public class EmailMessageCursor extends CursorWrapper {
- private static final BlockingQueue<Runnable> sPoolWorkQueue =
- new LinkedBlockingQueue<Runnable>(128);
-
- private static final ThreadFactory sThreadFactory = new ThreadFactory() {
- private final AtomicInteger mCount = new AtomicInteger(1);
-
- public Thread newThread(Runnable r) {
- return new Thread(r, "EmailMessageCursor #" + mCount.getAndIncrement());
- }
- };
-
- /**
- * An {@link Executor} that executes tasks which feed text and html email bodies into streams.
- *
- * It is important that this Executor is private to this class since we don't want to risk
- * sharing a common Executor with Threads that *read* from the stream. If that were to happen
- * it is possible for all Threads in the Executor to be blocked reads and thus starvation
- * occurs.
- */
- private static final Executor THREAD_POOL_EXECUTOR
- = new ThreadPoolExecutor(1, 5, 1, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
-
private final SparseArray<String> mTextParts;
private final SparseArray<String> mHtmlParts;
private final int mTextColumnIndex;
private final int mHtmlColumnIndex;
- private final boolean mFromUiQuery;
public EmailMessageCursor(final Cursor cursor, final SQLiteDatabase db, final String htmlColumn,
- final String textColumn, final boolean fromUiQuery) {
+ final String textColumn) {
super(cursor);
- mFromUiQuery = fromUiQuery;
mHtmlColumnIndex = cursor.getColumnIndex(htmlColumn);
mTextColumnIndex = cursor.getColumnIndex(textColumn);
final int cursorSize = cursor.getCount();
mHtmlParts = new SparseArray<String>(cursorSize);
mTextParts = new SparseArray<String>(cursorSize);
- final String rowIdColumn;
- if (fromUiQuery) {
- // In the UI query, the _id column is the id in the message table so it is
- // messageKey in the Body table.
- rowIdColumn = BodyColumns.MESSAGE_KEY;
- } else {
- // In the non-UI query, the _id column is the id in the Body table.
- rowIdColumn = BaseColumns._ID;
- }
-
+ // TODO: Load this from the provider instead of duplicating the loading code here
final SQLiteStatement htmlSql = db.compileStatement(
"SELECT " + BodyColumns.HTML_CONTENT +
" FROM " + Body.TABLE_NAME +
- " WHERE " + rowIdColumn + "=?"
+ " WHERE " + BodyColumns.MESSAGE_KEY + "=?"
);
final SQLiteStatement textSql = db.compileStatement(
"SELECT " + BodyColumns.TEXT_CONTENT +
" FROM " + Body.TABLE_NAME +
- " WHERE " + rowIdColumn + "=?"
+ " WHERE " + BodyColumns.MESSAGE_KEY + "=?"
);
while (cursor.moveToNext()) {
@@ -155,12 +101,10 @@ public class EmailMessageCursor extends CursorWrapper {
@Override
public String getString(final int columnIndex) {
- if (mFromUiQuery) {
- if (columnIndex == mHtmlColumnIndex) {
- return mHtmlParts.get(getPosition());
- } else if (columnIndex == mTextColumnIndex) {
- return mTextParts.get(getPosition());
- }
+ if (columnIndex == mHtmlColumnIndex) {
+ return mHtmlParts.get(getPosition());
+ } else if (columnIndex == mTextColumnIndex) {
+ return mTextParts.get(getPosition());
}
return super.getString(columnIndex);
}
@@ -175,53 +119,4 @@ 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(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 b86bf11e0..6add748ba 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -40,7 +40,9 @@ import android.database.DatabaseUtils;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -51,6 +53,7 @@ import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.text.TextUtils;
@@ -126,6 +129,7 @@ import com.google.common.collect.Sets;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -134,6 +138,13 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
/**
@@ -267,6 +278,8 @@ public class EmailProvider extends ContentProvider {
private static final int BODY_BASE = 0xA000;
private static final int BODY = BODY_BASE;
private static final int BODY_ID = BODY_BASE + 1;
+ private static final int BODY_HTML = BODY_BASE + 2;
+ private static final int BODY_TEXT = BODY_BASE + 3;
private static final int CREDENTIAL_BASE = 0xB000;
private static final int CREDENTIAL = CREDENTIAL_BASE;
@@ -1067,6 +1080,10 @@ public class EmailProvider extends ContentProvider {
sURIMatcher.addURI(EmailContent.AUTHORITY, "body", BODY);
// A specific mail body
sURIMatcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
+ // A specific HTML body part, for openFile
+ sURIMatcher.addURI(EmailContent.AUTHORITY, "bodyHtml/#", BODY_HTML);
+ // A specific text body part, for openFile
+ sURIMatcher.addURI(EmailContent.AUTHORITY, "bodyText/#", BODY_TEXT);
// All hostauth records
sURIMatcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
@@ -1299,9 +1316,18 @@ public class EmailProvider extends ContentProvider {
final ProjectionMap map = new ProjectionMap.Builder()
.addAll(projection)
.build();
+ if (map.containsKey(BodyColumns.HTML_CONTENT) ||
+ map.containsKey(BodyColumns.TEXT_CONTENT)) {
+ throw new IllegalArgumentException(
+ "Body content cannot be returned in the cursor");
+ }
+
final ContentValues cv = new ContentValues(2);
- cv.put(BodyColumns.HTML_CONTENT, ""); // Loaded in EmailMessageCursor
- cv.put(BodyColumns.TEXT_CONTENT, ""); // Loaded in EmailMessageCursor
+ cv.put(BodyColumns.HTML_CONTENT_URI, "@" + uriWithColumn("bodyHtml",
+ BodyColumns.MESSAGE_KEY));
+ cv.put(BodyColumns.TEXT_CONTENT_URI, "@" + uriWithColumn("bodyText",
+ BodyColumns.MESSAGE_KEY));
+
final StringBuilder sb = genSelect(map, projection, cv);
sb.append(" FROM ").append(Body.TABLE_NAME);
if (match == BODY_ID) {
@@ -1317,13 +1343,6 @@ public class EmailProvider extends ContentProvider {
sb.append(" LIMIT ").append(limit);
}
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, false);
- }
break;
}
case MESSAGE_ID:
@@ -2076,8 +2095,34 @@ public class EmailProvider extends ContentProvider {
return result;
}
+ // TODO: remove this when we move message bodies to actual files
+ private static final BlockingQueue<Runnable> sPoolWorkQueue =
+ new LinkedBlockingQueue<Runnable>(128);
+
+ private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+ private final AtomicInteger mCount = new AtomicInteger(1);
+
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "EmailProviderOpenFile #" + mCount.getAndIncrement());
+ }
+ };
+
+ /**
+ * An {@link java.util.concurrent.Executor} that executes tasks which feed text and html email
+ * bodies into streams.
+ *
+ * It is important that this Executor is private to this class since we don't want to risk
+ * sharing a common Executor with Threads that *read* from the stream. If that were to happen
+ * it is possible for all Threads in the Executor to be blocked reads and thus starvation
+ * occurs.
+ */
+ private static final Executor OPEN_FILE_EXECUTOR = new ThreadPoolExecutor(1 /* corePoolSize */,
+ 5 /* maxPoolSize */, 1 /* keepAliveTime */, TimeUnit.SECONDS,
+ sPoolWorkQueue, sThreadFactory);
+
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ public ParcelFileDescriptor openFile(final Uri uri, final String mode)
+ throws FileNotFoundException {
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) {
LogUtils.d(TAG, "EmailProvider.openFile: %s", LogUtils.contentUriToString(TAG, uri));
}
@@ -2103,6 +2148,71 @@ public class EmailProvider extends ContentProvider {
}
}
break;
+ case BODY_HTML:
+ case BODY_TEXT:
+ final ParcelFileDescriptor descriptors[];
+ try {
+ descriptors = ParcelFileDescriptor.createPipe();
+ } catch (final IOException e) {
+ throw new FileNotFoundException();
+ }
+ final ParcelFileDescriptor readDescriptor = descriptors[0];
+ final ParcelFileDescriptor writeDescriptor = descriptors[1];
+
+ final SQLiteDatabase db = getDatabase(getContext());
+ final SQLiteStatement sql;
+
+ if (match == BODY_HTML) {
+ sql = db.compileStatement(
+ "SELECT " + BodyColumns.HTML_CONTENT +
+ " FROM " + Body.TABLE_NAME +
+ " WHERE " + BodyColumns.MESSAGE_KEY + "=?");
+ } else { // BODY_TEXT
+ sql = db.compileStatement(
+ "SELECT " + BodyColumns.TEXT_CONTENT +
+ " FROM " + Body.TABLE_NAME +
+ " WHERE " + BodyColumns.MESSAGE_KEY + "=?");
+ }
+
+ final long messageKey = Long.valueOf(uri.getLastPathSegment());
+ sql.bindLong(1, messageKey);
+ final String contents;
+ try {
+ contents = sql.simpleQueryForString();
+ } catch (final SQLiteDoneException e) {
+ LogUtils.v(LogUtils.TAG, e,
+ "Done exception while reading %s body for message %d",
+ match == BODY_HTML ? "html" : "text", messageKey);
+ throw new FileNotFoundException();
+ }
+
+ if (TextUtils.isEmpty(contents)) {
+ throw new FileNotFoundException("Body field is empty");
+ }
+
+ 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(OPEN_FILE_EXECUTOR);
+ return readDescriptor;
+ // break;
}
throw new FileNotFoundException("unable to open file");
@@ -4353,7 +4463,7 @@ public class EmailProvider extends ContentProvider {
}
if (c != null) {
c = new EmailMessageCursor(c, db, UIProvider.MessageColumns.BODY_HTML,
- UIProvider.MessageColumns.BODY_TEXT, true /* deliverColumnsInline */);
+ UIProvider.MessageColumns.BODY_TEXT);
}
notifyUri = UIPROVIDER_MESSAGE_NOTIFIER.buildUpon().appendPath(id).build();
break;
diff --git a/src/com/android/email/provider/Utilities.java b/src/com/android/email/provider/Utilities.java
index 289be5bf9..aaf7875a7 100644
--- a/src/com/android/email/provider/Utilities.java
+++ b/src/com/android/email/provider/Utilities.java
@@ -71,7 +71,7 @@ public class Utilities {
if (c == null) {
return;
} else if (c.moveToNext()) {
- localMessage = EmailContent.getContent(c, EmailContent.Message.class);
+ localMessage = EmailContent.getContent(context, c, EmailContent.Message.class);
} else {
localMessage = new EmailContent.Message();
}
diff --git a/src/com/android/email/service/ImapService.java b/src/com/android/email/service/ImapService.java
index d1444d1ad..9efed3be9 100644
--- a/src/com/android/email/service/ImapService.java
+++ b/src/com/android/email/service/ImapService.java
@@ -824,7 +824,7 @@ public class ImapService extends Service {
// loop through messages marked as deleted
while (deletes.moveToNext()) {
EmailContent.Message oldMessage =
- EmailContent.getContent(deletes, EmailContent.Message.class);
+ EmailContent.getContent(context, deletes, EmailContent.Message.class);
if (oldMessage != null) {
lastMessageId = oldMessage.mId;
@@ -961,7 +961,7 @@ public class ImapService extends Service {
boolean changeAnswered = false;
EmailContent.Message oldMessage =
- EmailContent.getContent(updates, EmailContent.Message.class);
+ EmailContent.getContent(context, updates, EmailContent.Message.class);
lastMessageId = oldMessage.mId;
EmailContent.Message newMessage =
EmailContent.Message.restoreMessageWithId(context, oldMessage.mId);