From c0c9c33322deecace00a32766e0a1b355aad4b31 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Wed, 19 Aug 2009 19:07:29 -0700 Subject: [PATCH] Add URI's that atomically add a value to a particular field (Account or Mailbox) * Message, etc. could be added, if this had a use case * Unit test added for both Account and Mailbox cases * Eclipse changed some parens around; it won't happen again --- .../android/email/provider/EmailContent.java | 11 +++-- .../android/email/provider/EmailProvider.java | 45 ++++++++++++++++++- .../android/email/service/MailService.java | 30 ++++++++++--- .../exchange/adapter/EmailSyncAdapter.java | 33 +++++--------- .../android/email/provider/ProviderTests.java | 33 +++++++++++++- 5 files changed, 117 insertions(+), 35 deletions(-) diff --git a/src/com/android/email/provider/EmailContent.java b/src/com/android/email/provider/EmailContent.java index 2af3cc124..35af01a2f 100644 --- a/src/com/android/email/provider/EmailContent.java +++ b/src/com/android/email/provider/EmailContent.java @@ -70,6 +70,9 @@ public abstract class EmailContent { }; private static final int ID_PROJECTION_COLUMN = 0; + public static final String FIELD_COLUMN_NAME = "field"; + public static final String ADD_COLUMN_NAME = "add"; + // Newly created objects get this id private static final int NOT_SAVED = -1; // The base Uri that this piece of content came from @@ -275,7 +278,7 @@ public abstract class EmailContent { * If the message has no body, a new body is inserted for the message. * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY. */ - public static void updateBodyWithMessageId(Context context, long messageId, + public static void updateBodyWithMessageId(Context context, long messageId, ContentValues values) { ContentResolver resolver = context.getContentResolver(); long bodyId = lookupBodyIdWithMessageId(resolver, messageId); @@ -754,7 +757,6 @@ public abstract class EmailContent { return b; } } - } return null; } @@ -797,7 +799,8 @@ public abstract class EmailContent { public static final class Account extends EmailContent implements AccountColumns, Parcelable { public static final String TABLE_NAME = "Account"; public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account"); - + public static final Uri ADD_TO_FIELD_URI = + Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField"); public final static int FLAGS_NOTIFY_NEW_MAIL = 1; public final static int FLAGS_VIBRATE = 2; @@ -1750,6 +1753,8 @@ public abstract class EmailContent { public static final class Mailbox extends EmailContent implements SyncColumns, MailboxColumns { public static final String TABLE_NAME = "Mailbox"; public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox"); + public static final Uri ADD_TO_FIELD_URI = + Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdAddToField"); public String mDisplayName; public String mServerId; diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java index de3136ed0..32d93176b 100644 --- a/src/com/android/email/provider/EmailProvider.java +++ b/src/com/android/email/provider/EmailProvider.java @@ -66,11 +66,13 @@ public class EmailProvider extends ContentProvider { private static final int ACCOUNT = ACCOUNT_BASE; private static final int ACCOUNT_MAILBOXES = ACCOUNT_BASE + 1; private static final int ACCOUNT_ID = ACCOUNT_BASE + 2; + private static final int ACCOUNT_ID_ADD_TO_FIELD = ACCOUNT_BASE + 3; private static final int MAILBOX_BASE = 0x1000; private static final int MAILBOX = MAILBOX_BASE; private static final int MAILBOX_MESSAGES = MAILBOX_BASE + 1; private static final int MAILBOX_ID = MAILBOX_BASE + 2; + private static final int MAILBOX_ID_ADD_TO_FIELD = MAILBOX_BASE + 3; private static final int MESSAGE_BASE = 0x2000; private static final int MESSAGE = MESSAGE_BASE; @@ -146,6 +148,8 @@ public class EmailProvider extends ContentProvider { private static final String DELETE_BODY = "delete from " + Body.TABLE_NAME + " where " + BodyColumns.MESSAGE_KEY + '='; + private static final String ID_EQUALS = EmailContent.RECORD_ID + "=?"; + static { // Email URI matching table UriMatcher matcher = sURIMatcher; @@ -199,9 +203,13 @@ public class EmailProvider extends ContentProvider { // A specific hostauth matcher.addURI(EMAIL_AUTHORITY, "hostauth/#", HOSTAUTH_ID); + // Atomically a constant value to a particular field of a mailbox/account + matcher.addURI(EMAIL_AUTHORITY, "mailboxIdAddToField/#", MAILBOX_ID_ADD_TO_FIELD); + matcher.addURI(EMAIL_AUTHORITY, "accountIdAddToField/#", ACCOUNT_ID_ADD_TO_FIELD); + /** * THIS URI HAS SPECIAL SEMANTICS - * ITS USE IS INDENTED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK + * ITS USE IS INTENDED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK * TO A SERVER VIA A SYNC ADAPTER */ matcher.addURI(EMAIL_AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID); @@ -876,7 +884,40 @@ public class EmailProvider extends ContentProvider { values.remove(MailboxColumns.UNREAD_COUNT); } + String id; switch (match) { + case MAILBOX_ID_ADD_TO_FIELD: + case ACCOUNT_ID_ADD_TO_FIELD: + if (!mInTransaction) { + db.beginTransaction(); + } + id = uri.getPathSegments().get(1); + String field = values.getAsString(EmailContent.FIELD_COLUMN_NAME); + Long add = values.getAsLong(EmailContent.ADD_COLUMN_NAME); + if (field == null || add == null) { + throw new IllegalArgumentException("No field/add specified " + uri); + } + Cursor c = db.query(TABLE_NAMES[table], + new String[] {EmailContent.RECORD_ID, field}, whereWithId(id, selection), + selectionArgs, null, null, null); + try { + result = 0; + ContentValues cv = new ContentValues(); + String[] bind = new String[1]; + while (c.moveToNext()) { + bind[0] = c.getString(0); + long value = c.getLong(1) + add; + cv.put(field, value); + result = db.update(TABLE_NAMES[table], cv, ID_EQUALS, bind); + } + } finally { + c.close(); + } + if (!mInTransaction) { + db.setTransactionSuccessful(); + db.endTransaction(); + } + break; case BODY_ID: case MESSAGE_ID: case SYNCED_MESSAGE_ID: @@ -885,7 +926,7 @@ public class EmailProvider extends ContentProvider { case MAILBOX_ID: case ACCOUNT_ID: case HOSTAUTH_ID: - String id = uri.getPathSegments().get(1); + id = uri.getPathSegments().get(1); if (match == SYNCED_MESSAGE_ID) { // For synced messages, first copy the old message to the updated table // Note the insert or ignore semantics, guaranteeing that only the first diff --git a/src/com/android/email/service/MailService.java b/src/com/android/email/service/MailService.java index 89cac8351..bd469a6f4 100644 --- a/src/com/android/email/service/MailService.java +++ b/src/com/android/email/service/MailService.java @@ -22,6 +22,7 @@ import com.android.email.R; import com.android.email.activity.MessageList; import com.android.email.mail.MessagingException; import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.AccountColumns; import com.android.email.provider.EmailContent.Mailbox; import android.app.AlarmManager; @@ -62,7 +63,9 @@ public class MailService extends Service { private static final String EXTRA_CHECK_ACCOUNT = "com.android.email.intent.extra.ACCOUNT"; private static final String EXTRA_ACCOUNT_INFO = "com.android.email.intent.extra.ACCOUNT_INFO"; - private static final String EXTRA_MESSAGE_COUNT = "com.android.email.intent.extra.COUNT"; + + private static final String[] NEW_MESSAGE_COUNT_PROJECTION = + new String[] {AccountColumns.NEW_MESSAGE_COUNT}; private Controller.Result mControllerCallback = new ControllerResults(); @@ -120,21 +123,20 @@ public class MailService extends Service { } context.getContentResolver().update(uri, mClearNewMessages, null, null); } - + /** * Entry point for asynchronous message services (e.g. push mode) to post notifications of new * messages. This assumes that the push provider has already synced the messages into the * appropriate database - this simply triggers the notification mechanism. - * + * * @param context a context * @param accountId the id of the account that is reporting new messages * @param newCount the number of new messages */ - public static void actionNotifyNewMessages(Context context, long accountId, int newCount) { + public static void actionNotifyNewMessages(Context context, long accountId) { Intent i = new Intent(ACTION_NOTIFY_MAIL); i.setClass(context, MailService.class); i.putExtra(EXTRA_CHECK_ACCOUNT, accountId); - i.putExtra(EXTRA_MESSAGE_COUNT, newCount); context.startService(i); } @@ -156,7 +158,7 @@ public class MailService extends Service { // If we have the data, restore the last-sync-times for each account // These are cached in the wakeup intent in case the process was killed. restoreSyncReports(intent); - + // Sync a specific account if given long checkAccountId = intent.getLongExtra(EXTRA_CHECK_ACCOUNT, -1); if (checkAccountId != -1) { @@ -185,7 +187,21 @@ public class MailService extends Service { stopSelf(startId); } else if (ACTION_NOTIFY_MAIL.equals(action)) { long accountId = intent.getLongExtra(EXTRA_CHECK_ACCOUNT, -1); - int newMessageCount = intent.getIntExtra(EXTRA_MESSAGE_COUNT, -1); + // Get the current new message count + Cursor c = getContentResolver().query( + ContentUris.withAppendedId(Account.CONTENT_URI, accountId), + NEW_MESSAGE_COUNT_PROJECTION, null, null, null); + int newMessageCount = 0; + try { + if (c.moveToFirst()) { + newMessageCount = c.getInt(0); + } else { + // If the account no longer exists, set to -1 (which is handled below) + accountId = -1; + } + } finally { + c.close(); + } if (Config.LOGD && Email.DEBUG) { Log.d(Email.LOG_TAG, "*** MailService: notify accountId=" + Long.toString(accountId) + " count=" + newMessageCount); diff --git a/src/com/android/exchange/adapter/EmailSyncAdapter.java b/src/com/android/exchange/adapter/EmailSyncAdapter.java index 9bb479b7c..f9aae9bf3 100644 --- a/src/com/android/exchange/adapter/EmailSyncAdapter.java +++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java @@ -18,8 +18,10 @@ package com.android.exchange.adapter; import com.android.email.mail.Address; +import com.android.email.provider.EmailContent; import com.android.email.provider.EmailProvider; import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.AccountColumns; import com.android.email.provider.EmailContent.Attachment; import com.android.email.provider.EmailContent.Mailbox; import com.android.email.provider.EmailContent.Message; @@ -464,28 +466,17 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { } } - // TODO: This should be implemented using an "add to unread messages" URI, - // and then it could be handled in the previous section as just another "op" - int totalNewCount = 0; if (notifyCount > 0) { - Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId); - Cursor c = mContentResolver.query(uri, - new String[] { Account.NEW_MESSAGE_COUNT }, null, null, null); - try { - if (c.moveToNext()) { - int oldCount = c.getInt(0); - ContentValues cv = new ContentValues(); - totalNewCount = oldCount + notifyCount; - cv.put(Account.NEW_MESSAGE_COUNT, totalNewCount); - mContentResolver.update(uri, cv, null, null); - } - } finally { - c.close(); - } - } - - if (totalNewCount > 0) { - MailService.actionNotifyNewMessages(mContext, mAccount.mId, totalNewCount); + // Use the new atomic add URI in EmailProvider + // We could add this to the operations being done, but it's not strictly + // speaking necessary, as the previous batch preserves the integrity of the + // database, whereas this is purely for notification purposes, and is itself atomic + ContentValues cv = new ContentValues(); + cv.put(EmailContent.FIELD_COLUMN_NAME, AccountColumns.NEW_MESSAGE_COUNT); + cv.put(EmailContent.ADD_COLUMN_NAME, notifyCount); + Uri uri = ContentUris.withAppendedId(Account.ADD_TO_FIELD_URI, mAccount.mId); + mContentResolver.update(uri, cv, null, null); + MailService.actionNotifyNewMessages(mContext, mAccount.mId); } } } diff --git a/tests/src/com/android/email/provider/ProviderTests.java b/tests/src/com/android/email/provider/ProviderTests.java index 17c347857..115818633 100644 --- a/tests/src/com/android/email/provider/ProviderTests.java +++ b/tests/src/com/android/email/provider/ProviderTests.java @@ -445,7 +445,7 @@ public class ProviderTests extends ProviderTestCase2 { // 3. delete first message resolver.delete(ContentUris.withAppendedId(Message.CONTENT_URI, message1Id), null, null); - + // 4. verify body for second message wasn't deleted assertNotNull(loadBodyForMessageId(message2Id)); @@ -483,7 +483,6 @@ public class ProviderTests extends ProviderTestCase2 { true, mMockContext); long message2Id = message2.mId; //verify body is there - Body body = loadBodyForMessageId(message2Id); assertNotNull(loadBodyForMessageId(message2Id)); // 3. delete first message @@ -1030,4 +1029,34 @@ public class ProviderTests extends ProviderTestCase2 { String newStr = EmailProvider.createIndex(Message.TABLE_NAME, MessageColumns.TIMESTAMP); assertEquals(newStr, oldStr); } + + public void testIdAddToField() { + ContentResolver cr = mMockContext.getContentResolver(); + ContentValues cv = new ContentValues(); + + // Try changing the newMessageCount of an account + Account account = ProviderTestUtils.setupAccount("field-add", true, mMockContext); + int startCount = account.mNewMessageCount; + // "field" and "add" are the two required elements + cv.put(EmailContent.FIELD_COLUMN_NAME, AccountColumns.NEW_MESSAGE_COUNT); + cv.put(EmailContent.ADD_COLUMN_NAME, 17); + cr.update(ContentUris.withAppendedId(Account.ADD_TO_FIELD_URI, account.mId), + cv, null, null); + Account restoredAccount = Account.restoreAccountWithId(mMockContext, account.mId); + assertEquals(17 + startCount, restoredAccount.mNewMessageCount); + cv.put(EmailContent.ADD_COLUMN_NAME, -11); + cr.update(ContentUris.withAppendedId(Account.ADD_TO_FIELD_URI, account.mId), + cv, null, null); + restoredAccount = Account.restoreAccountWithId(mMockContext, account.mId); + assertEquals(17 - 11 + startCount, restoredAccount.mNewMessageCount); + + // Now try with a mailbox + Mailbox boxA = ProviderTestUtils.setupMailbox("boxA", account.mId, true, mMockContext); + assertEquals(0, boxA.mUnreadCount); + cv.put(EmailContent.FIELD_COLUMN_NAME, MailboxColumns.UNREAD_COUNT); + cv.put(EmailContent.ADD_COLUMN_NAME, 11); + cr.update(ContentUris.withAppendedId(Mailbox.ADD_TO_FIELD_URI, boxA.mId), cv, null, null); + Mailbox restoredBoxA = Mailbox.restoreMailboxWithId(mMockContext, boxA.mId); + assertEquals(11, restoredBoxA.mUnreadCount); + } }