diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java index e6caa157e..ef0f21fb0 100644 --- a/src/com/fsck/k9/K9.java +++ b/src/com/fsck/k9/K9.java @@ -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); diff --git a/src/com/fsck/k9/SearchAccount.java b/src/com/fsck/k9/SearchAccount.java deleted file mode 100644 index bf2f25570..000000000 --- a/src/com/fsck/k9/SearchAccount.java +++ /dev/null @@ -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; - } -} diff --git a/src/com/fsck/k9/SearchSpecification.java b/src/com/fsck/k9/SearchSpecification.java deleted file mode 100644 index a1f1de0a8..000000000 --- a/src/com/fsck/k9/SearchSpecification.java +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/src/com/fsck/k9/activity/AccountList.java b/src/com/fsck/k9/activity/AccountList.java index 52c64dbe3..9c17ffd42 100644 --- a/src/com/fsck/k9/activity/AccountList.java +++ b/src/com/fsck/k9/activity/AccountList.java @@ -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; /** diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index f45fe52e5..596801554 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -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); } } diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java index fa2deb5c9..6c8477833 100644 --- a/src/com/fsck/k9/activity/FolderList.java +++ b/src/com/fsck/k9/activity/FolderList.java @@ -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(); + } } } diff --git a/src/com/fsck/k9/activity/LauncherShortcuts.java b/src/com/fsck/k9/activity/LauncherShortcuts.java index 415689057..f302051dc 100644 --- a/src/com/fsck/k9/activity/LauncherShortcuts.java +++ b/src/com/fsck/k9/activity/LauncherShortcuts.java @@ -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); diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index eb7c91ee4..aca636bbd 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -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); } } diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index e4cf48734..181cb0177 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -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 accountUuidsSet = new HashSet(); - if (accountUuids != null) { - accountUuidsSet.addAll(Arrays.asList(accountUuids)); - } - final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext()); - List 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 messagesToSearch = null; - if (messages != null) { - messagesToSearch = new LinkedList(); - 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 tmpFoldersToSearch = new LinkedList(); - try { - LocalStore store = account.getLocalStore(); - List folders = store.getPersonalNamespaces(false); - Set folderNameSet = null; - if (folderNames != null) { - folderNameSet = new HashSet(); - 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 uuidSet = new HashSet(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 messages = new ArrayList(); @@ -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); diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java index 593bbf8e9..31538910d 100644 --- a/src/com/fsck/k9/fragment/MessageListFragment.java +++ b/src/com/fsck/k9/fragment/MessageListFragment.java @@ -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 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() { - @Override - public void onRefresh(PullToRefreshBase refreshView) { - checkMail(); - } - }); - } else if (allowRemoteSearch && !mRemoteSearch && !mIntegrate && mHasConnectivity) { - // mQueryString != null is implied if we get this far. - mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() { - @Override - public void onRefresh(PullToRefreshBase 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() { + @Override + public void onRefresh(PullToRefreshBase refreshView) { + checkMail(); + } + }); + // TODO this has to go! find better remote search integration + } else { + mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() { + @Override + public void onRefresh(PullToRefreshBase 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(); } diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 641466b3c..13302b896 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -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; /** *
@@ -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() {
+            @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  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 folders, Message[] messages, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException {
-        List args = new LinkedList();
-
-        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
diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java
index e49face93..019391003 100644
--- a/src/com/fsck/k9/preferences/GlobalSettings.java
+++ b/src/com/fsck/k9/preferences/GlobalSettings.java
@@ -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))
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index 1e351d057..d40ed23e0 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -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 holders = queue.take();
diff --git a/src/com/fsck/k9/provider/UnreadWidgetProvider.java b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
index 2af479d15..d8daeaa0b 100644
--- a/src/com/fsck/k9/provider/UnreadWidgetProvider.java
+++ b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
@@ -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);
             }
diff --git a/src/com/fsck/k9/search/ConditionsTreeNode.java b/src/com/fsck/k9/search/ConditionsTreeNode.java
new file mode 100644
index 000000000..650755a7d
--- /dev/null
+++ b/src/com/fsck/k9/search/ConditionsTreeNode.java
@@ -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 stack = new Stack();
+    	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 getLeafSet() {	
+		HashSet leafSet = new HashSet();
+		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 preorder() {
+		ArrayList result = new ArrayList();
+		Stack stack = new Stack();
+	    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 getLeafSet(HashSet 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 CREATOR
+	    = new Parcelable.Creator() {
+	    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;
+		}
+	}
+}
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
new file mode 100644
index 000000000..c801a5308
--- /dev/null
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -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 mAccountUuids = new HashSet();   
+    private ConditionsTreeNode mConditions = null;  
+    private HashSet mLeafSet = new HashSet();
+    
+    
+	///////////////////////////////////////////////////////////////
+	// 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();
+    	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 getFolderNames() {
+		ArrayList results = new ArrayList();
+		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 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(mAccountUuids));
+		dest.writeParcelable(mConditions, flags);
+	}
+	
+	public static final Parcelable.Creator CREATOR
+	    = new Parcelable.Creator() {
+	    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();
+	}
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/search/SearchAccount.java b/src/com/fsck/k9/search/SearchAccount.java
new file mode 100644
index 000000000..d76a8a0dd
--- /dev/null
+++ b/src/com/fsck/k9/search/SearchAccount.java
@@ -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;
+    }
+}
diff --git a/src/com/fsck/k9/activity/SearchModifier.java b/src/com/fsck/k9/search/SearchModifier.java
similarity index 70%
rename from src/com/fsck/k9/activity/SearchModifier.java
rename to src/com/fsck/k9/search/SearchModifier.java
index 8b610ff6e..f027ab062 100644
--- a/src/com/fsck/k9/activity/SearchModifier.java
+++ b/src/com/fsck/k9/search/SearchModifier.java
@@ -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;
diff --git a/src/com/fsck/k9/search/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java
new file mode 100644
index 000000000..6f6a38fa5
--- /dev/null
+++ b/src/com/fsck/k9/search/SearchSpecification.java
@@ -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 CREATOR
+	        = new Parcelable.Creator() {
+		    public SearchCondition createFromParcel(Parcel in) {
+		        return new SearchCondition(in);
+		    }
+		
+		    public SearchCondition[] newArray(int size) {
+		        return new SearchCondition[size];
+		    }
+		};
+    }
+}
\ No newline at end of file