From 251428e963aef6c2db5055ba54308caea73a580c Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 7 Dec 2012 12:03:04 +0100 Subject: [PATCH] Restore behavior of unread/flagged filtered message list The unread/flagged count/view for accounts now excludes special folders and only includes displayable folders as specified by the display class. --- src/com/fsck/k9/Account.java | 103 +++++++++++++++++- src/com/fsck/k9/activity/Accounts.java | 9 +- .../k9/controller/MessagingController.java | 4 +- src/com/fsck/k9/helper/StringUtils.java | 13 +++ src/com/fsck/k9/helper/Utility.java | 9 ++ src/com/fsck/k9/provider/EmailProvider.java | 54 +++++++-- .../fsck/k9/search/SearchSpecification.java | 3 +- src/com/fsck/k9/search/SqlQueryBuilder.java | 15 ++- 8 files changed, 192 insertions(+), 18 deletions(-) diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index 4044c1b3f..6987144b3 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -27,11 +27,18 @@ import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Store; +import com.fsck.k9.mail.Folder.FolderClass; import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.provider.EmailProvider; import com.fsck.k9.provider.EmailProvider.StatsColumns; +import com.fsck.k9.search.ConditionsTreeNode; +import com.fsck.k9.search.LocalSearch; +import com.fsck.k9.search.SqlQueryBuilder; +import com.fsck.k9.search.SearchSpecification.Attribute; +import com.fsck.k9.search.SearchSpecification.SearchCondition; +import com.fsck.k9.search.SearchSpecification.Searchfield; import com.fsck.k9.view.ColorChip; import java.util.HashMap; @@ -783,10 +790,22 @@ public class Account implements BaseAccount { StatsColumns.FLAGGED_COUNT }; - //TODO: Only count messages in folders that are displayed, exclude special folders like - // Trash, Spam, Outbox, Drafts, Sent. + // Create LocalSearch instance to exclude special folders (Trash, Drafts, Spam, Outbox, + // Sent) and limit the search to displayable folders. + LocalSearch search = new LocalSearch(); + excludeSpecialFolders(search); + limitToDisplayableFolders(search); - Cursor cursor = cr.query(uri, projection, null, null, null); + // Use the LocalSearch instance to create a WHERE clause to query the content provider + StringBuilder query = new StringBuilder(); + List queryArgs = new ArrayList(); + ConditionsTreeNode conditions = search.getConditions(); + SqlQueryBuilder.buildWhereClause(this, conditions, query, queryArgs); + + String selection = query.toString(); + String[] selectionArgs = queryArgs.toArray(new String[0]); + + Cursor cursor = cr.query(uri, projection, selection, selectionArgs, null); try { if (cursor.moveToFirst()) { stats.unreadMessageCount = cursor.getInt(0); @@ -1763,5 +1782,83 @@ public class Account implements BaseAccount { mRemoteSearchFullText = val; } + /** + * Modify the supplied {@link LocalSearch} instance to limit the search to displayable folders. + * + *

+ * This method uses the current folder display mode to decide what folders to include/exclude. + *

+ * + * @param search + * The {@code LocalSearch} instance to modify. + * + * @see #getFolderDisplayMode() + */ + public void limitToDisplayableFolders(LocalSearch search) { + final Account.FolderMode displayMode = getFolderDisplayMode(); + switch (displayMode) { + case FIRST_CLASS: { + // Count messages in the INBOX and non-special first class folders + search.and(Searchfield.DISPLAY_CLASS, FolderClass.FIRST_CLASS.name(), + Attribute.EQUALS); + break; + } + case FIRST_AND_SECOND_CLASS: { + // Count messages in the INBOX and non-special first and second class folders + search.and(Searchfield.DISPLAY_CLASS, FolderClass.FIRST_CLASS.name(), + Attribute.EQUALS); + + // TODO: Create a proper interface for creating arbitrary condition trees + SearchCondition searchCondition = new SearchCondition(Searchfield.DISPLAY_CLASS, + Attribute.EQUALS, FolderClass.SECOND_CLASS.name()); + ConditionsTreeNode root = search.getConditions(); + if (root.mRight != null) { + root.mRight.or(searchCondition); + } else { + search.or(searchCondition); + } + break; + } + case NOT_SECOND_CLASS: { + // Count messages in the INBOX and non-special non-second-class folders + search.and(Searchfield.DISPLAY_CLASS, FolderClass.SECOND_CLASS.name(), + Attribute.NOT_EQUALS); + break; + } + default: + case ALL: { + // Count messages in the INBOX and non-special folders + break; + } + } + } + + /** + * Modify the supplied {@link LocalSearch} instance to exclude special folders. + * + *

+ * Currently the following folders are excluded: + *

    + *
  • Trash
  • + *
  • Drafts
  • + *
  • Spam
  • + *
  • Outbox
  • + *
  • Sent
  • + *
+ * The Inbox will always be included even if one of the special folders is configured to point + * to the Inbox. + *

+ * + * @param search + * The {@code LocalSearch} instance to modify. + */ + public void excludeSpecialFolders(LocalSearch search) { + search.and(Searchfield.FOLDER, getTrashFolderName(), Attribute.NOT_EQUALS); + search.and(Searchfield.FOLDER, getDraftsFolderName(), Attribute.NOT_EQUALS); + search.and(Searchfield.FOLDER, getSpamFolderName(), Attribute.NOT_EQUALS); + search.and(Searchfield.FOLDER, getOutboxFolderName(), Attribute.NOT_EQUALS); + search.and(Searchfield.FOLDER, getSentFolderName(), Attribute.NOT_EQUALS); + search.or(new SearchCondition(Searchfield.FOLDER, Attribute.EQUALS, getInboxFolderName())); + } } diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 37e2f8d83..3fd451c02 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -68,7 +68,6 @@ import com.fsck.k9.activity.setup.AccountSetupBasics; import com.fsck.k9.activity.setup.Prefs; import com.fsck.k9.activity.setup.WelcomeMessage; import com.fsck.k9.controller.MessagingController; -import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.SizeFormatter; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.ServerSettings; @@ -1759,6 +1758,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener { } else { search = new LocalSearch(searchTitle); search.addAccountUuid(account.getUuid()); + + Account realAccount = (Account) account; + realAccount.excludeSpecialFolders(search); + realAccount.limitToDisplayableFolders(search); } search.and(Searchfield.FLAGGED, "1", Attribute.EQUALS); @@ -1777,6 +1780,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener { } else { search = new LocalSearch(searchTitle); search.addAccountUuid(account.getUuid()); + + Account realAccount = (Account) account; + realAccount.excludeSpecialFolders(search); + realAccount.limitToDisplayableFolders(search); } search.and(Searchfield.READ, "1", Attribute.NOT_EQUALS); diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 5dbbb4141..49214c970 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -3452,9 +3452,7 @@ public class MessagingController implements Runnable { ConditionsTreeNode conditions = search.getConditions(); SqlQueryBuilder.buildWhereClause(account, conditions, query, queryArgs); - // Make sure we don't use the empty string as selection - String selection = (query.length() == 0) ? null : query.toString(); - + String selection = query.toString(); String[] selectionArgs = queryArgs.toArray(EMPTY_STRING_ARRAY); Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, diff --git a/src/com/fsck/k9/helper/StringUtils.java b/src/com/fsck/k9/helper/StringUtils.java index fd536aa2c..8ffc06a9a 100644 --- a/src/com/fsck/k9/helper/StringUtils.java +++ b/src/com/fsck/k9/helper/StringUtils.java @@ -6,4 +6,17 @@ public final class StringUtils { return string == null || string.length() == 0; } + public static boolean containsAny(String haystack, String[] needles) { + if (haystack == null) { + return false; + } + + for (String needle : needles) { + if (haystack.contains(needle)) { + return true; + } + } + + return false; + } } diff --git a/src/com/fsck/k9/helper/Utility.java b/src/com/fsck/k9/helper/Utility.java index 7d7754998..0bb105621 100644 --- a/src/com/fsck/k9/helper/Utility.java +++ b/src/com/fsck/k9/helper/Utility.java @@ -75,6 +75,15 @@ public class Utility { return false; } + public static boolean arrayContainsAny(Object[] a, Object... o) { + for (Object element : a) { + if (arrayContains(o, element)) { + return true; + } + } + return false; + } + /** * Combines the given array of Objects into a single String using * each Object's toString() method and the separator character diff --git a/src/com/fsck/k9/provider/EmailProvider.java b/src/com/fsck/k9/provider/EmailProvider.java index 811246d13..deb195293 100644 --- a/src/com/fsck/k9/provider/EmailProvider.java +++ b/src/com/fsck/k9/provider/EmailProvider.java @@ -91,6 +91,27 @@ public class EmailProvider extends ContentProvider { InternalMessageColumns.MIME_TYPE }; + private static final String[] FIXUP_MESSAGES_COLUMNS = { + MessageColumns.ID + }; + + private static final String[] FOLDERS_COLUMNS = { + FolderColumns.ID, + FolderColumns.NAME, + FolderColumns.LAST_UPDATED, + FolderColumns.UNREAD_COUNT, + FolderColumns.VISIBLE_LIMIT, + FolderColumns.STATUS, + FolderColumns.PUSH_STATE, + FolderColumns.LAST_PUSHED, + FolderColumns.FLAGGED_COUNT, + FolderColumns.INTEGRATE, + FolderColumns.TOP_GROUP, + FolderColumns.POLL_CLASS, + FolderColumns.PUSH_CLASS, + FolderColumns.DISPLAY_CLASS + }; + static { UriMatcher matcher = sUriMatcher; @@ -141,6 +162,23 @@ public class EmailProvider extends ContentProvider { public static final String MIME_TYPE = "mime_type"; } + public interface FolderColumns { + public static final String ID = "id"; + public static final String NAME = "name"; + public static final String LAST_UPDATED = "last_updated"; + public static final String UNREAD_COUNT = "unread_count"; + public static final String VISIBLE_LIMIT = "visible_limit"; + public static final String STATUS = "status"; + public static final String PUSH_STATE = "push_state"; + public static final String LAST_PUSHED = "last_pushed"; + public static final String FLAGGED_COUNT = "flagged_count"; + public static final String INTEGRATE = "integrate"; + public static final String TOP_GROUP = "top_group"; + public static final String POLL_CLASS = "poll_class"; + public static final String PUSH_CLASS = "push_class"; + public static final String DISPLAY_CLASS = "display_class"; + } + public interface StatsColumns { public static final String UNREAD_COUNT = "unread_count"; public static final String FLAGGED_COUNT = "flagged_count"; @@ -151,6 +189,7 @@ public class EmailProvider extends ContentProvider { StatsColumns.FLAGGED_COUNT }; + private Preferences mPreferences; @@ -267,8 +306,7 @@ public class EmailProvider extends ContentProvider { } final Cursor cursor; - //TODO: check projection and selection for folder columns - if (Utility.arrayContains(projection, SpecialColumns.FOLDER_NAME)) { + if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) { StringBuilder query = new StringBuilder(); query.append("SELECT "); boolean first = true; @@ -292,10 +330,10 @@ public class EmailProvider extends ContentProvider { query.append(" FROM messages m " + "LEFT JOIN folders f ON (m.folder_id = f.id) " + "WHERE "); - query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS, + query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS, "m.", where)); query.append(" ORDER BY "); - query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS, + query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS, "m.", sortOrder)); cursor = db.rawQuery(query.toString(), selectionArgs); @@ -356,8 +394,7 @@ public class EmailProvider extends ContentProvider { " FROM messages h JOIN messages m " + "ON (h.id = m.thread_root OR h.id = m.id) "); - //TODO: check projection and selection for folder columns - if (Utility.arrayContains(projection, SpecialColumns.FOLDER_NAME)) { + if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) { query.append("LEFT JOIN folders f ON (m.folder_id = f.id) "); } @@ -424,13 +461,14 @@ public class EmailProvider extends ContentProvider { // Table selection sql.append(" FROM messages"); - if (selection != null && selection.contains(SpecialColumns.INTEGRATE)) { + + if (StringUtils.containsAny(selection, FOLDERS_COLUMNS)) { sql.append(" JOIN folders ON (folders.id = messages.folder_id)"); } // WHERE clause sql.append(" WHERE (deleted=0 AND (empty IS NULL OR empty!=1))"); - if (selection != null) { + if (!StringUtils.isNullOrEmpty(selection)) { sql.append(" AND ("); sql.append(selection); sql.append(")"); diff --git a/src/com/fsck/k9/search/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java index 2002a5098..c2ebc1601 100644 --- a/src/com/fsck/k9/search/SearchSpecification.java +++ b/src/com/fsck/k9/search/SearchSpecification.java @@ -80,7 +80,8 @@ public interface SearchSpecification extends Parcelable { ID, INTEGRATE, READ, - FLAGGED + FLAGGED, + DISPLAY_CLASS } diff --git a/src/com/fsck/k9/search/SqlQueryBuilder.java b/src/com/fsck/k9/search/SqlQueryBuilder.java index 0fcd3ae54..8630f7580 100644 --- a/src/com/fsck/k9/search/SqlQueryBuilder.java +++ b/src/com/fsck/k9/search/SqlQueryBuilder.java @@ -7,6 +7,7 @@ import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Folder.OpenMode; import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.LocalStore.LocalFolder; +import com.fsck.k9.search.SearchSpecification.Attribute; import com.fsck.k9.search.SearchSpecification.SearchCondition; import com.fsck.k9.search.SearchSpecification.Searchfield; @@ -29,7 +30,11 @@ public class SqlQueryBuilder { case FOLDER: { String folderName = condition.value; long folderId = getFolderId(account, folderName); - query.append("folder_id = ?"); + if (condition.attribute == Attribute.EQUALS) { + query.append("folder_id = ?"); + } else { + query.append("folder_id != ?"); + } selectionArgs.add(Long.toString(folderId)); break; } @@ -144,6 +149,10 @@ public class SqlQueryBuilder { columnName = "flagged"; break; } + case DISPLAY_CLASS: { + columnName = "display_class"; + break; + } } if (columnName == null) { @@ -220,7 +229,9 @@ public class SqlQueryBuilder { case FOLDER: case ID: case INTEGRATE: - case THREAD_ROOT: { + case THREAD_ROOT: + case READ: + case FLAGGED: { return true; } default: {