Convert AttachmentProvider to use new Provider db.

* Rewrite internals to use EmailProvider instead of raw db access
  * Minor changes to APIs as necessary (and scattered changes to follow)
  * Convert unit tests and check them
This commit is contained in:
Andrew Stadler 2009-07-27 15:52:42 -07:00
parent 63b2ad5bfb
commit 3d25a519ab
7 changed files with 106 additions and 135 deletions

View File

@ -1322,7 +1322,7 @@ public class MessageView extends Activity
String text = MimeUtility.getTextFromPart(part);
if (part.getMimeType().equalsIgnoreCase("text/html")) {
text = EmailHtmlUtil.resolveInlineImage(
getContentResolver(), mAccount, text, mOldMessage, 0);
getContentResolver(), mAccount.mId, text, mOldMessage, 0);
} else {
// And also escape special character, such as "<>&",
// to HTML escape sequence.

View File

@ -48,7 +48,7 @@ public class EmailHtmlUtil {
* @return html text in which src attribute of img tag may be replaced with content uri
*/
public static String resolveInlineImage(
ContentResolver resolver, EmailContent.Account account, String text, Part part, int depth)
ContentResolver resolver, long accountId, String text, Part part, int depth)
throws MessagingException {
// avoid too deep recursive call.
if (depth >= 10 || text == null) {
@ -60,8 +60,10 @@ public class EmailHtmlUtil {
contentId != null &&
part instanceof LocalAttachmentBodyPart) {
LocalAttachmentBodyPart attachment = (LocalAttachmentBodyPart)part;
Uri contentUri = AttachmentProvider.resolveAttachmentIdToContentUri(
resolver, AttachmentProvider.getAttachmentUri(account, attachment.getAttachmentId()));
Uri attachmentUri =
AttachmentProvider.getAttachmentUri(accountId, attachment.getAttachmentId());
Uri contentUri =
AttachmentProvider.resolveAttachmentIdToContentUri(resolver, attachmentUri);
// Regexp which matches ' src="cid:contentId"'.
String contentIdRe = "\\s+(?i)src=\"cid(?-i):\\Q" + contentId + "\\E\"";
// Replace all occurrences of src attribute with ' src="content://contentUri"'.
@ -71,7 +73,7 @@ public class EmailHtmlUtil {
if (part.getBody() instanceof Multipart) {
Multipart mp = (Multipart)part.getBody();
for (int i = 0; i < mp.getCount(); i++) {
text = resolveInlineImage(resolver, account, text, mp.getBodyPart(i), depth + 1);
text = resolveInlineImage(resolver, accountId, text, mp.getBodyPart(i), depth + 1);
}
}

View File

@ -1375,9 +1375,10 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
if (tempAttachmentFile != null) {
File attachmentFile = new File(mAttachmentsDir, Long.toString(attachmentId));
tempAttachmentFile.renameTo(attachmentFile);
contentUri = AttachmentProvider.getAttachmentUri(
new File(mPath).getName(),
attachmentId);
// Doing this requires knowing the account id
// contentUri = AttachmentProvider.getAttachmentUri(
// new File(mPath).getName(),
// attachmentId);
attachment.setBody(new LocalAttachmentBody(contentUri, mContext));
ContentValues cv = new ContentValues();
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);

View File

@ -17,13 +17,15 @@
package com.android.email.provider;
import com.android.email.mail.internet.MimeUtility;
import com.android.email.provider.EmailContent.Attachment;
import com.android.email.provider.EmailContent.AttachmentColumns;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
@ -38,6 +40,12 @@ import java.util.List;
/*
* A simple ContentProvider that allows file access to Email's attachments.
*
* The URI scheme is as follows. For raw file access:
* content://com.android.email.attachmentprovider/acct#/attach#/RAW
*
* And for access to thumbnails:
* content://com.android.email.attachmentprovider/acct#/attach#/THUMBNAIL/width#/height#
*/
public class AttachmentProvider extends ContentProvider {
@ -54,9 +62,13 @@ public class AttachmentProvider extends ContentProvider {
public static final String SIZE = "_size";
}
public static Uri getAttachmentUri(EmailContent.Account account, long id) {
private String[] PROJECTION_MIME_TYPE = new String[] { AttachmentColumns.MIME_TYPE };
private String[] PROJECTION_QUERY = new String[] { AttachmentColumns.FILENAME,
AttachmentColumns.SIZE, AttachmentColumns.CONTENT_URI };
public static Uri getAttachmentUri(long accountId, long id) {
return CONTENT_URI.buildUpon()
.appendPath(account.getUuid() + ".db")
.appendPath(Long.toString(accountId))
.appendPath(Long.toString(id))
.appendPath(FORMAT_RAW)
.build();
@ -65,7 +77,7 @@ public class AttachmentProvider extends ContentProvider {
public static Uri getAttachmentThumbnailUri(EmailContent.Account account, long id,
int width, int height) {
return CONTENT_URI.buildUpon()
.appendPath(account.getUuid() + ".db")
.appendPath(Long.toString(account.mId))
.appendPath(Long.toString(id))
.appendPath(FORMAT_THUMBNAIL)
.appendPath(Integer.toString(width))
@ -73,14 +85,6 @@ public class AttachmentProvider extends ContentProvider {
.build();
}
public static Uri getAttachmentUri(String db, long id) {
return CONTENT_URI.buildUpon()
.appendPath(db)
.appendPath(Long.toString(id))
.appendPath(FORMAT_RAW)
.build();
}
@Override
public boolean onCreate() {
/*
@ -106,42 +110,23 @@ public class AttachmentProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0);
String accountId = segments.get(0);
String id = segments.get(1);
String format = segments.get(2);
if (FORMAT_THUMBNAIL.equals(format)) {
return "image/png";
}
else {
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
SQLiteDatabase db = null;
Cursor cursor = null;
} else {
uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
Cursor c = getContext().getContentResolver().query(uri, PROJECTION_MIME_TYPE,
null, null, null);
try {
db = SQLiteDatabase.openDatabase(path, null, 0);
cursor = db.query(
"attachments",
new String[] { "mime_type" },
"id = ?",
new String[] { id },
null,
null,
null);
cursor.moveToFirst();
String type = cursor.getString(0);
cursor.close();
db.close();
return type;
}
finally {
if (cursor != null) {
cursor.close();
if (c.moveToFirst()) {
return c.getString(0);
}
if (db != null) {
db.close();
}
} finally {
c.close();
}
return null;
}
}
@ -159,23 +144,25 @@ public class AttachmentProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0);
String accountId = segments.get(0);
String id = segments.get(1);
String format = segments.get(2);
if (FORMAT_THUMBNAIL.equals(format)) {
int width = Integer.parseInt(segments.get(3));
int height = Integer.parseInt(segments.get(4));
String filename = "thmb_" + dbName + "_" + id;
String filename = "thmb_" + accountId + "_" + id;
File dir = getContext().getCacheDir();
File file = new File(dir, filename);
if (!file.exists()) {
Uri attachmentUri = getAttachmentUri(dbName, Long.parseLong(id));
Uri attachmentUri = getAttachmentUri(Long.parseLong(accountId), Long.parseLong(id));
Cursor c = query(attachmentUri,
new String[] { AttachmentProviderColumns.DATA }, null, null, null);
if (c != null) {
try {
if (c.moveToFirst()) {
attachmentUri = Uri.parse(c.getString(0));
} else {
return null;
}
} finally {
c.close();
@ -200,7 +187,7 @@ public class AttachmentProvider extends ContentProvider {
}
else {
return ParcelFileDescriptor.open(
new File(getContext().getDatabasePath(dbName + "_att"), id),
new File(getContext().getDatabasePath(accountId + ".db_att"), id),
ParcelFileDescriptor.MODE_READ_ONLY);
}
}
@ -221,9 +208,6 @@ public class AttachmentProvider extends ContentProvider {
*
* Supports REST Uri only, for a single row - selection, selection args, and sortOrder are
* ignored (non-null values should probably throw an exception....)
*
* TODO: Throws an SQLite exception on a missing DB file (e.g. unknown URI) instead of just
* returning null, as it should.
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
@ -237,39 +221,26 @@ public class AttachmentProvider extends ContentProvider {
}
List<String> segments = uri.getPathSegments();
String dbName = segments.get(0);
String accountId = segments.get(0);
String id = segments.get(1);
String format = segments.get(2);
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
String name = null;
int size = -1;
String contentUri = null;
SQLiteDatabase db = null;
Cursor cursor = null;
uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, Long.parseLong(id));
Cursor c = getContext().getContentResolver().query(uri, PROJECTION_QUERY,
null, null, null);
try {
db = SQLiteDatabase.openDatabase(path, null, 0);
cursor = db.query(
"attachments",
new String[] { "name", "size", "content_uri" },
"id = ?",
new String[] { id },
null,
null,
null);
if (!cursor.moveToFirst()) {
if (c.moveToFirst()) {
name = c.getString(0);
size = c.getInt(1);
contentUri = c.getString(2);
} else {
return null;
}
name = cursor.getString(0);
size = cursor.getInt(1);
contentUri = cursor.getString(2);
}
finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
} finally {
c.close();
}
MatrixCursor ret = new MatrixCursor(projection);

View File

@ -66,7 +66,7 @@ public class MessageTestUtils {
* @return AttachmentProvider content URI
*/
public static Uri contentUri(long attachmentId, EmailContent.Account account) {
return AttachmentProvider.getAttachmentUri(account, attachmentId);
return AttachmentProvider.getAttachmentUri(account.mId, attachmentId);
}
/**

View File

@ -32,6 +32,12 @@ import android.test.suitebuilder.annotation.MediumTest;
import java.io.IOException;
/**
* Tests of the Email HTML utils.
*
* You can run this entire test case with:
* runtest -c com.android.email.mail.internet.EmailHtmlUtilTest email
*/
@MediumTest
public class EmailHtmlUtilTest extends AndroidTestCase {
private EmailContent.Account mAccount;
@ -74,13 +80,13 @@ public class EmailHtmlUtilTest extends AndroidTestCase {
.build();
// Simple case.
final String actual1 = EmailHtmlUtil.resolveInlineImage(
getContext().getContentResolver(), mAccount, text1, msg1, 0);
getContext().getContentResolver(), mAccount.mId, text1, msg1, 0);
assertEquals("one content id reference is not resolved",
expected1, actual1);
// Exceed recursive limit.
final String actual0 = EmailHtmlUtil.resolveInlineImage(
getContext().getContentResolver(), mAccount, text1, msg1, 10);
getContext().getContentResolver(), mAccount.mId, text1, msg1, 10);
assertEquals("recursive call limit may exceeded",
text1, actual0);
@ -100,7 +106,7 @@ public class EmailHtmlUtilTest extends AndroidTestCase {
.build();
// cid1 is not replaced
final String actual2 = EmailHtmlUtil.resolveInlineImage(
getContext().getContentResolver(), mAccount, text1 + text2, msg2, 0);
getContext().getContentResolver(), mAccount.mId, text1 + text2, msg2, 0);
assertEquals("only one of two content id is resolved",
text1 + expected2, actual2);
@ -114,7 +120,7 @@ public class EmailHtmlUtilTest extends AndroidTestCase {
.build();
// cid1 and cid2 are replaced
final String actual3 = EmailHtmlUtil.resolveInlineImage(
getContext().getContentResolver(), mAccount, text2 + text1, msg3, 0);
getContext().getContentResolver(), mAccount.mId, text2 + text1, msg3, 0);
assertEquals("two content ids are resolved correctly",
expected2 + expected1, actual3);
@ -133,13 +139,13 @@ public class EmailHtmlUtilTest extends AndroidTestCase {
.build();
// cid1 and cid2 are replaced
final String actual4 = EmailHtmlUtil.resolveInlineImage(
getContext().getContentResolver(), mAccount, text2 + text1, msg4, 0);
getContext().getContentResolver(), mAccount.mId, text2 + text1, msg4, 0);
assertEquals("two content ids in deep multipart level are resolved",
expected2 + expected1, actual4);
// No crash on null text
final String actual5 = EmailHtmlUtil.resolveInlineImage(getContext().getContentResolver(),
mAccount, null, msg4, 0);
mAccount.mId, null, msg4, 0);
assertNull(actual5);
}

View File

@ -29,7 +29,6 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
@ -54,7 +53,7 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
* "old" LocalStore model to the "new" provider model. After the transition is complete,
* this flag (and its associated code) can be removed.
*/
private final boolean USE_LOCALSTORE = true;
private final boolean USE_LOCALSTORE = false;
LocalStore mLocalStore = null;
EmailProvider mEmailProvider;
@ -118,18 +117,13 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
// attachment we add will be id=1 and the 2nd will have id=2. This could fail on
// a legitimate implementation. Asserts below will catch this and fail the test
// if necessary.
Uri attachment1Uri = AttachmentProvider.getAttachmentUri(account1, attachment1Id);
Uri attachment2Uri = AttachmentProvider.getAttachmentUri(account1, attachment2Id);
Uri attachment3Uri = AttachmentProvider.getAttachmentUri(account1, attachment3Id);
Uri attachment1Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment1Id);
Uri attachment2Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id);
Uri attachment3Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment3Id);
// Test with no attached database - should return null, but throws SQLiteException
Cursor c;
try {
c = mMockResolver.query(attachment1Uri, (String[])null, null, (String[])null, null);
fail("Should throw an exception on a bad URI");
} catch (SQLiteException sqe) {
// expected
}
// Test with no attached database - should return null
Cursor c = mMockResolver.query(attachment1Uri, (String[])null, null, (String[])null, null);
assertNull(c);
// Test with an attached database, but no attachment found - should return null
setupAttachmentDatabase(account1);
@ -141,26 +135,26 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
Attachment newAttachment1 = ProviderTestUtils.setupAttachment(message1Id, "file1", 100,
false, mMockContext);
newAttachment1.mContentUri =
AttachmentProvider.getAttachmentUri(account1, attachment1Id).toString();
AttachmentProvider.getAttachmentUri(account1.mId, attachment1Id).toString();
attachment1Id = addAttachmentToDb(account1, newAttachment1);
assertEquals("Broken test: Unexpected id assignment", 1, attachment1Id);
Attachment newAttachment2 = ProviderTestUtils.setupAttachment(message1Id, "file2", 200,
false, mMockContext);
newAttachment2.mContentUri =
AttachmentProvider.getAttachmentUri(account1, attachment2Id).toString();
AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id).toString();
attachment2Id = addAttachmentToDb(account1, newAttachment2);
assertEquals("Broken test: Unexpected id assignment", 2, attachment2Id);
Attachment newAttachment3 = ProviderTestUtils.setupAttachment(message1Id, "file3", 300,
false, mMockContext);
newAttachment3.mContentUri =
AttachmentProvider.getAttachmentUri(account1, attachment3Id).toString();
AttachmentProvider.getAttachmentUri(account1.mId, attachment3Id).toString();
attachment3Id = addAttachmentToDb(account1, newAttachment3);
assertEquals("Broken test: Unexpected id assignment", 3, attachment3Id);
// Return a row with all columns specified
attachment2Uri = AttachmentProvider.getAttachmentUri(account1, attachment2Id);
attachment2Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id);
c = mMockResolver.query(
attachment2Uri,
new String[] { AttachmentProviderColumns._ID, AttachmentProviderColumns.DATA,
@ -175,7 +169,7 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
assertEquals(200, c.getInt(3)); // size
// Return a row with permuted columns
attachment3Uri = AttachmentProvider.getAttachmentUri(account1, attachment3Id);
attachment3Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment3Id);
c = mMockResolver.query(
attachment3Uri,
new String[] { AttachmentProviderColumns.SIZE,
@ -204,7 +198,7 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
long attachment2Id = 2;
long attachment3Id = 3;
Uri attachment1Uri = AttachmentProvider.getAttachmentUri(account1, attachment1Id);
Uri attachment1Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment1Id);
// Test with no attached database - should return null
String type = mMockResolver.getType(attachment1Uri);
@ -224,14 +218,14 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
Attachment newAttachment3 = ProviderTestUtils.setupAttachment(message1Id, "file3", 100,
false, mMockContext);
newAttachment2.mMimeType = "text/plain";
attachment3Id = addAttachmentToDb(account1, newAttachment2);
newAttachment3.mMimeType = "text/plain";
attachment3Id = addAttachmentToDb(account1, newAttachment3);
// Check the returned filetypes
Uri uri = AttachmentProvider.getAttachmentUri(account1, attachment2Id);
Uri uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id);
type = mMockResolver.getType(uri);
assertEquals("image/jpg", type);
uri = AttachmentProvider.getAttachmentUri(account1, attachment3Id);
uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment3Id);
type = mMockResolver.getType(uri);
assertEquals("text/plain", type);
@ -261,8 +255,8 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
// attachment we add will be id=1 and the 2nd will have id=2. This could fail on
// a legitimate implementation. Asserts below will catch this and fail the test
// if necessary.
Uri file1Uri = AttachmentProvider.getAttachmentUri(account1, attachment1Id);
Uri file2Uri = AttachmentProvider.getAttachmentUri(account1, attachment2Id);
Uri file1Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment1Id);
Uri file2Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id);
// Test with no attached database - should throw an exception
AssetFileDescriptor afd;
@ -302,7 +296,7 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
false, mMockContext);
newAttachment2.mContentId = null;
newAttachment2.mContentUri =
AttachmentProvider.getAttachmentUri(account1, attachment2Id).toString();
AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id).toString();
newAttachment2.mMimeType = "image/png";
attachment2Id = addAttachmentToDb(account1, newAttachment2);
assertEquals("Broken test: Unexpected id assignment", 2, attachment2Id);
@ -340,17 +334,13 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
Uri thumb2Uri = AttachmentProvider.getAttachmentThumbnailUri(account1, attachment2Id,
62, 62);
// Test with no attached database - should throw an exception
try {
/* AssetFileDescriptor afd = */ mMockResolver.openAssetFileDescriptor(thumb1Uri, "r");
fail("Should throw an exception on a bad URI");
} catch (SQLiteException sqe) {
// expected
}
// Test with no attached database - should return null (used to throw SQLiteException)
AssetFileDescriptor afd = mMockResolver.openAssetFileDescriptor(thumb1Uri, "r");
assertNull(afd);
// Test with an attached database, but no attachment found
setupAttachmentDatabase(account1);
AssetFileDescriptor afd = mMockResolver.openAssetFileDescriptor(thumb1Uri, "r");
afd = mMockResolver.openAssetFileDescriptor(thumb1Uri, "r");
assertNull(afd);
// Add an attachment (but no associated file)
@ -369,7 +359,7 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
false, mMockContext);
newAttachment2.mContentId = null;
newAttachment2.mContentUri =
AttachmentProvider.getAttachmentUri(account1, attachment2Id).toString();
AttachmentProvider.getAttachmentUri(account1.mId, attachment2Id).toString();
newAttachment2.mMimeType = "image/png";
attachment2Id = addAttachmentToDb(account1, newAttachment2);
assertEquals("Broken test: Unexpected id assignment", 2, attachment2Id);
@ -397,20 +387,16 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
// attachment we add will be id=1 and the 2nd will have id=2. This could fail on
// a legitimate implementation. Asserts below will catch this and fail the test
// if necessary.
Uri attachment1Uri = AttachmentProvider.getAttachmentUri(account1, attachment1Id);
Uri attachment1Uri = AttachmentProvider.getAttachmentUri(account1.mId, attachment1Id);
// Test with no attached database - should return null, but throws SQLiteException
try {
Uri result = AttachmentProvider.resolveAttachmentIdToContentUri(
// Test with no attached database - should return input
Uri result = AttachmentProvider.resolveAttachmentIdToContentUri(
mMockResolver, attachment1Uri);
fail("Expected an exception on a bad URI");
} catch (SQLiteException sqe) {
// expected
}
assertEquals(attachment1Uri, result);
// Test with an attached database, but no attachment found - should return input
setupAttachmentDatabase(account1);
Uri result = AttachmentProvider.resolveAttachmentIdToContentUri(
result = AttachmentProvider.resolveAttachmentIdToContentUri(
mMockResolver, attachment1Uri);
assertEquals(attachment1Uri, result);
@ -451,7 +437,7 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
String localStoreUri = "local://localhost/" + dbName(forAccount);
mLocalStore = (LocalStore) LocalStore.newInstance(localStoreUri, mMockContext, null);
} else {
throw new java.lang.UnsupportedOperationException();
// Nothing to do - EmailProvider is already available for us
}
}
@ -482,7 +468,8 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
}
}
} else {
throw new java.lang.UnsupportedOperationException();
newAttachment.save(mMockContext);
attachmentId = newAttachment.mId;
}
return attachmentId;
}
@ -507,7 +494,11 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
return new File(mMockContext.getDatabasePath(forAccount.mCompatibilityUuid + ".db_att"),
idString);
} else {
throw new java.lang.UnsupportedOperationException();
File attachmentsDir = mMockContext.getDatabasePath(forAccount.mId + ".db_att");
if (!attachmentsDir.exists()) {
attachmentsDir.mkdirs();
}
return new File(attachmentsDir, idString);
}
}
}