From c3ceed68948ef6720ae7b861955b4d341f331643 Mon Sep 17 00:00:00 2001 From: Yu Ping Hu Date: Tue, 6 Aug 2013 16:09:00 -0700 Subject: [PATCH] Fix ui notifications on folder list changes. uifolders and uiallfolders cursors now setNotificationUri on the appropriate uri. That uri is notified whenever: - A folder is inserted, deleted, or modified. - A message is inserted or modified (since this can affect message counts). This second one is still not quite right: there are some conditions where counts aren't updating correctly. While I was here, I renamed the notification uris to avoid collisions between different versions of the app. Bug: 9111855 Change-Id: Ia29bb6a65b4f673bf352fdf0e14270b3f1443ca8 --- .../android/emailcommon/provider/Mailbox.java | 20 +++ .../android/email/provider/EmailProvider.java | 167 +++++++++++------- 2 files changed, 123 insertions(+), 64 deletions(-) diff --git a/emailcommon/src/com/android/emailcommon/provider/Mailbox.java b/emailcommon/src/com/android/emailcommon/provider/Mailbox.java index efb0a874e..8a0506dbb 100644 --- a/emailcommon/src/com/android/emailcommon/provider/Mailbox.java +++ b/emailcommon/src/com/android/emailcommon/provider/Mailbox.java @@ -131,6 +131,12 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable }; private static final int MAILBOX_DISPLAY_NAME_COLUMN = 0; + /** + * Projection to use when reading {@link MailboxColumns#ACCOUNT_KEY} for a mailbox. + */ + private static final String[] ACCOUNT_KEY_PROJECTION = { MailboxColumns.ACCOUNT_KEY }; + private static final int ACCOUNT_KEY_PROJECTION_ACCOUNT_KEY_COLUMN = 0; + public static final long NO_MAILBOX = -1; // Sentinel values for the mSyncInterval field of both Mailbox records @@ -760,4 +766,18 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable OUTBOX_PLUS_SYNCING_AND_ACCOUNT_SELECTION, new String[] { Long.toString(accountId) }, null); } + + /** + * Get the account id for a mailbox. + * @param context The {@link Context}. + * @param mailboxId The id of the mailbox we're interested in, as a {@link String}. + * @return The account id for the mailbox, or {@link Account#NO_ACCOUNT} if the mailbox doesn't + * exist. + */ + public static long getAccountIdForMailbox(final Context context, final String mailboxId) { + return Utility.getFirstRowLong(context, + Mailbox.CONTENT_URI.buildUpon().appendEncodedPath(mailboxId).build(), + ACCOUNT_KEY_PROJECTION, null, null, null, + ACCOUNT_KEY_PROJECTION_ACCOUNT_KEY_COLUMN, Account.NO_ACCOUNT); + } } diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java index 59deb8304..bf24e80a9 100644 --- a/src/com/android/email/provider/EmailProvider.java +++ b/src/com/android/email/provider/EmailProvider.java @@ -577,13 +577,20 @@ public class EmailProvider extends ContentProvider { db.execSQL(UPDATED_MESSAGE_DELETE + id); } + final long accountId; + if (match == MAILBOX_ID) { + accountId = Mailbox.getAccountIdForMailbox(context, id); + } else { + accountId = Account.NO_ACCOUNT; + } + result = db.delete(tableName, whereWithId(id, selection), selectionArgs); if (match == ACCOUNT_ID) { notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, id); resolver.notifyChange(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null); } else if (match == MAILBOX_ID) { - notifyUIFolder(id, null); + notifyUIFolder(id, accountId); } else if (match == ATTACHMENT_ID) { notifyUI(UIPROVIDER_ATTACHMENT_NOTIFIER, id); } @@ -684,26 +691,30 @@ public class EmailProvider extends ContentProvider { } } + // These URIs are used for specific UI notifications. We don't use EmailContent.CONTENT_URI + // as the base because that gets spammed. + private static final String UI_NOTIFICATION_AUTHORITY = + EmailContent.EMAIL_PACKAGE_NAME + ".uinotifications"; private static final Uri UIPROVIDER_CONVERSATION_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uimessages"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uimessages"); private static final Uri UIPROVIDER_FOLDER_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uifolder"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uifolder"); private static final Uri UIPROVIDER_FOLDERLIST_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uifolders"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uifolders"); private static final Uri UIPROVIDER_ACCOUNT_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uiaccount"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uiaccount"); public static final Uri UIPROVIDER_SETTINGS_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uisettings"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uisettings"); private static final Uri UIPROVIDER_ATTACHMENT_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uiattachment"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uiattachment"); private static final Uri UIPROVIDER_ATTACHMENTS_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uiattachments"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uiattachments"); public static final Uri UIPROVIDER_ALL_ACCOUNTS_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uiaccts"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uiaccts"); private static final Uri UIPROVIDER_MESSAGE_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uimessage"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uimessage"); private static final Uri UIPROVIDER_RECENT_FOLDERS_NOTIFIER = - Uri.parse("content://" + UIProvider.AUTHORITY + "/uirecentfolders"); + Uri.parse("content://" + UI_NOTIFICATION_AUTHORITY + "/uirecentfolders"); @Override public Uri insert(Uri uri, ContentValues values) { @@ -744,25 +755,25 @@ public class EmailProvider extends ContentProvider { resultUri = ContentUris.withAppendedId(uri, longId); switch(match) { case MESSAGE: + final long mailboxId = values.getAsLong(Message.MAILBOX_KEY); if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) { - notifyUIConversationMailbox(values.getAsLong(Message.MAILBOX_KEY)); + notifyUIConversationMailbox(mailboxId); } + notifyUIFolder(mailboxId, values.getAsLong(Message.ACCOUNT_KEY)); break; case MAILBOX: if (values.containsKey(MailboxColumns.TYPE)) { - // Only notify for special mailbox types - int type = values.getAsInteger(MailboxColumns.TYPE); - if (type != Mailbox.TYPE_INBOX && type != Mailbox.TYPE_OUTBOX && - type != Mailbox.TYPE_DRAFTS && type != Mailbox.TYPE_SENT && - type != Mailbox.TYPE_TRASH && type != Mailbox.TYPE_SEARCH) { - break; + if (values.getAsInteger(MailboxColumns.TYPE) < + Mailbox.TYPE_NOT_EMAIL) { + // Notify the account when a new mailbox is added + final Long accountId = + values.getAsLong(MailboxColumns.ACCOUNT_KEY); + if (accountId != null && accountId.longValue() > 0) { + notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, accountId); + notifyUI(UIPROVIDER_FOLDERLIST_NOTIFIER, accountId); + } } } - // Notify the account when a new mailbox is added - Long accountId = values.getAsLong(MailboxColumns.ACCOUNT_KEY); - if (accountId != null && accountId.longValue() > 0) { - notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, accountId); - } break; case ACCOUNT: updateAccountSyncInterval(longId, values); @@ -1438,27 +1449,19 @@ public class EmailProvider extends ContentProvider { case QUICK_RESPONSE_ID: case POLICY_ID: id = uri.getPathSegments().get(1); - try { - 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 - // update will be reflected in the updated message table; therefore this - // row will always have the "original" data - db.execSQL(UPDATED_MESSAGE_INSERT + id); - } else if (match == MESSAGE_ID) { - db.execSQL(UPDATED_MESSAGE_DELETE + id); - } - result = db.update(tableName, values, whereWithId(id, selection), - selectionArgs); - } catch (SQLiteException e) { - // Null out values (so they aren't cached) and re-throw - values = null; - throw e; + 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 + // update will be reflected in the updated message table; therefore this + // row will always have the "original" data + db.execSQL(UPDATED_MESSAGE_INSERT + id); + } else if (match == MESSAGE_ID) { + db.execSQL(UPDATED_MESSAGE_DELETE + id); } + result = db.update(tableName, values, whereWithId(id, selection), + selectionArgs); if (match == MESSAGE_ID || match == SYNCED_MESSAGE_ID) { - if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) { - notifyUIConversation(uri); - } + handleMessageUpdateNotifications(uri, id, values); } else if (match == ATTACHMENT_ID) { long attId = Integer.parseInt(id); if (values.containsKey(Attachment.FLAGS)) { @@ -1477,9 +1480,8 @@ public class EmailProvider extends ContentProvider { notifyUI(UIPROVIDER_ATTACHMENTS_NOTIFIER, att.mMessageKey); } } - } else if (match == MAILBOX_ID && values.containsKey(Mailbox.UI_SYNC_STATUS)) { - // TODO: should this notify on keys other than sync status? - notifyUIFolder(id, null); + } else if (match == MAILBOX_ID) { + notifyUIFolder(id, Mailbox.getAccountIdForMailbox(context, id)); } else if (match == ACCOUNT_ID) { updateAccountSyncInterval(Long.parseLong(id), values); // Notify individual account and "all accounts" @@ -3055,8 +3057,10 @@ public class EmailProvider extends ContentProvider { row = getVirtualMailboxRow(acctId, Mailbox.TYPE_UNREAD); mc.addRow(row); - mc.setNotificationUri(context.getContentResolver(), uri); - c.setNotificationUri(context.getContentResolver(), uri); + final Uri notifyUri = + UIPROVIDER_FOLDERLIST_NOTIFIER.buildUpon().appendEncodedPath(id).build(); + mc.setNotificationUri(context.getContentResolver(), notifyUri); + c.setNotificationUri(context.getContentResolver(), notifyUri); Cursor[] cursors = new Cursor[] {mc, c}; return new MergeCursor(cursors); } @@ -3436,8 +3440,11 @@ public class EmailProvider extends ContentProvider { Uri notifyUri = null; switch(match) { case UI_ALL_FOLDERS: + // TODO: Should this just do uiFolders()? c = db.rawQuery(genQueryAccountAllMailboxes(uiProjection), new String[] {id}); c = getFolderListCursor(db, c, uiProjection); + notifyUri = + UIPROVIDER_FOLDERLIST_NOTIFIER.buildUpon().appendEncodedPath(id).build(); break; case UI_RECENT_FOLDERS: c = db.rawQuery(genQueryRecentMailboxes(uiProjection), new String[] {id}); @@ -3446,6 +3453,11 @@ public class EmailProvider extends ContentProvider { case UI_SUBFOLDERS: c = db.rawQuery(genQuerySubfolders(uiProjection), new String[] {id}); c = getFolderListCursor(db, c, uiProjection); + // Get notifications for any folder changes on this account. This is broader than + // we need but otherwise we'd need for every folder change to notify on all relevant + // subtrees. For now we opt for simplicity. + final long accountId = Mailbox.getAccountIdForMailbox(context, id); + notifyUri = ContentUris.withAppendedId(UIPROVIDER_FOLDERLIST_NOTIFIER, accountId); break; case UI_MESSAGES: long mailboxId = Long.parseLong(id); @@ -4117,9 +4129,48 @@ public class EmailProvider extends ContentProvider { .build(); addToSequence(uri, op); } + return update(ourUri, ourValues, null, null); } + /** + * Projection for use with getting mailbox & account keys for a message. + */ + private static final String[] MESSAGE_KEYS_PROJECTION = + { MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY }; + private static final int MESSAGE_KEYS_MAILBOX_KEY_COLUMN = 0; + private static final int MESSAGE_KEYS_ACCOUNT_KEY_COLUMN = 1; + + /** + * Notify necessary UI components in response to a message update. + * @param uri The {@link Uri} for this message update. + * @param messageId The id of the message that's been updated. + * @param values The {@link ContentValues} that were updated in the message. + */ + private void handleMessageUpdateNotifications(final Uri uri, final String messageId, + final ContentValues values) { + if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) { + notifyUIConversation(uri); + } + // TODO: Ideally, also test that the values actually changed. + if (values.containsKey(MessageColumns.FLAG_READ) || + values.containsKey(MessageColumns.MAILBOX_KEY)) { + final Cursor c = query( + Message.CONTENT_URI.buildUpon().appendEncodedPath(messageId).build(), + MESSAGE_KEYS_PROJECTION, null, null, null); + if (c != null) { + try { + if (c.moveToFirst()) { + notifyUIFolder(c.getLong(MESSAGE_KEYS_MAILBOX_KEY_COLUMN), + c.getLong(MESSAGE_KEYS_ACCOUNT_KEY_COLUMN)); + } + } finally { + c.close(); + } + } + } + } + public static final String PICKER_UI_ACCOUNT = "picker_ui_account"; public static final String PICKER_MAILBOX_TYPE = "picker_mailbox_type"; public static final String PICKER_MESSAGE_ID = "picker_message_id"; @@ -4248,30 +4299,18 @@ public class EmailProvider extends ContentProvider { * Notify about a folder update. Because folder changes can affect the conversation cursor's * extras, the conversation must also be notified here. * @param folderId the folder id to be notified - * @param accountId the account id to be notified (for folder list notification); if null, then - * lookup the accountId from the folder. + * @param accountId the account id to be notified (for folder list notification). */ - private void notifyUIFolder(String folderId, String accountId) { + private void notifyUIFolder(final String folderId, final long accountId) { notifyUI(UIPROVIDER_CONVERSATION_NOTIFIER, folderId); notifyUI(UIPROVIDER_FOLDER_NOTIFIER, folderId); - if (accountId == null) { - try { - final Mailbox mailbox = Mailbox.restoreMailboxWithId(getContext(), - Long.parseLong(folderId)); - if (mailbox != null) { - accountId = Long.toString(mailbox.mAccountKey); - } - } catch (NumberFormatException e) { - // Bad folderId, so we can't lookup account. - } - } - if (accountId != null) { + if (accountId != Account.NO_ACCOUNT) { notifyUI(UIPROVIDER_FOLDERLIST_NOTIFIER, accountId); } } - private void notifyUIFolder(long folderId, long accountId) { - notifyUIFolder(Long.toString(folderId), Long.toString(accountId)); + private void notifyUIFolder(final long folderId, final long accountId) { + notifyUIFolder(Long.toString(folderId), accountId); } private void notifyUI(Uri uri, String id) {