1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-24 02:12:15 -05:00

Merge branch 'new_search_framework' into content_provider

Conflicts:
	src/com/fsck/k9/fragment/MessageListFragment.java
This commit is contained in:
cketti 2012-10-16 20:57:47 +02:00
commit 83d5102f3d
19 changed files with 1431 additions and 970 deletions

View File

@ -579,8 +579,8 @@ public class K9 extends Application {
public static void loadPrefs(Preferences prefs) { public static void loadPrefs(Preferences prefs) {
SharedPreferences sprefs = prefs.getPreferences(); SharedPreferences sprefs = prefs.getPreferences();
DEBUG = sprefs.getBoolean("enableDebugLogging", false); DEBUG = sprefs.getBoolean("enableDebugLogging", true);
DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", false); DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", true);
mAnimations = sprefs.getBoolean("animations", true); mAnimations = sprefs.getBoolean("animations", true);
mGesturesEnabled = sprefs.getBoolean("gesturesEnabled", false); mGesturesEnabled = sprefs.getBoolean("gesturesEnabled", false);
mUseVolumeKeysForNavigation = sprefs.getBoolean("useVolumeKeysForNavigation", false); mUseVolumeKeysForNavigation = sprefs.getBoolean("useVolumeKeysForNavigation", false);

View File

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

View File

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

View File

@ -21,7 +21,7 @@ import com.fsck.k9.FontSizes;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.SearchAccount; import com.fsck.k9.search.SearchAccount;
/** /**

View File

@ -62,8 +62,6 @@ import com.fsck.k9.FontSizes;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.R; 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.ExtendedAsyncTask;
import com.fsck.k9.activity.misc.NonConfigurationInstance; import com.fsck.k9.activity.misc.NonConfigurationInstance;
import com.fsck.k9.activity.setup.AccountSettings; 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.internet.MimeUtility;
import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.WebDavStore; 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.view.ColorChip;
import com.fsck.k9.preferences.SettingsExporter; import com.fsck.k9.preferences.SettingsExporter;
import com.fsck.k9.preferences.SettingsImportExportException; 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') * Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
*/ */
private void createSpecialAccounts() { private void createSpecialAccounts() {
integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(this); // create the unified inbox meta account ( all accounts is default when none specified )
unreadAccount = SearchAccount.createAllMessagesAccount(this); 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") @SuppressWarnings("unchecked")
@ -550,7 +562,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
pendingWork.put(account, "true"); pendingWork.put(account, "true");
final SearchAccount searchAccount = (SearchAccount)account; final SearchAccount searchAccount = (SearchAccount)account;
MessagingController.getInstance(getApplication()).searchLocalMessages(searchAccount, null, new MessagingListener() { MessagingController.getInstance(getApplication())
.searchLocalMessages(searchAccount.getRelatedSearch(), new MessagingListener() {
@Override @Override
public void searchStats(AccountStats stats) { public void searchStats(AccountStats stats) {
mListener.accountStatusChanged(searchAccount, stats); mListener.accountStatusChanged(searchAccount, stats);
@ -607,7 +620,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private boolean onOpenAccount(BaseAccount account) { private boolean onOpenAccount(BaseAccount account) {
if (account instanceof SearchAccount) { if (account instanceof SearchAccount) {
SearchAccount searchAccount = (SearchAccount)account; SearchAccount searchAccount = (SearchAccount)account;
MessageList.actionHandle(this, searchAccount.getDescription(), searchAccount); MessageList.actionDisplaySearch(this, searchAccount.getRelatedSearch(), false);
} else { } else {
Account realAccount = (Account)account; Account realAccount = (Account)account;
if (!realAccount.isEnabled()) { if (!realAccount.isEnabled()) {
@ -624,8 +637,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) { if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) {
FolderList.actionHandleAccount(this, realAccount); FolderList.actionHandleAccount(this, realAccount);
} else { } 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; return true;
} }
@ -1769,49 +1784,20 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
} }
@Override @Override
public void onClick(View v) { 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) { if (account instanceof SearchAccount) {
SearchAccount searchAccount = (SearchAccount)account; search = ((SearchAccount) account).getRelatedSearch();
search.setName(description);
MessageList.actionHandle(Accounts.this,
description, "", searchAccount.isIntegrate(),
combine(searchAccount.getRequiredFlags(), searchModifier.requiredFlags),
combine(searchAccount.getForbiddenFlags(), searchModifier.forbiddenFlags));
} else { } else {
SearchSpecification searchSpec = new SearchSpecification() { search = new LocalSearch(description);
@Override search.addAccountUuid(account.getUuid());
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.allRequiredFlags(searchModifier.requiredFlags);
search.allForbiddenFlags(searchModifier.forbiddenFlags);
MessageList.actionDisplaySearch(Accounts.this, search, false);
} }
} }

View File

@ -52,7 +52,6 @@ import com.fsck.k9.FontSizes;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.FolderList.FolderListAdapter.FolderListFilter; import com.fsck.k9.activity.FolderList.FolderListAdapter.FolderListFilter;
import com.fsck.k9.activity.misc.ActionBarNavigationSpinner; import com.fsck.k9.activity.misc.ActionBarNavigationSpinner;
import com.fsck.k9.activity.setup.AccountSettings; 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.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; 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; import com.fsck.k9.service.MailService;
/** /**
@ -620,7 +622,10 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
} }
private void onOpenFolder(String folder) { 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) { private void onCompact(Account account) {
@ -1257,86 +1262,34 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
} }
@Override @Override
public void onClick(View v) { 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(R.string.message_list_title, account.getDescription(), displayName),
getString(searchModifier.resId)); getString(searchModifier.resId));
SearchSpecification searchSpec = new SearchSpecification() { LocalSearch search = new LocalSearch(description);
@Override try {
public String[] getAccountUuids() { search.allRequiredFlags(searchModifier.requiredFlags);
return new String[] { account.getUuid() }; search.allForbiddenFlags(searchModifier.forbiddenFlags);
} } catch (Exception e) {
// TODO Auto-generated catch block
@Override e.printStackTrace();
public Flag[] getForbiddenFlags() { }
return searchModifier.forbiddenFlags; search.addAllowedFolder(folderName);
} search.addAccountUuid(account.getUuid());
MessageList.actionDisplaySearch(FolderList.this, search, false);
@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);
} }
} }
private static Flag[] UNREAD_FLAG_ARRAY = { Flag.SEEN };
private void openUnreadSearch(Context context, final Account account) { private void openUnreadSearch(Context context, final Account account) {
String description = getString(R.string.search_title, mAccount.getDescription(), getString(R.string.unread_modifier)); String description = getString(R.string.search_title, mAccount.getDescription(), getString(R.string.unread_modifier));
LocalSearch search = new LocalSearch(description);
SearchSpecification searchSpec = new SearchSpecification() { search.addAccountUuid(account.getUuid());
//interface has no override @Override try {
public String[] getAccountUuids() { search.allRequiredFlags(new Flag[]{Flag.SEEN});
return new String[] { account.getUuid() }; } catch (Exception e) {
} // TODO Auto-generated catch block
e.printStackTrace();
//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);
} }
} }

View File

@ -7,7 +7,7 @@ import android.os.Parcelable;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.BaseAccount; import com.fsck.k9.BaseAccount;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.SearchSpecification; import com.fsck.k9.search.SearchSpecification;
public class LauncherShortcuts extends AccountList { public class LauncherShortcuts extends AccountList {
@Override @Override
@ -31,8 +31,7 @@ public class LauncherShortcuts extends AccountList {
Intent shortcutIntent = null; Intent shortcutIntent = null;
if (account instanceof SearchSpecification) { if (account instanceof SearchSpecification) {
shortcutIntent = MessageList.actionHandleAccountIntent(this, account.getDescription(), shortcutIntent = MessageList.intentDisplaySearch(this, (SearchSpecification) account, true, true);
(SearchSpecification) account);
} else { } else {
shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null, shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null,
true); true);

View File

@ -24,7 +24,6 @@ import com.fsck.k9.Account.SortType;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener; import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
import com.fsck.k9.activity.setup.AccountSettings; import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.activity.setup.FolderSettings; 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.Flag;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.StorageManager; 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, public class MessageList extends K9FragmentActivity implements MessageListFragmentListener,
OnBackStackChangedListener, OnSwipeGestureListener { 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_ACCOUNT = "com.fsck.k9.search_account";
private static final String EXTRA_SEARCH_FOLDER = "com.fsck.k9.search_folder"; 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 actionDisplaySearch(Context context, SearchSpecification search, boolean newTask) {
public static void actionHandleFolder(Context context, Account account, String folder) { actionDisplaySearch(context, search, newTask, true);
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 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 intent = new Intent(context, MessageList.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | intent.putExtra(EXTRA_SEARCH, search);
Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(EXTRA_ACCOUNT, account.getUuid()); if (clearTop) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
if (folder != null) {
intent.putExtra(EXTRA_FOLDER, folder);
} }
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
return intent; 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(); private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
@ -131,31 +85,22 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
private TextView mActionBarTitle; private TextView mActionBarTitle;
private TextView mActionBarSubTitle; private TextView mActionBarSubTitle;
private TextView mActionBarUnread; private TextView mActionBarUnread;
private String mTitle;
private Menu mMenu; private Menu mMenu;
private MessageListFragment mMessageListFragment; private MessageListFragment mMessageListFragment;
private Account mAccount; private Account mAccount;
private String mQueryString;
private String mFolderName; private String mFolderName;
private Flag[] mQueryFlags; private LocalSearch mSearch;
private Flag[] mForbiddenFlags; private boolean mSingleFolderMode;
private String mSearchAccount = null; private boolean mSingleAccountMode;
private String mSearchFolder = null; private boolean mIsRemote;
private boolean mIntegrate;
private String[] mAccountUuids;
private String[] mFolderNames;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.message_list); setContentView(R.layout.message_list);
// need this for actionbar initialization
mQueryString = getIntent().getStringExtra(SearchManager.QUERY);
mActionBar = getSupportActionBar(); mActionBar = getSupportActionBar();
initializeActionBar(); initializeActionBar();
@ -171,76 +116,56 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
if (mMessageListFragment == null) { if (mMessageListFragment == null) {
FragmentTransaction ft = fragmentManager.beginTransaction(); FragmentTransaction ft = fragmentManager.beginTransaction();
if (mQueryString == null) { mMessageListFragment = MessageListFragment.newInstance(mSearch, mIsRemote);
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);
}
ft.add(R.id.message_list_container, mMessageListFragment); ft.add(R.id.message_list_container, mMessageListFragment);
ft.commit(); ft.commit();
} }
} }
private void decodeExtras(Intent intent) { private void decodeExtras(Intent intent) {
mQueryString = intent.getStringExtra(SearchManager.QUERY); // check if this intent comes from the system search ( remote )
mFolderName = null; if (intent.getStringExtra(SearchManager.QUERY) != null) {
mSearchAccount = null;
mSearchFolder = null;
if (mQueryString != null) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
//Query was received from Search Dialog //Query was received from Search Dialog
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA); Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) { if (appData != null) {
mSearchAccount = appData.getString(EXTRA_SEARCH_ACCOUNT); mSearch = new LocalSearch();
mSearchFolder = appData.getString(EXTRA_SEARCH_FOLDER); 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); String[] accounts = mSearch.getAccountUuids();
mFolderName = intent.getStringExtra(EXTRA_FOLDER); mSingleAccountMode = ( accounts != null && accounts.length == 1
&& !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS));
mAccount = Preferences.getPreferences(this).getAccount(accountUuid); mSingleFolderMode = mSingleAccountMode && (mSearch.getFolderNames().size() == 1);
if (mAccount != null && !mAccount.isAvailable(this)) { if (mSingleAccountMode) {
Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account"); mAccount = Preferences.getPreferences(this).getAccount(accounts[0]);
onAccountUnavailable();
return; if (mAccount != null && !mAccount.isAvailable(this)) {
} Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
onAccountUnavailable();
String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS); return;
if (queryFlags != null) { }
String[] flagStrings = queryFlags.split(","); }
mQueryFlags = new Flag[flagStrings.length];
for (int i = 0; i < flagStrings.length; i++) { if (mSingleFolderMode) {
mQueryFlags[i] = Flag.valueOf(flagStrings[i]); mFolderName = mSearch.getFolderNames().get(0);
} }
}
String forbiddenFlags = intent.getStringExtra(EXTRA_FORBIDDEN_FLAGS); // now we know if we are in single account mode and need a subtitle
if (forbiddenFlags != null) { mActionBarSubTitle.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
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();
}
} }
@Override @Override
@ -276,10 +201,6 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
mActionBarSubTitle = (TextView) customView.findViewById(R.id.actionbar_title_sub); mActionBarSubTitle = (TextView) customView.findViewById(R.id.actionbar_title_sub);
mActionBarUnread = (TextView) customView.findViewById(R.id.actionbar_unread_count); mActionBarUnread = (TextView) customView.findViewById(R.id.actionbar_unread_count);
if (mQueryString != null) {
mActionBarSubTitle.setVisibility(View.GONE);
}
mActionBar.setDisplayHomeAsUpEnabled(true); mActionBar.setDisplayHomeAsUpEnabled(true);
} }
@ -407,17 +328,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
FragmentManager fragmentManager = getSupportFragmentManager(); FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) { if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack(); fragmentManager.popBackStack();
} else if (mIntegrate) { } else if (!mSingleFolderMode) {
// If we were in one of the integrated mailboxes (think All Mail or Integrated Inbox), then onBackPressed();
// go to accounts. } else {
onAccounts(); onShowFolderList();
} 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();
}
return true; return true;
} }
case R.id.compose: { 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 // 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 //TODO: This is not true for "unread" and "starred" searches in regular folders
return false; 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.select_all).setVisible(true);
menu.findItem(R.id.settings).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.expunge).setVisible(false);
menu.findItem(R.id.check_mail).setVisible(false); menu.findItem(R.id.check_mail).setVisible(false);
menu.findItem(R.id.send_messages).setVisible(false); menu.findItem(R.id.send_messages).setVisible(false);
@ -666,8 +581,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override @Override
public void showMoreFromSameSender(String senderAddress) { public void showMoreFromSameSender(String senderAddress) {
MessageListFragment fragment = MessageListFragment.newInstance("From " + senderAddress, LocalSearch tmpSearch = new LocalSearch("From " + senderAddress);
null, null, senderAddress, null, null, false); tmpSearch.addAccountUuids(mSearch.getAccountUuids());
tmpSearch.and(SEARCHFIELD.SENDER, senderAddress, ATTRIBUTE.CONTAINS);
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
addMessageListFragment(fragment); addMessageListFragment(fragment);
} }
@ -716,8 +634,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override @Override
public void remoteSearch(String searchAccount, String searchFolder, String queryString) { public void remoteSearch(String searchAccount, String searchFolder, String queryString) {
MessageListFragment fragment = MessageListFragment.newInstance(searchAccount, searchFolder, MessageListFragment fragment = MessageListFragment.newInstance(mSearch, true);
queryString, true);
addMessageListFragment(fragment); addMessageListFragment(fragment);
} }
@ -751,9 +668,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override @Override
public void showThread(Account account, String folderName, long threadRootId) { public void showThread(Account account, String folderName, long threadRootId) {
MessageListFragment fragment = MessageListFragment.newInstance(account, folderName, LocalSearch tmpSearch = new LocalSearch();
threadRootId); tmpSearch.addAccountUuids(mSearch.getAccountUuids());
tmpSearch.and(SEARCHFIELD.THREAD_ROOT, String.valueOf(threadRootId), ATTRIBUTE.EQUALS);
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
addMessageListFragment(fragment); addMessageListFragment(fragment);
} }
} }

View File

@ -40,7 +40,6 @@ import com.fsck.k9.K9.Intents;
import com.fsck.k9.NotificationSetting; import com.fsck.k9.NotificationSetting;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.FolderList; import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList; import com.fsck.k9.activity.MessageList;
import com.fsck.k9.helper.NotificationBuilder; 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.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.UnavailableAccountException; import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException; 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' * Find all messages in any local account which match the query 'query'
* @throws MessagingException * @throws MessagingException
*/ */
public void searchLocalMessages(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate, public void searchLocalMessages(final LocalSearch search, final MessagingListener listener) {
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, ',')
+ ")");
}
threadPool.execute(new Runnable() { threadPool.execute(new Runnable() {
@Override @Override
public void run() { 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 AccountStats stats = new AccountStats();
final Set<String> accountUuidsSet = new HashSet<String>(); final HashSet<String> uuidSet = new HashSet<String>(Arrays.asList(search.getAccountUuids()));
if (accountUuids != null) { Account[] accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts();
accountUuidsSet.addAll(Arrays.asList(accountUuids)); boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS);
}
final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext()); // for every account we want to search do the query in the localstore
List<LocalFolder> foldersToSearch = null; for (final Account account : accounts) {
boolean displayableOnly = false;
boolean noSpecialFolders = true; if (!allAccounts && !uuidSet.contains(account.getUuid())) {
for (final Account account : prefs.getAvailableAccounts()) { continue;
if (accountUuids != null && !accountUuidsSet.contains(account.getUuid())) { }
continue;
} // Collecting statistics of the search result
if (accountUuids != null && accountUuidsSet.contains(account.getUuid())) {
displayableOnly = true;
noSpecialFolders = true;
} else if (!integrate && folderNames == null) {
Account.Searchable searchableFolders = account.getSearchableFolders();
switch (searchableFolders) {
case NONE:
continue;
case DISPLAYABLE:
displayableOnly = true;
break;
}
}
List<Message> messagesToSearch = null;
if (messages != null) {
messagesToSearch = new LinkedList<Message>();
for (Message message : messages) {
if (message.getFolder().getAccount().getUuid().equals(account.getUuid())) {
messagesToSearch.add(message);
}
}
if (messagesToSearch.isEmpty()) {
continue;
}
}
if (listener != null) {
listener.listLocalMessagesStarted(account, null);
}
if (integrate || displayableOnly || folderNames != null || noSpecialFolders) {
List<LocalFolder> tmpFoldersToSearch = new LinkedList<LocalFolder>();
try {
LocalStore store = account.getLocalStore();
List <? extends Folder > folders = store.getPersonalNamespaces(false);
Set<String> folderNameSet = null;
if (folderNames != null) {
folderNameSet = new HashSet<String>();
folderNameSet.addAll(Arrays.asList(folderNames));
}
for (Folder folder : folders) {
LocalFolder localFolder = (LocalFolder)folder;
boolean include = true;
folder.refresh(prefs);
String localFolderName = localFolder.getName();
if (integrate) {
include = localFolder.isIntegrate();
} else {
if (folderNameSet != null) {
if (!folderNameSet.contains(localFolderName))
{
include = false;
}
}
// Never exclude the INBOX (see issue 1817)
else if (noSpecialFolders && !localFolderName.equalsIgnoreCase(account.getInboxFolderName()) &&
!localFolderName.equals(account.getArchiveFolderName()) && account.isSpecialFolder(localFolderName)) {
include = false;
} else if (displayableOnly && modeMismatch(account.getFolderDisplayMode(), folder.getDisplayClass())) {
include = false;
}
}
if (include) {
tmpFoldersToSearch.add(localFolder);
}
}
if (tmpFoldersToSearch.size() < 1) {
continue;
}
foldersToSearch = tmpFoldersToSearch;
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Unable to restrict search folders in Account " + account.getDescription() + ", searching all", me);
addErrorMessage(account, null, me);
}
}
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() { MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
@Override @Override
public void messageStarted(String message, int number, int ofTotal) {} public void messageStarted(String message, int number, int ofTotal) {}
@Override @Override
public void messagesFinished(int number) {}
@Override
public void messageFinished(Message message, int number, int ofTotal) { public void messageFinished(Message message, int number, int ofTotal) {
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) { if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
List<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
@ -760,22 +664,18 @@ public class MessagingController implements Runnable {
listener.listLocalMessagesAddMessages(account, null, messages); 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 { try {
String[] queryFields = {"html_content", "subject", "sender_list"}; LocalStore localStore = account.getLocalStore();
LocalStore localStore = account.getLocalStore(); localStore.searchForMessages(retrievalListener, search);
localStore.searchForMessages(retrievalListener, queryFields
, query, foldersToSearch,
messagesToSearch == null ? null : messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY),
requiredFlags, forbiddenFlags);
} catch (Exception e) { } catch (Exception e) {
if (listener != null) { if (listener != null) {
listener.listLocalMessagesFailed(account, null, e.getMessage()); listener.listLocalMessagesFailed(account, null, e.getMessage());
@ -786,7 +686,9 @@ public class MessagingController implements Runnable {
listener.listLocalMessagesFinished(account, null); listener.listLocalMessagesFinished(account, null);
} }
} }
} }
// publish the total search statistics
if (listener != null) { if (listener != null) {
listener.searchStats(stats); listener.searchStats(stats);
} }
@ -3258,8 +3160,11 @@ public class MessagingController implements Runnable {
builder.setContentTitle(mApplication.getString(R.string.notification_bg_send_title)); builder.setContentTitle(mApplication.getString(R.string.notification_bg_send_title));
builder.setContentText(account.getDescription()); builder.setContentText(account.getDescription());
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account, LocalSearch search = new LocalSearch(account.getInboxFolderName());
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); PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi); builder.setContentIntent(pi);
@ -3341,8 +3246,11 @@ public class MessagingController implements Runnable {
mApplication.getString(R.string.notification_bg_title_separator) + mApplication.getString(R.string.notification_bg_title_separator) +
folder.getName()); folder.getName());
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account, LocalSearch search = new LocalSearch(account.getInboxFolderName());
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); PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi); builder.setContentIntent(pi);

View File

@ -11,7 +11,6 @@ import java.util.Map;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import android.app.Activity; import android.app.Activity;
import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences.Editor; 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.Folder.OpenMode;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; 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;
import com.fsck.k9.provider.EmailProvider.MessageColumns; 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.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView; 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; 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(); MessageListFragment fragment = new MessageListFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(ARG_ACCOUNT, account.getUuid()); args.putParcelable(ARG_SEARCH, search);
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.putBoolean(ARG_REMOTE_SEARCH, remoteSearch); args.putBoolean(ARG_REMOTE_SEARCH, remoteSearch);
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
/** /**
* Reverses the result of a {@link Comparator}. * 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_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2; private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
private static final String ARG_ACCOUNT = "account"; private static final String ARG_SEARCH = "searchObject";
private static final String ARG_FOLDER = "folder"; private static final String ARG_REMOTE_SEARCH = "remoteSearch";
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 STATE_LIST_POSITION = "listPosition"; 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. * 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 boolean mRemoteSearch = false;
private String mSearchAccount = null;
private String mSearchFolder = null;
private Future mRemoteSearchFuture = null; private Future mRemoteSearchFuture = null;
private boolean mIntegrate = false;
private String[] mAccountUuids = null;
private String[] mFolderNames = null;
private String mTitle; private String mTitle;
private LocalSearch mSearch = null;
private boolean mSingleAccountMode;
private boolean mSingleFolderMode;
private MessageListHandler mHandler = new MessageListHandler(); private MessageListHandler mHandler = new MessageListHandler();
@ -591,7 +528,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void setWindowTitle() { private void setWindowTitle() {
// regular folder content display // regular folder content display
if (mFolderName != null) { if (mSingleFolderMode) {
Activity activity = getActivity(); Activity activity = getActivity();
String displayName = FolderInfoHolder.getDisplayName(activity, mAccount, String displayName = FolderInfoHolder.getDisplayName(activity, mAccount,
mFolderName); mFolderName);
@ -604,7 +541,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} else { } else {
mFragmentListener.setMessageListSubTitle(operation); 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. // query result display. This may be for a search folder as opposed to a user-initiated search.
if (mTitle != null) { if (mTitle != null) {
// This was a search folder; the search folder has overridden our title. // 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) { if (mUnreadMessageCount == 0) {
mFragmentListener.setUnreadCount(0); mFragmentListener.setUnreadCount(0);
} else { } else {
if (mQueryString != null && mTitle == null) { if (!mSingleFolderMode && mTitle == null) {
// This is a search result. The unread message count is easily confused // The unread message count is easily confused
// with total number of messages in the search result, so let's hide it. // with total number of messages in the search result, so let's hide it.
mFragmentListener.setUnreadCount(0); mFragmentListener.setUnreadCount(0);
} else { } else {
@ -668,7 +605,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
updateFooter("", false); updateFooter("", false);
return; return;
} }
int limit = account.getRemoteSearchNumResults(); int limit = mAccount.getRemoteSearchNumResults();
List<Message> toProcess = mAdapter.mExtraSearchResults; List<Message> toProcess = mAdapter.mExtraSearchResults;
if (limit > 0 && numResults > limit) { if (limit > 0 && numResults > limit) {
toProcess = toProcess.subList(0, limit); toProcess = toProcess.subList(0, limit);
@ -677,7 +614,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mAdapter.mExtraSearchResults = null; mAdapter.mExtraSearchResults = null;
updateFooter("", false); updateFooter("", false);
} }
mController.loadSearchResults(account, mSearchFolder, toProcess, mAdapter.mListener); mController.loadSearchResults(mAccount, mCurrentFolder.name, toProcess, mListener);
}*/ }*/
return; return;
} }
@ -755,40 +692,26 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void decodeArguments() { private void decodeArguments() {
Bundle args = getArguments(); Bundle args = getArguments();
mQueryString = args.getString(SearchManager.QUERY);
mFolderName = args.getString(ARG_FOLDER);
mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false); mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false);
mSearchAccount = args.getString(ARG_SEARCH_ACCOUNT); mSearch = args.getParcelable(ARG_SEARCH);
mSearchFolder = args.getString(ARG_SEARCH_FOLDER); mTitle = args.getString(mSearch.getName());
mThreadId = args.getLong(ARG_THREAD_ID, -1);
String accountUuid = args.getString(ARG_ACCOUNT);
Context appContext = getActivity().getApplicationContext(); Context appContext = getActivity().getApplicationContext();
mAccount = Preferences.getPreferences(appContext).getAccount(accountUuid); String[] accounts = mSearch.getAccountUuids();
String queryFlags = args.getString(ARG_QUERY_FLAGS); mSingleAccountMode = false;
if (queryFlags != null) { if (accounts != null && accounts.length == 1
String[] flagStrings = queryFlags.split(","); && !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS)) {
mQueryFlags = new Flag[flagStrings.length]; mSingleAccountMode = true;
for (int i = 0; i < flagStrings.length; i++) { mAccount = Preferences.getPreferences(appContext).getAccount(accounts[0]);
mQueryFlags[i] = Flag.valueOf(flagStrings[i]);
}
} }
String forbiddenFlags = args.getString(ARG_FORBIDDEN_FLAGS); mSingleFolderMode = false;
if (forbiddenFlags != null) { if (mSingleAccountMode && (mSearch.getFolderNames().size() == 1)) {
String[] flagStrings = forbiddenFlags.split(","); mSingleFolderMode = true;
mForbiddenFlags = new Flag[flagStrings.length]; mFolderName = mSearch.getFolderNames().get(0);
for (int i = 0; i < flagStrings.length; i++) { mCurrentFolder = getFolder(mFolderName, mAccount);
mForbiddenFlags[i] = Flag.valueOf(flagStrings[i]);
}
} }
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() { private void initializeMessageList() {
@ -799,7 +722,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} }
// Hide "Load up to x more" footer for search views // 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()); mController = MessagingController.getInstance(getActivity().getApplication());
mListView.setAdapter(mAdapter); mListView.setAdapter(mAdapter);
@ -868,14 +791,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
final Preferences prefs = Preferences.getPreferences(appContext); 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. // Check if we have connectivity. Cache the value.
if (mHasConnectivity == null) { if (mHasConnectivity == null) {
final ConnectivityManager connectivityManager = final ConnectivityManager connectivityManager =
@ -889,24 +804,26 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} }
} }
if (mQueryString == null) { if (mSingleFolderMode) {
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() { if (!mAccount.allowRemoteSearch()) {
@Override mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
public void onRefresh(PullToRefreshBase<ListView> refreshView) { @Override
checkMail(); public void onRefresh(PullToRefreshBase<ListView> refreshView) {
} checkMail();
}); }
} else if (allowRemoteSearch && !mRemoteSearch && !mIntegrate && mHasConnectivity) { });
// mQueryString != null is implied if we get this far. // TODO this has to go! find better remote search integration
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() { } else {
@Override mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
public void onRefresh(PullToRefreshBase<ListView> refreshView) { @Override
mPullToRefreshView.onRefreshComplete(); public void onRefresh(PullToRefreshBase<ListView> refreshView) {
onRemoteSearchRequested(true); 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)); 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 { } else {
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED); 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 //Cancel pending new mail notifications when we open an account
Account[] accountsWithNotification; Account[] accountsWithNotification;
Account account = getCurrentAccount(prefs); Account account = mAccount;
if (account != null) { if (account != null) {
accountsWithNotification = new Account[] { account }; accountsWithNotification = new Account[] { account };
@ -934,56 +851,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mController.notifyAccountCancel(appContext, accountWithNotification); 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) { if (mAccount != null && mFolderName != null && !mRemoteSearch) {
mController.getFolderUnreadMessageCount(mAccount, mFolderName, mListener); mController.getFolderUnreadMessageCount(mAccount, mFolderName, mListener);
} }
@ -1009,7 +876,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} }
public void onCompose() { public void onCompose() {
if (mQueryString != null) { if (!mSingleAccountMode) {
/* /*
* If we have a query string, we don't have an account to let * If we have a query string, we don't have an account to let
* compose start the default action. * 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. * 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 searchAccount;
String searchFolder; String searchFolder;
if (fromLocalSearch) { searchAccount = mAccount.getUuid();
searchAccount = mSearchAccount; searchFolder = mCurrentFolder.name;
searchFolder = mSearchFolder;
} else {
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; mSortType = sortType;
Preferences prefs = Preferences.getPreferences(getActivity().getApplicationContext()); Preferences prefs = Preferences.getPreferences(getActivity().getApplicationContext());
Account account = getCurrentAccount(prefs); Account account = mAccount;
if (account != null) { if (account != null) {
account.setSortType(mSortType); 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 // 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 //TODO: This is not true for "unread" and "starred" searches in regular folders
return false; return false;
@ -1558,38 +1418,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
super.synchronizeMailboxFailed(account, folder, message); 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 @Override
public void searchStats(AccountStats stats) { public void searchStats(AccountStats stats) {
mUnreadMessageCount = stats.unreadMessageCount; mUnreadMessageCount = stats.unreadMessageCount;
@ -1605,6 +1433,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} }
private boolean updateForMe(Account account, String folder) { private boolean updateForMe(Account account, String folder) {
//FIXME
return ((account.equals(mAccount) && folder.equals(mFolderName))); 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. * @return The {@code Account} all displayed messages belong to.
*/ */
private Account getCurrentAccount(Preferences prefs) { //TODO: remove
/*private Account getCurrentAccount(Preferences prefs) {
Account account = null; Account account = null;
if (mQueryString != null && !mIntegrate && mAccountUuids != null && if (mQueryString != null && !mIntegrate && mAccountUuids != null &&
mAccountUuids.length == 1) { mAccountUuids.length == 1) {
@ -2288,7 +2118,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} }
return account; return account;
} }*/
class ActionModeCallback implements ActionMode.Callback { class ActionModeCallback implements ActionMode.Callback {
@ -2306,7 +2136,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mFlag = menu.findItem(R.id.flag); mFlag = menu.findItem(R.id.flag);
mUnflag = menu.findItem(R.id.unflag); mUnflag = menu.findItem(R.id.unflag);
if (mQueryString != null) { // we don't support cross account actions atm
if (!mSingleAccountMode) {
// show all // show all
menu.findItem(R.id.move).setVisible(true); menu.findItem(R.id.move).setVisible(true);
menu.findItem(R.id.archive).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); inflater.inflate(R.menu.message_list_context, menu);
// check capabilities // check capabilities
if (mQueryString == null) { setContextCapabilities(mAccount, menu);
setContextCapabilities(mAccount, menu);
}
return true; 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 * TODO get rid of this when we finally split the messagelist into
* a folder content display and a search result display * 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.move).setVisible(false);
menu.findItem(R.id.copy).setVisible(false); menu.findItem(R.id.copy).setVisible(false);
menu.findItem(R.id.archive).setVisible(false); menu.findItem(R.id.archive).setVisible(false);
menu.findItem(R.id.spam).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.isMoveCapable(mAccount)) {
if (!mController.isCopyCapable(mAccount)) { 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);
}
if (!mController.isMoveCapable(mAccount)) { if (!mAccount.hasArchiveFolder()) {
menu.findItem(R.id.move).setVisible(false); menu.findItem(R.id.archive).setVisible(false);
menu.findItem(R.id.archive).setVisible(false); }
menu.findItem(R.id.spam).setVisible(false);
}
if (!mAccount.hasArchiveFolder()) { if (!mAccount.hasSpamFolder()) {
menu.findItem(R.id.archive).setVisible(false); 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 @Override
public void onStop() { public void onStop() {
// If we represent a remote search, then kill that before going back. // If we represent a remote search, then kill that before going back.
if (mSearchAccount != null && mSearchFolder != null && mRemoteSearchFuture != null) { if (isRemoteSearch() && mRemoteSearchFuture != null) {
try { try {
Log.i(K9.LOG_TAG, "Remote search in progress, attempting to abort..."); Log.i(K9.LOG_TAG, "Remote search in progress, attempting to abort...");
// Canceling the future stops any message fetches in progress. // 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."); Log.e(K9.LOG_TAG, "Could not cancel remote search future.");
} }
// Closing the folder will kill off the connection if we're mid-search. // Closing the folder will kill off the connection if we're mid-search.
Context appContext = getActivity().getApplicationContext(); final Account searchAccount = mAccount;
final Account searchAccount = Preferences.getPreferences(appContext).getAccount(mSearchAccount); final Folder remoteFolder = mCurrentFolder.folder;
final Store remoteStore = searchAccount.getRemoteStore();
final Folder remoteFolder = remoteStore.getFolder(mSearchFolder);
remoteFolder.close(); remoteFolder.close();
// Send a remoteSearchFinished() message for good measure. // 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) { } catch (Exception e) {
// Since the user is going back, log and squash any exceptions. // 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); 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() { public void onToggleFlag() {
Message message = getSelectedMessage(); Message message = getSelectedMessage();
if (message != null) { if (message != null) {
System.out.println("FLAGGED: " + message.isSet(Flag.FLAGGED));
setFlag(message, Flag.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() { public boolean isSearchQuery() {
return (mQueryString != null || mIntegrate); return (mSearch.getRemoteSearchArguments() != null || !mSingleAccountMode);
} }
public boolean isOutbox() { public boolean isOutbox() {
@ -2733,7 +2558,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public void onRemoteSearch() { public void onRemoteSearch() {
// Remote search is useless without the network. // Remote search is useless without the network.
if (mHasConnectivity) { if (mHasConnectivity) {
onRemoteSearchRequested(true); onRemoteSearchRequested();
} else { } else {
Toast.makeText(getActivity(), getText(R.string.remote_search_unavailable_no_network), Toast.makeText(getActivity(), getText(R.string.remote_search_unavailable_no_network),
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
@ -2745,7 +2570,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} }
public boolean isRemoteSearchAllowed() { public boolean isRemoteSearchAllowed() {
if (!isSearchQuery() || mRemoteSearch || mSearchFolder == null || mSearchAccount == null) { if (!isSearchQuery() || mRemoteSearch || !mSingleFolderMode) {
return false; return false;
} }
@ -2753,7 +2578,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
final Preferences prefs = Preferences.getPreferences(appContext); final Preferences prefs = Preferences.getPreferences(appContext);
boolean allowRemoteSearch = false; boolean allowRemoteSearch = false;
final Account searchAccount = prefs.getAccount(mSearchAccount); final Account searchAccount = mAccount;
if (searchAccount != null) { if (searchAccount != null) {
allowRemoteSearch = searchAccount.allowRemoteSearch(); allowRemoteSearch = searchAccount.allowRemoteSearch();
} }

View File

@ -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.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.provider.AttachmentProvider; import com.fsck.k9.provider.AttachmentProvider;
import com.fsck.k9.search.ConditionsTreeNode;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
/** /**
* <pre> * <pre>
@ -658,6 +661,22 @@ public class LocalStore extends Store implements Serializable {
return new LocalFolder(name); return new LocalFolder(name);
} }
private long getFolderId(final String name) throws MessagingException {
return database.execute(false, new DbCallback<Long>() {
@Override
public Long doDbWork(final SQLiteDatabase db) {
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT id FROM folders WHERE name = '" + name + "'", null);
cursor.moveToFirst();
return cursor.getLong(0);
} finally {
Utility.closeQuietly(cursor);
}
}
});
}
// TODO this takes about 260-300ms, seems slow. // TODO this takes about 260-300ms, seems slow.
@Override @Override
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException { public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
@ -890,97 +909,57 @@ public class LocalStore extends Store implements Serializable {
return true; return true;
} }
public Message[] searchForMessages(MessageRetrievalListener listener, String[] queryFields, String queryString, // TODO find beter solution
List<LocalFolder> folders, Message[] messages, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException { private static boolean isFolderId(String str) {
List<String> args = new LinkedList<String>(); if (str == null) {
return false;
StringBuilder whereClause = new StringBuilder(); }
if (queryString != null && queryString.length() > 0) { int length = str.length();
boolean anyAdded = false; if (length == 0) {
String likeString = "%" + queryString + "%"; return false;
whereClause.append(" AND ("); }
for (String queryField : queryFields) { int i = 0;
if (str.charAt(0) == '-') {
if (anyAdded) { return false;
whereClause.append(" OR "); }
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()) { return true;
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)
);
} }
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 * Given a query string, actually do the query for the messages and
* call the MessageRetrievalListener for each one * call the MessageRetrievalListener for each one

View File

@ -64,10 +64,10 @@ public class GlobalSettings {
new V(1, new DateFormatSetting(DateFormatter.DEFAULT_FORMAT)) new V(1, new DateFormatSetting(DateFormatter.DEFAULT_FORMAT))
)); ));
s.put("enableDebugLogging", Settings.versions( s.put("enableDebugLogging", Settings.versions(
new V(1, new BooleanSetting(false)) new V(1, new BooleanSetting(true))
)); ));
s.put("enableSensitiveLogging", Settings.versions( s.put("enableSensitiveLogging", Settings.versions(
new V(1, new BooleanSetting(false)) new V(1, new BooleanSetting(true))
)); ));
s.put("fontSizeAccountDescription", Settings.versions( s.put("fontSizeAccountDescription", Settings.versions(
new V(1, new FontSizeSetting(FontSizes.SMALL)) new V(1, new FontSizeSetting(FontSizes.SMALL))

View File

@ -21,7 +21,6 @@ import com.fsck.k9.Account;
import com.fsck.k9.AccountStats; import com.fsck.k9.AccountStats;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.SearchAccount;
import com.fsck.k9.activity.FolderInfoHolder; import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.MessageInfoHolder; import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.activity.MessageList; 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.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.search.SearchAccount;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
@ -302,7 +302,7 @@ public class MessageProvider extends ContentProvider {
final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext()); final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext());
final MessagingController msgController = MessagingController.getInstance(K9.app); final MessagingController msgController = MessagingController.getInstance(K9.app);
msgController.searchLocalMessages(integratedInboxAccount, null, msgController.searchLocalMessages(integratedInboxAccount.getRelatedSearch(),
new MesssageInfoHolderRetrieverListener(queue)); new MesssageInfoHolderRetrieverListener(queue));
final List<MessageInfoHolder> holders = queue.take(); final List<MessageInfoHolder> holders = queue.take();

View File

@ -8,6 +8,7 @@ import com.fsck.k9.R;
import com.fsck.k9.activity.UnreadWidgetConfiguration; import com.fsck.k9.activity.UnreadWidgetConfiguration;
import com.fsck.k9.activity.FolderList; import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList; import com.fsck.k9.activity.MessageList;
import com.fsck.k9.search.LocalSearch;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
@ -62,8 +63,10 @@ public class UnreadWidgetProvider extends AppWidgetProvider {
clickIntent = FolderList.actionHandleAccountIntent(context, account, null, clickIntent = FolderList.actionHandleAccountIntent(context, account, null,
false); false);
} else { } else {
clickIntent = MessageList.actionHandleFolderIntent(context, account, LocalSearch search = new LocalSearch(account.getAutoExpandFolderName());
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); clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
} }

View File

@ -0,0 +1,401 @@
package com.fsck.k9.search;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
/**
* This class stores search conditions. It's basically a boolean expression binary tree.
* The output will be SQL queries ( obtained by traversing inorder ).
*
* TODO removing conditions from the tree
* TODO implement NOT as a node again
*
* @author dzan
*/
public class ConditionsTreeNode implements Parcelable{
public enum OPERATOR {
AND, OR, CONDITION;
}
public ConditionsTreeNode mLeft;
public ConditionsTreeNode mRight;
public ConditionsTreeNode mParent;
/*
* If mValue isn't CONDITION then mCondition contains a real
* condition, otherwise it's null.
*/
public OPERATOR mValue;
public SearchCondition mCondition;
/*
* Used for storing and retrieving the tree to/from the database.
* The algorithm is called "modified preorder tree traversal".
*/
public int mLeftMPTTMarker;
public int mRightMPTTMarker;
///////////////////////////////////////////////////////////////
// Static Helpers to restore a tree from a database cursor
///////////////////////////////////////////////////////////////
/**
* Builds a condition tree starting from a database cursor. The cursor
* should point to rows representing the nodes of the tree.
*
* @param cursor Cursor pointing to the first of a bunch or rows. Each rows
* should contains 1 tree node.
* @return A condition tree.
*/
public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
ConditionsTreeNode tmp = null;
// root node
if (cursor.moveToFirst()) {
tmp = buildNodeFromRow(cursor);
stack.push(tmp);
}
// other nodes
while (cursor.moveToNext()) {
tmp = buildNodeFromRow(cursor);
if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
stack.peek().mLeft = tmp;
stack.push(tmp);
} else {
while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
stack.pop();
}
stack.peek().mRight = tmp;
}
}
return tmp;
}
/**
* Converts a single database row to a single condition node.
*
* @param cursor Cursor pointing to the row we want to convert.
* @return A single ConditionsTreeNode
*/
private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
ConditionsTreeNode result = null;
SearchCondition condition = null;
OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
if (tmpValue == OPERATOR.CONDITION) {
condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
}
result = new ConditionsTreeNode(condition);
result.mValue = tmpValue;
result.mLeftMPTTMarker = cursor.getInt(3);
result.mRightMPTTMarker = cursor.getInt(4);
return result;
}
///////////////////////////////////////////////////////////////
// Constructors
///////////////////////////////////////////////////////////////
public ConditionsTreeNode(SearchCondition condition) {
mParent = null;
mCondition = condition;
mValue = OPERATOR.CONDITION;
}
public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
mParent = parent;
mValue = op;
mCondition = null;
}
///////////////////////////////////////////////////////////////
// Public modifiers
///////////////////////////////////////////////////////////////
/**
* Adds the expression as the second argument of an AND
* clause to this node.
*
* @param expr Expression to 'AND' with.
* @return New top AND node.
* @throws Exception
*/
public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
return add(expr, OPERATOR.AND);
}
/**
* Convenience method.
* Adds the provided condition as the second argument of an AND
* clause to this node.
*
* @param condition Condition to 'AND' with.
* @return New top AND node, new root.
*/
public ConditionsTreeNode and(SearchCondition condition) {
try {
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
return and(tmp);
} catch (Exception e) {
// impossible
return null;
}
}
/**
* Adds the expression as the second argument of an OR
* clause to this node.
*
* @param expr Expression to 'OR' with.
* @return New top OR node.
* @throws Exception
*/
public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
return add(expr, OPERATOR.OR);
}
/**
* Convenience method.
* Adds the provided condition as the second argument of an OR
* clause to this node.
*
* @param condition Condition to 'OR' with.
* @return New top OR node, new root.
*/
public ConditionsTreeNode or(SearchCondition condition) {
try {
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
return or(tmp);
} catch (Exception e) {
// impossible
return null;
}
}
/**
* This applies the MPTT labeling to the subtree of which this node
* is the root node.
*
* For a description on MPTT see:
* http://www.sitepoint.com/hierarchical-data-database-2/
*/
public void applyMPTTLabel() {
applyMPTTLabel(1);
}
///////////////////////////////////////////////////////////////
// Public accessors
///////////////////////////////////////////////////////////////
/**
* Returns the condition stored in this node.
* @return Condition stored in the node.
*/
public SearchCondition getCondition() {
return mCondition;
}
/**
* This will traverse the tree inorder and call toString recursively resulting
* in a valid SQL where clause.
*/
@Override
public String toString() {
return (mLeft == null ? "" : "(" + mLeft + ")")
+ " " + ( mCondition == null ? mValue.name() : mCondition ) + " "
+ (mRight == null ? "" : "(" + mRight + ")") ;
}
/**
* Get a set of all the leaves in the tree.
* @return Set of all the leaves.
*/
public HashSet<ConditionsTreeNode> getLeafSet() {
HashSet<ConditionsTreeNode> leafSet = new HashSet<ConditionsTreeNode>();
return getLeafSet(leafSet);
}
/**
* Returns a list of all the nodes in the subtree of which this node
* is the root. The list contains the nodes in a pre traversal order.
*
* @return List of all nodes in subtree in preorder.
*/
public List<ConditionsTreeNode> preorder() {
ArrayList<ConditionsTreeNode> result = new ArrayList<ConditionsTreeNode>();
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
stack.push(this);
while(!stack.isEmpty()) {
ConditionsTreeNode current = stack.pop( );
if( current.mLeft != null ) stack.push( current.mLeft );
if( current.mRight != null ) stack.push( current.mRight );
result.add(current);
}
return result;
}
///////////////////////////////////////////////////////////////
// Private class logic
///////////////////////////////////////////////////////////////
/**
* Adds two new ConditionTreeNodes, one for the operator and one for the
* new condition. The current node will end up on the same level as the
* one provided in the arguments, they will be siblings. Their common
* parent node will be one containing the operator provided in the arguments.
* The method will update all the required references so the tree ends up in
* a valid state.
*
* This method only supports node arguments with a null parent node.
*
* @param Node to add.
* @param Operator that will connect the new node with this one.
* @return New parent node, containing the operator.
* @throws Exception Throws when the provided new node does not have a null parent.
*/
private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
if (node.mParent != null) {
throw new Exception("Can only add new expressions from root node down.");
}
ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
tmpNode.mLeft = this;
tmpNode.mRight = node;
if (mParent != null) {
mParent.updateChild(this, tmpNode);
}
this.mParent = tmpNode;
node.mParent = tmpNode;
return tmpNode;
}
/**
* Helper method that replaces a child of the current node with a new node.
* If the provided old child node was the left one, left will be replaced with
* the new one. Same goes for the right one.
*
* @param oldChild Old child node to be replaced.
* @param newChild New child node.
*/
private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
// we can compare objects id's because this is the desired behaviour in this case
if (mLeft == oldChild) {
mLeft = newChild;
} else if (mRight == oldChild) {
mRight = newChild;
}
}
/**
* Recursive function to gather all the leaves in the subtree of which
* this node is the root.
*
* @param leafSet Leafset that's being built.
* @return Set of leaves being completed.
*/
private HashSet<ConditionsTreeNode> getLeafSet(HashSet<ConditionsTreeNode> leafSet) {
// if we ended up in a leaf, add ourself and return
if (mLeft == null && mRight == null) {
leafSet.add(this);
return leafSet;
// we didn't end up in a leaf
} else {
if (mLeft != null) {
mLeft.getLeafSet(leafSet);
}
if (mRight != null) {
mRight.getLeafSet(leafSet);
}
return leafSet;
}
}
/**
* This applies the MPTT labeling to the subtree of which this node
* is the root node.
*
* For a description on MPTT see:
* http://www.sitepoint.com/hierarchical-data-database-2/
*/
private int applyMPTTLabel(int label) {
mLeftMPTTMarker = label;
if (mLeft != null){
label = mLeft.applyMPTTLabel(label += 1);
}
if (mRight != null){
label = mRight.applyMPTTLabel(label += 1);
}
++label;
mRightMPTTMarker = label;
return label;
}
///////////////////////////////////////////////////////////////
// Parcelable
//
// This whole class has to be parcelable because it's passed
// on through intents.
///////////////////////////////////////////////////////////////
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mValue.ordinal());
dest.writeParcelable(mCondition, flags);
dest.writeParcelable(mLeft, flags);
dest.writeParcelable(mRight, flags);
}
public static final Parcelable.Creator<ConditionsTreeNode> CREATOR
= new Parcelable.Creator<ConditionsTreeNode>() {
public ConditionsTreeNode createFromParcel(Parcel in) {
return new ConditionsTreeNode(in);
}
public ConditionsTreeNode[] newArray(int size) {
return new ConditionsTreeNode[size];
}
};
private ConditionsTreeNode(Parcel in) {
mValue = OPERATOR.values()[in.readInt()];
mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
mParent = null;
if (mLeft != null) {
mLeft.mParent = this;
}
if (mRight != null) {
mRight.mParent = this;
}
}
}

View File

@ -0,0 +1,388 @@
package com.fsck.k9.search;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.os.Parcel;
import android.os.Parcelable;
import com.fsck.k9.mail.Flag;
/**
* This class represents a local search.
* Removing conditions could be done through matching there unique id in the leafset and then
* removing them from the tree.
*
* @author dzan
*
* TODO implement a complete addAllowedFolder method
* TODO conflicting conditions check on add
* TODO duplicate condition checking?
* TODO assign each node a unique id that's used to retrieve it from the leaveset and remove.
*
*/
public class LocalSearch implements SearchSpecification {
private String mName;
private boolean mPredefined;
// since the uuid isn't in the message table it's not in the tree neither
private HashSet<String> mAccountUuids = new HashSet<String>();
private ConditionsTreeNode mConditions = null;
private HashSet<ConditionsTreeNode> mLeafSet = new HashSet<ConditionsTreeNode>();
///////////////////////////////////////////////////////////////
// Constructors
///////////////////////////////////////////////////////////////
/**
* Use this only if the search won't be saved. Saved searches need
* a name!
*/
public LocalSearch(){}
/**
*
* @param name
*/
public LocalSearch(String name) {
this.mName = name;
}
/**
* Use this constructor when you know what you'r doing. Normally it's only used
* when restoring these search objects from the database.
*
* @param name Name of the search
* @param searchConditions SearchConditions, may contains flags and folders
* @param accounts Relative Account's uuid's
* @param predefined Is this a predefined search or a user created one?
*/
protected LocalSearch(String name, ConditionsTreeNode searchConditions,
String accounts, boolean predefined) {
this(name);
mConditions = searchConditions;
mPredefined = predefined;
mLeafSet = new HashSet<ConditionsTreeNode>();
if (mConditions != null) {
mLeafSet.addAll(mConditions.getLeafSet());
}
// initialize accounts
if (accounts != null) {
for (String account : accounts.split(",")) {
mAccountUuids.add(account);
}
} else {
// impossible but still not unrecoverable
}
}
///////////////////////////////////////////////////////////////
// Public manipulation methods
///////////////////////////////////////////////////////////////
/**
* Sets the name of the saved search. If one existed it will
* be overwritten.
*
* @param name Name to be set.
*/
public void setName(String name) {
this.mName = name;
}
/**
* Add a new account to the search. When no accounts are
* added manually we search all accounts on the device.
*
* @param uuid Uuid of the account to be added.
*/
public void addAccountUuid(String uuid) {
if (uuid.equals(ALL_ACCOUNTS)) {
mAccountUuids.clear();
}
mAccountUuids.add(uuid);
}
/**
* Adds all the account uuids in the provided array to
* be matched by the seach.
*
* @param accountUuids
*/
public void addAccountUuids(String[] accountUuids) {
for (String acc : accountUuids) {
addAccountUuid(acc);
}
}
/**
* Removes an account UUID from the current search.
*
* @param uuid Account UUID to remove.
* @return True if removed, false otherwise.
*/
public boolean removeAccountUuid(String uuid) {
return mAccountUuids.remove(uuid);
}
/**
* Adds the provided node as the second argument of an AND
* clause to this node.
*
* @param field Message table field to match against.
* @param string Value to look for.
* @param contains Attribute to use when matching.
*
* @throws IllegalConditionException
*/
public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
and(new SearchCondition(field, attribute, value));
}
/**
* Adds the provided condition as the second argument of an AND
* clause to this node.
*
* @param condition Condition to 'AND' with.
* @return New top AND node, new root.
*/
public ConditionsTreeNode and(SearchCondition condition) {
try {
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
return and(tmp);
} catch (Exception e) {
// impossible
return null;
}
}
/**
* Adds the provided node as the second argument of an AND
* clause to this node.
*
* @param node Node to 'AND' with.
* @return New top AND node, new root.
* @throws Exception
*/
public ConditionsTreeNode and(ConditionsTreeNode node) throws Exception {
mLeafSet.addAll(node.getLeafSet());
if (mConditions == null) {
mConditions = node;
return node;
}
return mConditions.and(node);
}
/**
* Adds the provided condition as the second argument of an OR
* clause to this node.
*
* @param condition Condition to 'OR' with.
* @return New top OR node, new root.
*/
public ConditionsTreeNode or(SearchCondition condition) {
try {
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
return or(tmp);
} catch (Exception e) {
// impossible
return null;
}
}
/**
* Adds the provided node as the second argument of an OR
* clause to this node.
*
* @param node Node to 'OR' with.
* @return New top OR node, new root.
* @throws Exception
*/
public ConditionsTreeNode or(ConditionsTreeNode node) throws Exception {
mLeafSet.addAll(node.getLeafSet());
if (mConditions == null) {
mConditions = node;
return node;
}
return mConditions.or(node);
}
/**
* Add all the flags to this node as required flags. The
* provided flags will be combined using AND with the root.
*
* @param requiredFlags Array of required flags.
*/
public void allRequiredFlags(Flag[] requiredFlags) {
if (requiredFlags != null) {
for (Flag f : requiredFlags) {
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
}
}
}
/**
* Add all the flags to this node as forbidden flags. The
* provided flags will be combined using AND with the root.
*
* @param forbiddenFlags Array of forbidden flags.
*/
public void allForbiddenFlags(Flag[] forbiddenFlags) {
if (forbiddenFlags != null) {
for (Flag f : forbiddenFlags) {
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
}
}
}
/**
* TODO
* FOR NOW: And the folder with the root.
*
* Add the folder as another folder to search in. The folder
* will be added AND to the root if no 'folder subtree' was found.
* Otherwise the folder will be added OR to that tree.
*
* @param name Name of the folder to add.
*/
public void addAllowedFolder(String name) {
/*
* TODO find folder sub-tree
* - do and on root of it & rest of search
* - do or between folder nodes
*/
and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
}
/*
* TODO make this more advanced!
* This is a temporarely solution that does NOT WORK for
* real searches because of possible extra conditions to a folder requirement.
*/
public List<String> getFolderNames() {
ArrayList<String> results = new ArrayList<String>();
for (ConditionsTreeNode node : mLeafSet) {
if (node.mCondition.field == SEARCHFIELD.FOLDER
&& node.mCondition.attribute == ATTRIBUTE.EQUALS) {
results.add(node.mCondition.value);
}
}
return results;
}
/**
* Gets the leafset of the related condition tree.
*
* @return All the leaf conditions as a set.
*/
public Set<ConditionsTreeNode> getLeafSet() {
return mLeafSet;
}
///////////////////////////////////////////////////////////////
// Public accesor methods
///////////////////////////////////////////////////////////////
/**
* TODO THIS HAS TO GO!!!!
* very dirty fix for remotesearch support atm
*/
public String getRemoteSearchArguments() {
for (ConditionsTreeNode node : getLeafSet()) {
if (node.getCondition().field == SEARCHFIELD.SUBJECT
|| node.getCondition().field == SEARCHFIELD.SENDER ) {
return node.getCondition().value;
}
}
return null;
}
/**
* Returns the name of the saved search.
*
* @return Name of the search.
*/
public String getName() {
return (mName == null ? "" : mName);
}
/**
* Checks if this search was hard coded and shipped with K-9
*
* @return True is search was shipped with K-9
*/
public boolean isPredefined() {
return mPredefined;
}
/**
* Returns all the account uuids that this search will try to
* match against.
*
* @return Array of account uuids.
*/
@Override
public String[] getAccountUuids() {
if (mAccountUuids.size() == 0) {
return new String[] {SearchSpecification.ALL_ACCOUNTS};
}
String[] tmp = new String[mAccountUuids.size()];
mAccountUuids.toArray(tmp);
return tmp;
}
/**
* Get the condition tree.
*
* @return The root node of the related conditions tree.
*/
@Override
public ConditionsTreeNode getConditions() {
return mConditions;
}
///////////////////////////////////////////////////////////////
// Parcelable
///////////////////////////////////////////////////////////////
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeByte((byte) (mPredefined ? 1 : 0));
dest.writeStringList(new ArrayList<String>(mAccountUuids));
dest.writeParcelable(mConditions, flags);
}
public static final Parcelable.Creator<LocalSearch> CREATOR
= new Parcelable.Creator<LocalSearch>() {
public LocalSearch createFromParcel(Parcel in) {
return new LocalSearch(in);
}
public LocalSearch[] newArray(int size) {
return new LocalSearch[size];
}
};
public LocalSearch(Parcel in) {
mName = in.readString();
mPredefined = in.readByte() == 1;
mAccountUuids.addAll(in.createStringArrayList());
mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
mLeafSet = mConditions.getLeafSet();
}
}

View File

@ -0,0 +1,87 @@
package com.fsck.k9.search;
import java.util.UUID;
import android.content.Context;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.R;
/**
* This class is basically a wrapper around a LocalSearch. It allows to expose it as
* an account. This is a meta-account containing all the e-mail that matches the search.
*/
public class SearchAccount implements BaseAccount {
// create the all messages search ( all accounts is default when none specified )
public static SearchAccount createAllMessagesAccount(Context context) {
String name = context.getString(R.string.search_all_messages_title);
LocalSearch tmpSearch = new LocalSearch(name);
return new SearchAccount(tmpSearch, name,
context.getString(R.string.search_all_messages_detail));
}
// create the unified inbox meta account ( all accounts is default when none specified )
public static SearchAccount createUnifiedInboxAccount(Context context) {
String name = context.getString(R.string.integrated_inbox_title);
LocalSearch tmpSearch = new LocalSearch(name);
tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
return new SearchAccount(tmpSearch, name,
context.getString(R.string.integrated_inbox_detail));
}
private String mEmail = null;
private String mDescription = null;
private LocalSearch mSearch = null;
private String mFakeUuid = null;
public SearchAccount(LocalSearch search, String description, String email) throws IllegalArgumentException{
if (search == null) {
throw new IllegalArgumentException("Provided LocalSearch was null");
}
this.mSearch = search;
this.mDescription = description;
this.mEmail = email;
}
@Override
public synchronized String getEmail() {
return mEmail;
}
@Override
public synchronized void setEmail(String email) {
this.mEmail = email;
}
@Override
public String getDescription() {
return mDescription;
}
@Override
public void setDescription(String description) {
this.mDescription = description;
}
public LocalSearch getRelatedSearch() {
return mSearch;
}
@Override
/*
* This will only be used when accessed as an Account. If that
* is the case we don't want to return the uuid of a real account since
* this is posing as a fake meta-account. If this object is accesed as
* a Search then methods from LocalSearch will be called which do handle
* things nice.
*/
public String getUuid() {
if (mFakeUuid == null){
mFakeUuid = UUID.randomUUID().toString();
}
return mFakeUuid;
}
}

View File

@ -1,18 +1,18 @@
package com.fsck.k9.activity; package com.fsck.k9.search;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.mail.Flag; 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), FLAGGED(R.string.flagged_modifier, new Flag[]{Flag.FLAGGED}, null),
UNREAD(R.string.unread_modifier, null, new Flag[]{Flag.SEEN}); UNREAD(R.string.unread_modifier, null, new Flag[]{Flag.SEEN});
final int resId; public final int resId;
final Flag[] requiredFlags; public final Flag[] requiredFlags;
final Flag[] forbiddenFlags; public final Flag[] forbiddenFlags;
SearchModifier(int nResId, Flag[] nRequiredFlags, Flag[] nForbiddenFlags) { SearchModifier(int nResId, Flag[] nRequiredFlags, Flag[] nForbiddenFlags) {
resId = nResId; resId = nResId;

View File

@ -0,0 +1,184 @@
package com.fsck.k9.search;
import android.os.Parcel;
import android.os.Parcelable;
public interface SearchSpecification extends Parcelable {
/**
* Get all the uuids of accounts this search acts on.
* @return Array of uuids.
*/
public String[] getAccountUuids();
/**
* Returns the search's name if it was named.
* @return Name of the search.
*/
public String getName();
/**
* Returns the root node of the condition tree accompanying
* the search.
*
* @return Root node of conditions tree.
*/
public ConditionsTreeNode getConditions();
/*
* Some meta names for certain conditions.
*/
public static final String ALL_ACCOUNTS = "allAccounts";
public static final String GENERIC_INBOX_NAME = "genericInboxName";
///////////////////////////////////////////////////////////////
// ATTRIBUTE enum
///////////////////////////////////////////////////////////////
public enum ATTRIBUTE {
CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
private boolean mNegation;
private ATTRIBUTE(boolean negation) {
this.mNegation = negation;
}
public String formQuery(String value) {
String queryPart = "";
switch (this) {
case NOT_CONTAINS:
case CONTAINS:
queryPart = "'%"+value+"%'";
break;
case NOT_EQUALS:
case EQUALS:
queryPart = "'"+value+"'";
break;
case NOT_STARTSWITH:
case STARTSWITH:
queryPart = "'%"+value+"'";
break;
case NOT_ENDSWITH:
case ENDSWITH:
queryPart = "'"+value+"%'";
break;
default: queryPart = "'"+value+"'";
}
return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
}
};
///////////////////////////////////////////////////////////////
// SEARCHFIELD enum
///////////////////////////////////////////////////////////////
/*
* Using an enum in order to have more robust code. Users ( & coders )
* are prevented from passing illegal fields. No database overhead
* when invalid fields passed.
*
* By result, only the fields in here are searchable.
*
* Fields not in here at this moment ( and by effect not searchable ):
* id, html_content, internal_date, message_id,
* preview, mime_type
*
*/
public enum SEARCHFIELD {
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
ATTACHMENT_COUNT("attachment_count"), DELETED("deleted"), THREAD_ROOT("thread_root");
private String dbName;
private SEARCHFIELD(String dbName) {
this.dbName = dbName;
}
public String getDatabaseName() {
return dbName;
}
}
///////////////////////////////////////////////////////////////
// SearchCondition class
///////////////////////////////////////////////////////////////
/**
* This class represents 1 value for a certain search field. One
* value consists of three things:
* an attribute: equals, starts with, contains,...
* a searchfield: date, flags, sender, subject,...
* a value: "apple", "jesse",..
*
* @author dzan
*/
public class SearchCondition implements Parcelable{
public String value;
public ATTRIBUTE attribute;
public SEARCHFIELD field;
public SearchCondition(SEARCHFIELD field, ATTRIBUTE attribute, String value) {
this.value = value;
this.attribute = attribute;
this.field = field;
}
private SearchCondition(Parcel in) {
this.value = in.readString();
this.attribute = ATTRIBUTE.values()[in.readInt()];
this.field = SEARCHFIELD.values()[in.readInt()];
}
public String toHumanString() {
return field.toString() + attribute.toString();
}
@Override
public String toString() {
return field.getDatabaseName() + attribute.formQuery(value);
}
@Override
public boolean equals(Object o) {
if (o instanceof SearchCondition) {
SearchCondition tmp = (SearchCondition) o;
if (tmp.attribute == attribute
&& tmp.value.equals(value)
&& tmp.field == field) {
return true;
} else {
return false;
}
} else {
return false;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(value);
dest.writeInt(attribute.ordinal());
dest.writeInt(field.ordinal());
}
public static final Parcelable.Creator<SearchCondition> CREATOR
= new Parcelable.Creator<SearchCondition>() {
public SearchCondition createFromParcel(Parcel in) {
return new SearchCondition(in);
}
public SearchCondition[] newArray(int size) {
return new SearchCondition[size];
}
};
}
}