Merge "Move email bodies to files" into ub-mail-master
This commit is contained in:
commit
c1265d2090
|
@ -574,20 +574,29 @@ public abstract class EmailContent {
|
||||||
0, 0L);
|
0, 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri getBodyTextUriForMessageWithId(long messageId) {
|
||||||
|
return EmailContent.CONTENT_URI.buildUpon()
|
||||||
|
.appendPath("bodyText").appendPath(Long.toString(messageId)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri getBodyHtmlUriForMessageWithId(long messageId) {
|
||||||
|
return EmailContent.CONTENT_URI.buildUpon()
|
||||||
|
.appendPath("bodyHtml").appendPath(Long.toString(messageId)).build();
|
||||||
|
}
|
||||||
|
|
||||||
public static String restoreBodyTextWithMessageId(Context context, long messageId) {
|
public static String restoreBodyTextWithMessageId(Context context, long messageId) {
|
||||||
return readBodyFromProvider(context, EmailContent.CONTENT_URI.buildUpon()
|
return readBodyFromProvider(context,
|
||||||
.appendPath("bodyText").appendPath(Long.toString(messageId)).toString());
|
getBodyTextUriForMessageWithId(messageId).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
|
public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
|
||||||
return readBodyFromProvider(context, EmailContent.CONTENT_URI.buildUpon()
|
return readBodyFromProvider(context,
|
||||||
.appendPath("bodyHtml").appendPath(Long.toString(messageId)).toString());
|
getBodyHtmlUriForMessageWithId(messageId).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String readBodyFromProvider(final Context context, final String uri) {
|
private static String readBodyFromProvider(final Context context, final String uri) {
|
||||||
String content = null;
|
String content = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final InputStream bodyInput =
|
final InputStream bodyInput =
|
||||||
context.getContentResolver().openInputStream(Uri.parse(uri));
|
context.getContentResolver().openInputStream(Uri.parse(uri));
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -23,7 +23,9 @@ import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.SQLException;
|
import android.database.SQLException;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteDoneException;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.database.sqlite.SQLiteStatement;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.provider.CalendarContract;
|
import android.provider.CalendarContract;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
|
@ -61,6 +63,9 @@ import com.android.mail.utils.LogUtils;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class DBHelper {
|
public final class DBHelper {
|
||||||
|
@ -189,7 +194,9 @@ public final class DBHelper {
|
||||||
// Version 6: Adding Body.mIntroText column
|
// Version 6: Adding Body.mIntroText column
|
||||||
// Version 7/8: Adding quoted text start pos
|
// Version 7/8: Adding quoted text start pos
|
||||||
// Version 8 is last Email1 version
|
// Version 8 is last Email1 version
|
||||||
public static final int BODY_DATABASE_VERSION = 100;
|
// Version 100 is the first Email2 version
|
||||||
|
// Version 101: Move body contents to external files
|
||||||
|
public static final int BODY_DATABASE_VERSION = 101;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal helper method for index creation.
|
* Internal helper method for index creation.
|
||||||
|
@ -577,6 +584,7 @@ public final class DBHelper {
|
||||||
createHostAuthTable(db);
|
createHostAuthTable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
static void createMailboxTable(SQLiteDatabase db) {
|
static void createMailboxTable(SQLiteDatabase db) {
|
||||||
String s = " (" + MailboxColumns._ID + " integer primary key autoincrement, "
|
String s = " (" + MailboxColumns._ID + " integer primary key autoincrement, "
|
||||||
+ MailboxColumns.DISPLAY_NAME + " text, "
|
+ MailboxColumns.DISPLAY_NAME + " text, "
|
||||||
|
@ -661,6 +669,7 @@ public final class DBHelper {
|
||||||
db.execSQL("create table " + QuickResponse.TABLE_NAME + s);
|
db.execSQL("create table " + QuickResponse.TABLE_NAME + s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
static void createBodyTable(SQLiteDatabase db) {
|
static void createBodyTable(SQLiteDatabase db) {
|
||||||
String s = " (" + BodyColumns._ID + " integer primary key autoincrement, "
|
String s = " (" + BodyColumns._ID + " integer primary key autoincrement, "
|
||||||
+ BodyColumns.MESSAGE_KEY + " integer, "
|
+ BodyColumns.MESSAGE_KEY + " integer, "
|
||||||
|
@ -676,44 +685,115 @@ public final class DBHelper {
|
||||||
db.execSQL(createIndex(Body.TABLE_NAME, BodyColumns.MESSAGE_KEY));
|
db.execSQL(createIndex(Body.TABLE_NAME, BodyColumns.MESSAGE_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void upgradeBodyTable(SQLiteDatabase db, int oldVersion, int newVersion) {
|
private static void upgradeBodyToVersion5(final SQLiteDatabase db) {
|
||||||
if (oldVersion < 5) {
|
try {
|
||||||
try {
|
db.execSQL("drop table " + Body.TABLE_NAME);
|
||||||
db.execSQL("drop table " + Body.TABLE_NAME);
|
createBodyTable(db);
|
||||||
createBodyTable(db);
|
} catch (final SQLException e) {
|
||||||
oldVersion = 5;
|
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||||
} catch (SQLException e) {
|
LogUtils.w(TAG, e, "Exception upgrading EmailProviderBody.db from <v5");
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldVersion == 5) {
|
|
||||||
try {
|
|
||||||
db.execSQL("alter table " + Body.TABLE_NAME
|
|
||||||
+ " add " + BodyColumns.INTRO_TEXT + " text");
|
|
||||||
} catch (SQLException e) {
|
|
||||||
// Shouldn't be needed unless we're debugging and interrupt the process
|
|
||||||
LogUtils.w(TAG, "Exception upgrading EmailProviderBody.db from v5 to v6", e);
|
|
||||||
}
|
|
||||||
oldVersion = 6;
|
|
||||||
}
|
|
||||||
if (oldVersion == 6 || oldVersion == 7) {
|
|
||||||
try {
|
|
||||||
db.execSQL("alter table " + Body.TABLE_NAME
|
|
||||||
+ " add " + BodyColumns.QUOTED_TEXT_START_POS + " integer");
|
|
||||||
} catch (SQLException e) {
|
|
||||||
// Shouldn't be needed unless we're debugging and interrupt the process
|
|
||||||
LogUtils.w(TAG, "Exception upgrading EmailProviderBody.db from v6 to v8", e);
|
|
||||||
}
|
|
||||||
oldVersion = 8;
|
|
||||||
}
|
|
||||||
if (oldVersion == 8) {
|
|
||||||
// Move to Email2 version
|
|
||||||
oldVersion = 100;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static void upgradeBodyFromVersion5ToVersion6(final SQLiteDatabase db) {
|
||||||
|
try {
|
||||||
|
db.execSQL("alter table " + Body.TABLE_NAME
|
||||||
|
+ " add " + BodyColumns.INTRO_TEXT + " text");
|
||||||
|
} catch (final SQLException e) {
|
||||||
|
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||||
|
LogUtils.w(TAG, e, "Exception upgrading EmailProviderBody.db from v5 to v6");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void upgradeBodyFromVersion6ToVersion8(final SQLiteDatabase db) {
|
||||||
|
try {
|
||||||
|
db.execSQL("alter table " + Body.TABLE_NAME
|
||||||
|
+ " add " + BodyColumns.QUOTED_TEXT_START_POS + " integer");
|
||||||
|
} catch (final SQLException e) {
|
||||||
|
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||||
|
LogUtils.w(TAG, e, "Exception upgrading EmailProviderBody.db from v6 to v8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This upgrade migrates email bodies out of the database and into individual files.
|
||||||
|
*/
|
||||||
|
private static void upgradeBodyFromVersion100ToVersion101(final Context context,
|
||||||
|
final SQLiteDatabase db) {
|
||||||
|
try {
|
||||||
|
// We can't read the body parts through the cursor because they might be over 2MB
|
||||||
|
final String projection[] = { BodyColumns.MESSAGE_KEY };
|
||||||
|
final Cursor cursor = db.query(Body.TABLE_NAME, projection,
|
||||||
|
null, null, null, null, null);
|
||||||
|
if (cursor == null) {
|
||||||
|
throw new IllegalStateException("Could not read body table for upgrade");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 long messageId = cursor.getLong(0);
|
||||||
|
htmlSql.bindLong(1, messageId);
|
||||||
|
try {
|
||||||
|
final String htmlString = htmlSql.simpleQueryForString();
|
||||||
|
if (!TextUtils.isEmpty(htmlString)) {
|
||||||
|
final File htmlFile = EmailProvider.getBodyFile(context, messageId, "html");
|
||||||
|
final FileWriter w = new FileWriter(htmlFile);
|
||||||
|
try {
|
||||||
|
w.write(htmlString);
|
||||||
|
} finally {
|
||||||
|
w.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final SQLiteDoneException e) {
|
||||||
|
LogUtils.v(LogUtils.TAG, e, "Done with the HTML column");
|
||||||
|
}
|
||||||
|
textSql.bindLong(1, messageId);
|
||||||
|
try {
|
||||||
|
final String textString = textSql.simpleQueryForString();
|
||||||
|
if (!TextUtils.isEmpty(textString)) {
|
||||||
|
final File textFile = EmailProvider.getBodyFile(context, messageId, "txt");
|
||||||
|
final FileWriter w = new FileWriter(textFile);
|
||||||
|
try {
|
||||||
|
w.write(textString);
|
||||||
|
} finally {
|
||||||
|
w.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final SQLiteDoneException e) {
|
||||||
|
LogUtils.v(LogUtils.TAG, e, "Done with the text column");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.execSQL("update " + Body.TABLE_NAME +
|
||||||
|
" set " + BodyColumns.HTML_CONTENT + "=NULL,"
|
||||||
|
+ BodyColumns.TEXT_CONTENT + "=NULL");
|
||||||
|
} catch (final SQLException e) {
|
||||||
|
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||||
|
LogUtils.w(TAG, e, "Exception upgrading EmailProviderBody.db from v100 to v101");
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static class BodyDatabaseHelper extends SQLiteOpenHelper {
|
protected static class BodyDatabaseHelper extends SQLiteOpenHelper {
|
||||||
|
final Context mContext;
|
||||||
|
|
||||||
BodyDatabaseHelper(Context context, String name) {
|
BodyDatabaseHelper(Context context, String name) {
|
||||||
super(context, name, null, BODY_DATABASE_VERSION);
|
super(context, name, null, BODY_DATABASE_VERSION);
|
||||||
|
mContext = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -723,8 +803,19 @@ public final class DBHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
|
||||||
upgradeBodyTable(db, oldVersion, newVersion);
|
if (oldVersion < 5) {
|
||||||
|
upgradeBodyToVersion5(db);
|
||||||
|
}
|
||||||
|
if (oldVersion < 6) {
|
||||||
|
upgradeBodyFromVersion5ToVersion6(db);
|
||||||
|
}
|
||||||
|
if (oldVersion < 8) {
|
||||||
|
upgradeBodyFromVersion6ToVersion8(db);
|
||||||
|
}
|
||||||
|
if (oldVersion < 101) {
|
||||||
|
upgradeBodyFromVersion100ToVersion101(mContext, db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -742,7 +833,7 @@ public final class DBHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class DatabaseHelper extends SQLiteOpenHelper {
|
protected static class DatabaseHelper extends SQLiteOpenHelper {
|
||||||
Context mContext;
|
final Context mContext;
|
||||||
|
|
||||||
DatabaseHelper(Context context, String name) {
|
DatabaseHelper(Context context, String name) {
|
||||||
super(context, name, null, DATABASE_VERSION);
|
super(context, name, null, DATABASE_VERSION);
|
||||||
|
|
|
@ -16,34 +16,33 @@
|
||||||
|
|
||||||
package com.android.email.provider;
|
package com.android.email.provider;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.CursorWrapper;
|
import android.database.CursorWrapper;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.net.Uri;
|
||||||
import android.database.sqlite.SQLiteDoneException;
|
|
||||||
import android.database.sqlite.SQLiteStatement;
|
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Body;
|
import com.android.emailcommon.provider.EmailContent.Body;
|
||||||
import com.android.emailcommon.provider.EmailContent.BodyColumns;
|
|
||||||
import com.android.mail.utils.LogUtils;
|
import com.android.mail.utils.LogUtils;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class wraps a cursor for the purpose of bypassing the CursorWindow object for the
|
* 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
|
* 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.
|
* 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
|
* 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.
|
* load the content from the provider, skipping the cursor window.
|
||||||
*
|
*
|
||||||
* This will still potentially blow up if this cursor gets wrapped in a CrossProcessCursorWrapper
|
* 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
|
* which uses a CursorWindow to shuffle results between processes. Since we're only using this for
|
||||||
* Exchange, and only for outgoing mail, so hopefully users never type more than 2MB of email on
|
* passing a cursor back to UnifiedEmail this shouldn't be an issue.
|
||||||
* 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 {
|
public class EmailMessageCursor extends CursorWrapper {
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ public class EmailMessageCursor extends CursorWrapper {
|
||||||
private final int mTextColumnIndex;
|
private final int mTextColumnIndex;
|
||||||
private final int mHtmlColumnIndex;
|
private final int mHtmlColumnIndex;
|
||||||
|
|
||||||
public EmailMessageCursor(final Cursor cursor, final SQLiteDatabase db, final String htmlColumn,
|
public EmailMessageCursor(final Context c, final Cursor cursor, final String htmlColumn,
|
||||||
final String textColumn) {
|
final String textColumn) {
|
||||||
super(cursor);
|
super(cursor);
|
||||||
mHtmlColumnIndex = cursor.getColumnIndex(htmlColumn);
|
mHtmlColumnIndex = cursor.getColumnIndex(htmlColumn);
|
||||||
|
@ -61,39 +60,30 @@ public class EmailMessageCursor extends CursorWrapper {
|
||||||
mHtmlParts = new SparseArray<String>(cursorSize);
|
mHtmlParts = new SparseArray<String>(cursorSize);
|
||||||
mTextParts = new SparseArray<String>(cursorSize);
|
mTextParts = new SparseArray<String>(cursorSize);
|
||||||
|
|
||||||
// TODO: Load this from the provider instead of duplicating the loading code here
|
final ContentResolver cr = c.getContentResolver();
|
||||||
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()) {
|
while (cursor.moveToNext()) {
|
||||||
final int position = cursor.getPosition();
|
final int position = cursor.getPosition();
|
||||||
final long rowId = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
|
final long messageId = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
|
||||||
htmlSql.bindLong(1, rowId);
|
|
||||||
try {
|
try {
|
||||||
if (mHtmlColumnIndex != -1) {
|
if (mHtmlColumnIndex != -1) {
|
||||||
final String underlyingHtmlString = htmlSql.simpleQueryForString();
|
final Uri htmlUri = Body.getBodyHtmlUriForMessageWithId(messageId);
|
||||||
|
final InputStream in = cr.openInputStream(htmlUri);
|
||||||
|
final String underlyingHtmlString = IOUtils.toString(in);
|
||||||
mHtmlParts.put(position, underlyingHtmlString);
|
mHtmlParts.put(position, underlyingHtmlString);
|
||||||
}
|
}
|
||||||
} catch (final SQLiteDoneException e) {
|
} catch (final IOException e) {
|
||||||
LogUtils.d(LogUtils.TAG, e, "Done with the HTML column");
|
LogUtils.v(LogUtils.TAG, e, "Did not find html body for message %d", messageId);
|
||||||
}
|
}
|
||||||
textSql.bindLong(1, rowId);
|
|
||||||
try {
|
try {
|
||||||
if (mTextColumnIndex != -1) {
|
if (mTextColumnIndex != -1) {
|
||||||
final String underlyingTextString = textSql.simpleQueryForString();
|
final Uri textUri = Body.getBodyTextUriForMessageWithId(messageId);
|
||||||
|
final InputStream in = cr.openInputStream(textUri);
|
||||||
|
final String underlyingTextString = IOUtils.toString(in);
|
||||||
mTextParts.put(position, underlyingTextString);
|
mTextParts.put(position, underlyingTextString);
|
||||||
}
|
}
|
||||||
} catch (final SQLiteDoneException e) {
|
} catch (final IOException e) {
|
||||||
LogUtils.d(LogUtils.TAG, e, "Done with the text column");
|
LogUtils.v(LogUtils.TAG, e, "Did not find text body for message %d", messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cursor.moveToPosition(-1);
|
cursor.moveToPosition(-1);
|
||||||
|
|
|
@ -41,7 +41,6 @@ import android.database.DatabaseUtils;
|
||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
import android.database.MergeCursor;
|
import android.database.MergeCursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteDoneException;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.database.sqlite.SQLiteStatement;
|
import android.database.sqlite.SQLiteStatement;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -54,7 +53,6 @@ import android.os.Handler.Callback;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -131,6 +129,7 @@ import com.google.common.collect.Sets;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -141,13 +140,6 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -334,10 +326,12 @@ public class EmailProvider extends ContentProvider {
|
||||||
Message.DELETED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " +
|
Message.DELETED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " +
|
||||||
BaseColumns._ID + '=';
|
BaseColumns._ID + '=';
|
||||||
|
|
||||||
|
private static final String ORPHAN_BODY_MESSAGE_ID_SELECT =
|
||||||
|
"select " + BodyColumns.MESSAGE_KEY + " from " + Body.TABLE_NAME +
|
||||||
|
" except select " + BaseColumns._ID + " from " + Message.TABLE_NAME;
|
||||||
|
|
||||||
private static final String DELETE_ORPHAN_BODIES = "delete from " + Body.TABLE_NAME +
|
private static final String DELETE_ORPHAN_BODIES = "delete from " + Body.TABLE_NAME +
|
||||||
" where " + BodyColumns.MESSAGE_KEY + " in " + "(select " + BodyColumns.MESSAGE_KEY +
|
" where " + BodyColumns.MESSAGE_KEY + " in " + '(' + ORPHAN_BODY_MESSAGE_ID_SELECT + ')';
|
||||||
" from " + Body.TABLE_NAME + " except select " + BaseColumns._ID + " from " +
|
|
||||||
Message.TABLE_NAME + ')';
|
|
||||||
|
|
||||||
private static final String DELETE_BODY = "delete from " + Body.TABLE_NAME +
|
private static final String DELETE_BODY = "delete from " + Body.TABLE_NAME +
|
||||||
" where " + BodyColumns.MESSAGE_KEY + '=';
|
" where " + BodyColumns.MESSAGE_KEY + '=';
|
||||||
|
@ -757,9 +751,34 @@ public class EmailProvider extends ContentProvider {
|
||||||
if (messageDeletion) {
|
if (messageDeletion) {
|
||||||
if (match == MESSAGE_ID) {
|
if (match == MESSAGE_ID) {
|
||||||
// Delete the Body record associated with the deleted message
|
// Delete the Body record associated with the deleted message
|
||||||
|
final ContentValues emptyValues = new ContentValues(2);
|
||||||
|
emptyValues.putNull(BodyColumns.HTML_CONTENT);
|
||||||
|
emptyValues.putNull(BodyColumns.TEXT_CONTENT);
|
||||||
|
final long messageId = Long.valueOf(id);
|
||||||
|
try {
|
||||||
|
writeBodyFiles(context, messageId, emptyValues);
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
LogUtils.v(LogUtils.TAG, e, "Exception while deleting bodies");
|
||||||
|
}
|
||||||
db.execSQL(DELETE_BODY + id);
|
db.execSQL(DELETE_BODY + id);
|
||||||
} else {
|
} else {
|
||||||
// Delete any orphaned Body records
|
// Delete any orphaned Body records
|
||||||
|
final Cursor orphans = db.rawQuery(ORPHAN_BODY_MESSAGE_ID_SELECT, null);
|
||||||
|
try {
|
||||||
|
final ContentValues emptyValues = new ContentValues(2);
|
||||||
|
emptyValues.putNull(BodyColumns.HTML_CONTENT);
|
||||||
|
emptyValues.putNull(BodyColumns.TEXT_CONTENT);
|
||||||
|
while (orphans.moveToNext()) {
|
||||||
|
final long messageId = orphans.getLong(0);
|
||||||
|
try {
|
||||||
|
writeBodyFiles(context, messageId, emptyValues);
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
LogUtils.v(LogUtils.TAG, e, "Exception while deleting bodies");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
orphans.close();
|
||||||
|
}
|
||||||
db.execSQL(DELETE_ORPHAN_BODIES);
|
db.execSQL(DELETE_ORPHAN_BODIES);
|
||||||
}
|
}
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
@ -866,13 +885,30 @@ public class EmailProvider extends ContentProvider {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (match) {
|
switch (match) {
|
||||||
|
case BODY:
|
||||||
|
final ContentValues dbValues = new ContentValues(values);
|
||||||
|
// Prune out the content we don't want in the DB
|
||||||
|
dbValues.remove(BodyColumns.HTML_CONTENT);
|
||||||
|
dbValues.remove(BodyColumns.TEXT_CONTENT);
|
||||||
|
// TODO: move this to the message table
|
||||||
|
longId = db.insert(Body.TABLE_NAME, "foo", dbValues);
|
||||||
|
resultUri = ContentUris.withAppendedId(uri, longId);
|
||||||
|
// Write content to the filesystem where appropriate
|
||||||
|
// This will look less ugly once the body table is folded into the message table
|
||||||
|
// and we can just use longId instead
|
||||||
|
if (!values.containsKey(BodyColumns.MESSAGE_KEY)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot insert body without MESSAGE_KEY");
|
||||||
|
}
|
||||||
|
final long messageId = values.getAsLong(BodyColumns.MESSAGE_KEY);
|
||||||
|
writeBodyFiles(getContext(), messageId, values);
|
||||||
|
break;
|
||||||
// NOTE: It is NOT legal for production code to insert directly into UPDATED_MESSAGE
|
// NOTE: It is NOT legal for production code to insert directly into UPDATED_MESSAGE
|
||||||
// or DELETED_MESSAGE; see the comment below for details
|
// or DELETED_MESSAGE; see the comment below for details
|
||||||
case UPDATED_MESSAGE:
|
case UPDATED_MESSAGE:
|
||||||
case DELETED_MESSAGE:
|
case DELETED_MESSAGE:
|
||||||
case MESSAGE:
|
case MESSAGE:
|
||||||
decodeEmailAddresses(values);
|
decodeEmailAddresses(values);
|
||||||
case BODY:
|
|
||||||
case ATTACHMENT:
|
case ATTACHMENT:
|
||||||
case MAILBOX:
|
case MAILBOX:
|
||||||
case ACCOUNT:
|
case ACCOUNT:
|
||||||
|
@ -1845,7 +1881,6 @@ public class EmailProvider extends ContentProvider {
|
||||||
case SYNCED_MESSAGE_ID:
|
case SYNCED_MESSAGE_ID:
|
||||||
case UPDATED_MESSAGE_ID:
|
case UPDATED_MESSAGE_ID:
|
||||||
case MESSAGE_ID:
|
case MESSAGE_ID:
|
||||||
case BODY_ID:
|
|
||||||
case ATTACHMENT_ID:
|
case ATTACHMENT_ID:
|
||||||
case MAILBOX_ID:
|
case MAILBOX_ID:
|
||||||
case ACCOUNT_ID:
|
case ACCOUNT_ID:
|
||||||
|
@ -1966,8 +2001,40 @@ public class EmailProvider extends ContentProvider {
|
||||||
restartPushForAccount(context, db, values, id);
|
restartPushForAccount(context, db, values, id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BODY:
|
case BODY_ID: {
|
||||||
result = db.update(tableName, values, selection, selectionArgs);
|
final ContentValues updateValues = new ContentValues(values);
|
||||||
|
updateValues.remove(BodyColumns.HTML_CONTENT);
|
||||||
|
updateValues.remove(BodyColumns.TEXT_CONTENT);
|
||||||
|
|
||||||
|
result = db.update(tableName, updateValues, whereWithId(id, selection),
|
||||||
|
selectionArgs);
|
||||||
|
|
||||||
|
if (values.containsKey(BodyColumns.HTML_CONTENT) ||
|
||||||
|
values.containsKey(BodyColumns.TEXT_CONTENT)) {
|
||||||
|
final long messageId;
|
||||||
|
if (values.containsKey(BodyColumns.MESSAGE_KEY)) {
|
||||||
|
messageId = values.getAsLong(BodyColumns.MESSAGE_KEY);
|
||||||
|
} else {
|
||||||
|
final long bodyId = Long.parseLong(id);
|
||||||
|
final SQLiteStatement sql = db.compileStatement(
|
||||||
|
"select " + BodyColumns.MESSAGE_KEY +
|
||||||
|
" from " + Body.TABLE_NAME +
|
||||||
|
" where " + BodyColumns._ID + "=" + Long
|
||||||
|
.toString(bodyId)
|
||||||
|
);
|
||||||
|
messageId = sql.simpleQueryForLong();
|
||||||
|
}
|
||||||
|
writeBodyFiles(context, messageId, values);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BODY: {
|
||||||
|
final ContentValues updateValues = new ContentValues(values);
|
||||||
|
updateValues.remove(BodyColumns.HTML_CONTENT);
|
||||||
|
updateValues.remove(BodyColumns.TEXT_CONTENT);
|
||||||
|
|
||||||
|
result = db.update(tableName, updateValues, selection, selectionArgs);
|
||||||
|
|
||||||
if (result == 0 && selection.equals(Body.SELECTION_BY_MESSAGE_KEY)) {
|
if (result == 0 && selection.equals(Body.SELECTION_BY_MESSAGE_KEY)) {
|
||||||
// TODO: This is a hack. Notably, the selection equality test above
|
// TODO: This is a hack. Notably, the selection equality test above
|
||||||
// is hokey at best.
|
// is hokey at best.
|
||||||
|
@ -1975,8 +2042,50 @@ public class EmailProvider extends ContentProvider {
|
||||||
final ContentValues insertValues = new ContentValues(values);
|
final ContentValues insertValues = new ContentValues(values);
|
||||||
insertValues.put(BodyColumns.MESSAGE_KEY, selectionArgs[0]);
|
insertValues.put(BodyColumns.MESSAGE_KEY, selectionArgs[0]);
|
||||||
insert(Body.CONTENT_URI, insertValues);
|
insert(Body.CONTENT_URI, insertValues);
|
||||||
|
} else {
|
||||||
|
// possibly need to write new body values
|
||||||
|
if (values.containsKey(BodyColumns.HTML_CONTENT) ||
|
||||||
|
values.containsKey(BodyColumns.TEXT_CONTENT)) {
|
||||||
|
final long messageIds[];
|
||||||
|
if (values.containsKey(BodyColumns.MESSAGE_KEY)) {
|
||||||
|
messageIds = new long[] {values.getAsLong(BodyColumns.MESSAGE_KEY)};
|
||||||
|
} else if (values.containsKey(BodyColumns._ID)) {
|
||||||
|
final long bodyId = values.getAsLong(BodyColumns._ID);
|
||||||
|
final SQLiteStatement sql = db.compileStatement(
|
||||||
|
"select " + BodyColumns.MESSAGE_KEY +
|
||||||
|
" from " + Body.TABLE_NAME +
|
||||||
|
" where " + BodyColumns._ID + "=" + Long
|
||||||
|
.toString(bodyId)
|
||||||
|
);
|
||||||
|
messageIds = new long[] {sql.simpleQueryForLong()};
|
||||||
|
} else {
|
||||||
|
final String proj[] = {BodyColumns.MESSAGE_KEY};
|
||||||
|
final Cursor c = db.query(Body.TABLE_NAME, proj,
|
||||||
|
selection, selectionArgs,
|
||||||
|
null, null, null);
|
||||||
|
try {
|
||||||
|
final int count = c.getCount();
|
||||||
|
if (count == 0) {
|
||||||
|
throw new IllegalStateException("Can't find body record");
|
||||||
|
}
|
||||||
|
messageIds = new long[count];
|
||||||
|
int i = 0;
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
messageIds[i++] = c.getLong(0);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is probably overkill
|
||||||
|
for (int i = 0; i < messageIds.length; i++) {
|
||||||
|
final long messageId = messageIds[i];
|
||||||
|
writeBodyFiles(context, messageId, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case MESSAGE:
|
case MESSAGE:
|
||||||
decodeEmailAddresses(values);
|
decodeEmailAddresses(values);
|
||||||
case UPDATED_MESSAGE:
|
case UPDATED_MESSAGE:
|
||||||
|
@ -2088,30 +2197,86 @@ public class EmailProvider extends ContentProvider {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove this when we move message bodies to actual files
|
/**
|
||||||
private static final BlockingQueue<Runnable> sPoolWorkQueue =
|
* Writes message bodies to disk, read from a set of ContentValues
|
||||||
new LinkedBlockingQueue<Runnable>(128);
|
*
|
||||||
|
* @param c Context for finding files
|
||||||
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
|
* @param messageId id of message to write body for
|
||||||
private final AtomicInteger mCount = new AtomicInteger(1);
|
* @param cv {@link ContentValues} containing {@link BodyColumns#HTML_CONTENT} and/or
|
||||||
|
* {@link BodyColumns#TEXT_CONTENT}. Inserting a null or empty value will delete the
|
||||||
public Thread newThread(Runnable r) {
|
* associated text or html body file
|
||||||
return new Thread(r, "EmailProviderOpenFile #" + mCount.getAndIncrement());
|
* @throws IllegalStateException
|
||||||
|
*/
|
||||||
|
private static void writeBodyFiles(final Context c, final long messageId,
|
||||||
|
final ContentValues cv) throws IllegalStateException {
|
||||||
|
if (cv.containsKey(BodyColumns.HTML_CONTENT)) {
|
||||||
|
final String htmlContent = cv.getAsString(BodyColumns.HTML_CONTENT);
|
||||||
|
try {
|
||||||
|
writeBodyFile(c, messageId, "html", htmlContent);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new IllegalStateException("IOException while writing html body " +
|
||||||
|
"for message id " + Long.toString(messageId), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
if (cv.containsKey(BodyColumns.TEXT_CONTENT)) {
|
||||||
|
final String textContent = cv.getAsString(BodyColumns.TEXT_CONTENT);
|
||||||
|
try {
|
||||||
|
writeBodyFile(c, messageId, "txt", textContent);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new IllegalStateException("IOException while writing text body " +
|
||||||
|
"for message id " + Long.toString(messageId), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link java.util.concurrent.Executor} that executes tasks which feed text and html email
|
* Writes a message body file to disk
|
||||||
* bodies into streams.
|
|
||||||
*
|
*
|
||||||
* It is important that this Executor is private to this class since we don't want to risk
|
* @param c Context for finding files dir
|
||||||
* sharing a common Executor with Threads that *read* from the stream. If that were to happen
|
* @param messageId id of message to write body for
|
||||||
* it is possible for all Threads in the Executor to be blocked reads and thus starvation
|
* @param ext "html" or "txt"
|
||||||
* occurs.
|
* @param content Body content to write to file, or null/empty to delete file
|
||||||
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private static final Executor OPEN_FILE_EXECUTOR = new ThreadPoolExecutor(1 /* corePoolSize */,
|
private static void writeBodyFile(final Context c, final long messageId, final String ext,
|
||||||
5 /* maxPoolSize */, 1 /* keepAliveTime */, TimeUnit.SECONDS,
|
final String content) throws IOException {
|
||||||
sPoolWorkQueue, sThreadFactory);
|
final File textFile = getBodyFile(c, messageId, ext);
|
||||||
|
if (TextUtils.isEmpty(content)) {
|
||||||
|
if (!textFile.delete()) {
|
||||||
|
LogUtils.v(LogUtils.TAG, "did not delete text body for %d", messageId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final FileWriter w = new FileWriter(textFile);
|
||||||
|
try {
|
||||||
|
w.write(content);
|
||||||
|
} finally {
|
||||||
|
w.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link java.io.File} object pointing to the body content file for the message
|
||||||
|
*
|
||||||
|
* @param c Context for finding files dir
|
||||||
|
* @param messageId id of message to locate
|
||||||
|
* @param ext "html" or "txt"
|
||||||
|
* @return File ready for operating upon
|
||||||
|
*/
|
||||||
|
protected static File getBodyFile(final Context c, final long messageId, final String ext)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
if (!TextUtils.equals(ext, "html") && !TextUtils.equals(ext, "txt")) {
|
||||||
|
throw new IllegalArgumentException("ext must be one of 'html' or 'txt'");
|
||||||
|
}
|
||||||
|
long l1 = messageId / 100 % 100;
|
||||||
|
long l2 = messageId % 100;
|
||||||
|
final File dir = new File(c.getFilesDir(),
|
||||||
|
"body/" + Long.toString(l1) + "/" + Long.toString(l2) + "/");
|
||||||
|
if (!dir.isDirectory() && !dir.mkdirs()) {
|
||||||
|
throw new FileNotFoundException("Could not create directory for body file");
|
||||||
|
}
|
||||||
|
return new File(dir, Long.toString(messageId) + "." + ext);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openFile(final Uri uri, final String mode)
|
public ParcelFileDescriptor openFile(final Uri uri, final String mode)
|
||||||
|
@ -2141,71 +2306,16 @@ public class EmailProvider extends ContentProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BODY_HTML:
|
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());
|
final long messageKey = Long.valueOf(uri.getLastPathSegment());
|
||||||
sql.bindLong(1, messageKey);
|
return ParcelFileDescriptor.open(getBodyFile(getContext(), messageKey, "html"),
|
||||||
final String contents;
|
Utilities.parseMode(mode));
|
||||||
try {
|
}
|
||||||
contents = sql.simpleQueryForString();
|
case BODY_TEXT:{
|
||||||
} catch (final SQLiteDoneException e) {
|
final long messageKey = Long.valueOf(uri.getLastPathSegment());
|
||||||
LogUtils.v(LogUtils.TAG, e,
|
return ParcelFileDescriptor.open(getBodyFile(getContext(), messageKey, "txt"),
|
||||||
"Done exception while reading %s body for message %d",
|
Utilities.parseMode(mode));
|
||||||
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");
|
throw new FileNotFoundException("unable to open file");
|
||||||
|
@ -4455,7 +4565,7 @@ public class EmailProvider extends ContentProvider {
|
||||||
c = db.rawQuery(sql, new String[] {id});
|
c = db.rawQuery(sql, new String[] {id});
|
||||||
}
|
}
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
c = new EmailMessageCursor(c, db, UIProvider.MessageColumns.BODY_HTML,
|
c = new EmailMessageCursor(getContext(), c, UIProvider.MessageColumns.BODY_HTML,
|
||||||
UIProvider.MessageColumns.BODY_TEXT);
|
UIProvider.MessageColumns.BODY_TEXT);
|
||||||
}
|
}
|
||||||
notifyUri = UIPROVIDER_MESSAGE_NOTIFIER.buildUpon().appendPath(id).build();
|
notifyUri = UIPROVIDER_MESSAGE_NOTIFIER.buildUpon().appendPath(id).build();
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
|
|
||||||
package com.android.email.provider;
|
package com.android.email.provider;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
|
||||||
import com.android.email.LegacyConversions;
|
import com.android.email.LegacyConversions;
|
||||||
import com.android.emailcommon.Logging;
|
import com.android.emailcommon.Logging;
|
||||||
|
@ -36,6 +38,7 @@ import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
import com.android.emailcommon.utility.ConversionUtilities;
|
import com.android.emailcommon.utility.ConversionUtilities;
|
||||||
import com.android.mail.utils.LogUtils;
|
import com.android.mail.utils.LogUtils;
|
||||||
|
import com.android.mail.utils.Utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -192,4 +195,40 @@ public class Utilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string representing a file mode, such as "rw", into a bitmask suitable for use
|
||||||
|
* with {@link android.os.ParcelFileDescriptor#open}.
|
||||||
|
* <p>
|
||||||
|
* @param mode The string representation of the file mode.
|
||||||
|
* @return A bitmask representing the given file mode.
|
||||||
|
* @throws IllegalArgumentException if the given string does not match a known file mode.
|
||||||
|
*/
|
||||||
|
@TargetApi(19)
|
||||||
|
public static int parseMode(String mode) {
|
||||||
|
if (Utils.isRunningKitkatOrLater()) {
|
||||||
|
return ParcelFileDescriptor.parseMode(mode);
|
||||||
|
}
|
||||||
|
final int modeBits;
|
||||||
|
if ("r".equals(mode)) {
|
||||||
|
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
|
||||||
|
} else if ("w".equals(mode) || "wt".equals(mode)) {
|
||||||
|
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||||
|
| ParcelFileDescriptor.MODE_CREATE
|
||||||
|
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||||
|
} else if ("wa".equals(mode)) {
|
||||||
|
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
|
||||||
|
| ParcelFileDescriptor.MODE_CREATE
|
||||||
|
| ParcelFileDescriptor.MODE_APPEND;
|
||||||
|
} else if ("rw".equals(mode)) {
|
||||||
|
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||||
|
| ParcelFileDescriptor.MODE_CREATE;
|
||||||
|
} else if ("rwt".equals(mode)) {
|
||||||
|
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
|
||||||
|
| ParcelFileDescriptor.MODE_CREATE
|
||||||
|
| ParcelFileDescriptor.MODE_TRUNCATE;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bad mode '" + mode + "'");
|
||||||
|
}
|
||||||
|
return modeBits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue