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.
This commit is contained in:
cketti 2012-12-07 12:03:04 +01:00
parent 73757af680
commit 251428e963
8 changed files with 192 additions and 18 deletions

View File

@ -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<String> queryArgs = new ArrayList<String>();
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.
*
* <p>
* This method uses the current folder display mode to decide what folders to include/exclude.
* </p>
*
* @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.
*
* <p>
* Currently the following folders are excluded:
* <ul>
* <li>Trash</li>
* <li>Drafts</li>
* <li>Spam</li>
* <li>Outbox</li>
* <li>Sent</li>
* </ul>
* The Inbox will always be included even if one of the special folders is configured to point
* to the Inbox.
* </p>
*
* @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()));
}
}

View File

@ -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);

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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

View File

@ -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(")");

View File

@ -80,7 +80,8 @@ public interface SearchSpecification extends Parcelable {
ID,
INTEGRATE,
READ,
FLAGGED
FLAGGED,
DISPLAY_CLASS
}

View File

@ -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: {