Merge branch 'new_search_framework' into content_provider
Conflicts: src/com/fsck/k9/fragment/MessageListFragment.java
This commit is contained in:
commit
83d5102f3d
19 changed files with 1431 additions and 970 deletions
|
@ -579,8 +579,8 @@ public class K9 extends Application {
|
|||
|
||||
public static void loadPrefs(Preferences prefs) {
|
||||
SharedPreferences sprefs = prefs.getPreferences();
|
||||
DEBUG = sprefs.getBoolean("enableDebugLogging", false);
|
||||
DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", false);
|
||||
DEBUG = sprefs.getBoolean("enableDebugLogging", true);
|
||||
DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", true);
|
||||
mAnimations = sprefs.getBoolean("animations", true);
|
||||
mGesturesEnabled = sprefs.getBoolean("gesturesEnabled", false);
|
||||
mUseVolumeKeysForNavigation = sprefs.getBoolean("useVolumeKeysForNavigation", false);
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
package com.fsck.k9;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.fsck.k9.mail.Flag;
|
||||
|
||||
/**
|
||||
* This is a meta-Account that represents one or more accounts with filters on them. The filter specification
|
||||
* is defined by {@link com.fsck.k9.activity.SearchModifier}.
|
||||
*/
|
||||
public class SearchAccount implements BaseAccount, SearchSpecification, Serializable {
|
||||
/**
|
||||
* Create a {@code SearchAccount} instance for the Unified Inbox.
|
||||
*
|
||||
* @param context
|
||||
* A {@link Context} instance that will be used to get localized strings and will be
|
||||
* passed on to the {@code SearchAccount} instance.
|
||||
*
|
||||
* @return The {@link SearchAccount} instance for the Unified Inbox.
|
||||
*/
|
||||
public static SearchAccount createUnifiedInboxAccount(Context context) {
|
||||
SearchAccount unifiedInbox = new SearchAccount(context, true, null, null);
|
||||
unifiedInbox.setDescription(context.getString(R.string.integrated_inbox_title));
|
||||
unifiedInbox.setEmail(context.getString(R.string.integrated_inbox_detail));
|
||||
return unifiedInbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code SearchAccount} instance for the special account "All messages".
|
||||
*
|
||||
* @param context
|
||||
* A {@link Context} instance that will be used to get localized strings and will be
|
||||
* passed on to the {@code SearchAccount} instance.
|
||||
*
|
||||
* @return The {@link SearchAccount} instance for the Unified Inbox.
|
||||
*/
|
||||
public static SearchAccount createAllMessagesAccount(Context context) {
|
||||
SearchAccount allMessages = new SearchAccount(context, false, null, null);
|
||||
allMessages.setDescription(context.getString(R.string.search_all_messages_title));
|
||||
allMessages.setEmail(context.getString(R.string.search_all_messages_detail));
|
||||
return allMessages;
|
||||
}
|
||||
|
||||
|
||||
private static final long serialVersionUID = -4388420303235543976L;
|
||||
private Flag[] mRequiredFlags = null;
|
||||
private Flag[] mForbiddenFlags = null;
|
||||
private String email = null;
|
||||
private String description = null;
|
||||
private String query = "";
|
||||
private boolean integrate = false;
|
||||
private String mUuid = null;
|
||||
private boolean builtin = false;
|
||||
private String[] accountUuids = null;
|
||||
private String[] folderNames = null;
|
||||
|
||||
public SearchAccount(Preferences preferences) {
|
||||
}
|
||||
|
||||
protected synchronized void delete(Preferences preferences) {
|
||||
}
|
||||
|
||||
public synchronized void save(Preferences preferences) {
|
||||
}
|
||||
|
||||
public SearchAccount(Context context, boolean nintegrate, Flag[] requiredFlags, Flag[] forbiddenFlags) {
|
||||
mRequiredFlags = requiredFlags;
|
||||
mForbiddenFlags = forbiddenFlags;
|
||||
integrate = nintegrate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Flag[] getRequiredFlags() {
|
||||
return mRequiredFlags;
|
||||
}
|
||||
|
||||
public Flag[] getForbiddenFlags() {
|
||||
return mForbiddenFlags;
|
||||
}
|
||||
|
||||
public boolean isIntegrate() {
|
||||
return integrate;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
if (mUuid == null) {
|
||||
setUuid(UUID.randomUUID().toString());
|
||||
}
|
||||
return mUuid;
|
||||
}
|
||||
|
||||
public void setUuid(String nUuid) {
|
||||
mUuid = nUuid;
|
||||
}
|
||||
|
||||
public void setIntegrate(boolean integrate) {
|
||||
this.integrate = integrate;
|
||||
}
|
||||
|
||||
public boolean isBuiltin() {
|
||||
return builtin;
|
||||
}
|
||||
|
||||
public void setBuiltin(boolean builtin) {
|
||||
this.builtin = builtin;
|
||||
}
|
||||
|
||||
public String[] getAccountUuids() {
|
||||
return accountUuids;
|
||||
}
|
||||
|
||||
public void setAccountUuids(String[] accountUuids) {
|
||||
this.accountUuids = accountUuids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getFolderNames() {
|
||||
return folderNames;
|
||||
}
|
||||
|
||||
public void setFolderNames(String[] folderNames) {
|
||||
this.folderNames = folderNames;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
package com.fsck.k9;
|
||||
|
||||
import com.fsck.k9.mail.Flag;
|
||||
|
||||
public interface SearchSpecification {
|
||||
|
||||
public Flag[] getRequiredFlags();
|
||||
|
||||
public Flag[] getForbiddenFlags();
|
||||
|
||||
public boolean isIntegrate();
|
||||
|
||||
public String getQuery();
|
||||
|
||||
public String[] getAccountUuids();
|
||||
|
||||
public String[] getFolderNames();
|
||||
}
|
|
@ -21,7 +21,7 @@ import com.fsck.k9.FontSizes;
|
|||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.SearchAccount;
|
||||
import com.fsck.k9.search.SearchAccount;
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,8 +62,6 @@ import com.fsck.k9.FontSizes;
|
|||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.SearchAccount;
|
||||
import com.fsck.k9.SearchSpecification;
|
||||
import com.fsck.k9.activity.misc.ExtendedAsyncTask;
|
||||
import com.fsck.k9.activity.misc.NonConfigurationInstance;
|
||||
import com.fsck.k9.activity.setup.AccountSettings;
|
||||
|
@ -80,6 +78,10 @@ import com.fsck.k9.mail.Transport;
|
|||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
import com.fsck.k9.mail.store.WebDavStore;
|
||||
import com.fsck.k9.search.LocalSearch;
|
||||
import com.fsck.k9.search.SearchAccount;
|
||||
import com.fsck.k9.search.SearchModifier;
|
||||
import com.fsck.k9.search.SearchSpecification;
|
||||
import com.fsck.k9.view.ColorChip;
|
||||
import com.fsck.k9.preferences.SettingsExporter;
|
||||
import com.fsck.k9.preferences.SettingsImportExportException;
|
||||
|
@ -424,8 +426,18 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||
* Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
|
||||
*/
|
||||
private void createSpecialAccounts() {
|
||||
integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
|
||||
unreadAccount = SearchAccount.createAllMessagesAccount(this);
|
||||
// create the unified inbox meta account ( all accounts is default when none specified )
|
||||
String name = getString(R.string.integrated_inbox_title);
|
||||
LocalSearch tmpSearch = new LocalSearch(name);
|
||||
tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
|
||||
integratedInboxAccount = new SearchAccount(tmpSearch, name,
|
||||
getString(R.string.integrated_inbox_detail));
|
||||
|
||||
// create the all messages search ( all accounts is default when none specified )
|
||||
name = getString(R.string.search_all_messages_title);
|
||||
tmpSearch = new LocalSearch(name);
|
||||
unreadAccount = new SearchAccount(tmpSearch, name,
|
||||
getString(R.string.search_all_messages_detail));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -550,7 +562,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||
pendingWork.put(account, "true");
|
||||
final SearchAccount searchAccount = (SearchAccount)account;
|
||||
|
||||
MessagingController.getInstance(getApplication()).searchLocalMessages(searchAccount, null, new MessagingListener() {
|
||||
MessagingController.getInstance(getApplication())
|
||||
.searchLocalMessages(searchAccount.getRelatedSearch(), new MessagingListener() {
|
||||
@Override
|
||||
public void searchStats(AccountStats stats) {
|
||||
mListener.accountStatusChanged(searchAccount, stats);
|
||||
|
@ -607,7 +620,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||
private boolean onOpenAccount(BaseAccount account) {
|
||||
if (account instanceof SearchAccount) {
|
||||
SearchAccount searchAccount = (SearchAccount)account;
|
||||
MessageList.actionHandle(this, searchAccount.getDescription(), searchAccount);
|
||||
MessageList.actionDisplaySearch(this, searchAccount.getRelatedSearch(), false);
|
||||
} else {
|
||||
Account realAccount = (Account)account;
|
||||
if (!realAccount.isEnabled()) {
|
||||
|
@ -624,8 +637,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||
if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) {
|
||||
FolderList.actionHandleAccount(this, realAccount);
|
||||
} else {
|
||||
MessageList.actionHandleFolder(this, realAccount, realAccount.getAutoExpandFolderName());
|
||||
}
|
||||
LocalSearch search = new LocalSearch(realAccount.getAutoExpandFolderName());
|
||||
search.addAllowedFolder(realAccount.getAutoExpandFolderName());
|
||||
search.addAccountUuid(realAccount.getUuid());
|
||||
MessageList.actionDisplaySearch(this, search, true);}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1769,49 +1784,20 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||
}
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
|
||||
final String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
|
||||
LocalSearch search = null;
|
||||
|
||||
if (account instanceof SearchAccount) {
|
||||
SearchAccount searchAccount = (SearchAccount)account;
|
||||
|
||||
MessageList.actionHandle(Accounts.this,
|
||||
description, "", searchAccount.isIntegrate(),
|
||||
combine(searchAccount.getRequiredFlags(), searchModifier.requiredFlags),
|
||||
combine(searchAccount.getForbiddenFlags(), searchModifier.forbiddenFlags));
|
||||
search = ((SearchAccount) account).getRelatedSearch();
|
||||
search.setName(description);
|
||||
} else {
|
||||
SearchSpecification searchSpec = new SearchSpecification() {
|
||||
@Override
|
||||
public String[] getAccountUuids() {
|
||||
return new String[] { account.getUuid() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flag[] getForbiddenFlags() {
|
||||
return searchModifier.forbiddenFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuery() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flag[] getRequiredFlags() {
|
||||
return searchModifier.requiredFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIntegrate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getFolderNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
MessageList.actionHandle(Accounts.this, description, searchSpec);
|
||||
search = new LocalSearch(description);
|
||||
search.addAccountUuid(account.getUuid());
|
||||
}
|
||||
|
||||
search.allRequiredFlags(searchModifier.requiredFlags);
|
||||
search.allForbiddenFlags(searchModifier.forbiddenFlags);
|
||||
MessageList.actionDisplaySearch(Accounts.this, search, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ import com.fsck.k9.FontSizes;
|
|||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.SearchSpecification;
|
||||
import com.fsck.k9.activity.FolderList.FolderListAdapter.FolderListFilter;
|
||||
import com.fsck.k9.activity.misc.ActionBarNavigationSpinner;
|
||||
import com.fsck.k9.activity.setup.AccountSettings;
|
||||
|
@ -68,6 +67,9 @@ import com.fsck.k9.mail.Folder;
|
|||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||
import com.fsck.k9.search.LocalSearch;
|
||||
import com.fsck.k9.search.SearchModifier;
|
||||
import com.fsck.k9.search.SearchSpecification;
|
||||
import com.fsck.k9.service.MailService;
|
||||
|
||||
/**
|
||||
|
@ -620,7 +622,10 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
|
|||
}
|
||||
|
||||
private void onOpenFolder(String folder) {
|
||||
MessageList.actionHandleFolder(this, mAccount, folder);
|
||||
LocalSearch search = new LocalSearch(folder);
|
||||
search.addAccountUuid(mAccount.getUuid());
|
||||
search.addAllowedFolder(folder);
|
||||
MessageList.actionDisplaySearch(this, search, false);
|
||||
}
|
||||
|
||||
private void onCompact(Account account) {
|
||||
|
@ -1257,86 +1262,34 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
|
|||
}
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String description = getString(R.string.search_title,
|
||||
final String description = getString(R.string.search_title,
|
||||
getString(R.string.message_list_title, account.getDescription(), displayName),
|
||||
getString(searchModifier.resId));
|
||||
|
||||
SearchSpecification searchSpec = new SearchSpecification() {
|
||||
@Override
|
||||
public String[] getAccountUuids() {
|
||||
return new String[] { account.getUuid() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flag[] getForbiddenFlags() {
|
||||
return searchModifier.forbiddenFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuery() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flag[] getRequiredFlags() {
|
||||
return searchModifier.requiredFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIntegrate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getFolderNames() {
|
||||
return new String[] { folderName };
|
||||
}
|
||||
|
||||
};
|
||||
MessageList.actionHandle(FolderList.this, description, searchSpec);
|
||||
|
||||
|
||||
LocalSearch search = new LocalSearch(description);
|
||||
try {
|
||||
search.allRequiredFlags(searchModifier.requiredFlags);
|
||||
search.allForbiddenFlags(searchModifier.forbiddenFlags);
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
search.addAllowedFolder(folderName);
|
||||
search.addAccountUuid(account.getUuid());
|
||||
MessageList.actionDisplaySearch(FolderList.this, search, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Flag[] UNREAD_FLAG_ARRAY = { Flag.SEEN };
|
||||
|
||||
private void openUnreadSearch(Context context, final Account account) {
|
||||
String description = getString(R.string.search_title, mAccount.getDescription(), getString(R.string.unread_modifier));
|
||||
|
||||
SearchSpecification searchSpec = new SearchSpecification() {
|
||||
//interface has no override @Override
|
||||
public String[] getAccountUuids() {
|
||||
return new String[] { account.getUuid() };
|
||||
}
|
||||
|
||||
//interface has no override @Override
|
||||
public Flag[] getForbiddenFlags() {
|
||||
return UNREAD_FLAG_ARRAY;
|
||||
}
|
||||
|
||||
//interface has no override @Override
|
||||
public String getQuery() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flag[] getRequiredFlags() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIntegrate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getFolderNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
MessageList.actionHandle(context, description, searchSpec);
|
||||
LocalSearch search = new LocalSearch(description);
|
||||
search.addAccountUuid(account.getUuid());
|
||||
try {
|
||||
search.allRequiredFlags(new Flag[]{Flag.SEEN});
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import android.os.Parcelable;
|
|||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.BaseAccount;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.SearchSpecification;
|
||||
import com.fsck.k9.search.SearchSpecification;
|
||||
|
||||
public class LauncherShortcuts extends AccountList {
|
||||
@Override
|
||||
|
@ -31,8 +31,7 @@ public class LauncherShortcuts extends AccountList {
|
|||
Intent shortcutIntent = null;
|
||||
|
||||
if (account instanceof SearchSpecification) {
|
||||
shortcutIntent = MessageList.actionHandleAccountIntent(this, account.getDescription(),
|
||||
(SearchSpecification) account);
|
||||
shortcutIntent = MessageList.intentDisplaySearch(this, (SearchSpecification) account, true, true);
|
||||
} else {
|
||||
shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null,
|
||||
true);
|
||||
|
|
|
@ -24,7 +24,6 @@ import com.fsck.k9.Account.SortType;
|
|||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.SearchSpecification;
|
||||
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
||||
import com.fsck.k9.activity.setup.AccountSettings;
|
||||
import com.fsck.k9.activity.setup.FolderSettings;
|
||||
|
@ -35,6 +34,11 @@ import com.fsck.k9.helper.Utility;
|
|||
import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
import com.fsck.k9.search.LocalSearch;
|
||||
import com.fsck.k9.search.SearchSpecification;
|
||||
import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
|
||||
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
|
||||
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -44,86 +48,36 @@ import com.fsck.k9.mail.store.StorageManager;
|
|||
*/
|
||||
public class MessageList extends K9FragmentActivity implements MessageListFragmentListener,
|
||||
OnBackStackChangedListener, OnSwipeGestureListener {
|
||||
private static final String EXTRA_ACCOUNT = "account";
|
||||
private static final String EXTRA_FOLDER = "folder";
|
||||
|
||||
// for this activity
|
||||
private static final String EXTRA_SEARCH = "search";
|
||||
|
||||
// used for remote search
|
||||
private static final String EXTRA_SEARCH_ACCOUNT = "com.fsck.k9.search_account";
|
||||
private static final String EXTRA_SEARCH_FOLDER = "com.fsck.k9.search_folder";
|
||||
private static final String EXTRA_QUERY_FLAGS = "queryFlags";
|
||||
private static final String EXTRA_FORBIDDEN_FLAGS = "forbiddenFlags";
|
||||
private static final String EXTRA_INTEGRATE = "integrate";
|
||||
private static final String EXTRA_ACCOUNT_UUIDS = "accountUuids";
|
||||
private static final String EXTRA_FOLDER_NAMES = "folderNames";
|
||||
private static final String EXTRA_TITLE = "title";
|
||||
|
||||
|
||||
public static void actionHandleFolder(Context context, Account account, String folder) {
|
||||
Intent intent = new Intent(context, MessageList.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
|
||||
if (folder != null) {
|
||||
intent.putExtra(EXTRA_FOLDER, folder);
|
||||
}
|
||||
context.startActivity(intent);
|
||||
public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask) {
|
||||
actionDisplaySearch(context, search, newTask, true);
|
||||
}
|
||||
|
||||
public static Intent actionHandleFolderIntent(Context context, Account account, String folder) {
|
||||
|
||||
public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
|
||||
context.startActivity(intentDisplaySearch(context, search, newTask, clearTop));
|
||||
}
|
||||
|
||||
public static Intent intentDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
|
||||
Intent intent = new Intent(context, MessageList.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
|
||||
if (folder != null) {
|
||||
intent.putExtra(EXTRA_FOLDER, folder);
|
||||
intent.putExtra(EXTRA_SEARCH, search);
|
||||
|
||||
if (clearTop) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
if (newTask) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void actionHandle(Context context, String title, String queryString, boolean integrate, Flag[] flags, Flag[] forbiddenFlags) {
|
||||
Intent intent = new Intent(context, MessageList.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtra(SearchManager.QUERY, queryString);
|
||||
if (flags != null) {
|
||||
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(flags, ','));
|
||||
}
|
||||
if (forbiddenFlags != null) {
|
||||
intent.putExtra(EXTRA_FORBIDDEN_FLAGS, Utility.combine(forbiddenFlags, ','));
|
||||
}
|
||||
intent.putExtra(EXTRA_INTEGRATE, integrate);
|
||||
intent.putExtra(EXTRA_TITLE, title);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an intent that opens Unified Inbox or All Messages screen.
|
||||
*/
|
||||
public static Intent actionHandleAccountIntent(Context context, String title,
|
||||
SearchSpecification searchSpecification) {
|
||||
Intent intent = new Intent(context, MessageList.class);
|
||||
intent.putExtra(SearchManager.QUERY, searchSpecification.getQuery());
|
||||
if (searchSpecification.getRequiredFlags() != null) {
|
||||
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(searchSpecification.getRequiredFlags(), ','));
|
||||
}
|
||||
if (searchSpecification.getForbiddenFlags() != null) {
|
||||
intent.putExtra(EXTRA_FORBIDDEN_FLAGS, Utility.combine(searchSpecification.getForbiddenFlags(), ','));
|
||||
}
|
||||
intent.putExtra(EXTRA_INTEGRATE, searchSpecification.isIntegrate());
|
||||
intent.putExtra(EXTRA_ACCOUNT_UUIDS, searchSpecification.getAccountUuids());
|
||||
intent.putExtra(EXTRA_FOLDER_NAMES, searchSpecification.getFolderNames());
|
||||
intent.putExtra(EXTRA_TITLE, title);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void actionHandle(Context context, String title,
|
||||
SearchSpecification searchSpecification) {
|
||||
Intent intent = actionHandleAccountIntent(context, title, searchSpecification);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
|
||||
|
||||
|
@ -131,31 +85,22 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
private TextView mActionBarTitle;
|
||||
private TextView mActionBarSubTitle;
|
||||
private TextView mActionBarUnread;
|
||||
private String mTitle;
|
||||
private Menu mMenu;
|
||||
|
||||
private MessageListFragment mMessageListFragment;
|
||||
|
||||
private Account mAccount;
|
||||
private String mQueryString;
|
||||
private String mFolderName;
|
||||
private Flag[] mQueryFlags;
|
||||
private Flag[] mForbiddenFlags;
|
||||
private String mSearchAccount = null;
|
||||
private String mSearchFolder = null;
|
||||
private boolean mIntegrate;
|
||||
private String[] mAccountUuids;
|
||||
private String[] mFolderNames;
|
||||
|
||||
|
||||
private LocalSearch mSearch;
|
||||
private boolean mSingleFolderMode;
|
||||
private boolean mSingleAccountMode;
|
||||
private boolean mIsRemote;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.message_list);
|
||||
|
||||
// need this for actionbar initialization
|
||||
mQueryString = getIntent().getStringExtra(SearchManager.QUERY);
|
||||
|
||||
mActionBar = getSupportActionBar();
|
||||
initializeActionBar();
|
||||
|
||||
|
@ -171,76 +116,56 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
|
||||
if (mMessageListFragment == null) {
|
||||
FragmentTransaction ft = fragmentManager.beginTransaction();
|
||||
if (mQueryString == null) {
|
||||
mMessageListFragment = MessageListFragment.newInstance(mAccount, mFolderName);
|
||||
} else if (mSearchAccount != null) {
|
||||
mMessageListFragment = MessageListFragment.newInstance(mSearchAccount,
|
||||
mSearchFolder, mQueryString, false);
|
||||
} else {
|
||||
mMessageListFragment = MessageListFragment.newInstance(mTitle, mAccountUuids,
|
||||
mFolderNames, mQueryString, mQueryFlags, mForbiddenFlags, mIntegrate);
|
||||
}
|
||||
mMessageListFragment = MessageListFragment.newInstance(mSearch, mIsRemote);
|
||||
ft.add(R.id.message_list_container, mMessageListFragment);
|
||||
ft.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeExtras(Intent intent) {
|
||||
mQueryString = intent.getStringExtra(SearchManager.QUERY);
|
||||
mFolderName = null;
|
||||
mSearchAccount = null;
|
||||
mSearchFolder = null;
|
||||
if (mQueryString != null) {
|
||||
// check if this intent comes from the system search ( remote )
|
||||
if (intent.getStringExtra(SearchManager.QUERY) != null) {
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
//Query was received from Search Dialog
|
||||
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
|
||||
if (appData != null) {
|
||||
mSearchAccount = appData.getString(EXTRA_SEARCH_ACCOUNT);
|
||||
mSearchFolder = appData.getString(EXTRA_SEARCH_FOLDER);
|
||||
mSearch = new LocalSearch();
|
||||
mSearch.addAccountUuid(appData.getString(EXTRA_SEARCH_ACCOUNT));
|
||||
mSearch.addAllowedFolder(appData.getString(EXTRA_SEARCH_FOLDER));
|
||||
|
||||
String query = intent.getStringExtra(SearchManager.QUERY);
|
||||
mSearch.or(new SearchCondition(SEARCHFIELD.SENDER, ATTRIBUTE.CONTAINS, query));
|
||||
mSearch.or(new SearchCondition(SEARCHFIELD.SUBJECT, ATTRIBUTE.CONTAINS, query));
|
||||
|
||||
mIsRemote = true;
|
||||
}
|
||||
} else {
|
||||
mSearchAccount = intent.getStringExtra(EXTRA_SEARCH_ACCOUNT);
|
||||
mSearchFolder = intent.getStringExtra(EXTRA_SEARCH_FOLDER);
|
||||
}
|
||||
} else {
|
||||
// regular LocalSearch object was passed
|
||||
mSearch = intent.getParcelableExtra(EXTRA_SEARCH);
|
||||
}
|
||||
|
||||
String accountUuid = intent.getStringExtra(EXTRA_ACCOUNT);
|
||||
mFolderName = intent.getStringExtra(EXTRA_FOLDER);
|
||||
|
||||
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
|
||||
|
||||
if (mAccount != null && !mAccount.isAvailable(this)) {
|
||||
Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
|
||||
onAccountUnavailable();
|
||||
return;
|
||||
}
|
||||
|
||||
String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS);
|
||||
if (queryFlags != null) {
|
||||
String[] flagStrings = queryFlags.split(",");
|
||||
mQueryFlags = new Flag[flagStrings.length];
|
||||
for (int i = 0; i < flagStrings.length; i++) {
|
||||
mQueryFlags[i] = Flag.valueOf(flagStrings[i]);
|
||||
}
|
||||
}
|
||||
String forbiddenFlags = intent.getStringExtra(EXTRA_FORBIDDEN_FLAGS);
|
||||
if (forbiddenFlags != null) {
|
||||
String[] flagStrings = forbiddenFlags.split(",");
|
||||
mForbiddenFlags = new Flag[flagStrings.length];
|
||||
for (int i = 0; i < flagStrings.length; i++) {
|
||||
mForbiddenFlags[i] = Flag.valueOf(flagStrings[i]);
|
||||
}
|
||||
}
|
||||
mIntegrate = intent.getBooleanExtra(EXTRA_INTEGRATE, false);
|
||||
mAccountUuids = intent.getStringArrayExtra(EXTRA_ACCOUNT_UUIDS);
|
||||
mFolderNames = intent.getStringArrayExtra(EXTRA_FOLDER_NAMES);
|
||||
mTitle = intent.getStringExtra(EXTRA_TITLE);
|
||||
|
||||
// Take the initial folder into account only if we are *not* restoring
|
||||
// the activity already.
|
||||
if (mFolderName == null && mQueryString == null) {
|
||||
mFolderName = mAccount.getAutoExpandFolderName();
|
||||
}
|
||||
String[] accounts = mSearch.getAccountUuids();
|
||||
mSingleAccountMode = ( accounts != null && accounts.length == 1
|
||||
&& !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS));
|
||||
mSingleFolderMode = mSingleAccountMode && (mSearch.getFolderNames().size() == 1);
|
||||
|
||||
if (mSingleAccountMode) {
|
||||
mAccount = Preferences.getPreferences(this).getAccount(accounts[0]);
|
||||
|
||||
if (mAccount != null && !mAccount.isAvailable(this)) {
|
||||
Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
|
||||
onAccountUnavailable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mSingleFolderMode) {
|
||||
mFolderName = mSearch.getFolderNames().get(0);
|
||||
}
|
||||
|
||||
// now we know if we are in single account mode and need a subtitle
|
||||
mActionBarSubTitle.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -276,10 +201,6 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
mActionBarSubTitle = (TextView) customView.findViewById(R.id.actionbar_title_sub);
|
||||
mActionBarUnread = (TextView) customView.findViewById(R.id.actionbar_unread_count);
|
||||
|
||||
if (mQueryString != null) {
|
||||
mActionBarSubTitle.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
|
@ -407,17 +328,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
if (fragmentManager.getBackStackEntryCount() > 0) {
|
||||
fragmentManager.popBackStack();
|
||||
} else if (mIntegrate) {
|
||||
// If we were in one of the integrated mailboxes (think All Mail or Integrated Inbox), then
|
||||
// go to accounts.
|
||||
onAccounts();
|
||||
} else if (mQueryString != null) {
|
||||
// We did a search of some sort. Go back to wherever the user searched from.
|
||||
onBackPressed();
|
||||
} else {
|
||||
// In a standard message list of a folder. Go to folder list.
|
||||
onShowFolderList();
|
||||
}
|
||||
} else if (!mSingleFolderMode) {
|
||||
onBackPressed();
|
||||
} else {
|
||||
onShowFolderList();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case R.id.compose: {
|
||||
|
@ -470,7 +385,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
}
|
||||
}
|
||||
|
||||
if (mQueryString != null) {
|
||||
if (!mSingleFolderMode) {
|
||||
// None of the options after this point are "safe" for search results
|
||||
//TODO: This is not true for "unread" and "starred" searches in regular folders
|
||||
return false;
|
||||
|
@ -534,7 +449,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
menu.findItem(R.id.select_all).setVisible(true);
|
||||
menu.findItem(R.id.settings).setVisible(true);
|
||||
|
||||
if (mMessageListFragment.isSearchQuery()) {
|
||||
if (!mSingleAccountMode) {
|
||||
menu.findItem(R.id.expunge).setVisible(false);
|
||||
menu.findItem(R.id.check_mail).setVisible(false);
|
||||
menu.findItem(R.id.send_messages).setVisible(false);
|
||||
|
@ -666,8 +581,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
|
||||
@Override
|
||||
public void showMoreFromSameSender(String senderAddress) {
|
||||
MessageListFragment fragment = MessageListFragment.newInstance("From " + senderAddress,
|
||||
null, null, senderAddress, null, null, false);
|
||||
LocalSearch tmpSearch = new LocalSearch("From " + senderAddress);
|
||||
tmpSearch.addAccountUuids(mSearch.getAccountUuids());
|
||||
tmpSearch.and(SEARCHFIELD.SENDER, senderAddress, ATTRIBUTE.CONTAINS);
|
||||
|
||||
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
|
||||
|
||||
addMessageListFragment(fragment);
|
||||
}
|
||||
|
@ -716,8 +634,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
|
||||
@Override
|
||||
public void remoteSearch(String searchAccount, String searchFolder, String queryString) {
|
||||
MessageListFragment fragment = MessageListFragment.newInstance(searchAccount, searchFolder,
|
||||
queryString, true);
|
||||
MessageListFragment fragment = MessageListFragment.newInstance(mSearch, true);
|
||||
|
||||
addMessageListFragment(fragment);
|
||||
}
|
||||
|
@ -751,9 +668,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||
|
||||
@Override
|
||||
public void showThread(Account account, String folderName, long threadRootId) {
|
||||
MessageListFragment fragment = MessageListFragment.newInstance(account, folderName,
|
||||
threadRootId);
|
||||
|
||||
LocalSearch tmpSearch = new LocalSearch();
|
||||
tmpSearch.addAccountUuids(mSearch.getAccountUuids());
|
||||
tmpSearch.and(SEARCHFIELD.THREAD_ROOT, String.valueOf(threadRootId), ATTRIBUTE.EQUALS);
|
||||
|
||||
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
|
||||
addMessageListFragment(fragment);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ import com.fsck.k9.K9.Intents;
|
|||
import com.fsck.k9.NotificationSetting;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.SearchSpecification;
|
||||
import com.fsck.k9.activity.FolderList;
|
||||
import com.fsck.k9.activity.MessageList;
|
||||
import com.fsck.k9.helper.NotificationBuilder;
|
||||
|
@ -70,6 +69,8 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
|||
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
|
||||
import com.fsck.k9.mail.store.UnavailableAccountException;
|
||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||
import com.fsck.k9.search.LocalSearch;
|
||||
import com.fsck.k9.search.SearchSpecification;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -619,136 +620,39 @@ public class MessagingController implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
public void searchLocalMessages(SearchSpecification searchSpecification, final Message[] messages, final MessagingListener listener) {
|
||||
searchLocalMessages(searchSpecification.getAccountUuids(), searchSpecification.getFolderNames(), messages,
|
||||
searchSpecification.getQuery(), searchSpecification.isIntegrate(), searchSpecification.getRequiredFlags(), searchSpecification.getForbiddenFlags(), listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find all messages in any local account which match the query 'query'
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public void searchLocalMessages(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate,
|
||||
final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
|
||||
if (K9.DEBUG) {
|
||||
Log.i(K9.LOG_TAG, "searchLocalMessages ("
|
||||
+ "accountUuids=" + Utility.combine(accountUuids, ',')
|
||||
+ ", folderNames = " + Utility.combine(folderNames, ',')
|
||||
+ ", messages.size() = " + (messages != null ? messages.length : -1)
|
||||
+ ", query = " + query
|
||||
+ ", integrate = " + integrate
|
||||
+ ", requiredFlags = " + Utility.combine(requiredFlags, ',')
|
||||
+ ", forbiddenFlags = " + Utility.combine(forbiddenFlags, ',')
|
||||
+ ")");
|
||||
}
|
||||
|
||||
public void searchLocalMessages(final LocalSearch search, final MessagingListener listener) {
|
||||
threadPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
searchLocalMessagesSynchronous(accountUuids, folderNames, messages, query, integrate, requiredFlags, forbiddenFlags, listener);
|
||||
searchLocalMessagesSynchronous(search, listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
public void searchLocalMessagesSynchronous(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate, final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
|
||||
|
||||
|
||||
public void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) {
|
||||
final AccountStats stats = new AccountStats();
|
||||
final Set<String> accountUuidsSet = new HashSet<String>();
|
||||
if (accountUuids != null) {
|
||||
accountUuidsSet.addAll(Arrays.asList(accountUuids));
|
||||
}
|
||||
final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext());
|
||||
List<LocalFolder> foldersToSearch = null;
|
||||
boolean displayableOnly = false;
|
||||
boolean noSpecialFolders = true;
|
||||
for (final Account account : prefs.getAvailableAccounts()) {
|
||||
if (accountUuids != null && !accountUuidsSet.contains(account.getUuid())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (accountUuids != null && accountUuidsSet.contains(account.getUuid())) {
|
||||
displayableOnly = true;
|
||||
noSpecialFolders = true;
|
||||
} else if (!integrate && folderNames == null) {
|
||||
Account.Searchable searchableFolders = account.getSearchableFolders();
|
||||
switch (searchableFolders) {
|
||||
case NONE:
|
||||
continue;
|
||||
case DISPLAYABLE:
|
||||
displayableOnly = true;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
List<Message> messagesToSearch = null;
|
||||
if (messages != null) {
|
||||
messagesToSearch = new LinkedList<Message>();
|
||||
for (Message message : messages) {
|
||||
if (message.getFolder().getAccount().getUuid().equals(account.getUuid())) {
|
||||
messagesToSearch.add(message);
|
||||
}
|
||||
}
|
||||
if (messagesToSearch.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.listLocalMessagesStarted(account, null);
|
||||
}
|
||||
|
||||
if (integrate || displayableOnly || folderNames != null || noSpecialFolders) {
|
||||
List<LocalFolder> tmpFoldersToSearch = new LinkedList<LocalFolder>();
|
||||
try {
|
||||
LocalStore store = account.getLocalStore();
|
||||
List <? extends Folder > folders = store.getPersonalNamespaces(false);
|
||||
Set<String> folderNameSet = null;
|
||||
if (folderNames != null) {
|
||||
folderNameSet = new HashSet<String>();
|
||||
folderNameSet.addAll(Arrays.asList(folderNames));
|
||||
}
|
||||
for (Folder folder : folders) {
|
||||
LocalFolder localFolder = (LocalFolder)folder;
|
||||
boolean include = true;
|
||||
folder.refresh(prefs);
|
||||
String localFolderName = localFolder.getName();
|
||||
if (integrate) {
|
||||
include = localFolder.isIntegrate();
|
||||
} else {
|
||||
if (folderNameSet != null) {
|
||||
if (!folderNameSet.contains(localFolderName))
|
||||
|
||||
{
|
||||
include = false;
|
||||
}
|
||||
}
|
||||
// Never exclude the INBOX (see issue 1817)
|
||||
else if (noSpecialFolders && !localFolderName.equalsIgnoreCase(account.getInboxFolderName()) &&
|
||||
!localFolderName.equals(account.getArchiveFolderName()) && account.isSpecialFolder(localFolderName)) {
|
||||
include = false;
|
||||
} else if (displayableOnly && modeMismatch(account.getFolderDisplayMode(), folder.getDisplayClass())) {
|
||||
include = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (include) {
|
||||
tmpFoldersToSearch.add(localFolder);
|
||||
}
|
||||
}
|
||||
if (tmpFoldersToSearch.size() < 1) {
|
||||
continue;
|
||||
}
|
||||
foldersToSearch = tmpFoldersToSearch;
|
||||
} catch (MessagingException me) {
|
||||
Log.e(K9.LOG_TAG, "Unable to restrict search folders in Account " + account.getDescription() + ", searching all", me);
|
||||
addErrorMessage(account, null, me);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final HashSet<String> uuidSet = new HashSet<String>(Arrays.asList(search.getAccountUuids()));
|
||||
Account[] accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts();
|
||||
boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS);
|
||||
|
||||
// for every account we want to search do the query in the localstore
|
||||
for (final Account account : accounts) {
|
||||
|
||||
if (!allAccounts && !uuidSet.contains(account.getUuid())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collecting statistics of the search result
|
||||
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
|
||||
@Override
|
||||
public void messageStarted(String message, int number, int ofTotal) {}
|
||||
@Override
|
||||
public void messagesFinished(int number) {}
|
||||
@Override
|
||||
public void messageFinished(Message message, int number, int ofTotal) {
|
||||
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
|
||||
List<Message> messages = new ArrayList<Message>();
|
||||
|
@ -760,22 +664,18 @@ public class MessagingController implements Runnable {
|
|||
listener.listLocalMessagesAddMessages(account, null, messages);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void messagesFinished(int number) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// alert everyone the search has started
|
||||
if (listener != null) {
|
||||
listener.listLocalMessagesStarted(account, null);
|
||||
}
|
||||
|
||||
// build and do the query in the localstore
|
||||
try {
|
||||
String[] queryFields = {"html_content", "subject", "sender_list"};
|
||||
LocalStore localStore = account.getLocalStore();
|
||||
localStore.searchForMessages(retrievalListener, queryFields
|
||||
, query, foldersToSearch,
|
||||
messagesToSearch == null ? null : messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY),
|
||||
requiredFlags, forbiddenFlags);
|
||||
|
||||
LocalStore localStore = account.getLocalStore();
|
||||
localStore.searchForMessages(retrievalListener, search);
|
||||
} catch (Exception e) {
|
||||
if (listener != null) {
|
||||
listener.listLocalMessagesFailed(account, null, e.getMessage());
|
||||
|
@ -786,7 +686,9 @@ public class MessagingController implements Runnable {
|
|||
listener.listLocalMessagesFinished(account, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// publish the total search statistics
|
||||
if (listener != null) {
|
||||
listener.searchStats(stats);
|
||||
}
|
||||
|
@ -3258,8 +3160,11 @@ public class MessagingController implements Runnable {
|
|||
builder.setContentTitle(mApplication.getString(R.string.notification_bg_send_title));
|
||||
builder.setContentText(account.getDescription());
|
||||
|
||||
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account,
|
||||
account.getInboxFolderName());
|
||||
LocalSearch search = new LocalSearch(account.getInboxFolderName());
|
||||
search.addAllowedFolder(account.getInboxFolderName());
|
||||
search.addAccountUuid(account.getUuid());
|
||||
Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
|
||||
builder.setContentIntent(pi);
|
||||
|
||||
|
@ -3341,8 +3246,11 @@ public class MessagingController implements Runnable {
|
|||
mApplication.getString(R.string.notification_bg_title_separator) +
|
||||
folder.getName());
|
||||
|
||||
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account,
|
||||
account.getInboxFolderName());
|
||||
LocalSearch search = new LocalSearch(account.getInboxFolderName());
|
||||
search.addAllowedFolder(account.getInboxFolderName());
|
||||
search.addAccountUuid(account.getUuid());
|
||||
Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
|
||||
builder.setContentIntent(pi);
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import java.util.Map;
|
|||
import java.util.concurrent.Future;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
@ -82,8 +81,15 @@ import com.fsck.k9.mail.Store;
|
|||
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.mail.store.LocalStore.LocalMessage;
|
||||
import com.fsck.k9.provider.EmailProvider;
|
||||
import com.fsck.k9.provider.EmailProvider.MessageColumns;
|
||||
import com.fsck.k9.search.ConditionsTreeNode;
|
||||
import com.fsck.k9.search.LocalSearch;
|
||||
import com.fsck.k9.search.SearchSpecification;
|
||||
import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
|
||||
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
|
||||
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase;
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshListView;
|
||||
|
||||
|
@ -124,68 +130,15 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
private static final int THREAD_PARENT_COLUMN = 13;
|
||||
|
||||
|
||||
public static MessageListFragment newInstance(Account account, String folderName) {
|
||||
public static MessageListFragment newInstance(LocalSearch search, boolean remoteSearch) {
|
||||
MessageListFragment fragment = new MessageListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_ACCOUNT, account.getUuid());
|
||||
args.putString(ARG_FOLDER, folderName);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static MessageListFragment newInstance(Account account, String folderName,
|
||||
long threadRootId) {
|
||||
MessageListFragment fragment = new MessageListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_ACCOUNT, account.getUuid());
|
||||
args.putString(ARG_FOLDER, folderName);
|
||||
args.putLong(ARG_THREAD_ID, threadRootId);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static MessageListFragment newInstance(String title, String[] accountUuids,
|
||||
String[] folderNames, String queryString, Flag[] flags,
|
||||
Flag[] forbiddenFlags, boolean integrate) {
|
||||
|
||||
MessageListFragment fragment = new MessageListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putStringArray(ARG_ACCOUNT_UUIDS, accountUuids);
|
||||
args.putStringArray(ARG_FOLDER_NAMES, folderNames);
|
||||
args.putString(ARG_QUERY, queryString);
|
||||
if (flags != null) {
|
||||
args.putString(ARG_QUERY_FLAGS, Utility.combine(flags, ','));
|
||||
}
|
||||
if (forbiddenFlags != null) {
|
||||
args.putString(ARG_FORBIDDEN_FLAGS, Utility.combine(forbiddenFlags, ','));
|
||||
}
|
||||
args.putBoolean(ARG_INTEGRATE, integrate);
|
||||
args.putString(ARG_TITLE, title);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static MessageListFragment newInstance(String searchAccount, String searchFolder,
|
||||
String queryString, boolean remoteSearch) {
|
||||
MessageListFragment fragment = new MessageListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_SEARCH_ACCOUNT, searchAccount);
|
||||
args.putString(ARG_SEARCH_FOLDER, searchFolder);
|
||||
args.putString(ARG_QUERY, queryString);
|
||||
args.putParcelable(ARG_SEARCH, search);
|
||||
args.putBoolean(ARG_REMOTE_SEARCH, remoteSearch);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverses the result of a {@link Comparator}.
|
||||
*
|
||||
|
@ -327,20 +280,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
|
||||
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
|
||||
|
||||
private static final String ARG_ACCOUNT = "account";
|
||||
private static final String ARG_FOLDER = "folder";
|
||||
private static final String ARG_REMOTE_SEARCH = "remote_search";
|
||||
private static final String ARG_QUERY = "query";
|
||||
private static final String ARG_SEARCH_ACCOUNT = "search_account";
|
||||
private static final String ARG_SEARCH_FOLDER = "search_folder";
|
||||
private static final String ARG_QUERY_FLAGS = "queryFlags";
|
||||
private static final String ARG_FORBIDDEN_FLAGS = "forbiddenFlags";
|
||||
private static final String ARG_INTEGRATE = "integrate";
|
||||
private static final String ARG_ACCOUNT_UUIDS = "accountUuids";
|
||||
private static final String ARG_FOLDER_NAMES = "folderNames";
|
||||
private static final String ARG_TITLE = "title";
|
||||
private static final String ARG_THREAD_ID = "thread_id";
|
||||
|
||||
private static final String ARG_SEARCH = "searchObject";
|
||||
private static final String ARG_REMOTE_SEARCH = "remoteSearch";
|
||||
private static final String STATE_LIST_POSITION = "listPosition";
|
||||
|
||||
/**
|
||||
|
@ -391,17 +332,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
/**
|
||||
* If we're doing a search, this contains the query string.
|
||||
*/
|
||||
private String mQueryString;
|
||||
private Flag[] mQueryFlags = null;
|
||||
private Flag[] mForbiddenFlags = null;
|
||||
private boolean mRemoteSearch = false;
|
||||
private String mSearchAccount = null;
|
||||
private String mSearchFolder = null;
|
||||
private Future mRemoteSearchFuture = null;
|
||||
private boolean mIntegrate = false;
|
||||
private String[] mAccountUuids = null;
|
||||
private String[] mFolderNames = null;
|
||||
|
||||
private String mTitle;
|
||||
private LocalSearch mSearch = null;
|
||||
private boolean mSingleAccountMode;
|
||||
private boolean mSingleFolderMode;
|
||||
|
||||
private MessageListHandler mHandler = new MessageListHandler();
|
||||
|
||||
|
@ -591,7 +528,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
|
||||
private void setWindowTitle() {
|
||||
// regular folder content display
|
||||
if (mFolderName != null) {
|
||||
if (mSingleFolderMode) {
|
||||
Activity activity = getActivity();
|
||||
String displayName = FolderInfoHolder.getDisplayName(activity, mAccount,
|
||||
mFolderName);
|
||||
|
@ -604,7 +541,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
} else {
|
||||
mFragmentListener.setMessageListSubTitle(operation);
|
||||
}
|
||||
} else if (mQueryString != null) {
|
||||
} else {
|
||||
// query result display. This may be for a search folder as opposed to a user-initiated search.
|
||||
if (mTitle != null) {
|
||||
// This was a search folder; the search folder has overridden our title.
|
||||
|
@ -621,8 +558,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
if (mUnreadMessageCount == 0) {
|
||||
mFragmentListener.setUnreadCount(0);
|
||||
} else {
|
||||
if (mQueryString != null && mTitle == null) {
|
||||
// This is a search result. The unread message count is easily confused
|
||||
if (!mSingleFolderMode && mTitle == null) {
|
||||
// The unread message count is easily confused
|
||||
// with total number of messages in the search result, so let's hide it.
|
||||
mFragmentListener.setUnreadCount(0);
|
||||
} else {
|
||||
|
@ -668,7 +605,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
updateFooter("", false);
|
||||
return;
|
||||
}
|
||||
int limit = account.getRemoteSearchNumResults();
|
||||
int limit = mAccount.getRemoteSearchNumResults();
|
||||
List<Message> toProcess = mAdapter.mExtraSearchResults;
|
||||
if (limit > 0 && numResults > limit) {
|
||||
toProcess = toProcess.subList(0, limit);
|
||||
|
@ -677,7 +614,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
mAdapter.mExtraSearchResults = null;
|
||||
updateFooter("", false);
|
||||
}
|
||||
mController.loadSearchResults(account, mSearchFolder, toProcess, mAdapter.mListener);
|
||||
mController.loadSearchResults(mAccount, mCurrentFolder.name, toProcess, mListener);
|
||||
}*/
|
||||
return;
|
||||
}
|
||||
|
@ -755,40 +692,26 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
private void decodeArguments() {
|
||||
Bundle args = getArguments();
|
||||
|
||||
mQueryString = args.getString(SearchManager.QUERY);
|
||||
mFolderName = args.getString(ARG_FOLDER);
|
||||
mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false);
|
||||
mSearchAccount = args.getString(ARG_SEARCH_ACCOUNT);
|
||||
mSearchFolder = args.getString(ARG_SEARCH_FOLDER);
|
||||
mThreadId = args.getLong(ARG_THREAD_ID, -1);
|
||||
|
||||
String accountUuid = args.getString(ARG_ACCOUNT);
|
||||
mSearch = args.getParcelable(ARG_SEARCH);
|
||||
mTitle = args.getString(mSearch.getName());
|
||||
|
||||
Context appContext = getActivity().getApplicationContext();
|
||||
mAccount = Preferences.getPreferences(appContext).getAccount(accountUuid);
|
||||
String[] accounts = mSearch.getAccountUuids();
|
||||
|
||||
String queryFlags = args.getString(ARG_QUERY_FLAGS);
|
||||
if (queryFlags != null) {
|
||||
String[] flagStrings = queryFlags.split(",");
|
||||
mQueryFlags = new Flag[flagStrings.length];
|
||||
for (int i = 0; i < flagStrings.length; i++) {
|
||||
mQueryFlags[i] = Flag.valueOf(flagStrings[i]);
|
||||
}
|
||||
mSingleAccountMode = false;
|
||||
if (accounts != null && accounts.length == 1
|
||||
&& !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS)) {
|
||||
mSingleAccountMode = true;
|
||||
mAccount = Preferences.getPreferences(appContext).getAccount(accounts[0]);
|
||||
}
|
||||
|
||||
String forbiddenFlags = args.getString(ARG_FORBIDDEN_FLAGS);
|
||||
if (forbiddenFlags != null) {
|
||||
String[] flagStrings = forbiddenFlags.split(",");
|
||||
mForbiddenFlags = new Flag[flagStrings.length];
|
||||
for (int i = 0; i < flagStrings.length; i++) {
|
||||
mForbiddenFlags[i] = Flag.valueOf(flagStrings[i]);
|
||||
}
|
||||
mSingleFolderMode = false;
|
||||
if (mSingleAccountMode && (mSearch.getFolderNames().size() == 1)) {
|
||||
mSingleFolderMode = true;
|
||||
mFolderName = mSearch.getFolderNames().get(0);
|
||||
mCurrentFolder = getFolder(mFolderName, mAccount);
|
||||
}
|
||||
|
||||
mIntegrate = args.getBoolean(ARG_INTEGRATE, false);
|
||||
mAccountUuids = args.getStringArray(ARG_ACCOUNT_UUIDS);
|
||||
mFolderNames = args.getStringArray(ARG_FOLDER_NAMES);
|
||||
mTitle = args.getString(ARG_TITLE);
|
||||
}
|
||||
|
||||
private void initializeMessageList() {
|
||||
|
@ -799,7 +722,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
|
||||
// Hide "Load up to x more" footer for search views
|
||||
mFooterView.setVisibility((mQueryString != null) ? View.GONE : View.VISIBLE);
|
||||
mFooterView.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
|
||||
|
||||
mController = MessagingController.getInstance(getActivity().getApplication());
|
||||
mListView.setAdapter(mAdapter);
|
||||
|
@ -868,14 +791,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
|
||||
final Preferences prefs = Preferences.getPreferences(appContext);
|
||||
|
||||
boolean allowRemoteSearch = false;
|
||||
if (mSearchAccount != null) {
|
||||
final Account searchAccount = prefs.getAccount(mSearchAccount);
|
||||
if (searchAccount != null) {
|
||||
allowRemoteSearch = searchAccount.allowRemoteSearch();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have connectivity. Cache the value.
|
||||
if (mHasConnectivity == null) {
|
||||
final ConnectivityManager connectivityManager =
|
||||
|
@ -889,24 +804,26 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
}
|
||||
|
||||
if (mQueryString == null) {
|
||||
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
||||
checkMail();
|
||||
}
|
||||
});
|
||||
} else if (allowRemoteSearch && !mRemoteSearch && !mIntegrate && mHasConnectivity) {
|
||||
// mQueryString != null is implied if we get this far.
|
||||
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
||||
mPullToRefreshView.onRefreshComplete();
|
||||
onRemoteSearchRequested(true);
|
||||
}
|
||||
});
|
||||
mPullToRefreshView.setPullLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_pull));
|
||||
mPullToRefreshView.setReleaseLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_release));
|
||||
if (mSingleFolderMode) {
|
||||
if (!mAccount.allowRemoteSearch()) {
|
||||
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
||||
checkMail();
|
||||
}
|
||||
});
|
||||
// TODO this has to go! find better remote search integration
|
||||
} else {
|
||||
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
||||
mPullToRefreshView.onRefreshComplete();
|
||||
onRemoteSearchRequested();
|
||||
}
|
||||
});
|
||||
mPullToRefreshView.setPullLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_pull));
|
||||
mPullToRefreshView.setReleaseLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_release));
|
||||
}
|
||||
} else {
|
||||
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
|
||||
}
|
||||
|
@ -916,7 +833,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
//Cancel pending new mail notifications when we open an account
|
||||
Account[] accountsWithNotification;
|
||||
|
||||
Account account = getCurrentAccount(prefs);
|
||||
Account account = mAccount;
|
||||
|
||||
if (account != null) {
|
||||
accountsWithNotification = new Account[] { account };
|
||||
|
@ -934,56 +851,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
mController.notifyAccountCancel(appContext, accountWithNotification);
|
||||
}
|
||||
|
||||
/*
|
||||
if (mAdapter.isEmpty()) {
|
||||
if (mRemoteSearch) {
|
||||
//TODO: Support flag based search
|
||||
mRemoteSearchFuture = mController.searchRemoteMessages(mSearchAccount, mSearchFolder, mQueryString, null, null, mAdapter.mListener);
|
||||
} else if (mFolderName != null) {
|
||||
mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener, mThreadViewEnabled, mThreadId);
|
||||
|
||||
// Hide the archive button if we don't have an archive folder.
|
||||
if (!mAccount.hasArchiveFolder()) {
|
||||
// mBatchArchiveButton.setVisibility(View.GONE);
|
||||
}
|
||||
} else if (mQueryString != null) {
|
||||
mController.searchLocalMessages(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
|
||||
// Don't show the archive button if this is a search.
|
||||
// mBatchArchiveButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
} else {
|
||||
// reread the selected date format preference in case it has changed
|
||||
mMessageHelper.refresh();
|
||||
|
||||
mAdapter.markAllMessagesAsDirty();
|
||||
|
||||
if (!mRemoteSearch) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mFolderName != null) {
|
||||
mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener, mThreadViewEnabled, mThreadId);
|
||||
} else if (mQueryString != null) {
|
||||
mController.searchLocalMessagesSynchronous(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
|
||||
}
|
||||
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mAdapter.pruneDirtyMessages();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
restoreListState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
.start();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (mAccount != null && mFolderName != null && !mRemoteSearch) {
|
||||
mController.getFolderUnreadMessageCount(mAccount, mFolderName, mListener);
|
||||
}
|
||||
|
@ -1009,7 +876,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
|
||||
public void onCompose() {
|
||||
if (mQueryString != null) {
|
||||
if (!mSingleAccountMode) {
|
||||
/*
|
||||
* If we have a query string, we don't have an account to let
|
||||
* compose start the default action.
|
||||
|
@ -1043,22 +910,15 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
|
||||
/**
|
||||
* User has requested a remote search. Setup the bundle and start the intent.
|
||||
* @param fromLocalSearch true if this is being called from a local search result screen. This affects
|
||||
* where we pull the account and folder info used for the next search.
|
||||
*/
|
||||
public void onRemoteSearchRequested(final boolean fromLocalSearch) {
|
||||
public void onRemoteSearchRequested() {
|
||||
String searchAccount;
|
||||
String searchFolder;
|
||||
|
||||
if (fromLocalSearch) {
|
||||
searchAccount = mSearchAccount;
|
||||
searchFolder = mSearchFolder;
|
||||
} else {
|
||||
searchAccount = mAccount.getUuid();
|
||||
searchFolder = mCurrentFolder.name;
|
||||
}
|
||||
searchAccount = mAccount.getUuid();
|
||||
searchFolder = mCurrentFolder.name;
|
||||
|
||||
mFragmentListener.remoteSearch(searchAccount, searchFolder, mQueryString);
|
||||
mFragmentListener.remoteSearch(searchAccount, searchFolder, mSearch.getRemoteSearchArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1075,7 +935,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
mSortType = sortType;
|
||||
|
||||
Preferences prefs = Preferences.getPreferences(getActivity().getApplicationContext());
|
||||
Account account = getCurrentAccount(prefs);
|
||||
Account account = mAccount;
|
||||
|
||||
if (account != null) {
|
||||
account.setSortType(mSortType);
|
||||
|
@ -1262,7 +1122,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
}
|
||||
|
||||
if (mQueryString != null) {
|
||||
if (!mSingleAccountMode) {
|
||||
// None of the options after this point are "safe" for search results
|
||||
//TODO: This is not true for "unread" and "starred" searches in regular folders
|
||||
return false;
|
||||
|
@ -1558,38 +1418,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
super.synchronizeMailboxFailed(account, folder, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listLocalMessagesStarted(Account account, String folder) {
|
||||
if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
|
||||
mHandler.progress(true);
|
||||
if (folder != null) {
|
||||
mHandler.folderLoading(folder, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listLocalMessagesFailed(Account account, String folder, String message) {
|
||||
if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
|
||||
mHandler.sortMessages();
|
||||
mHandler.progress(false);
|
||||
if (folder != null) {
|
||||
mHandler.folderLoading(folder, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listLocalMessagesFinished(Account account, String folder) {
|
||||
if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
|
||||
mHandler.sortMessages();
|
||||
mHandler.progress(false);
|
||||
if (folder != null) {
|
||||
mHandler.folderLoading(folder, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void searchStats(AccountStats stats) {
|
||||
mUnreadMessageCount = stats.unreadMessageCount;
|
||||
|
@ -1605,6 +1433,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
|
||||
private boolean updateForMe(Account account, String folder) {
|
||||
//FIXME
|
||||
return ((account.equals(mAccount) && folder.equals(mFolderName)));
|
||||
}
|
||||
}
|
||||
|
@ -2277,7 +2106,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
*
|
||||
* @return The {@code Account} all displayed messages belong to.
|
||||
*/
|
||||
private Account getCurrentAccount(Preferences prefs) {
|
||||
//TODO: remove
|
||||
/*private Account getCurrentAccount(Preferences prefs) {
|
||||
Account account = null;
|
||||
if (mQueryString != null && !mIntegrate && mAccountUuids != null &&
|
||||
mAccountUuids.length == 1) {
|
||||
|
@ -2288,7 +2118,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
class ActionModeCallback implements ActionMode.Callback {
|
||||
|
@ -2306,7 +2136,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
mFlag = menu.findItem(R.id.flag);
|
||||
mUnflag = menu.findItem(R.id.unflag);
|
||||
|
||||
if (mQueryString != null) {
|
||||
// we don't support cross account actions atm
|
||||
if (!mSingleAccountMode) {
|
||||
// show all
|
||||
menu.findItem(R.id.move).setVisible(true);
|
||||
menu.findItem(R.id.archive).setVisible(true);
|
||||
|
@ -2347,9 +2178,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
inflater.inflate(R.menu.message_list_context, menu);
|
||||
|
||||
// check capabilities
|
||||
if (mQueryString == null) {
|
||||
setContextCapabilities(mAccount, menu);
|
||||
}
|
||||
setContextCapabilities(mAccount, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2367,33 +2196,32 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
* TODO get rid of this when we finally split the messagelist into
|
||||
* a folder content display and a search result display
|
||||
*/
|
||||
if (mQueryString != null) {
|
||||
if (!mSingleAccountMode) {
|
||||
menu.findItem(R.id.move).setVisible(false);
|
||||
menu.findItem(R.id.copy).setVisible(false);
|
||||
|
||||
menu.findItem(R.id.archive).setVisible(false);
|
||||
menu.findItem(R.id.spam).setVisible(false);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// hide unsupported
|
||||
if (!mController.isCopyCapable(mAccount)) {
|
||||
menu.findItem(R.id.copy).setVisible(false);
|
||||
}
|
||||
|
||||
// hide unsupported
|
||||
if (!mController.isCopyCapable(mAccount)) {
|
||||
menu.findItem(R.id.copy).setVisible(false);
|
||||
}
|
||||
if (!mController.isMoveCapable(mAccount)) {
|
||||
menu.findItem(R.id.move).setVisible(false);
|
||||
menu.findItem(R.id.archive).setVisible(false);
|
||||
menu.findItem(R.id.spam).setVisible(false);
|
||||
}
|
||||
|
||||
if (!mController.isMoveCapable(mAccount)) {
|
||||
menu.findItem(R.id.move).setVisible(false);
|
||||
menu.findItem(R.id.archive).setVisible(false);
|
||||
menu.findItem(R.id.spam).setVisible(false);
|
||||
}
|
||||
if (!mAccount.hasArchiveFolder()) {
|
||||
menu.findItem(R.id.archive).setVisible(false);
|
||||
}
|
||||
|
||||
if (!mAccount.hasArchiveFolder()) {
|
||||
menu.findItem(R.id.archive).setVisible(false);
|
||||
}
|
||||
|
||||
if (!mAccount.hasSpamFolder()) {
|
||||
menu.findItem(R.id.spam).setVisible(false);
|
||||
if (!mAccount.hasSpamFolder()) {
|
||||
menu.findItem(R.id.spam).setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2527,7 +2355,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
@Override
|
||||
public void onStop() {
|
||||
// If we represent a remote search, then kill that before going back.
|
||||
if (mSearchAccount != null && mSearchFolder != null && mRemoteSearchFuture != null) {
|
||||
if (isRemoteSearch() && mRemoteSearchFuture != null) {
|
||||
try {
|
||||
Log.i(K9.LOG_TAG, "Remote search in progress, attempting to abort...");
|
||||
// Canceling the future stops any message fetches in progress.
|
||||
|
@ -2536,13 +2364,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
Log.e(K9.LOG_TAG, "Could not cancel remote search future.");
|
||||
}
|
||||
// Closing the folder will kill off the connection if we're mid-search.
|
||||
Context appContext = getActivity().getApplicationContext();
|
||||
final Account searchAccount = Preferences.getPreferences(appContext).getAccount(mSearchAccount);
|
||||
final Store remoteStore = searchAccount.getRemoteStore();
|
||||
final Folder remoteFolder = remoteStore.getFolder(mSearchFolder);
|
||||
final Account searchAccount = mAccount;
|
||||
final Folder remoteFolder = mCurrentFolder.folder;
|
||||
remoteFolder.close();
|
||||
// Send a remoteSearchFinished() message for good measure.
|
||||
//mAdapter.mListener.remoteSearchFinished(searchAccount, mSearchFolder, 0, null);
|
||||
//mAdapter.mListener.remoteSearchFinished(searchAccount, mCurrentFolder.name, 0, null);
|
||||
} catch (Exception e) {
|
||||
// Since the user is going back, log and squash any exceptions.
|
||||
Log.e(K9.LOG_TAG, "Could not abort remote search before going back", e);
|
||||
|
@ -2664,7 +2490,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
public void onToggleFlag() {
|
||||
Message message = getSelectedMessage();
|
||||
if (message != null) {
|
||||
System.out.println("FLAGGED: " + message.isSet(Flag.FLAGGED));
|
||||
setFlag(message, Flag.FLAGGED, !message.isSet(Flag.FLAGGED));
|
||||
}
|
||||
}
|
||||
|
@ -2698,7 +2523,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
|
||||
public boolean isSearchQuery() {
|
||||
return (mQueryString != null || mIntegrate);
|
||||
return (mSearch.getRemoteSearchArguments() != null || !mSingleAccountMode);
|
||||
}
|
||||
|
||||
public boolean isOutbox() {
|
||||
|
@ -2733,7 +2558,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
public void onRemoteSearch() {
|
||||
// Remote search is useless without the network.
|
||||
if (mHasConnectivity) {
|
||||
onRemoteSearchRequested(true);
|
||||
onRemoteSearchRequested();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), getText(R.string.remote_search_unavailable_no_network),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
@ -2745,7 +2570,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
}
|
||||
|
||||
public boolean isRemoteSearchAllowed() {
|
||||
if (!isSearchQuery() || mRemoteSearch || mSearchFolder == null || mSearchAccount == null) {
|
||||
if (!isSearchQuery() || mRemoteSearch || !mSingleFolderMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2753,7 +2578,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||
final Preferences prefs = Preferences.getPreferences(appContext);
|
||||
|
||||
boolean allowRemoteSearch = false;
|
||||
final Account searchAccount = prefs.getAccount(mSearchAccount);
|
||||
final Account searchAccount = mAccount;
|
||||
if (searchAccount != null) {
|
||||
allowRemoteSearch = searchAccount.allowRemoteSearch();
|
||||
}
|
||||
|
|
|
@ -69,6 +69,9 @@ import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
|
|||
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
|
||||
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
||||
import com.fsck.k9.provider.AttachmentProvider;
|
||||
import com.fsck.k9.search.ConditionsTreeNode;
|
||||
import com.fsck.k9.search.LocalSearch;
|
||||
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
|
@ -658,6 +661,22 @@ public class LocalStore extends Store implements Serializable {
|
|||
return new LocalFolder(name);
|
||||
}
|
||||
|
||||
private long getFolderId(final String name) throws MessagingException {
|
||||
return database.execute(false, new DbCallback<Long>() {
|
||||
@Override
|
||||
public Long doDbWork(final SQLiteDatabase db) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = db.rawQuery("SELECT id FROM folders WHERE name = '" + name + "'", null);
|
||||
cursor.moveToFirst();
|
||||
return cursor.getLong(0);
|
||||
} finally {
|
||||
Utility.closeQuietly(cursor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO this takes about 260-300ms, seems slow.
|
||||
@Override
|
||||
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
|
||||
|
@ -890,97 +909,57 @@ public class LocalStore extends Store implements Serializable {
|
|||
return true;
|
||||
}
|
||||
|
||||
public Message[] searchForMessages(MessageRetrievalListener listener, String[] queryFields, String queryString,
|
||||
List<LocalFolder> folders, Message[] messages, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException {
|
||||
List<String> args = new LinkedList<String>();
|
||||
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
if (queryString != null && queryString.length() > 0) {
|
||||
boolean anyAdded = false;
|
||||
String likeString = "%" + queryString + "%";
|
||||
whereClause.append(" AND (");
|
||||
for (String queryField : queryFields) {
|
||||
|
||||
if (anyAdded) {
|
||||
whereClause.append(" OR ");
|
||||
// TODO find beter solution
|
||||
private static boolean isFolderId(String str) {
|
||||
if (str == null) {
|
||||
return false;
|
||||
}
|
||||
int length = str.length();
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
int i = 0;
|
||||
if (str.charAt(0) == '-') {
|
||||
return false;
|
||||
}
|
||||
for (; i < length; i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c <= '/' || c >= ':') {
|
||||
return false;
|
||||
}
|
||||
whereClause.append(queryField).append(" LIKE ? ");
|
||||
args.add(likeString);
|
||||
anyAdded = true;
|
||||
}
|
||||
|
||||
|
||||
whereClause.append(" )");
|
||||
}
|
||||
if (folders != null && !folders.isEmpty()) {
|
||||
whereClause.append(" AND folder_id in (");
|
||||
boolean anyAdded = false;
|
||||
for (LocalFolder folder : folders) {
|
||||
if (anyAdded) {
|
||||
whereClause.append(",");
|
||||
}
|
||||
anyAdded = true;
|
||||
whereClause.append("?");
|
||||
args.add(Long.toString(folder.getId()));
|
||||
}
|
||||
whereClause.append(" )");
|
||||
}
|
||||
if (messages != null && messages.length > 0) {
|
||||
whereClause.append(" AND ( ");
|
||||
boolean anyAdded = false;
|
||||
for (Message message : messages) {
|
||||
if (anyAdded) {
|
||||
whereClause.append(" OR ");
|
||||
}
|
||||
anyAdded = true;
|
||||
whereClause.append(" ( uid = ? AND folder_id = ? ) ");
|
||||
args.add(message.getUid());
|
||||
args.add(Long.toString(((LocalFolder)message.getFolder()).getId()));
|
||||
}
|
||||
whereClause.append(" )");
|
||||
}
|
||||
if (forbiddenFlags != null && forbiddenFlags.length > 0) {
|
||||
whereClause.append(" AND (");
|
||||
boolean anyAdded = false;
|
||||
for (Flag flag : forbiddenFlags) {
|
||||
if (anyAdded) {
|
||||
whereClause.append(" AND ");
|
||||
}
|
||||
anyAdded = true;
|
||||
whereClause.append(" flags NOT LIKE ?");
|
||||
|
||||
args.add("%" + flag.toString() + "%");
|
||||
}
|
||||
whereClause.append(" )");
|
||||
}
|
||||
if (requiredFlags != null && requiredFlags.length > 0) {
|
||||
whereClause.append(" AND (");
|
||||
boolean anyAdded = false;
|
||||
for (Flag flag : requiredFlags) {
|
||||
if (anyAdded) {
|
||||
whereClause.append(" OR ");
|
||||
}
|
||||
anyAdded = true;
|
||||
whereClause.append(" flags LIKE ?");
|
||||
|
||||
args.add("%" + flag.toString() + "%");
|
||||
}
|
||||
whereClause.append(" )");
|
||||
}
|
||||
|
||||
if (K9.DEBUG) {
|
||||
Log.v(K9.LOG_TAG, "whereClause = " + whereClause.toString());
|
||||
Log.v(K9.LOG_TAG, "args = " + args);
|
||||
}
|
||||
return getMessages(
|
||||
listener,
|
||||
null,
|
||||
"SELECT "
|
||||
+ GET_MESSAGES_COLS
|
||||
+ "FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 " + whereClause.toString() + " ORDER BY date DESC"
|
||||
, args.toArray(EMPTY_STRING_ARRAY)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Message[] searchForMessages(MessageRetrievalListener retrievalListener,
|
||||
LocalSearch search) throws MessagingException {
|
||||
|
||||
// update some references in the search that have to be bound to this one store
|
||||
for (ConditionsTreeNode node : search.getLeafSet()) {
|
||||
if (node.mCondition.field == SEARCHFIELD.FOLDER) {
|
||||
// TODO find better solution
|
||||
if (isFolderId(node.mCondition.value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.mCondition.value.equals(LocalSearch.GENERIC_INBOX_NAME)) {
|
||||
node.mCondition.value = mAccount.getInboxFolderName();
|
||||
}
|
||||
node.mCondition.value = String.valueOf(getFolderId(node.mCondition.value));
|
||||
}
|
||||
}
|
||||
|
||||
// build sql query
|
||||
String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE deleted = 0 "
|
||||
+ (search.getConditions() != null ? "AND (" + search.getConditions() + ")" : "") + " ORDER BY date DESC";
|
||||
|
||||
if (K9.DEBUG) {
|
||||
Log.d(K9.LOG_TAG, "Query = " + sqlQuery);
|
||||
}
|
||||
|
||||
return getMessages(retrievalListener, null, sqlQuery, new String[] {});
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a query string, actually do the query for the messages and
|
||||
* call the MessageRetrievalListener for each one
|
||||
|
|
|
@ -64,10 +64,10 @@ public class GlobalSettings {
|
|||
new V(1, new DateFormatSetting(DateFormatter.DEFAULT_FORMAT))
|
||||
));
|
||||
s.put("enableDebugLogging", Settings.versions(
|
||||
new V(1, new BooleanSetting(false))
|
||||
new V(1, new BooleanSetting(true))
|
||||
));
|
||||
s.put("enableSensitiveLogging", Settings.versions(
|
||||
new V(1, new BooleanSetting(false))
|
||||
new V(1, new BooleanSetting(true))
|
||||
));
|
||||
s.put("fontSizeAccountDescription", Settings.versions(
|
||||
new V(1, new FontSizeSetting(FontSizes.SMALL))
|
||||
|
|
|
@ -21,7 +21,6 @@ import com.fsck.k9.Account;
|
|||
import com.fsck.k9.AccountStats;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.SearchAccount;
|
||||
import com.fsck.k9.activity.FolderInfoHolder;
|
||||
import com.fsck.k9.activity.MessageInfoHolder;
|
||||
import com.fsck.k9.activity.MessageList;
|
||||
|
@ -34,6 +33,7 @@ import com.fsck.k9.mail.Folder;
|
|||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.search.SearchAccount;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
@ -302,7 +302,7 @@ public class MessageProvider extends ContentProvider {
|
|||
final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext());
|
||||
final MessagingController msgController = MessagingController.getInstance(K9.app);
|
||||
|
||||
msgController.searchLocalMessages(integratedInboxAccount, null,
|
||||
msgController.searchLocalMessages(integratedInboxAccount.getRelatedSearch(),
|
||||
new MesssageInfoHolderRetrieverListener(queue));
|
||||
|
||||
final List<MessageInfoHolder> holders = queue.take();
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.fsck.k9.R;
|
|||
import com.fsck.k9.activity.UnreadWidgetConfiguration;
|
||||
import com.fsck.k9.activity.FolderList;
|
||||
import com.fsck.k9.activity.MessageList;
|
||||
import com.fsck.k9.search.LocalSearch;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
|
@ -62,8 +63,10 @@ public class UnreadWidgetProvider extends AppWidgetProvider {
|
|||
clickIntent = FolderList.actionHandleAccountIntent(context, account, null,
|
||||
false);
|
||||
} else {
|
||||
clickIntent = MessageList.actionHandleFolderIntent(context, account,
|
||||
account.getAutoExpandFolderName());
|
||||
LocalSearch search = new LocalSearch(account.getAutoExpandFolderName());
|
||||
search.addAllowedFolder(account.getAutoExpandFolderName());
|
||||
search.addAccountUuid(account.getUuid());
|
||||
clickIntent = MessageList.intentDisplaySearch(context, search, true, true);
|
||||
}
|
||||
clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
}
|
||||
|
|
401
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
401
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
|
@ -0,0 +1,401 @@
|
|||
package com.fsck.k9.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
|
||||
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
|
||||
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||
|
||||
/**
|
||||
* This class stores search conditions. It's basically a boolean expression binary tree.
|
||||
* The output will be SQL queries ( obtained by traversing inorder ).
|
||||
*
|
||||
* TODO removing conditions from the tree
|
||||
* TODO implement NOT as a node again
|
||||
*
|
||||
* @author dzan
|
||||
*/
|
||||
public class ConditionsTreeNode implements Parcelable{
|
||||
|
||||
public enum OPERATOR {
|
||||
AND, OR, CONDITION;
|
||||
}
|
||||
|
||||
public ConditionsTreeNode mLeft;
|
||||
public ConditionsTreeNode mRight;
|
||||
public ConditionsTreeNode mParent;
|
||||
|
||||
/*
|
||||
* If mValue isn't CONDITION then mCondition contains a real
|
||||
* condition, otherwise it's null.
|
||||
*/
|
||||
public OPERATOR mValue;
|
||||
public SearchCondition mCondition;
|
||||
|
||||
/*
|
||||
* Used for storing and retrieving the tree to/from the database.
|
||||
* The algorithm is called "modified preorder tree traversal".
|
||||
*/
|
||||
public int mLeftMPTTMarker;
|
||||
public int mRightMPTTMarker;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Static Helpers to restore a tree from a database cursor
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Builds a condition tree starting from a database cursor. The cursor
|
||||
* should point to rows representing the nodes of the tree.
|
||||
*
|
||||
* @param cursor Cursor pointing to the first of a bunch or rows. Each rows
|
||||
* should contains 1 tree node.
|
||||
* @return A condition tree.
|
||||
*/
|
||||
public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
|
||||
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||
ConditionsTreeNode tmp = null;
|
||||
|
||||
// root node
|
||||
if (cursor.moveToFirst()) {
|
||||
tmp = buildNodeFromRow(cursor);
|
||||
stack.push(tmp);
|
||||
}
|
||||
|
||||
// other nodes
|
||||
while (cursor.moveToNext()) {
|
||||
tmp = buildNodeFromRow(cursor);
|
||||
if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
|
||||
stack.peek().mLeft = tmp;
|
||||
stack.push(tmp);
|
||||
} else {
|
||||
while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
|
||||
stack.pop();
|
||||
}
|
||||
stack.peek().mRight = tmp;
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single database row to a single condition node.
|
||||
*
|
||||
* @param cursor Cursor pointing to the row we want to convert.
|
||||
* @return A single ConditionsTreeNode
|
||||
*/
|
||||
private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
|
||||
ConditionsTreeNode result = null;
|
||||
SearchCondition condition = null;
|
||||
|
||||
OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
|
||||
|
||||
if (tmpValue == OPERATOR.CONDITION) {
|
||||
condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
|
||||
ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
|
||||
}
|
||||
|
||||
result = new ConditionsTreeNode(condition);
|
||||
result.mValue = tmpValue;
|
||||
result.mLeftMPTTMarker = cursor.getInt(3);
|
||||
result.mRightMPTTMarker = cursor.getInt(4);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Constructors
|
||||
///////////////////////////////////////////////////////////////
|
||||
public ConditionsTreeNode(SearchCondition condition) {
|
||||
mParent = null;
|
||||
mCondition = condition;
|
||||
mValue = OPERATOR.CONDITION;
|
||||
}
|
||||
|
||||
public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
|
||||
mParent = parent;
|
||||
mValue = op;
|
||||
mCondition = null;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public modifiers
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Adds the expression as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param expr Expression to 'AND' with.
|
||||
* @return New top AND node.
|
||||
* @throws Exception
|
||||
*/
|
||||
public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
|
||||
return add(expr, OPERATOR.AND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method.
|
||||
* Adds the provided condition as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param condition Condition to 'AND' with.
|
||||
* @return New top AND node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode and(SearchCondition condition) {
|
||||
try {
|
||||
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||
return and(tmp);
|
||||
} catch (Exception e) {
|
||||
// impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the expression as the second argument of an OR
|
||||
* clause to this node.
|
||||
*
|
||||
* @param expr Expression to 'OR' with.
|
||||
* @return New top OR node.
|
||||
* @throws Exception
|
||||
*/
|
||||
public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
|
||||
return add(expr, OPERATOR.OR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method.
|
||||
* Adds the provided condition as the second argument of an OR
|
||||
* clause to this node.
|
||||
*
|
||||
* @param condition Condition to 'OR' with.
|
||||
* @return New top OR node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode or(SearchCondition condition) {
|
||||
try {
|
||||
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||
return or(tmp);
|
||||
} catch (Exception e) {
|
||||
// impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies the MPTT labeling to the subtree of which this node
|
||||
* is the root node.
|
||||
*
|
||||
* For a description on MPTT see:
|
||||
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||
*/
|
||||
public void applyMPTTLabel() {
|
||||
applyMPTTLabel(1);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public accessors
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Returns the condition stored in this node.
|
||||
* @return Condition stored in the node.
|
||||
*/
|
||||
public SearchCondition getCondition() {
|
||||
return mCondition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will traverse the tree inorder and call toString recursively resulting
|
||||
* in a valid SQL where clause.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return (mLeft == null ? "" : "(" + mLeft + ")")
|
||||
+ " " + ( mCondition == null ? mValue.name() : mCondition ) + " "
|
||||
+ (mRight == null ? "" : "(" + mRight + ")") ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of all the leaves in the tree.
|
||||
* @return Set of all the leaves.
|
||||
*/
|
||||
public HashSet<ConditionsTreeNode> getLeafSet() {
|
||||
HashSet<ConditionsTreeNode> leafSet = new HashSet<ConditionsTreeNode>();
|
||||
return getLeafSet(leafSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the nodes in the subtree of which this node
|
||||
* is the root. The list contains the nodes in a pre traversal order.
|
||||
*
|
||||
* @return List of all nodes in subtree in preorder.
|
||||
*/
|
||||
public List<ConditionsTreeNode> preorder() {
|
||||
ArrayList<ConditionsTreeNode> result = new ArrayList<ConditionsTreeNode>();
|
||||
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||
stack.push(this);
|
||||
|
||||
while(!stack.isEmpty()) {
|
||||
ConditionsTreeNode current = stack.pop( );
|
||||
if( current.mLeft != null ) stack.push( current.mLeft );
|
||||
if( current.mRight != null ) stack.push( current.mRight );
|
||||
result.add(current);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Private class logic
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Adds two new ConditionTreeNodes, one for the operator and one for the
|
||||
* new condition. The current node will end up on the same level as the
|
||||
* one provided in the arguments, they will be siblings. Their common
|
||||
* parent node will be one containing the operator provided in the arguments.
|
||||
* The method will update all the required references so the tree ends up in
|
||||
* a valid state.
|
||||
*
|
||||
* This method only supports node arguments with a null parent node.
|
||||
*
|
||||
* @param Node to add.
|
||||
* @param Operator that will connect the new node with this one.
|
||||
* @return New parent node, containing the operator.
|
||||
* @throws Exception Throws when the provided new node does not have a null parent.
|
||||
*/
|
||||
private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
|
||||
if (node.mParent != null) {
|
||||
throw new Exception("Can only add new expressions from root node down.");
|
||||
}
|
||||
|
||||
ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
|
||||
tmpNode.mLeft = this;
|
||||
tmpNode.mRight = node;
|
||||
|
||||
if (mParent != null) {
|
||||
mParent.updateChild(this, tmpNode);
|
||||
}
|
||||
this.mParent = tmpNode;
|
||||
|
||||
node.mParent = tmpNode;
|
||||
|
||||
return tmpNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that replaces a child of the current node with a new node.
|
||||
* If the provided old child node was the left one, left will be replaced with
|
||||
* the new one. Same goes for the right one.
|
||||
*
|
||||
* @param oldChild Old child node to be replaced.
|
||||
* @param newChild New child node.
|
||||
*/
|
||||
private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
|
||||
// we can compare objects id's because this is the desired behaviour in this case
|
||||
if (mLeft == oldChild) {
|
||||
mLeft = newChild;
|
||||
} else if (mRight == oldChild) {
|
||||
mRight = newChild;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to gather all the leaves in the subtree of which
|
||||
* this node is the root.
|
||||
*
|
||||
* @param leafSet Leafset that's being built.
|
||||
* @return Set of leaves being completed.
|
||||
*/
|
||||
private HashSet<ConditionsTreeNode> getLeafSet(HashSet<ConditionsTreeNode> leafSet) {
|
||||
// if we ended up in a leaf, add ourself and return
|
||||
if (mLeft == null && mRight == null) {
|
||||
leafSet.add(this);
|
||||
return leafSet;
|
||||
// we didn't end up in a leaf
|
||||
} else {
|
||||
if (mLeft != null) {
|
||||
mLeft.getLeafSet(leafSet);
|
||||
}
|
||||
|
||||
if (mRight != null) {
|
||||
mRight.getLeafSet(leafSet);
|
||||
}
|
||||
return leafSet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies the MPTT labeling to the subtree of which this node
|
||||
* is the root node.
|
||||
*
|
||||
* For a description on MPTT see:
|
||||
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||
*/
|
||||
private int applyMPTTLabel(int label) {
|
||||
mLeftMPTTMarker = label;
|
||||
if (mLeft != null){
|
||||
label = mLeft.applyMPTTLabel(label += 1);
|
||||
}
|
||||
if (mRight != null){
|
||||
label = mRight.applyMPTTLabel(label += 1);
|
||||
}
|
||||
++label;
|
||||
mRightMPTTMarker = label;
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Parcelable
|
||||
//
|
||||
// This whole class has to be parcelable because it's passed
|
||||
// on through intents.
|
||||
///////////////////////////////////////////////////////////////
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mValue.ordinal());
|
||||
dest.writeParcelable(mCondition, flags);
|
||||
dest.writeParcelable(mLeft, flags);
|
||||
dest.writeParcelable(mRight, flags);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ConditionsTreeNode> CREATOR
|
||||
= new Parcelable.Creator<ConditionsTreeNode>() {
|
||||
public ConditionsTreeNode createFromParcel(Parcel in) {
|
||||
return new ConditionsTreeNode(in);
|
||||
}
|
||||
|
||||
public ConditionsTreeNode[] newArray(int size) {
|
||||
return new ConditionsTreeNode[size];
|
||||
}
|
||||
};
|
||||
|
||||
private ConditionsTreeNode(Parcel in) {
|
||||
mValue = OPERATOR.values()[in.readInt()];
|
||||
mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||
mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||
mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||
mParent = null;
|
||||
if (mLeft != null) {
|
||||
mLeft.mParent = this;
|
||||
}
|
||||
if (mRight != null) {
|
||||
mRight.mParent = this;
|
||||
}
|
||||
}
|
||||
}
|
388
src/com/fsck/k9/search/LocalSearch.java
Normal file
388
src/com/fsck/k9/search/LocalSearch.java
Normal file
|
@ -0,0 +1,388 @@
|
|||
package com.fsck.k9.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.fsck.k9.mail.Flag;
|
||||
|
||||
/**
|
||||
* This class represents a local search.
|
||||
|
||||
* Removing conditions could be done through matching there unique id in the leafset and then
|
||||
* removing them from the tree.
|
||||
*
|
||||
* @author dzan
|
||||
*
|
||||
* TODO implement a complete addAllowedFolder method
|
||||
* TODO conflicting conditions check on add
|
||||
* TODO duplicate condition checking?
|
||||
* TODO assign each node a unique id that's used to retrieve it from the leaveset and remove.
|
||||
*
|
||||
*/
|
||||
|
||||
public class LocalSearch implements SearchSpecification {
|
||||
|
||||
private String mName;
|
||||
private boolean mPredefined;
|
||||
|
||||
// since the uuid isn't in the message table it's not in the tree neither
|
||||
private HashSet<String> mAccountUuids = new HashSet<String>();
|
||||
private ConditionsTreeNode mConditions = null;
|
||||
private HashSet<ConditionsTreeNode> mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Constructors
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Use this only if the search won't be saved. Saved searches need
|
||||
* a name!
|
||||
*/
|
||||
public LocalSearch(){}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public LocalSearch(String name) {
|
||||
this.mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this constructor when you know what you'r doing. Normally it's only used
|
||||
* when restoring these search objects from the database.
|
||||
*
|
||||
* @param name Name of the search
|
||||
* @param searchConditions SearchConditions, may contains flags and folders
|
||||
* @param accounts Relative Account's uuid's
|
||||
* @param predefined Is this a predefined search or a user created one?
|
||||
*/
|
||||
protected LocalSearch(String name, ConditionsTreeNode searchConditions,
|
||||
String accounts, boolean predefined) {
|
||||
this(name);
|
||||
mConditions = searchConditions;
|
||||
mPredefined = predefined;
|
||||
mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||
if (mConditions != null) {
|
||||
mLeafSet.addAll(mConditions.getLeafSet());
|
||||
}
|
||||
|
||||
// initialize accounts
|
||||
if (accounts != null) {
|
||||
for (String account : accounts.split(",")) {
|
||||
mAccountUuids.add(account);
|
||||
}
|
||||
} else {
|
||||
// impossible but still not unrecoverable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public manipulation methods
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Sets the name of the saved search. If one existed it will
|
||||
* be overwritten.
|
||||
*
|
||||
* @param name Name to be set.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new account to the search. When no accounts are
|
||||
* added manually we search all accounts on the device.
|
||||
*
|
||||
* @param uuid Uuid of the account to be added.
|
||||
*/
|
||||
public void addAccountUuid(String uuid) {
|
||||
if (uuid.equals(ALL_ACCOUNTS)) {
|
||||
mAccountUuids.clear();
|
||||
}
|
||||
mAccountUuids.add(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the account uuids in the provided array to
|
||||
* be matched by the seach.
|
||||
*
|
||||
* @param accountUuids
|
||||
*/
|
||||
public void addAccountUuids(String[] accountUuids) {
|
||||
for (String acc : accountUuids) {
|
||||
addAccountUuid(acc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an account UUID from the current search.
|
||||
*
|
||||
* @param uuid Account UUID to remove.
|
||||
* @return True if removed, false otherwise.
|
||||
*/
|
||||
public boolean removeAccountUuid(String uuid) {
|
||||
return mAccountUuids.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided node as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param field Message table field to match against.
|
||||
* @param string Value to look for.
|
||||
* @param contains Attribute to use when matching.
|
||||
*
|
||||
* @throws IllegalConditionException
|
||||
*/
|
||||
public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
|
||||
and(new SearchCondition(field, attribute, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided condition as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param condition Condition to 'AND' with.
|
||||
* @return New top AND node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode and(SearchCondition condition) {
|
||||
try {
|
||||
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||
return and(tmp);
|
||||
} catch (Exception e) {
|
||||
// impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided node as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param node Node to 'AND' with.
|
||||
* @return New top AND node, new root.
|
||||
* @throws Exception
|
||||
*/
|
||||
public ConditionsTreeNode and(ConditionsTreeNode node) throws Exception {
|
||||
mLeafSet.addAll(node.getLeafSet());
|
||||
|
||||
if (mConditions == null) {
|
||||
mConditions = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
return mConditions.and(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided condition as the second argument of an OR
|
||||
* clause to this node.
|
||||
*
|
||||
* @param condition Condition to 'OR' with.
|
||||
* @return New top OR node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode or(SearchCondition condition) {
|
||||
try {
|
||||
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||
return or(tmp);
|
||||
} catch (Exception e) {
|
||||
// impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided node as the second argument of an OR
|
||||
* clause to this node.
|
||||
*
|
||||
* @param node Node to 'OR' with.
|
||||
* @return New top OR node, new root.
|
||||
* @throws Exception
|
||||
*/
|
||||
public ConditionsTreeNode or(ConditionsTreeNode node) throws Exception {
|
||||
mLeafSet.addAll(node.getLeafSet());
|
||||
|
||||
if (mConditions == null) {
|
||||
mConditions = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
return mConditions.or(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the flags to this node as required flags. The
|
||||
* provided flags will be combined using AND with the root.
|
||||
*
|
||||
* @param requiredFlags Array of required flags.
|
||||
*/
|
||||
public void allRequiredFlags(Flag[] requiredFlags) {
|
||||
if (requiredFlags != null) {
|
||||
for (Flag f : requiredFlags) {
|
||||
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the flags to this node as forbidden flags. The
|
||||
* provided flags will be combined using AND with the root.
|
||||
*
|
||||
* @param forbiddenFlags Array of forbidden flags.
|
||||
*/
|
||||
public void allForbiddenFlags(Flag[] forbiddenFlags) {
|
||||
if (forbiddenFlags != null) {
|
||||
for (Flag f : forbiddenFlags) {
|
||||
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* FOR NOW: And the folder with the root.
|
||||
*
|
||||
* Add the folder as another folder to search in. The folder
|
||||
* will be added AND to the root if no 'folder subtree' was found.
|
||||
* Otherwise the folder will be added OR to that tree.
|
||||
*
|
||||
* @param name Name of the folder to add.
|
||||
*/
|
||||
public void addAllowedFolder(String name) {
|
||||
/*
|
||||
* TODO find folder sub-tree
|
||||
* - do and on root of it & rest of search
|
||||
* - do or between folder nodes
|
||||
*/
|
||||
and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO make this more advanced!
|
||||
* This is a temporarely solution that does NOT WORK for
|
||||
* real searches because of possible extra conditions to a folder requirement.
|
||||
*/
|
||||
public List<String> getFolderNames() {
|
||||
ArrayList<String> results = new ArrayList<String>();
|
||||
for (ConditionsTreeNode node : mLeafSet) {
|
||||
if (node.mCondition.field == SEARCHFIELD.FOLDER
|
||||
&& node.mCondition.attribute == ATTRIBUTE.EQUALS) {
|
||||
results.add(node.mCondition.value);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the leafset of the related condition tree.
|
||||
*
|
||||
* @return All the leaf conditions as a set.
|
||||
*/
|
||||
public Set<ConditionsTreeNode> getLeafSet() {
|
||||
return mLeafSet;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public accesor methods
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* TODO THIS HAS TO GO!!!!
|
||||
* very dirty fix for remotesearch support atm
|
||||
*/
|
||||
public String getRemoteSearchArguments() {
|
||||
for (ConditionsTreeNode node : getLeafSet()) {
|
||||
if (node.getCondition().field == SEARCHFIELD.SUBJECT
|
||||
|| node.getCondition().field == SEARCHFIELD.SENDER ) {
|
||||
return node.getCondition().value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the saved search.
|
||||
*
|
||||
* @return Name of the search.
|
||||
*/
|
||||
public String getName() {
|
||||
return (mName == null ? "" : mName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this search was hard coded and shipped with K-9
|
||||
*
|
||||
* @return True is search was shipped with K-9
|
||||
*/
|
||||
public boolean isPredefined() {
|
||||
return mPredefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the account uuids that this search will try to
|
||||
* match against.
|
||||
*
|
||||
* @return Array of account uuids.
|
||||
*/
|
||||
@Override
|
||||
public String[] getAccountUuids() {
|
||||
if (mAccountUuids.size() == 0) {
|
||||
return new String[] {SearchSpecification.ALL_ACCOUNTS};
|
||||
}
|
||||
|
||||
String[] tmp = new String[mAccountUuids.size()];
|
||||
mAccountUuids.toArray(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the condition tree.
|
||||
*
|
||||
* @return The root node of the related conditions tree.
|
||||
*/
|
||||
@Override
|
||||
public ConditionsTreeNode getConditions() {
|
||||
return mConditions;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Parcelable
|
||||
///////////////////////////////////////////////////////////////
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mName);
|
||||
dest.writeByte((byte) (mPredefined ? 1 : 0));
|
||||
dest.writeStringList(new ArrayList<String>(mAccountUuids));
|
||||
dest.writeParcelable(mConditions, flags);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<LocalSearch> CREATOR
|
||||
= new Parcelable.Creator<LocalSearch>() {
|
||||
public LocalSearch createFromParcel(Parcel in) {
|
||||
return new LocalSearch(in);
|
||||
}
|
||||
|
||||
public LocalSearch[] newArray(int size) {
|
||||
return new LocalSearch[size];
|
||||
}
|
||||
};
|
||||
|
||||
public LocalSearch(Parcel in) {
|
||||
mName = in.readString();
|
||||
mPredefined = in.readByte() == 1;
|
||||
mAccountUuids.addAll(in.createStringArrayList());
|
||||
mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
|
||||
mLeafSet = mConditions.getLeafSet();
|
||||
}
|
||||
}
|
87
src/com/fsck/k9/search/SearchAccount.java
Normal file
87
src/com/fsck/k9/search/SearchAccount.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
package com.fsck.k9.search;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.fsck.k9.BaseAccount;
|
||||
import com.fsck.k9.R;
|
||||
|
||||
/**
|
||||
* This class is basically a wrapper around a LocalSearch. It allows to expose it as
|
||||
* an account. This is a meta-account containing all the e-mail that matches the search.
|
||||
*/
|
||||
public class SearchAccount implements BaseAccount {
|
||||
|
||||
// create the all messages search ( all accounts is default when none specified )
|
||||
public static SearchAccount createAllMessagesAccount(Context context) {
|
||||
String name = context.getString(R.string.search_all_messages_title);
|
||||
LocalSearch tmpSearch = new LocalSearch(name);
|
||||
return new SearchAccount(tmpSearch, name,
|
||||
context.getString(R.string.search_all_messages_detail));
|
||||
}
|
||||
|
||||
|
||||
// create the unified inbox meta account ( all accounts is default when none specified )
|
||||
public static SearchAccount createUnifiedInboxAccount(Context context) {
|
||||
String name = context.getString(R.string.integrated_inbox_title);
|
||||
LocalSearch tmpSearch = new LocalSearch(name);
|
||||
tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
|
||||
return new SearchAccount(tmpSearch, name,
|
||||
context.getString(R.string.integrated_inbox_detail));
|
||||
}
|
||||
|
||||
private String mEmail = null;
|
||||
private String mDescription = null;
|
||||
private LocalSearch mSearch = null;
|
||||
private String mFakeUuid = null;
|
||||
|
||||
public SearchAccount(LocalSearch search, String description, String email) throws IllegalArgumentException{
|
||||
if (search == null) {
|
||||
throw new IllegalArgumentException("Provided LocalSearch was null");
|
||||
}
|
||||
|
||||
this.mSearch = search;
|
||||
this.mDescription = description;
|
||||
this.mEmail = email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String getEmail() {
|
||||
return mEmail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setEmail(String email) {
|
||||
this.mEmail = email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
this.mDescription = description;
|
||||
}
|
||||
|
||||
public LocalSearch getRelatedSearch() {
|
||||
return mSearch;
|
||||
}
|
||||
|
||||
@Override
|
||||
/*
|
||||
* This will only be used when accessed as an Account. If that
|
||||
* is the case we don't want to return the uuid of a real account since
|
||||
* this is posing as a fake meta-account. If this object is accesed as
|
||||
* a Search then methods from LocalSearch will be called which do handle
|
||||
* things nice.
|
||||
*/
|
||||
public String getUuid() {
|
||||
if (mFakeUuid == null){
|
||||
mFakeUuid = UUID.randomUUID().toString();
|
||||
}
|
||||
return mFakeUuid;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
package com.fsck.k9.activity;
|
||||
package com.fsck.k9.search;
|
||||
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.mail.Flag;
|
||||
|
||||
/**
|
||||
* This enum represents filtering parameters used by {@link com.fsck.k9.SearchAccount}.
|
||||
* This enum represents filtering parameters used by {@link com.fsck.k9.search.SearchAccount}.
|
||||
*/
|
||||
enum SearchModifier {
|
||||
public enum SearchModifier {
|
||||
FLAGGED(R.string.flagged_modifier, new Flag[]{Flag.FLAGGED}, null),
|
||||
UNREAD(R.string.unread_modifier, null, new Flag[]{Flag.SEEN});
|
||||
|
||||
final int resId;
|
||||
final Flag[] requiredFlags;
|
||||
final Flag[] forbiddenFlags;
|
||||
public final int resId;
|
||||
public final Flag[] requiredFlags;
|
||||
public final Flag[] forbiddenFlags;
|
||||
|
||||
SearchModifier(int nResId, Flag[] nRequiredFlags, Flag[] nForbiddenFlags) {
|
||||
resId = nResId;
|
184
src/com/fsck/k9/search/SearchSpecification.java
Normal file
184
src/com/fsck/k9/search/SearchSpecification.java
Normal file
|
@ -0,0 +1,184 @@
|
|||
package com.fsck.k9.search;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public interface SearchSpecification extends Parcelable {
|
||||
|
||||
/**
|
||||
* Get all the uuids of accounts this search acts on.
|
||||
* @return Array of uuids.
|
||||
*/
|
||||
public String[] getAccountUuids();
|
||||
|
||||
/**
|
||||
* Returns the search's name if it was named.
|
||||
* @return Name of the search.
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Returns the root node of the condition tree accompanying
|
||||
* the search.
|
||||
*
|
||||
* @return Root node of conditions tree.
|
||||
*/
|
||||
public ConditionsTreeNode getConditions();
|
||||
|
||||
/*
|
||||
* Some meta names for certain conditions.
|
||||
*/
|
||||
public static final String ALL_ACCOUNTS = "allAccounts";
|
||||
public static final String GENERIC_INBOX_NAME = "genericInboxName";
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// ATTRIBUTE enum
|
||||
///////////////////////////////////////////////////////////////
|
||||
public enum ATTRIBUTE {
|
||||
CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
|
||||
NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
|
||||
|
||||
private boolean mNegation;
|
||||
|
||||
private ATTRIBUTE(boolean negation) {
|
||||
this.mNegation = negation;
|
||||
}
|
||||
|
||||
public String formQuery(String value) {
|
||||
String queryPart = "";
|
||||
|
||||
switch (this) {
|
||||
case NOT_CONTAINS:
|
||||
case CONTAINS:
|
||||
queryPart = "'%"+value+"%'";
|
||||
break;
|
||||
case NOT_EQUALS:
|
||||
case EQUALS:
|
||||
queryPart = "'"+value+"'";
|
||||
break;
|
||||
case NOT_STARTSWITH:
|
||||
case STARTSWITH:
|
||||
queryPart = "'%"+value+"'";
|
||||
break;
|
||||
case NOT_ENDSWITH:
|
||||
case ENDSWITH:
|
||||
queryPart = "'"+value+"%'";
|
||||
break;
|
||||
default: queryPart = "'"+value+"'";
|
||||
}
|
||||
|
||||
return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// SEARCHFIELD enum
|
||||
///////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* Using an enum in order to have more robust code. Users ( & coders )
|
||||
* are prevented from passing illegal fields. No database overhead
|
||||
* when invalid fields passed.
|
||||
*
|
||||
* By result, only the fields in here are searchable.
|
||||
*
|
||||
* Fields not in here at this moment ( and by effect not searchable ):
|
||||
* id, html_content, internal_date, message_id,
|
||||
* preview, mime_type
|
||||
*
|
||||
*/
|
||||
public enum SEARCHFIELD {
|
||||
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
|
||||
SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
|
||||
BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
|
||||
ATTACHMENT_COUNT("attachment_count"), DELETED("deleted"), THREAD_ROOT("thread_root");
|
||||
|
||||
private String dbName;
|
||||
|
||||
private SEARCHFIELD(String dbName) {
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
public String getDatabaseName() {
|
||||
return dbName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// SearchCondition class
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* This class represents 1 value for a certain search field. One
|
||||
* value consists of three things:
|
||||
* an attribute: equals, starts with, contains,...
|
||||
* a searchfield: date, flags, sender, subject,...
|
||||
* a value: "apple", "jesse",..
|
||||
*
|
||||
* @author dzan
|
||||
*/
|
||||
public class SearchCondition implements Parcelable{
|
||||
public String value;
|
||||
public ATTRIBUTE attribute;
|
||||
public SEARCHFIELD field;
|
||||
|
||||
public SearchCondition(SEARCHFIELD field, ATTRIBUTE attribute, String value) {
|
||||
this.value = value;
|
||||
this.attribute = attribute;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
private SearchCondition(Parcel in) {
|
||||
this.value = in.readString();
|
||||
this.attribute = ATTRIBUTE.values()[in.readInt()];
|
||||
this.field = SEARCHFIELD.values()[in.readInt()];
|
||||
}
|
||||
|
||||
public String toHumanString() {
|
||||
return field.toString() + attribute.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field.getDatabaseName() + attribute.formQuery(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof SearchCondition) {
|
||||
SearchCondition tmp = (SearchCondition) o;
|
||||
if (tmp.attribute == attribute
|
||||
&& tmp.value.equals(value)
|
||||
&& tmp.field == field) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(value);
|
||||
dest.writeInt(attribute.ordinal());
|
||||
dest.writeInt(field.ordinal());
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SearchCondition> CREATOR
|
||||
= new Parcelable.Creator<SearchCondition>() {
|
||||
public SearchCondition createFromParcel(Parcel in) {
|
||||
return new SearchCondition(in);
|
||||
}
|
||||
|
||||
public SearchCondition[] newArray(int size) {
|
||||
return new SearchCondition[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue