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:
commit
83d5102f3d
@ -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);
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Flag;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a meta-Account that represents one or more accounts with filters on them. The filter specification
|
|
||||||
* is defined by {@link com.fsck.k9.activity.SearchModifier}.
|
|
||||||
*/
|
|
||||||
public class SearchAccount implements BaseAccount, SearchSpecification, Serializable {
|
|
||||||
/**
|
|
||||||
* Create a {@code SearchAccount} instance for the Unified Inbox.
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* A {@link Context} instance that will be used to get localized strings and will be
|
|
||||||
* passed on to the {@code SearchAccount} instance.
|
|
||||||
*
|
|
||||||
* @return The {@link SearchAccount} instance for the Unified Inbox.
|
|
||||||
*/
|
|
||||||
public static SearchAccount createUnifiedInboxAccount(Context context) {
|
|
||||||
SearchAccount unifiedInbox = new SearchAccount(context, true, null, null);
|
|
||||||
unifiedInbox.setDescription(context.getString(R.string.integrated_inbox_title));
|
|
||||||
unifiedInbox.setEmail(context.getString(R.string.integrated_inbox_detail));
|
|
||||||
return unifiedInbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a {@code SearchAccount} instance for the special account "All messages".
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* A {@link Context} instance that will be used to get localized strings and will be
|
|
||||||
* passed on to the {@code SearchAccount} instance.
|
|
||||||
*
|
|
||||||
* @return The {@link SearchAccount} instance for the Unified Inbox.
|
|
||||||
*/
|
|
||||||
public static SearchAccount createAllMessagesAccount(Context context) {
|
|
||||||
SearchAccount allMessages = new SearchAccount(context, false, null, null);
|
|
||||||
allMessages.setDescription(context.getString(R.string.search_all_messages_title));
|
|
||||||
allMessages.setEmail(context.getString(R.string.search_all_messages_detail));
|
|
||||||
return allMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -4388420303235543976L;
|
|
||||||
private Flag[] mRequiredFlags = null;
|
|
||||||
private Flag[] mForbiddenFlags = null;
|
|
||||||
private String email = null;
|
|
||||||
private String description = null;
|
|
||||||
private String query = "";
|
|
||||||
private boolean integrate = false;
|
|
||||||
private String mUuid = null;
|
|
||||||
private boolean builtin = false;
|
|
||||||
private String[] accountUuids = null;
|
|
||||||
private String[] folderNames = null;
|
|
||||||
|
|
||||||
public SearchAccount(Preferences preferences) {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected synchronized void delete(Preferences preferences) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void save(Preferences preferences) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public SearchAccount(Context context, boolean nintegrate, Flag[] requiredFlags, Flag[] forbiddenFlags) {
|
|
||||||
mRequiredFlags = requiredFlags;
|
|
||||||
mForbiddenFlags = forbiddenFlags;
|
|
||||||
integrate = nintegrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Flag[] getRequiredFlags() {
|
|
||||||
return mRequiredFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Flag[] getForbiddenFlags() {
|
|
||||||
return mForbiddenFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIntegrate() {
|
|
||||||
return integrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription(String description) {
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuery() {
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuery(String query) {
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUuid() {
|
|
||||||
if (mUuid == null) {
|
|
||||||
setUuid(UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
return mUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUuid(String nUuid) {
|
|
||||||
mUuid = nUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIntegrate(boolean integrate) {
|
|
||||||
this.integrate = integrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBuiltin() {
|
|
||||||
return builtin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBuiltin(boolean builtin) {
|
|
||||||
this.builtin = builtin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getAccountUuids() {
|
|
||||||
return accountUuids;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccountUuids(String[] accountUuids) {
|
|
||||||
this.accountUuids = accountUuids;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getFolderNames() {
|
|
||||||
return folderNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFolderNames(String[] folderNames) {
|
|
||||||
this.folderNames = folderNames;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Flag;
|
|
||||||
|
|
||||||
public interface SearchSpecification {
|
|
||||||
|
|
||||||
public Flag[] getRequiredFlags();
|
|
||||||
|
|
||||||
public Flag[] getForbiddenFlags();
|
|
||||||
|
|
||||||
public boolean isIntegrate();
|
|
||||||
|
|
||||||
public String getQuery();
|
|
||||||
|
|
||||||
public String[] getAccountUuids();
|
|
||||||
|
|
||||||
public String[] getFolderNames();
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ import com.fsck.k9.FontSizes;
|
|||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.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;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
401
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
401
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class stores search conditions. It's basically a boolean expression binary tree.
|
||||||
|
* The output will be SQL queries ( obtained by traversing inorder ).
|
||||||
|
*
|
||||||
|
* TODO removing conditions from the tree
|
||||||
|
* TODO implement NOT as a node again
|
||||||
|
*
|
||||||
|
* @author dzan
|
||||||
|
*/
|
||||||
|
public class ConditionsTreeNode implements Parcelable{
|
||||||
|
|
||||||
|
public enum OPERATOR {
|
||||||
|
AND, OR, CONDITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionsTreeNode mLeft;
|
||||||
|
public ConditionsTreeNode mRight;
|
||||||
|
public ConditionsTreeNode mParent;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If mValue isn't CONDITION then mCondition contains a real
|
||||||
|
* condition, otherwise it's null.
|
||||||
|
*/
|
||||||
|
public OPERATOR mValue;
|
||||||
|
public SearchCondition mCondition;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used for storing and retrieving the tree to/from the database.
|
||||||
|
* The algorithm is called "modified preorder tree traversal".
|
||||||
|
*/
|
||||||
|
public int mLeftMPTTMarker;
|
||||||
|
public int mRightMPTTMarker;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Static Helpers to restore a tree from a database cursor
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Builds a condition tree starting from a database cursor. The cursor
|
||||||
|
* should point to rows representing the nodes of the tree.
|
||||||
|
*
|
||||||
|
* @param cursor Cursor pointing to the first of a bunch or rows. Each rows
|
||||||
|
* should contains 1 tree node.
|
||||||
|
* @return A condition tree.
|
||||||
|
*/
|
||||||
|
public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
|
||||||
|
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||||
|
ConditionsTreeNode tmp = null;
|
||||||
|
|
||||||
|
// root node
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
tmp = buildNodeFromRow(cursor);
|
||||||
|
stack.push(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// other nodes
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
tmp = buildNodeFromRow(cursor);
|
||||||
|
if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
|
||||||
|
stack.peek().mLeft = tmp;
|
||||||
|
stack.push(tmp);
|
||||||
|
} else {
|
||||||
|
while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
stack.peek().mRight = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a single database row to a single condition node.
|
||||||
|
*
|
||||||
|
* @param cursor Cursor pointing to the row we want to convert.
|
||||||
|
* @return A single ConditionsTreeNode
|
||||||
|
*/
|
||||||
|
private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
|
||||||
|
ConditionsTreeNode result = null;
|
||||||
|
SearchCondition condition = null;
|
||||||
|
|
||||||
|
OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
|
||||||
|
|
||||||
|
if (tmpValue == OPERATOR.CONDITION) {
|
||||||
|
condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
|
||||||
|
ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new ConditionsTreeNode(condition);
|
||||||
|
result.mValue = tmpValue;
|
||||||
|
result.mLeftMPTTMarker = cursor.getInt(3);
|
||||||
|
result.mRightMPTTMarker = cursor.getInt(4);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Constructors
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
public ConditionsTreeNode(SearchCondition condition) {
|
||||||
|
mParent = null;
|
||||||
|
mCondition = condition;
|
||||||
|
mValue = OPERATOR.CONDITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
|
||||||
|
mParent = parent;
|
||||||
|
mValue = op;
|
||||||
|
mCondition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public modifiers
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Adds the expression as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param expr Expression to 'AND' with.
|
||||||
|
* @return New top AND node.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
|
||||||
|
return add(expr, OPERATOR.AND);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method.
|
||||||
|
* Adds the provided condition as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'AND' with.
|
||||||
|
* @return New top AND node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return and(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the expression as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param expr Expression to 'OR' with.
|
||||||
|
* @return New top OR node.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
|
||||||
|
return add(expr, OPERATOR.OR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method.
|
||||||
|
* Adds the provided condition as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'OR' with.
|
||||||
|
* @return New top OR node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return or(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This applies the MPTT labeling to the subtree of which this node
|
||||||
|
* is the root node.
|
||||||
|
*
|
||||||
|
* For a description on MPTT see:
|
||||||
|
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||||
|
*/
|
||||||
|
public void applyMPTTLabel() {
|
||||||
|
applyMPTTLabel(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public accessors
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Returns the condition stored in this node.
|
||||||
|
* @return Condition stored in the node.
|
||||||
|
*/
|
||||||
|
public SearchCondition getCondition() {
|
||||||
|
return mCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will traverse the tree inorder and call toString recursively resulting
|
||||||
|
* in a valid SQL where clause.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return (mLeft == null ? "" : "(" + mLeft + ")")
|
||||||
|
+ " " + ( mCondition == null ? mValue.name() : mCondition ) + " "
|
||||||
|
+ (mRight == null ? "" : "(" + mRight + ")") ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a set of all the leaves in the tree.
|
||||||
|
* @return Set of all the leaves.
|
||||||
|
*/
|
||||||
|
public HashSet<ConditionsTreeNode> getLeafSet() {
|
||||||
|
HashSet<ConditionsTreeNode> leafSet = new HashSet<ConditionsTreeNode>();
|
||||||
|
return getLeafSet(leafSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all the nodes in the subtree of which this node
|
||||||
|
* is the root. The list contains the nodes in a pre traversal order.
|
||||||
|
*
|
||||||
|
* @return List of all nodes in subtree in preorder.
|
||||||
|
*/
|
||||||
|
public List<ConditionsTreeNode> preorder() {
|
||||||
|
ArrayList<ConditionsTreeNode> result = new ArrayList<ConditionsTreeNode>();
|
||||||
|
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||||
|
stack.push(this);
|
||||||
|
|
||||||
|
while(!stack.isEmpty()) {
|
||||||
|
ConditionsTreeNode current = stack.pop( );
|
||||||
|
if( current.mLeft != null ) stack.push( current.mLeft );
|
||||||
|
if( current.mRight != null ) stack.push( current.mRight );
|
||||||
|
result.add(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Private class logic
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Adds two new ConditionTreeNodes, one for the operator and one for the
|
||||||
|
* new condition. The current node will end up on the same level as the
|
||||||
|
* one provided in the arguments, they will be siblings. Their common
|
||||||
|
* parent node will be one containing the operator provided in the arguments.
|
||||||
|
* The method will update all the required references so the tree ends up in
|
||||||
|
* a valid state.
|
||||||
|
*
|
||||||
|
* This method only supports node arguments with a null parent node.
|
||||||
|
*
|
||||||
|
* @param Node to add.
|
||||||
|
* @param Operator that will connect the new node with this one.
|
||||||
|
* @return New parent node, containing the operator.
|
||||||
|
* @throws Exception Throws when the provided new node does not have a null parent.
|
||||||
|
*/
|
||||||
|
private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
|
||||||
|
if (node.mParent != null) {
|
||||||
|
throw new Exception("Can only add new expressions from root node down.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
|
||||||
|
tmpNode.mLeft = this;
|
||||||
|
tmpNode.mRight = node;
|
||||||
|
|
||||||
|
if (mParent != null) {
|
||||||
|
mParent.updateChild(this, tmpNode);
|
||||||
|
}
|
||||||
|
this.mParent = tmpNode;
|
||||||
|
|
||||||
|
node.mParent = tmpNode;
|
||||||
|
|
||||||
|
return tmpNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that replaces a child of the current node with a new node.
|
||||||
|
* If the provided old child node was the left one, left will be replaced with
|
||||||
|
* the new one. Same goes for the right one.
|
||||||
|
*
|
||||||
|
* @param oldChild Old child node to be replaced.
|
||||||
|
* @param newChild New child node.
|
||||||
|
*/
|
||||||
|
private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
|
||||||
|
// we can compare objects id's because this is the desired behaviour in this case
|
||||||
|
if (mLeft == oldChild) {
|
||||||
|
mLeft = newChild;
|
||||||
|
} else if (mRight == oldChild) {
|
||||||
|
mRight = newChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive function to gather all the leaves in the subtree of which
|
||||||
|
* this node is the root.
|
||||||
|
*
|
||||||
|
* @param leafSet Leafset that's being built.
|
||||||
|
* @return Set of leaves being completed.
|
||||||
|
*/
|
||||||
|
private HashSet<ConditionsTreeNode> getLeafSet(HashSet<ConditionsTreeNode> leafSet) {
|
||||||
|
// if we ended up in a leaf, add ourself and return
|
||||||
|
if (mLeft == null && mRight == null) {
|
||||||
|
leafSet.add(this);
|
||||||
|
return leafSet;
|
||||||
|
// we didn't end up in a leaf
|
||||||
|
} else {
|
||||||
|
if (mLeft != null) {
|
||||||
|
mLeft.getLeafSet(leafSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRight != null) {
|
||||||
|
mRight.getLeafSet(leafSet);
|
||||||
|
}
|
||||||
|
return leafSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This applies the MPTT labeling to the subtree of which this node
|
||||||
|
* is the root node.
|
||||||
|
*
|
||||||
|
* For a description on MPTT see:
|
||||||
|
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||||
|
*/
|
||||||
|
private int applyMPTTLabel(int label) {
|
||||||
|
mLeftMPTTMarker = label;
|
||||||
|
if (mLeft != null){
|
||||||
|
label = mLeft.applyMPTTLabel(label += 1);
|
||||||
|
}
|
||||||
|
if (mRight != null){
|
||||||
|
label = mRight.applyMPTTLabel(label += 1);
|
||||||
|
}
|
||||||
|
++label;
|
||||||
|
mRightMPTTMarker = label;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Parcelable
|
||||||
|
//
|
||||||
|
// This whole class has to be parcelable because it's passed
|
||||||
|
// on through intents.
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mValue.ordinal());
|
||||||
|
dest.writeParcelable(mCondition, flags);
|
||||||
|
dest.writeParcelable(mLeft, flags);
|
||||||
|
dest.writeParcelable(mRight, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<ConditionsTreeNode> CREATOR
|
||||||
|
= new Parcelable.Creator<ConditionsTreeNode>() {
|
||||||
|
public ConditionsTreeNode createFromParcel(Parcel in) {
|
||||||
|
return new ConditionsTreeNode(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionsTreeNode[] newArray(int size) {
|
||||||
|
return new ConditionsTreeNode[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private ConditionsTreeNode(Parcel in) {
|
||||||
|
mValue = OPERATOR.values()[in.readInt()];
|
||||||
|
mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||||
|
mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||||
|
mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||||
|
mParent = null;
|
||||||
|
if (mLeft != null) {
|
||||||
|
mLeft.mParent = this;
|
||||||
|
}
|
||||||
|
if (mRight != null) {
|
||||||
|
mRight.mParent = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
388
src/com/fsck/k9/search/LocalSearch.java
Normal file
388
src/com/fsck/k9/search/LocalSearch.java
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a local search.
|
||||||
|
|
||||||
|
* Removing conditions could be done through matching there unique id in the leafset and then
|
||||||
|
* removing them from the tree.
|
||||||
|
*
|
||||||
|
* @author dzan
|
||||||
|
*
|
||||||
|
* TODO implement a complete addAllowedFolder method
|
||||||
|
* TODO conflicting conditions check on add
|
||||||
|
* TODO duplicate condition checking?
|
||||||
|
* TODO assign each node a unique id that's used to retrieve it from the leaveset and remove.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LocalSearch implements SearchSpecification {
|
||||||
|
|
||||||
|
private String mName;
|
||||||
|
private boolean mPredefined;
|
||||||
|
|
||||||
|
// since the uuid isn't in the message table it's not in the tree neither
|
||||||
|
private HashSet<String> mAccountUuids = new HashSet<String>();
|
||||||
|
private ConditionsTreeNode mConditions = null;
|
||||||
|
private HashSet<ConditionsTreeNode> mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Constructors
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Use this only if the search won't be saved. Saved searches need
|
||||||
|
* a name!
|
||||||
|
*/
|
||||||
|
public LocalSearch(){}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public LocalSearch(String name) {
|
||||||
|
this.mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this constructor when you know what you'r doing. Normally it's only used
|
||||||
|
* when restoring these search objects from the database.
|
||||||
|
*
|
||||||
|
* @param name Name of the search
|
||||||
|
* @param searchConditions SearchConditions, may contains flags and folders
|
||||||
|
* @param accounts Relative Account's uuid's
|
||||||
|
* @param predefined Is this a predefined search or a user created one?
|
||||||
|
*/
|
||||||
|
protected LocalSearch(String name, ConditionsTreeNode searchConditions,
|
||||||
|
String accounts, boolean predefined) {
|
||||||
|
this(name);
|
||||||
|
mConditions = searchConditions;
|
||||||
|
mPredefined = predefined;
|
||||||
|
mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||||
|
if (mConditions != null) {
|
||||||
|
mLeafSet.addAll(mConditions.getLeafSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize accounts
|
||||||
|
if (accounts != null) {
|
||||||
|
for (String account : accounts.split(",")) {
|
||||||
|
mAccountUuids.add(account);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// impossible but still not unrecoverable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public manipulation methods
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Sets the name of the saved search. If one existed it will
|
||||||
|
* be overwritten.
|
||||||
|
*
|
||||||
|
* @param name Name to be set.
|
||||||
|
*/
|
||||||
|
public void setName(String name) {
|
||||||
|
this.mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new account to the search. When no accounts are
|
||||||
|
* added manually we search all accounts on the device.
|
||||||
|
*
|
||||||
|
* @param uuid Uuid of the account to be added.
|
||||||
|
*/
|
||||||
|
public void addAccountUuid(String uuid) {
|
||||||
|
if (uuid.equals(ALL_ACCOUNTS)) {
|
||||||
|
mAccountUuids.clear();
|
||||||
|
}
|
||||||
|
mAccountUuids.add(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds all the account uuids in the provided array to
|
||||||
|
* be matched by the seach.
|
||||||
|
*
|
||||||
|
* @param accountUuids
|
||||||
|
*/
|
||||||
|
public void addAccountUuids(String[] accountUuids) {
|
||||||
|
for (String acc : accountUuids) {
|
||||||
|
addAccountUuid(acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an account UUID from the current search.
|
||||||
|
*
|
||||||
|
* @param uuid Account UUID to remove.
|
||||||
|
* @return True if removed, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean removeAccountUuid(String uuid) {
|
||||||
|
return mAccountUuids.remove(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided node as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param field Message table field to match against.
|
||||||
|
* @param string Value to look for.
|
||||||
|
* @param contains Attribute to use when matching.
|
||||||
|
*
|
||||||
|
* @throws IllegalConditionException
|
||||||
|
*/
|
||||||
|
public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
|
||||||
|
and(new SearchCondition(field, attribute, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided condition as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'AND' with.
|
||||||
|
* @return New top AND node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return and(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided node as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param node Node to 'AND' with.
|
||||||
|
* @return New top AND node, new root.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(ConditionsTreeNode node) throws Exception {
|
||||||
|
mLeafSet.addAll(node.getLeafSet());
|
||||||
|
|
||||||
|
if (mConditions == null) {
|
||||||
|
mConditions = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mConditions.and(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided condition as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'OR' with.
|
||||||
|
* @return New top OR node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return or(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided node as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param node Node to 'OR' with.
|
||||||
|
* @return New top OR node, new root.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(ConditionsTreeNode node) throws Exception {
|
||||||
|
mLeafSet.addAll(node.getLeafSet());
|
||||||
|
|
||||||
|
if (mConditions == null) {
|
||||||
|
mConditions = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mConditions.or(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the flags to this node as required flags. The
|
||||||
|
* provided flags will be combined using AND with the root.
|
||||||
|
*
|
||||||
|
* @param requiredFlags Array of required flags.
|
||||||
|
*/
|
||||||
|
public void allRequiredFlags(Flag[] requiredFlags) {
|
||||||
|
if (requiredFlags != null) {
|
||||||
|
for (Flag f : requiredFlags) {
|
||||||
|
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the flags to this node as forbidden flags. The
|
||||||
|
* provided flags will be combined using AND with the root.
|
||||||
|
*
|
||||||
|
* @param forbiddenFlags Array of forbidden flags.
|
||||||
|
*/
|
||||||
|
public void allForbiddenFlags(Flag[] forbiddenFlags) {
|
||||||
|
if (forbiddenFlags != null) {
|
||||||
|
for (Flag f : forbiddenFlags) {
|
||||||
|
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* FOR NOW: And the folder with the root.
|
||||||
|
*
|
||||||
|
* Add the folder as another folder to search in. The folder
|
||||||
|
* will be added AND to the root if no 'folder subtree' was found.
|
||||||
|
* Otherwise the folder will be added OR to that tree.
|
||||||
|
*
|
||||||
|
* @param name Name of the folder to add.
|
||||||
|
*/
|
||||||
|
public void addAllowedFolder(String name) {
|
||||||
|
/*
|
||||||
|
* TODO find folder sub-tree
|
||||||
|
* - do and on root of it & rest of search
|
||||||
|
* - do or between folder nodes
|
||||||
|
*/
|
||||||
|
and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO make this more advanced!
|
||||||
|
* This is a temporarely solution that does NOT WORK for
|
||||||
|
* real searches because of possible extra conditions to a folder requirement.
|
||||||
|
*/
|
||||||
|
public List<String> getFolderNames() {
|
||||||
|
ArrayList<String> results = new ArrayList<String>();
|
||||||
|
for (ConditionsTreeNode node : mLeafSet) {
|
||||||
|
if (node.mCondition.field == SEARCHFIELD.FOLDER
|
||||||
|
&& node.mCondition.attribute == ATTRIBUTE.EQUALS) {
|
||||||
|
results.add(node.mCondition.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the leafset of the related condition tree.
|
||||||
|
*
|
||||||
|
* @return All the leaf conditions as a set.
|
||||||
|
*/
|
||||||
|
public Set<ConditionsTreeNode> getLeafSet() {
|
||||||
|
return mLeafSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public accesor methods
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* TODO THIS HAS TO GO!!!!
|
||||||
|
* very dirty fix for remotesearch support atm
|
||||||
|
*/
|
||||||
|
public String getRemoteSearchArguments() {
|
||||||
|
for (ConditionsTreeNode node : getLeafSet()) {
|
||||||
|
if (node.getCondition().field == SEARCHFIELD.SUBJECT
|
||||||
|
|| node.getCondition().field == SEARCHFIELD.SENDER ) {
|
||||||
|
return node.getCondition().value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the saved search.
|
||||||
|
*
|
||||||
|
* @return Name of the search.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return (mName == null ? "" : mName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this search was hard coded and shipped with K-9
|
||||||
|
*
|
||||||
|
* @return True is search was shipped with K-9
|
||||||
|
*/
|
||||||
|
public boolean isPredefined() {
|
||||||
|
return mPredefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the account uuids that this search will try to
|
||||||
|
* match against.
|
||||||
|
*
|
||||||
|
* @return Array of account uuids.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String[] getAccountUuids() {
|
||||||
|
if (mAccountUuids.size() == 0) {
|
||||||
|
return new String[] {SearchSpecification.ALL_ACCOUNTS};
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] tmp = new String[mAccountUuids.size()];
|
||||||
|
mAccountUuids.toArray(tmp);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the condition tree.
|
||||||
|
*
|
||||||
|
* @return The root node of the related conditions tree.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ConditionsTreeNode getConditions() {
|
||||||
|
return mConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Parcelable
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(mName);
|
||||||
|
dest.writeByte((byte) (mPredefined ? 1 : 0));
|
||||||
|
dest.writeStringList(new ArrayList<String>(mAccountUuids));
|
||||||
|
dest.writeParcelable(mConditions, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<LocalSearch> CREATOR
|
||||||
|
= new Parcelable.Creator<LocalSearch>() {
|
||||||
|
public LocalSearch createFromParcel(Parcel in) {
|
||||||
|
return new LocalSearch(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalSearch[] newArray(int size) {
|
||||||
|
return new LocalSearch[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public LocalSearch(Parcel in) {
|
||||||
|
mName = in.readString();
|
||||||
|
mPredefined = in.readByte() == 1;
|
||||||
|
mAccountUuids.addAll(in.createStringArrayList());
|
||||||
|
mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
|
||||||
|
mLeafSet = mConditions.getLeafSet();
|
||||||
|
}
|
||||||
|
}
|
87
src/com/fsck/k9/search/SearchAccount.java
Normal file
87
src/com/fsck/k9/search/SearchAccount.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.fsck.k9.BaseAccount;
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is basically a wrapper around a LocalSearch. It allows to expose it as
|
||||||
|
* an account. This is a meta-account containing all the e-mail that matches the search.
|
||||||
|
*/
|
||||||
|
public class SearchAccount implements BaseAccount {
|
||||||
|
|
||||||
|
// create the all messages search ( all accounts is default when none specified )
|
||||||
|
public static SearchAccount createAllMessagesAccount(Context context) {
|
||||||
|
String name = context.getString(R.string.search_all_messages_title);
|
||||||
|
LocalSearch tmpSearch = new LocalSearch(name);
|
||||||
|
return new SearchAccount(tmpSearch, name,
|
||||||
|
context.getString(R.string.search_all_messages_detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create the unified inbox meta account ( all accounts is default when none specified )
|
||||||
|
public static SearchAccount createUnifiedInboxAccount(Context context) {
|
||||||
|
String name = context.getString(R.string.integrated_inbox_title);
|
||||||
|
LocalSearch tmpSearch = new LocalSearch(name);
|
||||||
|
tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
|
||||||
|
return new SearchAccount(tmpSearch, name,
|
||||||
|
context.getString(R.string.integrated_inbox_detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mEmail = null;
|
||||||
|
private String mDescription = null;
|
||||||
|
private LocalSearch mSearch = null;
|
||||||
|
private String mFakeUuid = null;
|
||||||
|
|
||||||
|
public SearchAccount(LocalSearch search, String description, String email) throws IllegalArgumentException{
|
||||||
|
if (search == null) {
|
||||||
|
throw new IllegalArgumentException("Provided LocalSearch was null");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mSearch = search;
|
||||||
|
this.mDescription = description;
|
||||||
|
this.mEmail = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized String getEmail() {
|
||||||
|
return mEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setEmail(String email) {
|
||||||
|
this.mEmail = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return mDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.mDescription = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalSearch getRelatedSearch() {
|
||||||
|
return mSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/*
|
||||||
|
* This will only be used when accessed as an Account. If that
|
||||||
|
* is the case we don't want to return the uuid of a real account since
|
||||||
|
* this is posing as a fake meta-account. If this object is accesed as
|
||||||
|
* a Search then methods from LocalSearch will be called which do handle
|
||||||
|
* things nice.
|
||||||
|
*/
|
||||||
|
public String getUuid() {
|
||||||
|
if (mFakeUuid == null){
|
||||||
|
mFakeUuid = UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
return mFakeUuid;
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,18 @@
|
|||||||
package com.fsck.k9.activity;
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.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;
|
184
src/com/fsck/k9/search/SearchSpecification.java
Normal file
184
src/com/fsck/k9/search/SearchSpecification.java
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public interface SearchSpecification extends Parcelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the uuids of accounts this search acts on.
|
||||||
|
* @return Array of uuids.
|
||||||
|
*/
|
||||||
|
public String[] getAccountUuids();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the search's name if it was named.
|
||||||
|
* @return Name of the search.
|
||||||
|
*/
|
||||||
|
public String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root node of the condition tree accompanying
|
||||||
|
* the search.
|
||||||
|
*
|
||||||
|
* @return Root node of conditions tree.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode getConditions();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some meta names for certain conditions.
|
||||||
|
*/
|
||||||
|
public static final String ALL_ACCOUNTS = "allAccounts";
|
||||||
|
public static final String GENERIC_INBOX_NAME = "genericInboxName";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// ATTRIBUTE enum
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
public enum ATTRIBUTE {
|
||||||
|
CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
|
||||||
|
NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
|
||||||
|
|
||||||
|
private boolean mNegation;
|
||||||
|
|
||||||
|
private ATTRIBUTE(boolean negation) {
|
||||||
|
this.mNegation = negation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formQuery(String value) {
|
||||||
|
String queryPart = "";
|
||||||
|
|
||||||
|
switch (this) {
|
||||||
|
case NOT_CONTAINS:
|
||||||
|
case CONTAINS:
|
||||||
|
queryPart = "'%"+value+"%'";
|
||||||
|
break;
|
||||||
|
case NOT_EQUALS:
|
||||||
|
case EQUALS:
|
||||||
|
queryPart = "'"+value+"'";
|
||||||
|
break;
|
||||||
|
case NOT_STARTSWITH:
|
||||||
|
case STARTSWITH:
|
||||||
|
queryPart = "'%"+value+"'";
|
||||||
|
break;
|
||||||
|
case NOT_ENDSWITH:
|
||||||
|
case ENDSWITH:
|
||||||
|
queryPart = "'"+value+"%'";
|
||||||
|
break;
|
||||||
|
default: queryPart = "'"+value+"'";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// SEARCHFIELD enum
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/*
|
||||||
|
* Using an enum in order to have more robust code. Users ( & coders )
|
||||||
|
* are prevented from passing illegal fields. No database overhead
|
||||||
|
* when invalid fields passed.
|
||||||
|
*
|
||||||
|
* By result, only the fields in here are searchable.
|
||||||
|
*
|
||||||
|
* Fields not in here at this moment ( and by effect not searchable ):
|
||||||
|
* id, html_content, internal_date, message_id,
|
||||||
|
* preview, mime_type
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum SEARCHFIELD {
|
||||||
|
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
|
||||||
|
SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
|
||||||
|
BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
|
||||||
|
ATTACHMENT_COUNT("attachment_count"), DELETED("deleted"), THREAD_ROOT("thread_root");
|
||||||
|
|
||||||
|
private String dbName;
|
||||||
|
|
||||||
|
private SEARCHFIELD(String dbName) {
|
||||||
|
this.dbName = dbName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabaseName() {
|
||||||
|
return dbName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// SearchCondition class
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* This class represents 1 value for a certain search field. One
|
||||||
|
* value consists of three things:
|
||||||
|
* an attribute: equals, starts with, contains,...
|
||||||
|
* a searchfield: date, flags, sender, subject,...
|
||||||
|
* a value: "apple", "jesse",..
|
||||||
|
*
|
||||||
|
* @author dzan
|
||||||
|
*/
|
||||||
|
public class SearchCondition implements Parcelable{
|
||||||
|
public String value;
|
||||||
|
public ATTRIBUTE attribute;
|
||||||
|
public SEARCHFIELD field;
|
||||||
|
|
||||||
|
public SearchCondition(SEARCHFIELD field, ATTRIBUTE attribute, String value) {
|
||||||
|
this.value = value;
|
||||||
|
this.attribute = attribute;
|
||||||
|
this.field = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchCondition(Parcel in) {
|
||||||
|
this.value = in.readString();
|
||||||
|
this.attribute = ATTRIBUTE.values()[in.readInt()];
|
||||||
|
this.field = SEARCHFIELD.values()[in.readInt()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toHumanString() {
|
||||||
|
return field.toString() + attribute.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return field.getDatabaseName() + attribute.formQuery(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof SearchCondition) {
|
||||||
|
SearchCondition tmp = (SearchCondition) o;
|
||||||
|
if (tmp.attribute == attribute
|
||||||
|
&& tmp.value.equals(value)
|
||||||
|
&& tmp.field == field) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(value);
|
||||||
|
dest.writeInt(attribute.ordinal());
|
||||||
|
dest.writeInt(field.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<SearchCondition> CREATOR
|
||||||
|
= new Parcelable.Creator<SearchCondition>() {
|
||||||
|
public SearchCondition createFromParcel(Parcel in) {
|
||||||
|
return new SearchCondition(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchCondition[] newArray(int size) {
|
||||||
|
return new SearchCondition[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user