diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java index cacaad043..26d513910 100644 --- a/src/com/android/email/MessagingController.java +++ b/src/com/android/email/MessagingController.java @@ -286,7 +286,7 @@ public class MessagingController implements Runnable { try { Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null); Folder localFolder = localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); Message[] localMessages = localFolder.getMessages(null); ArrayList messages = new ArrayList(); for (Message message : localMessages) { @@ -388,7 +388,7 @@ public class MessagingController implements Runnable { final LocalStore localStore = (LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication, null); final LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); Message[] localMessages = localFolder.getMessages(null); HashMap localUidMap = new HashMap(); for (Message message : localMessages) { @@ -441,7 +441,7 @@ public class MessagingController implements Runnable { /* * Open the remote folder. This pre-loads certain metadata like message count. */ - remoteFolder.open(OpenMode.READ_WRITE); + remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); /* * Trash any remote messages that are marked as trashed locally. @@ -837,7 +837,7 @@ public class MessagingController implements Runnable { return; } } - remoteFolder.open(OpenMode.READ_WRITE); + remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); if (remoteFolder.getMode() != OpenMode.READ_WRITE) { return; } @@ -914,13 +914,17 @@ public class MessagingController implements Runnable { String folder = command.arguments[0]; String uid = command.arguments[1]; + LocalStore localStore = (LocalStore) Store.getInstance( + account.getLocalStoreUri(), mApplication, null); + LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder); + Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication, account.getStoreCallbacks()); Folder remoteFolder = remoteStore.getFolder(folder); if (!remoteFolder.exists()) { return; } - remoteFolder.open(OpenMode.READ_WRITE); + remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); if (remoteFolder.getMode() != OpenMode.READ_WRITE) { return; } @@ -965,13 +969,17 @@ public class MessagingController implements Runnable { String uid = command.arguments[1]; boolean read = Boolean.parseBoolean(command.arguments[2]); + LocalStore localStore = (LocalStore) Store.getInstance( + account.getLocalStoreUri(), mApplication, null); + LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder); + Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication, account.getStoreCallbacks()); Folder remoteFolder = remoteStore.getFolder(folder); if (!remoteFolder.exists()) { return; } - remoteFolder.open(OpenMode.READ_WRITE); + remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); if (remoteFolder.getMode() != OpenMode.READ_WRITE) { return; } @@ -1001,7 +1009,7 @@ public class MessagingController implements Runnable { try { Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null); Folder localFolder = localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); Message message = localFolder.getMessage(uid); message.setFlag(Flag.SEEN, seen); @@ -1024,7 +1032,7 @@ public class MessagingController implements Runnable { Store localStore = Store.getInstance( account.getLocalStoreUri(), mApplication, null); LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); Message message = localFolder.getMessage(uid); @@ -1056,7 +1064,7 @@ public class MessagingController implements Runnable { Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication, account.getStoreCallbacks()); Folder remoteFolder = remoteStore.getFolder(folder); - remoteFolder.open(OpenMode.READ_WRITE); + remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); // Get the remote message and fully download it Message remoteMessage = remoteFolder.getMessage(uid); @@ -1103,7 +1111,7 @@ public class MessagingController implements Runnable { try { Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null); LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); Message message = localFolder.getMessage(uid); @@ -1207,7 +1215,7 @@ public class MessagingController implements Runnable { LocalFolder localFolder = (LocalFolder) localStore.getFolder(message.getFolder().getName()); Folder remoteFolder = remoteStore.getFolder(message.getFolder().getName()); - remoteFolder.open(OpenMode.READ_WRITE); + remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); FetchProfile fp = new FetchProfile(); fp.add(part); @@ -1244,7 +1252,7 @@ public class MessagingController implements Runnable { Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null); LocalFolder localFolder = (LocalFolder) localStore.getFolder(account.getOutboxFolderName()); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); localFolder.appendMessages(new Message[] { message }); @@ -1287,7 +1295,7 @@ public class MessagingController implements Runnable { if (!localFolder.exists()) { return; } - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); Message[] localMessages = localFolder.getMessages(null); @@ -1393,7 +1401,7 @@ public class MessagingController implements Runnable { Store localStore = Store.getInstance( account.getLocalStoreUri(), mApplication, null); Folder localFolder = localStore.getFolder(account.getTrashFolderName()); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); Message[] messages = localFolder.getMessages(null); localFolder.setFlags(messages, new Flag[] { Flag.DELETED @@ -1451,7 +1459,7 @@ public class MessagingController implements Runnable { Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null); LocalFolder localFolder = (LocalFolder) localStore.getFolder(account.getDraftsFolderName()); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE, null); localFolder.appendMessages(new Message[] { message }); diff --git a/src/com/android/email/activity/FolderMessageList.java b/src/com/android/email/activity/FolderMessageList.java index c40226aab..73b1a338b 100644 --- a/src/com/android/email/activity/FolderMessageList.java +++ b/src/com/android/email/activity/FolderMessageList.java @@ -776,7 +776,7 @@ public class FolderMessageList extends ExpandableListActivity { holder.messages = new ArrayList(); } try { - folder.open(Folder.OpenMode.READ_WRITE); + folder.open(Folder.OpenMode.READ_WRITE, null); holder.unreadMessageCount = folder.getUnreadMessageCount(); folder.close(false); } diff --git a/src/com/android/email/mail/Folder.java b/src/com/android/email/mail/Folder.java index f5ceb570e..fc0a9a1d7 100644 --- a/src/com/android/email/mail/Folder.java +++ b/src/com/android/email/mail/Folder.java @@ -31,8 +31,12 @@ public abstract class Folder { * function returns without doing anything. * * @param mode READ_ONLY or READ_WRITE + * @param callbacks Pointer to callbacks class. This may be used by the folder between this + * time and when close() is called. This is only used for remote stores - should be null + * for LocalStore.LocalFolder. */ - public abstract void open(OpenMode mode) throws MessagingException; + public abstract void open(OpenMode mode, PersistentDataCallbacks callbacks) + throws MessagingException; /** * Forces a close of the MailProvider. Any further access will attempt to @@ -105,6 +109,28 @@ public abstract class Folder { public abstract Flag[] getPermanentFlags() throws MessagingException; + /** + * Callback interface by which a Folder can read and write persistent data. + * TODO This needs to be made more generic & flexible + */ + public interface PersistentDataCallbacks { + + /** + * Provides keyed storage of strings. Should be used for per-folder data. Do not use for + * per-message data. + * @param key identifier for the data (e.g. "sync.key" or "folder.id") + * @param value Data to persist. All data must be encoded into a string, + * so use base64 or some other encoding if necessary. + */ + public void setPersistentString(String key, String value); + + /** + * @param key identifier for the data of interest + * @return the data saved by the Folder, or defaultValue if never set. + */ + public String getPersistentString(String key, String defaultValue); + } + @Override public String toString() { return getName(); diff --git a/src/com/android/email/mail/exchange/ExchangeFolderExample.java b/src/com/android/email/mail/exchange/ExchangeFolderExample.java index 77b3e1761..3434f15a1 100644 --- a/src/com/android/email/mail/exchange/ExchangeFolderExample.java +++ b/src/com/android/email/mail/exchange/ExchangeFolderExample.java @@ -33,6 +33,8 @@ public class ExchangeFolderExample extends Folder { private final ExchangeStoreExample mStore; private final String mName; + private PersistentDataCallbacks mPersistenceCallbacks; + public ExchangeFolderExample(ExchangeStoreExample store, String name) throws MessagingException { mStore = store; @@ -50,6 +52,7 @@ public class ExchangeFolderExample extends Folder { @Override public void close(boolean expunge) throws MessagingException { + mPersistenceCallbacks = null; // TODO Implement this function } @@ -150,7 +153,8 @@ public class ExchangeFolderExample extends Folder { } @Override - public void open(OpenMode mode) throws MessagingException { + public void open(OpenMode mode, PersistentDataCallbacks callbacks) throws MessagingException { + mPersistenceCallbacks = callbacks; // TODO Implement this function } diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java index 2f924a5b6..1b09b6c85 100644 --- a/src/com/android/email/mail/store/ImapStore.java +++ b/src/com/android/email/mail/store/ImapStore.java @@ -29,6 +29,7 @@ import com.android.email.mail.MessagingException; import com.android.email.mail.Part; import com.android.email.mail.Store; import com.android.email.mail.Transport; +import com.android.email.mail.Folder.PersistentDataCallbacks; import com.android.email.mail.internet.MimeBodyPart; import com.android.email.mail.internet.MimeHeader; import com.android.email.mail.internet.MimeMessage; @@ -325,7 +326,8 @@ public class ImapStore extends Store { this.mName = name; } - public void open(OpenMode mode) throws MessagingException { + public void open(OpenMode mode, PersistentDataCallbacks callbacks) + throws MessagingException { if (isOpen() && mMode == mode) { // Make sure the connection is valid. If it's not we'll close it down and continue // on to get a new one. diff --git a/src/com/android/email/mail/store/LocalStore.java b/src/com/android/email/mail/store/LocalStore.java index 7630a6f69..12abe2da4 100644 --- a/src/com/android/email/mail/store/LocalStore.java +++ b/src/com/android/email/mail/store/LocalStore.java @@ -40,7 +40,6 @@ import com.android.email.provider.AttachmentProvider; import org.apache.commons.io.IOUtils; -import android.app.Application; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -78,9 +77,10 @@ public class LocalStore extends Store { * 18 1.0, 1.1 1.0 Release version. * 19 - Added message_id column to messages table. * 20 1.5 Added content_id column to attachments table. + * 21 - Added remote_store_data table */ - private static final int DB_VERSION = 20; + private static final int DB_VERSION = 21; private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN }; @@ -92,7 +92,8 @@ public class LocalStore extends Store { /** * Static named constructor. */ - public static Store newInstance(String uri, Context context) throws MessagingException { + public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks) + throws MessagingException { return new LocalStore(uri, context); } @@ -150,9 +151,10 @@ public class LocalStore extends Store { mDb.execSQL("DROP TABLE IF EXISTS pending_commands"); mDb.execSQL("CREATE TABLE pending_commands " + "(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)"); + + addRemoteStoreDataTable(); - mDb.execSQL("DROP TRIGGER IF EXISTS delete_folder"); - mDb.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;"); + addFolderDeleteTrigger(); mDb.execSQL("DROP TRIGGER IF EXISTS delete_message"); mDb.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; END;"); @@ -173,6 +175,14 @@ public class LocalStore extends Store { mDb.execSQL("ALTER TABLE attachments ADD COLUMN content_id TEXT;"); mDb.setVersion(20); } + if (oldVersion < 21) { + /** + * Upgrade 20 to 21: add remote_store_data and update triggers to match + */ + addRemoteStoreDataTable(); + addFolderDeleteTrigger(); + mDb.setVersion(21); + } } if (mDb.getVersion() != DB_VERSION) { @@ -184,6 +194,29 @@ public class LocalStore extends Store { mAttachmentsDir.mkdirs(); } } + + /** + * Common code to add the remote_store_data table + */ + private void addRemoteStoreDataTable() { + mDb.execSQL("DROP TABLE IF EXISTS remote_store_data"); + mDb.execSQL("CREATE TABLE remote_store_data " + + "(id INTEGER PRIMARY KEY, folder_id INTEGER, " + + "data_key TEXT, data TEXT)"); + } + + /** + * Common code to add folder delete trigger + */ + private void addFolderDeleteTrigger() { + mDb.execSQL("DROP TRIGGER IF EXISTS delete_folder"); + mDb.execSQL("CREATE TRIGGER delete_folder " + + "BEFORE DELETE ON folders " + + "BEGIN " + + "DELETE FROM messages WHERE old.id = folder_id; " + + "DELETE FROM remote_store_data WHERE old.id = folder_id; " + + "END;"); + } @Override public Folder getFolder(String name) throws MessagingException { @@ -369,7 +402,7 @@ public class LocalStore extends Store { } } - public class LocalFolder extends Folder { + public class LocalFolder extends Folder implements Folder.PersistentDataCallbacks { private String mName; private long mFolderId = -1; private int mUnreadMessageCount = -1; @@ -382,9 +415,17 @@ public class LocalStore extends Store { public long getId() { return mFolderId; } + + /** + * This is just used by the internal callers + */ + private void open(OpenMode mode) throws MessagingException { + open(mode, null); + } @Override - public void open(OpenMode mode) throws MessagingException { + public void open(OpenMode mode, PersistentDataCallbacks callbacks) + throws MessagingException { if (isOpen()) { return; } @@ -1104,6 +1145,65 @@ public class LocalStore extends Store { } } } + + /** + * Support for local persistence for our remote stores. + * Will open the folder if necessary. + */ + public Folder.PersistentDataCallbacks getPersistentCallbacks() throws MessagingException { + open(OpenMode.READ_WRITE); + return this; + } + + public String getPersistentString(String key, String defaultValue) { + String result = defaultValue; + Cursor cursor = null; + try { + cursor = mDb.query("remote_store_data", + new String[] { "data" }, + "folder_id = ? AND data_key = ?", + new String[] { Long.toString(mFolderId), key }, + null, + null, + null); + if (cursor != null && cursor.moveToNext()) { + result = cursor.getString(0); + } + } + finally { + if (cursor != null) { + cursor.close(); + } + } + return result; + } + + public void setPersistentString(String key, String value) { + // TODO apply sql-foo and replace this with a single statement + Cursor cursor = null; + try { + final String where = "folder_id = ? AND data_key = ?"; + String[] whereArgs = new String[] { Long.toString(mFolderId), key }; + ContentValues cv = new ContentValues(); + cv.put("data", value); + cursor = mDb.query("remote_store_data", + new String[] { "data" }, + where, whereArgs, + null, null, null); + if (cursor != null && cursor.moveToNext()) { + mDb.update("remote_store_data", cv, where, whereArgs); + } else { + cv.put("folder_id", Long.toString(mFolderId)); + cv.put("data_key", key); + mDb.insert("remote_store_data", null, cv); + } + } + finally { + if (cursor != null) { + cursor.close(); + } + } + } } public class LocalMessage extends MimeMessage { diff --git a/src/com/android/email/mail/store/Pop3Store.java b/src/com/android/email/mail/store/Pop3Store.java index 813879893..57aaef91b 100644 --- a/src/com/android/email/mail/store/Pop3Store.java +++ b/src/com/android/email/mail/store/Pop3Store.java @@ -28,6 +28,7 @@ import com.android.email.mail.MessagingException; import com.android.email.mail.Store; import com.android.email.mail.Transport; import com.android.email.mail.Folder.OpenMode; +import com.android.email.mail.Folder.PersistentDataCallbacks; import com.android.email.mail.internet.MimeMessage; import com.android.email.mail.transport.LoggingInputStream; import com.android.email.mail.transport.MailTransport; @@ -179,7 +180,7 @@ public class Pop3Store extends Store { public void checkSettings() throws MessagingException { Pop3Folder folder = new Pop3Folder("INBOX"); try { - folder.open(OpenMode.READ_WRITE); + folder.open(OpenMode.READ_WRITE, null); folder.checkSettings(); } finally { folder.close(false); // false == don't expunge anything @@ -229,7 +230,8 @@ public class Pop3Store extends Store { } @Override - public synchronized void open(OpenMode mode) throws MessagingException { + public synchronized void open(OpenMode mode, PersistentDataCallbacks callbacks) + throws MessagingException { if (mTransport.isOpen()) { return; } @@ -855,7 +857,7 @@ public class Pop3Store extends Store { */ private String executeSensitiveCommand(String command, String sensitiveReplacement) throws IOException, MessagingException { - open(OpenMode.READ_WRITE); + open(OpenMode.READ_WRITE, null); if (command != null) { mTransport.writeLine(command, sensitiveReplacement); diff --git a/tests/src/com/android/email/activity/MessageViewTests.java b/tests/src/com/android/email/activity/MessageViewTests.java index ea881f075..8fad155cc 100644 --- a/tests/src/com/android/email/activity/MessageViewTests.java +++ b/tests/src/com/android/email/activity/MessageViewTests.java @@ -156,7 +156,7 @@ public class MessageViewTests public void testResolveInlineImage() throws MessagingException, IOException { final MessageView a = getActivity(); final LocalStore store = (LocalStore) LocalStore.newInstance(mAccount.getLocalStoreUri(), - mContext); + mContext, null); // Single cid case. final String cid1 = "cid.1@android.com"; diff --git a/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java b/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java index ef0b2f6bc..a956bc0f7 100644 --- a/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java +++ b/tests/src/com/android/email/mail/store/ImapStoreUnitTests.java @@ -66,7 +66,7 @@ public class ImapStoreUnitTests extends AndroidTestCase { // try to open it setupOpenFolder(mockTransport); - mFolder.open(OpenMode.READ_WRITE); + mFolder.open(OpenMode.READ_WRITE, null); // TODO: inject specific facts in the initial folder SELECT and check them here } diff --git a/tests/src/com/android/email/mail/store/LocalStoreUnitTests.java b/tests/src/com/android/email/mail/store/LocalStoreUnitTests.java index c6a35875f..2801cc9fc 100644 --- a/tests/src/com/android/email/mail/store/LocalStoreUnitTests.java +++ b/tests/src/com/android/email/mail/store/LocalStoreUnitTests.java @@ -17,8 +17,10 @@ package com.android.email.mail.store; import com.android.email.mail.Address; +import com.android.email.mail.Folder; import com.android.email.mail.Message; import com.android.email.mail.MessagingException; +import com.android.email.mail.Folder.FolderType; import com.android.email.mail.Folder.OpenMode; import com.android.email.mail.Message.RecipientType; import com.android.email.mail.internet.BinaryTempFileBody; @@ -34,6 +36,9 @@ import android.test.suitebuilder.annotation.SmallTest; import java.io.File; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; /** * This is a series of unit tests for the LocalStore class. @@ -50,6 +55,8 @@ public class LocalStoreUnitTests extends AndroidTestCase { private static final String MESSAGE_ID = "Test-Message-ID"; private static final String MESSAGE_ID_2 = "Test-Message-ID-Second"; + private static final int DATABASE_VERSION = 21; + /* These values are provided by setUp() */ private String mLocalStoreUri = null; private LocalStore mStore = null; @@ -67,7 +74,7 @@ public class LocalStoreUnitTests extends AndroidTestCase { // Create a dummy database (be sure to delete it in tearDown()) mLocalStoreUri = "local://localhost/" + getContext().getDatabasePath(dbName); - mStore = (LocalStore) LocalStore.newInstance(mLocalStoreUri, getContext()); + mStore = (LocalStore) LocalStore.newInstance(mLocalStoreUri, getContext(), null); mFolder = (LocalStore.LocalFolder) mStore.getFolder("TEST"); // This is needed for parsing mime messages @@ -121,7 +128,7 @@ public class LocalStoreUnitTests extends AndroidTestCase { public void testMessageId_1() throws MessagingException { final MimeMessage message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY); message.setMessageId(MESSAGE_ID); - mFolder.open(OpenMode.READ_WRITE); + mFolder.open(OpenMode.READ_WRITE, null); mFolder.appendMessages(new Message[]{ message }); String localUid = message.getUid(); @@ -146,7 +153,7 @@ public class LocalStoreUnitTests extends AndroidTestCase { public void testMessageId_2() throws MessagingException { final MimeMessage message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY); message.setMessageId(MESSAGE_ID); - mFolder.open(OpenMode.READ_WRITE); + mFolder.open(OpenMode.READ_WRITE, null); mFolder.appendMessages(new Message[]{ message }); String localUid = message.getUid(); @@ -202,18 +209,59 @@ public class LocalStoreUnitTests extends AndroidTestCase { return message; } + /** + * Test functionality of setting & saving store persistence values + */ + public void testPersistentStorage() throws MessagingException { + mFolder.open(OpenMode.READ_WRITE, null); + + // set up a 2nd folder to confirm independent storage + LocalStore.LocalFolder folder2 = (LocalStore.LocalFolder) mStore.getFolder("FOLDER-2"); + assertFalse(folder2.exists()); + folder2.create(FolderType.HOLDS_MESSAGES); + folder2.open(OpenMode.READ_WRITE, null); + + // use the callbacks, as these are the "official" API + Folder.PersistentDataCallbacks callbacks = mFolder.getPersistentCallbacks(); + Folder.PersistentDataCallbacks callbacks2 = folder2.getPersistentCallbacks(); + + // set some values - tests independence & inserts + callbacks.setPersistentString("key1", "value-1-1"); + callbacks.setPersistentString("key2", "value-1-2"); + callbacks2.setPersistentString("key1", "value-2-1"); + callbacks2.setPersistentString("key2", "value-2-2"); + + // readback initial values + assertEquals("value-1-1", callbacks.getPersistentString("key1", null)); + assertEquals("value-1-2", callbacks.getPersistentString("key2", null)); + assertEquals("value-2-1", callbacks2.getPersistentString("key1", null)); + assertEquals("value-2-2", callbacks2.getPersistentString("key2", null)); + + // readback with default values + assertEquals("value-1-3", callbacks.getPersistentString("key3", "value-1-3")); + assertEquals("value-2-3", callbacks2.getPersistentString("key3", "value-2-3")); + + // partial updates + callbacks.setPersistentString("key1", "value-1-1b"); + callbacks2.setPersistentString("key2", "value-2-2b"); + assertEquals("value-1-1b", callbacks.getPersistentString("key1", null)); // changed + assertEquals("value-1-2", callbacks.getPersistentString("key2", null)); // same + assertEquals("value-2-1", callbacks2.getPersistentString("key1", null)); // same + assertEquals("value-2-2b", callbacks2.getPersistentString("key2", null)); // changed + } + /** * Tests for database version. */ public void testDbVersion() throws MessagingException, URISyntaxException { // build current version database. - LocalStore.newInstance(mLocalStoreUri, getContext()); + LocalStore.newInstance(mLocalStoreUri, getContext(), null); final URI uri = new URI(mLocalStoreUri); final String dbPath = uri.getPath(); final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); // database version should be latest. - assertEquals("database version should be latest", 20, db.getVersion()); + assertEquals("database version should be latest", DATABASE_VERSION, db.getVersion()); db.close(); } @@ -245,9 +293,20 @@ public class LocalStoreUnitTests extends AndroidTestCase { } /** - * Tests for database upgrade from version 18 to version 20. + * Helper function to read out Cursor columns */ - public void testDbUpgrade18To20() throws MessagingException, URISyntaxException { + private HashSet cursorToColumnNames(Cursor c) { + HashSet result = new HashSet(); + for (int i = 0, count = c.getColumnCount(); i < count; ++i) { + result.add(c.getColumnName(i)); + } + return result; + } + + /** + * Tests for database upgrade from version 18 to current version. + */ + public void testDbUpgrade18ToLatest() throws MessagingException, URISyntaxException { final URI uri = new URI(mLocalStoreUri); final String dbPath = uri.getPath(); SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); @@ -270,8 +329,8 @@ public class LocalStoreUnitTests extends AndroidTestCase { expectedAttachment.put("id", db.insert("attachments", null, initialAttachment)); db.close(); - // upgrade database 18 to 20 - LocalStore.newInstance(mLocalStoreUri, getContext()); + // upgrade database 18 to latest + LocalStore.newInstance(mLocalStoreUri, getContext(), null); // added message_id column should be initialized as null expectedMessage.put("message_id", (String) null); // message_id type text == String @@ -280,8 +339,11 @@ public class LocalStoreUnitTests extends AndroidTestCase { // database should be upgraded db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); - assertEquals("database should be upgraded", 20, db.getVersion()); + assertEquals("database should be upgraded", DATABASE_VERSION, db.getVersion()); Cursor c; + + // check for all "latest version" tables + checkAllTablesFound(db); // check message table c = db.query("messages", @@ -315,14 +377,14 @@ public class LocalStoreUnitTests extends AndroidTestCase { } /** - * Tests for database upgrade from version 19 to version 20. + * Tests for database upgrade from version 19 to current version. */ - public void testDbUpgrade19To20() throws MessagingException, URISyntaxException { + public void testDbUpgrade19ToLatest() throws MessagingException, URISyntaxException { final URI uri = new URI(mLocalStoreUri); final String dbPath = uri.getPath(); SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); - // create minimu version 18 db tables + // create sample version 19 db tables createSampleDb(db, 19); // sample message data and expected data @@ -342,17 +404,20 @@ public class LocalStoreUnitTests extends AndroidTestCase { db.close(); - // upgrade database 19 to 20 - LocalStore.newInstance(mLocalStoreUri, getContext()); + // upgrade database 19 to latest + LocalStore.newInstance(mLocalStoreUri, getContext(), null); // added content_id column should be initialized as null expectedAttachment.put("content_id", (String) null); // content_id type text == String // database should be upgraded db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); - assertEquals(20, db.getVersion()); + assertEquals("database should be upgraded", DATABASE_VERSION, db.getVersion()); Cursor c; + // check for all "latest version" tables + checkAllTablesFound(db); + // check message table c = db.query("messages", new String[] { "id", "folder_id", "internal_date", "message_id" }, @@ -381,6 +446,73 @@ public class LocalStoreUnitTests extends AndroidTestCase { db.close(); } + + /** + * Check upgrade from db version 20 to latest + */ + public void testDbUpgrade20ToLatest() throws MessagingException, URISyntaxException { + final URI uri = new URI(mLocalStoreUri); + final String dbPath = uri.getPath(); + SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); + + // create sample version 20 db tables + createSampleDb(db, 20); + db.close(); + + // upgrade database 20 to latest + LocalStore.newInstance(mLocalStoreUri, getContext(), null); + + // database should be upgraded + db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); + assertEquals("database should be upgraded", DATABASE_VERSION, db.getVersion()); + + // check for all "latest version" tables + checkAllTablesFound(db); + } + + /** + * Checks the database to confirm that all tables, with all expected columns are found. + */ + private void checkAllTablesFound(SQLiteDatabase db) { + Cursor c; + HashSet foundNames; + ArrayList expectedNames; + + // check for up-to-date messages table + c = db.query("messages", + null, + null, null, null, null, null); + foundNames = cursorToColumnNames(c); + expectedNames = new ArrayList(Arrays.asList( + new String[]{ "id", "folder_id", "uid", "subject", "date", "flags", "sender_list", + "to_list", "cc_list", "bcc_list", "reply_to_list", + "html_content", "text_content", "attachment_count", + "internal_date" } + )); + assertTrue("messages", foundNames.containsAll(expectedNames)); + + // check for up-to-date attachments table + c = db.query("attachments", + null, + null, null, null, null, null); + foundNames = cursorToColumnNames(c); + expectedNames = new ArrayList(Arrays.asList( + new String[]{ "id", "message_id", + "store_data", "content_uri", "size", "name", + "mime_type", "content_id" } + )); + assertTrue("attachments", foundNames.containsAll(expectedNames)); + + // check for up-to-date remote_store_data table + c = db.query("remote_store_data", + null, + null, null, null, null, null); + foundNames = cursorToColumnNames(c); + expectedNames = new ArrayList(Arrays.asList( + new String[]{ "id", "folder_id", "data_key", "data" } + )); + assertTrue("remote_store_data", foundNames.containsAll(expectedNames)); + } private static void createSampleDb(SQLiteDatabase db, int version) { db.execSQL("DROP TABLE IF EXISTS messages"); @@ -397,6 +529,14 @@ public class LocalStoreUnitTests extends AndroidTestCase { "mime_type TEXT" + ((version >= 20) ? ", content_id" : "") + ")"); + + if (version >= 21) { + db.execSQL("DROP TABLE IF EXISTS remote_store_data"); + db.execSQL("CREATE TABLE remote_store_data " + + "(id INTEGER PRIMARY KEY, folder_id INTEGER, " + + "data_key TEXT, data TEXT)"); + } + db.setVersion(version); } } diff --git a/tests/src/com/android/email/mail/store/Pop3StoreUnitTests.java b/tests/src/com/android/email/mail/store/Pop3StoreUnitTests.java index d05f3b331..f0b8d8e6f 100644 --- a/tests/src/com/android/email/mail/store/Pop3StoreUnitTests.java +++ b/tests/src/com/android/email/mail/store/Pop3StoreUnitTests.java @@ -129,7 +129,7 @@ public class Pop3StoreUnitTests extends AndroidTestCase { // try to open it setupOpenFolder(mockTransport, 0, null); - mFolder.open(OpenMode.READ_ONLY); + mFolder.open(OpenMode.READ_ONLY, null); } /** @@ -569,7 +569,7 @@ public class Pop3StoreUnitTests extends AndroidTestCase { private void openFolderWithMessage(MockTransport mockTransport) throws MessagingException { // try to open it setupOpenFolder(mockTransport, 1, null); - mFolder.open(OpenMode.READ_ONLY); + mFolder.open(OpenMode.READ_ONLY, null); // check message count assertEquals(1, mFolder.getMessageCount());