Add remote IMAP search support.

* rbayer/IMAPsearch: (21 commits)
  More cleanup
  Code Cleanup getRemoteSearchFullText -> isRemoteSearchFullText line wraps for preference items
  Refactor to allow fetching of extra search results beyond original request.  Most code moved out of ImapStore and ImapFolder and into MessagingController.searchRemoteMessagesSynchronous.  Should make it easier to add remoteSearch for other server types.
  Prevent delete of search results while search results open
  remove duplicated code block
  Don't hide Crypto when IMAPsearch disabled
  Code Style Cleanup: Tabs -> 4 spaces Remove trailing whitespace from blank lines
  tabs -> spaces (my bad...)
  Fix opening of folders to be Read-Write when necessary, even if they were previously opened Read-Only.
  add missing file
  Working IMAP search, with passable UI.
  UI improvements
  Simple help info when enabling Remote Search
  Dependency for preferences
  Basic IMAP search working
This commit is contained in:
Rob Bayer 2012-03-05 12:04:34 -08:00 committed by Andrew Chen
parent a1d9079f07
commit fdb1267cb1
16 changed files with 905 additions and 122 deletions

View File

@ -668,6 +668,26 @@
<item>HTML</item>
<item>AUTO</item>
</string-array>
<string-array name="account_settings_remote_search_num_results_entries">
<item >@string/account_settings_remote_search_num_results_entries_10</item>
<item >@string/account_settings_remote_search_num_results_entries_25</item>
<item >@string/account_settings_remote_search_num_results_entries_50</item>
<item >@string/account_settings_remote_search_num_results_entries_100</item>
<item >@string/account_settings_remote_search_num_results_entries_250</item>
<item >@string/account_settings_remote_search_num_results_entries_500</item>
<item >@string/account_settings_remote_search_num_results_entries_1000</item>
<item >@string/account_settings_remote_search_num_results_entries_all</item>
</string-array>
<string-array name="account_settings_remote_search_num_results_values">
<item >10</item>
<item >25</item>
<item >50</item>
<item >100</item>
<item >250</item>
<item >500</item>
<item >1000</item>
<item >0</item>
</string-array>
<string-array name="global_settings_notification_hide_subject_entries">
<item name="1">@string/global_settings_notification_hide_subject_never</item>

View File

@ -1120,4 +1120,29 @@ http://k9mail.googlecode.com/
<string name="image_saved_as">Saved image as \"<xliff:g id="filename">%s</xliff:g>\"</string>
<string name="image_saving_failed">Saving the image failed.</string>
<string name="account_settings_allow_remote_search_label">Allow remote search</string>
<string name="account_settings_allow_remote_search_summary">Enable remote searching for this account</string>
<string name="account_settings_remote_search_num_results_entries_all">All</string>
<string name="account_settings_remote_search_num_results_entries_10">10</string>
<string name="account_settings_remote_search_num_results_entries_25">25</string>
<string name="account_settings_remote_search_num_results_entries_50">50</string>
<string name="account_settings_remote_search_num_results_entries_100">100</string>
<string name="account_settings_remote_search_num_results_entries_250">250</string>
<string name="account_settings_remote_search_num_results_entries_500">500</string>
<string name="account_settings_remote_search_num_results_entries_1000">1000</string>
<string name="account_settings_remote_search_num_label">Results limit</string>
<string name="search_mode_local_all">All Local Folders</string>
<string name="search_mode_remote">Remote Folder</string>
<string name="search_mode_title">Search Location</string>
<string name="account_settings_remote_search">Remote folder searching</string>
<string name="account_settings_remote_search_full_text_summary">Can be slow</string>
<string name="account_settings_remote_search_full_text">Include body text</string>
<string name="account_settings_allow_remote_search_help">
To perform a remote search, press your device\'s Search button while viewing a folder from this account.
\n\nNote: Remote search is NOT available from the Unified Inbox or from a list of folders.
</string>
<string name="remote_search_sending_query">Sending query to server</string>
<string name="no_show_again">Don\'t Show Again</string>
<string name="remote_search_downloading">Fetching %d results</string>
<string name="remote_search_downloading_limited">Fetching %1$d of %2$d results</string>
</resources>

View File

@ -23,7 +23,9 @@
can be displayed after the device has been rotated.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="main">
<PreferenceScreen
android:title="@string/account_settings_general_title"
@ -460,6 +462,29 @@
</PreferenceScreen>
<PreferenceScreen android:title="@string/account_settings_remote_search" android:key="remote_search">
<CheckBoxPreference
android:key="account_allow_remote_search"
android:title="@string/account_settings_allow_remote_search_label"
android:persistent="false"/>
<ListPreference
android:entries="@array/account_settings_remote_search_num_results_entries"
android:entryValues="@array/account_settings_remote_search_num_results_values"
android:key="account_remote_search_num_results"
android:title="@string/account_settings_remote_search_num_label"
android:dialogTitle="@string/account_settings_remote_search_num_label"
android:dependency="account_allow_remote_search"/>
<CheckBoxPreference
android:key="account_remote_search_full_text"
android:title="@string/account_settings_remote_search_full_text"
android:summary="@string/account_settings_remote_search_full_text_summary"
android:persistent="false"
android:dependency="account_allow_remote_search"/>
</PreferenceScreen>
<PreferenceScreen
android:title="@string/account_settings_crypto"
android:key="crypto">

View File

@ -1,6 +1,18 @@
package com.fsck.k9;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
@ -68,6 +80,7 @@ public class Account implements BaseAccount {
public static final boolean DEFAULT_QUOTED_TEXT_SHOWN = true;
public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
public static final boolean DEFAULT_STRIP_SIGNATURE = true;
public static final int DEFAULT_REMOTE_SEARCH_NUM_RESULTS = 25;
public static final String ACCOUNT_DESCRIPTION_KEY = "description";
public static final String STORE_URI_KEY = "storeUri";
@ -186,6 +199,9 @@ public class Account implements BaseAccount {
private boolean mCryptoAutoEncrypt;
private boolean mMarkMessageAsReadOnView;
private boolean mAlwaysShowCcBcc;
private boolean mAllowRemoteSearch;
private boolean mRemoteSearchFullText;
private int mRemoteSearchNumResults;
private CryptoProvider mCryptoProvider = null;
@ -276,6 +292,9 @@ public class Account implements BaseAccount {
mCryptoApp = Apg.NAME;
mCryptoAutoSignature = false;
mCryptoAutoEncrypt = false;
mAllowRemoteSearch = false;
mRemoteSearchFullText = false;
mRemoteSearchNumResults = DEFAULT_REMOTE_SEARCH_NUM_RESULTS;
mEnabled = true;
mMarkMessageAsReadOnView = true;
mAlwaysShowCcBcc = false;
@ -441,6 +460,10 @@ public class Account implements BaseAccount {
mCryptoApp = prefs.getString(mUuid + ".cryptoApp", Apg.NAME);
mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false);
mCryptoAutoEncrypt = prefs.getBoolean(mUuid + ".cryptoAutoEncrypt", false);
mAllowRemoteSearch = prefs.getBoolean(mUuid + ".allowRemoteSearch", false);
mRemoteSearchFullText = prefs.getBoolean(mUuid + ".remoteSearchFullText", false);
mRemoteSearchNumResults = prefs.getInt(mUuid + ".remoteSearchNumResults", DEFAULT_REMOTE_SEARCH_NUM_RESULTS);
mEnabled = prefs.getBoolean(mUuid + ".enabled", true);
mMarkMessageAsReadOnView = prefs.getBoolean(mUuid + ".markMessageAsReadOnView", true);
mAlwaysShowCcBcc = prefs.getBoolean(mUuid + ".alwaysShowCcBcc", false);
@ -688,6 +711,9 @@ public class Account implements BaseAccount {
editor.putString(mUuid + ".cryptoApp", mCryptoApp);
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
editor.putBoolean(mUuid + ".cryptoAutoEncrypt", mCryptoAutoEncrypt);
editor.putBoolean(mUuid + ".allowRemoteSearch", mAllowRemoteSearch);
editor.putBoolean(mUuid + ".remoteSearchFullText", mRemoteSearchFullText);
editor.putInt(mUuid + ".remoteSearchNumResults", mRemoteSearchNumResults);
editor.putBoolean(mUuid + ".enabled", mEnabled);
editor.putBoolean(mUuid + ".markMessageAsReadOnView", mMarkMessageAsReadOnView);
editor.putBoolean(mUuid + ".alwaysShowCcBcc", mAlwaysShowCcBcc);
@ -1572,6 +1598,22 @@ public class Account implements BaseAccount {
mCryptoAutoEncrypt = cryptoAutoEncrypt;
}
public boolean allowRemoteSearch() {
return mAllowRemoteSearch;
}
public void setAllowRemoteSearch(boolean val) {
mAllowRemoteSearch = val;
}
public int getRemoteSearchNumResults() {
return mRemoteSearchNumResults;
}
public void setRemoteSearchNumResults(int val) {
mRemoteSearchNumResults = (val >= 0 ? val : 0);
}
public String getInboxFolderName() {
return mInboxFolderName;
}
@ -1642,4 +1684,13 @@ public class Account implements BaseAccount {
public synchronized void setAlwaysShowCcBcc(boolean show) {
mAlwaysShowCcBcc = show;
}
public boolean isRemoteSearchFullText() {
return mRemoteSearchFullText;
}
public void setRemoteSearchFullText(boolean val) {
mRemoteSearchFullText = val;
}
}

View File

@ -2,7 +2,7 @@ package com.fsck.k9.activity;
import java.util.Date;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.Message;
public class MessageInfoHolder {
public String date;
@ -19,7 +19,7 @@ public class MessageInfoHolder {
public boolean forwarded;
public boolean flagged;
public boolean dirty;
public LocalMessage message;
public Message message;
public FolderInfoHolder folder;
public boolean selected;
public String account;

View File

@ -11,7 +11,9 @@ import java.util.Map;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.SearchManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences.Editor;
import android.graphics.Color;
@ -74,7 +76,6 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.StorageManager;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
@ -173,7 +174,13 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return object1.compareCounterparty.toLowerCase().compareTo(object2.compareCounterparty.toLowerCase());
if (object1.compareCounterparty == null) {
return (object2.compareCounterparty == null ? 0 : 1);
} else if (object2.compareCounterparty == null) {
return -1;
} else {
return object1.compareCounterparty.toLowerCase().compareTo(object2.compareCounterparty.toLowerCase());
}
}
}
@ -182,7 +189,13 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return object1.compareDate.compareTo(object2.compareDate);
if (object1.compareDate == null) {
return (object2.compareDate == null ? 0 : 1);
} else if (object2.compareDate == null) {
return -1;
} else {
return object1.compareDate.compareTo(object2.compareDate);
}
}
}
@ -223,7 +236,9 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_FOLDER = "folder";
private static final String EXTRA_QUERY = "query";
private static final String EXTRA_REMOTE_SEARCH = "com.fsck.k9.remote_search";
private static final String EXTRA_SEARCH_ACCOUNT = "com.fsck.k9.search_account";
private static final String EXTRA_SEARCH_FOLDER = "com.fsck.k9.search_folder";
private static final String EXTRA_QUERY_FLAGS = "queryFlags";
private static final String EXTRA_FORBIDDEN_FLAGS = "forbiddenFlags";
private static final String EXTRA_INTEGRATE = "integrate";
@ -284,6 +299,9 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
private String mQueryString;
private Flag[] mQueryFlags = null;
private Flag[] mForbiddenFlags = null;
private boolean mRemoteSearch = false;
private String mSearchAccount = null;
private String mSearchFolder = null;
private boolean mIntegrate = false;
private String[] mAccountUuids = null;
private String[] mFolderNames = null;
@ -363,7 +381,6 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
private static final int ACTION_REFRESH_TITLE = 5;
private static final int ACTION_PROGRESS = 6;
public void removeMessage(MessageReference messageReference) {
android.os.Message msg = android.os.Message.obtain(this, ACTION_REMOVE_MESSAGE,
messageReference);
@ -392,6 +409,15 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
sendMessage(msg);
}
public void updateFooter(final String message, final boolean showProgress) {
runOnUiThread(new Runnable() {
@Override
public void run() {
MessageList.this.updateFooter(message, showProgress);
}
});
}
public void changeMessageUid(final MessageReference ref, final String newUid) {
// Instead of explicitly creating a container to be able to pass both arguments in a
// Message we post a Runnable to the message queue.
@ -481,10 +507,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
}
}
// build the comparator chain
final Comparator<MessageInfoHolder> chainComparator = new ComparatorChain<MessageInfoHolder>(chain);
return chainComparator;
return new ComparatorChain<MessageInfoHolder>(chain);
}
private void folderLoading(String folder, boolean loading) {
@ -496,7 +519,9 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
private void refreshTitle() {
setWindowTitle();
setWindowProgress();
if (!mRemoteSearch) {
setWindowProgress();
}
}
private void setWindowProgress() {
@ -625,7 +650,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
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(EXTRA_QUERY, queryString);
intent.putExtra(SearchManager.QUERY, queryString);
if (flags != null) {
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(flags, ','));
}
@ -643,7 +668,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
public static Intent actionHandleAccountIntent(Context context, String title,
SearchSpecification searchSpecification) {
Intent intent = new Intent(context, MessageList.class);
intent.putExtra(EXTRA_QUERY, searchSpecification.getQuery());
intent.putExtra(SearchManager.QUERY, searchSpecification.getQuery());
if (searchSpecification.getRequiredFlags() != null) {
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(searchSpecification.getRequiredFlags(), ','));
}
@ -670,8 +695,25 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view == mFooterView) {
if (mCurrentFolder != null) {
if (mCurrentFolder != null && !mRemoteSearch) {
mController.loadMoreMessages(mAccount, mFolderName, mAdapter.mListener);
} else if (mRemoteSearch && mAdapter.mExtraSearchResults != null && mAdapter.mExtraSearchResults.size() > 0 && mSearchAccount != null) {
int numResults = mAdapter.mExtraSearchResults.size();
Account account = Preferences.getPreferences(this).getAccount(mSearchAccount);
if (account == null) {
mHandler.updateFooter("", false);
return;
}
int limit = account.getRemoteSearchNumResults();
List<Message> toProcess = mAdapter.mExtraSearchResults;
if (limit > 0 && numResults > limit) {
toProcess = toProcess.subList(0, limit);
mAdapter.mExtraSearchResults = mAdapter.mExtraSearchResults.subList(limit, mAdapter.mExtraSearchResults.size());
} else {
mAdapter.mExtraSearchResults = null;
mHandler.updateFooter("", false);
}
mController.loadSearchResults(account, mSearchFolder, toProcess, mAdapter.mListener);
}
return;
}
@ -693,7 +735,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
mActionBarProgressView = getLayoutInflater().inflate(R.layout.actionbar_indeterminate_progress_actionview, null);
// need this for actionbar initialization
mQueryString = getIntent().getStringExtra(EXTRA_QUERY);
mQueryString = getIntent().getStringExtra(SearchManager.QUERY);
mPullToRefreshView = (PullToRefreshListView) findViewById(R.id.message_list);
@ -741,7 +783,30 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
return;
}
mQueryString = intent.getStringExtra(SearchManager.QUERY);
mFolderName = null;
mRemoteSearch = false;
mSearchAccount = null;
mSearchFolder = null;
if (mQueryString != null) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
//Query was received from Search Dialog
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
mSearchAccount = appData.getString(EXTRA_SEARCH_ACCOUNT);
mSearchFolder = appData.getString(EXTRA_SEARCH_FOLDER);
mRemoteSearch = appData.getBoolean(EXTRA_REMOTE_SEARCH);
}
} else {
mSearchAccount = intent.getStringExtra(EXTRA_SEARCH_ACCOUNT);
mSearchFolder = intent.getStringExtra(EXTRA_SEARCH_FOLDER);
}
}
String accountUuid = intent.getStringExtra(EXTRA_ACCOUNT);
mFolderName = intent.getStringExtra(EXTRA_FOLDER);
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
if (mAccount != null && !mAccount.isAvailable(this)) {
@ -750,8 +815,8 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
return;
}
mFolderName = intent.getStringExtra(EXTRA_FOLDER);
mQueryString = intent.getStringExtra(EXTRA_QUERY);
String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS);
if (queryFlags != null) {
@ -787,8 +852,8 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount);
}
// Hide "Load up to x more" footer for search views
mFooterView.setVisibility((mQueryString != null) ? View.GONE : View.VISIBLE);
// Hide "Load up to x more" footer for local search views
mFooterView.setVisibility((mQueryString != null && !mRemoteSearch) ? View.GONE : View.VISIBLE);
mController = MessagingController.getInstance(getApplication());
mListView.setAdapter(mAdapter);
@ -848,6 +913,14 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
onAccountUnavailable();
return;
}
if (!(this instanceof Search)) {
//necessary b/c no guarantee Search.onStop will be called before MessageList.onResume
//when returning from search results
Search.setActive(false);
}
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
mStars = K9.messageListStars();
@ -867,6 +940,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
mController.addListener(mAdapter.mListener);
//Cancel pending new mail notifications when we open an account
Account[] accountsWithNotification;
Preferences prefs = Preferences.getPreferences(getApplicationContext());
@ -888,8 +962,12 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
mController.notifyAccountCancel(this, accountWithNotification);
}
if (mAdapter.isEmpty()) {
if (mFolderName != null) {
if (mRemoteSearch) {
//TODO: Support flag based search
mController.searchRemoteMessages(mSearchAccount, mSearchFolder, mQueryString, null, null, mAdapter.mListener);
} else if (mFolderName != null) {
mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener);
// Hide the archive button if we don't have an archive folder.
if (!mAccount.hasArchiveFolder()) {
@ -900,7 +978,6 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
// 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();
@ -910,27 +987,29 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
new Thread() {
@Override
public void run() {
if (mFolderName != null) {
mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener);
} else if (mQueryString != null) {
mController.searchLocalMessagesSynchronous(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.pruneDirtyMessages();
mAdapter.notifyDataSetChanged();
restoreListState();
if (!mRemoteSearch) {
if (mFolderName != null) {
mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener);
} else if (mQueryString != null) {
mController.searchLocalMessagesSynchronous(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
}
});
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.pruneDirtyMessages();
mAdapter.notifyDataSetChanged();
restoreListState();
}
});
}
}
}
.start();
}
if (mAccount != null && mFolderName != null) {
if (mAccount != null && mFolderName != null && !mRemoteSearch) {
mController.getFolderUnreadMessageCount(mAccount, mFolderName, mAdapter.mListener);
}
@ -1065,6 +1144,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
}
}
//Shortcuts that only work when a message is selected
boolean retval = true;
int position = mListView.getSelectedItemPosition();
try {
@ -1205,6 +1285,44 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
AccountSettings.actionSettings(this, mAccount);
}
@Override
public boolean onSearchRequested() {
if (mAccount != null && mCurrentFolder != null && mAccount.allowRemoteSearch()) {
//if in a remote searchable folder, ask user what they want.
//TODO: Add ability to remember selection?
final CharSequence[] items = new CharSequence[2];
items[0] = getString(R.string.search_mode_local_all);
items[1] = getString(R.string.search_mode_remote);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.search_mode_title));
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
Bundle appData = null;
if (item == 1) {
appData = new Bundle();
appData.putString(EXTRA_SEARCH_ACCOUNT, mAccount.getUuid());
appData.putString(EXTRA_SEARCH_FOLDER, mCurrentFolder.name);
appData.putBoolean(EXTRA_REMOTE_SEARCH, true);
}
//else do regular search, which doesn't require any special parameter setup
startSearch(null, false, appData, false);
}
});
AlertDialog alert = builder.create();
alert.show();
return true;
}
startSearch(null, false, null, false);
return true;
}
private void changeSort(SortType sortType) {
Boolean sortAscending = (mSortType == sortType) ? !mSortAscending : null;
changeSort(sortType, sortAscending);
@ -1401,7 +1519,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
}
private void onToggleRead(final List<MessageInfoHolder> holders) {
LocalMessage message;
Message message;
Folder folder;
Account account;
String folderName;
@ -1422,7 +1540,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
}
private void onToggleFlag(final List<MessageInfoHolder> holders) {
LocalMessage message;
Message message;
Folder folder;
Account account;
String folderName;
@ -1548,7 +1666,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (mQueryString != null) {
if (mQueryString != null || mIntegrate) {
menu.findItem(R.id.expunge).setVisible(false);
menu.findItem(R.id.check_mail).setVisible(false);
menu.findItem(R.id.send_messages).setVisible(false);
@ -1582,8 +1700,65 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
private final List<MessageInfoHolder> mMessages =
Collections.synchronizedList(new ArrayList<MessageInfoHolder>());
public List<Message> mExtraSearchResults;
private final ActivityListener mListener = new ActivityListener() {
// TODO achen - may need to add a setSupportProgress to mHandler to run on the UI thread.
@Override
public void remoteSearchAddMessage(Account account, String folderName, Message message, final int numDone, final int numTotal) {
if (numTotal > 0 && numDone < numTotal) {
setSupportProgress(Window.PROGRESS_END / numTotal * numDone);
} else {
setSupportProgress(Window.PROGRESS_END);
}
addOrUpdateMessages(account, folderName, Collections.singletonList(message), false);
}
@Override
public void remoteSearchFailed(Account acct, String folder, final String err) {
//TODO: Better error handling
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getApplication(), err, Toast.LENGTH_LONG).show();
}
});
}
@Override
public void remoteSearchStarted(Account acct, String folder) {
mHandler.progress(true);
mHandler.updateFooter(getString(R.string.remote_search_sending_query), true);
}
@Override
public void remoteSearchFinished(Account acct, String folder, int numResults, List<Message> extraResults) {
mHandler.progress(false);
if (extraResults != null && extraResults.size() > 0) {
mExtraSearchResults = extraResults;
mHandler.updateFooter(String.format(getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()), false);
} else {
mHandler.updateFooter("", false);
}
setSupportProgress(Window.PROGRESS_END);
}
@Override
public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) {
mHandler.progress(true);
if (account != null && account.getRemoteSearchNumResults() != 0 && numResults > account.getRemoteSearchNumResults()) {
mHandler.updateFooter(getString(R.string.remote_search_downloading_limited, account.getRemoteSearchNumResults(), numResults), true);
} else {
mHandler.updateFooter(getString(R.string.remote_search_downloading, numResults), true);
}
setSupportProgress(Window.PROGRESS_START);
}
@Override
public void informUserOfStatus() {
mHandler.refreshTitle();
@ -2370,26 +2545,41 @@ public class MessageList extends K9ListActivity implements OnItemClickListener {
}
private void updateFooterView() {
FooterViewHolder holder = (FooterViewHolder) mFooterView.getTag();
if (mCurrentFolder != null && mAccount != null) {
if (mCurrentFolder.loading) {
holder.main.setText(getString(R.string.status_loading_more));
holder.progress.setVisibility(ProgressBar.VISIBLE);
final boolean showProgress = true;
updateFooter(getString(R.string.status_loading_more), showProgress);
} else {
String message;
if (!mCurrentFolder.lastCheckFailed) {
if (mAccount.getDisplayCount() == 0) {
holder.main.setText(getString(R.string.message_list_load_more_messages_action));
message = getString(R.string.message_list_load_more_messages_action);
} else {
holder.main.setText(String.format(getString(R.string.load_more_messages_fmt), mAccount.getDisplayCount()));
message = String.format(getString(R.string.load_more_messages_fmt), mAccount.getDisplayCount());
}
} else {
holder.main.setText(getString(R.string.status_loading_more_failed));
message = getString(R.string.status_loading_more_failed);
}
holder.progress.setVisibility(ProgressBar.INVISIBLE);
final boolean showProgress = false;
updateFooter(message, showProgress);
}
} else {
holder.progress.setVisibility(ProgressBar.INVISIBLE);
final boolean showProgress = false;
updateFooter(null, showProgress);
}
}
public void updateFooter(final String text, final boolean progressVisible) {
FooterViewHolder holder = (FooterViewHolder) mFooterView.getTag();
holder.progress.setVisibility(progressVisible ? ProgressBar.VISIBLE : ProgressBar.INVISIBLE);
if (text != null) {
holder.main.setText(text);
}
if (progressVisible || holder.main.getText().length() > 0) {
holder.main.setVisibility(View.VISIBLE);
} else {
holder.main.setVisibility(View.GONE);
}
}

View File

@ -1,7 +1,29 @@
package com.fsck.k9.activity;
import com.fsck.k9.activity.MessageList;
public class Search extends MessageList {
protected static boolean isActive = false;
public static boolean isActive() {
return isActive;
}
public static void setActive(boolean val) {
isActive = val;
}
@Override
public void onStart() {
setActive(true);
super.onStart();
}
@Override
public void onStop() {
setActive(false);
super.onStop();
}
}

View File

@ -2,20 +2,31 @@
package com.fsck.k9.activity.setup;
import android.app.Dialog;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.*;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceScreen;
import android.preference.RingtonePreference;
import android.util.Log;
import java.util.Iterator;
import java.util.Map;
import java.util.LinkedList;
import java.util.List;
import android.view.View;
import android.widget.CheckBox;
import com.fsck.k9.Account;
import com.fsck.k9.Account.FolderMode;
@ -24,18 +35,17 @@ import com.fsck.k9.K9;
import com.fsck.k9.NotificationSetting;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.activity.ChooseFolder;
import com.fsck.k9.activity.ChooseIdentity;
import com.fsck.k9.activity.ColorPickerDialog;
import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.activity.ManageIdentities;
import com.fsck.k9.crypto.Apg;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Store;
import com.fsck.k9.service.MailService;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.service.MailService;
public class AccountSettings extends K9PreferenceActivity {
@ -52,6 +62,7 @@ public class AccountSettings extends K9PreferenceActivity {
private static final String PREFERENCE_SCREEN_INCOMING = "incoming_prefs";
private static final String PREFERENCE_SCREEN_PUSH_ADVANCED = "push_advanced";
private static final String PREFERENCE_SCREEN_NOTIFICATIONS = "notifications";
private static final String PREFERENCE_SCREEN_REMOTE_SEARCH = "remote_search";
private static final String PREFERENCE_DESCRIPTION = "account_description";
private static final String PREFERENCE_MARK_MESSAGE_AS_READ_ON_VIEW = "mark_message_as_read_on_view";
@ -101,6 +112,10 @@ public class AccountSettings extends K9PreferenceActivity {
private static final String PREFERENCE_CRYPTO_APP = "crypto_app";
private static final String PREFERENCE_CRYPTO_AUTO_SIGNATURE = "crypto_auto_signature";
private static final String PREFERENCE_CRYPTO_AUTO_ENCRYPT = "crypto_auto_encrypt";
private static final String PREFERENCE_ALLOW_REMOTE_SEARCH = "account_allow_remote_search";
private static final String PREFERENCE_REMOTE_SEARCH_NUM_RESULTS = "account_remote_search_num_results";
private static final String PREFERENCE_REMOTE_SEARCH_FULL_TEXT = "account_remote_search_full_text";
private static final String PREFERENCE_LOCAL_STORAGE_PROVIDER = "local_storage_provider";
private static final String PREFERENCE_CATEGORY_FOLDERS = "folders";
private static final String PREFERENCE_ARCHIVE_FOLDER = "archive_folder";
@ -162,6 +177,9 @@ public class AccountSettings extends K9PreferenceActivity {
private ListPreference mCryptoApp;
private CheckBoxPreference mCryptoAutoSignature;
private CheckBoxPreference mCryptoAutoEncrypt;
private CheckBoxPreference mAllowRemoteSearch;
private ListPreference mRemoteSearchNumResults;
private CheckBoxPreference mRemoteSearchFullText;
private ListPreference mLocalStorageProvider;
private ListPreference mArchiveFolder;
private ListPreference mDraftsFolder;
@ -472,13 +490,29 @@ public class AccountSettings extends K9PreferenceActivity {
});
}
// IMAP-specific preferences
mAllowRemoteSearch = (CheckBoxPreference) findPreference(PREFERENCE_ALLOW_REMOTE_SEARCH);
mAllowRemoteSearch.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference pref, Object newVal) {
if ((Boolean) newVal) {
showRemoteSearchHelp();
}
return true;
}
});
mRemoteSearchNumResults = (ListPreference) findPreference(PREFERENCE_REMOTE_SEARCH_NUM_RESULTS);
mRemoteSearchFullText = (CheckBoxPreference) findPreference(PREFERENCE_REMOTE_SEARCH_FULL_TEXT);
mPushPollOnConnect = (CheckBoxPreference) findPreference(PREFERENCE_PUSH_POLL_ON_CONNECT);
mIdleRefreshPeriod = (ListPreference) findPreference(PREFERENCE_IDLE_REFRESH_PERIOD);
mMaxPushFolders = (ListPreference) findPreference(PREFERENCE_MAX_PUSH_FOLDERS);
if (mIsPushCapable) {
mPushPollOnConnect.setChecked(mAccount.isPushPollOnConnect());
mAllowRemoteSearch.setChecked(mAccount.allowRemoteSearch());
mRemoteSearchNumResults.setValue(Integer.toString(mAccount.getRemoteSearchNumResults()));
mRemoteSearchFullText.setChecked(mAccount.isRemoteSearchFullText());
mIdleRefreshPeriod.setValue(String.valueOf(mAccount.getIdleRefreshMinutes()));
mIdleRefreshPeriod.setSummary(mIdleRefreshPeriod.getEntry());
mIdleRefreshPeriod.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@ -516,8 +550,11 @@ public class AccountSettings extends K9PreferenceActivity {
});
} else {
PreferenceScreen incomingPrefs = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_INCOMING);
incomingPrefs.removePreference( (PreferenceScreen) findPreference(PREFERENCE_SCREEN_PUSH_ADVANCED));
incomingPrefs.removePreference( (ListPreference) findPreference(PREFERENCE_PUSH_MODE));
incomingPrefs.removePreference((PreferenceScreen) findPreference(PREFERENCE_SCREEN_PUSH_ADVANCED));
incomingPrefs.removePreference((ListPreference) findPreference(PREFERENCE_PUSH_MODE));
((PreferenceScreen) findPreference("main")).removePreference((PreferenceScreen) findPreference(PREFERENCE_SCREEN_REMOTE_SEARCH));
}
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
@ -677,6 +714,31 @@ public class AccountSettings extends K9PreferenceActivity {
handleCryptoAppDependencies();
}
protected void showRemoteSearchHelp() {
final String noShowHelpPref = "account_settings_remote_search_hide_help";
final SharedPreferences prefs = getPreferences(MODE_PRIVATE);
if (!prefs.getBoolean(noShowHelpPref, false)) {
AlertDialog.Builder adb = new AlertDialog.Builder(this);
final CheckBox noShowAgain = new CheckBox(this);
noShowAgain.setChecked(false);
noShowAgain.setText(R.string.no_show_again);
adb.setView(noShowAgain)
.setMessage(getString(R.string.account_settings_allow_remote_search_help))
.setCancelable(false)
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if(noShowAgain.isChecked()){
Editor edit = prefs.edit();
edit.putBoolean(noShowHelpPref, true);
edit.commit();
}
}
});
adb.create().show();
}
}
private void handleCryptoAppDependencies() {
if ("".equals(mCryptoApp.getValue())) {
mCryptoAutoSignature.setEnabled(false);
@ -745,11 +807,14 @@ public class AccountSettings extends K9PreferenceActivity {
mAccount.setTrashFolderName(mTrashFolder.getValue());
}
//IMAP stuff
if (mIsPushCapable) {
mAccount.setPushPollOnConnect(mPushPollOnConnect.isChecked());
mAccount.setIdleRefreshMinutes(Integer.parseInt(mIdleRefreshPeriod.getValue()));
mAccount.setMaxPushFolders(Integer.parseInt(mMaxPushFolders.getValue()));
mAccount.setAllowRemoteSearch(mAllowRemoteSearch.isChecked());
mAccount.setRemoteSearchNumResults(Integer.parseInt(mRemoteSearchNumResults.getValue()));
mAccount.setRemoteSearchFullText(mRemoteSearchFullText.isChecked());
}
if (!mIsMoveCapable) {
@ -776,6 +841,7 @@ public class AccountSettings extends K9PreferenceActivity {
mAccount.setShowPictures(Account.ShowPictures.valueOf(mAccountShowPictures.getValue()));
//IMAP specific stuff
if (mIsPushCapable) {
boolean needsPushRestart = mAccount.setFolderPushMode(Account.FolderMode.valueOf(mPushMode.getValue()));
if (mAccount.getFolderPushMode() != FolderMode.NONE) {

View File

@ -3,7 +3,19 @@ package com.fsck.k9.controller;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@ -31,11 +43,11 @@ import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.K9;
import com.fsck.k9.K9.NotificationHideSubject;
import com.fsck.k9.K9.Intents;
import com.fsck.k9.NotificationSetting;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.SearchSpecification;
import com.fsck.k9.K9.Intents;
import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
import com.fsck.k9.helper.NotificationBuilder;
@ -48,8 +60,8 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Folder.FolderType;
import com.fsck.k9.mail.Folder.OpenMode;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.PushReceiver;
@ -59,12 +71,12 @@ import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException;
/**
@ -126,6 +138,28 @@ public class MessagingController implements Runnable {
private static final String PENDING_COMMAND_MARK_ALL_AS_READ = "com.fsck.k9.MessagingController.markAllAsRead";
private static final String PENDING_COMMAND_EXPUNGE = "com.fsck.k9.MessagingController.expunge";
public static class UidReverseComparator implements Comparator<Message> {
@Override
public int compare(Message o1, Message o2) {
if (o1 == null || o2 == null || o1.getUid() == null || o2.getUid() == null) {
return 0;
}
int id1, id2;
try {
id1 = Integer.parseInt(o1.getUid());
id2 = Integer.parseInt(o2.getUid());
} catch (NumberFormatException e) {
return 0;
}
//reversed intentionally.
if (id1 < id2)
return 1;
if (id1 > id2)
return -1;
return 0;
}
}
/**
* Maximum number of unsynced messages to store at once
*/
@ -516,7 +550,7 @@ public class MessagingController implements Runnable {
l.listLocalMessagesStarted(account, folder);
}
Folder localFolder = null;
LocalFolder localFolder = null;
MessageRetrievalListener retrievalListener =
new MessageRetrievalListener() {
List<Message> pendingMessages = new ArrayList<Message>();
@ -554,10 +588,13 @@ public class MessagingController implements Runnable {
try {
Store localStore = account.getLocalStore();
LocalStore localStore = account.getLocalStore();
localFolder = localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
//Purging followed by getting requires 2 DB queries.
//TODO: Fix getMessages to allow auto-pruning at visible limit?
localFolder.purgeToVisibleLimit(null);
localFolder.getMessages(
retrievalListener,
false // Skip deleted messages
@ -750,6 +787,140 @@ public class MessagingController implements Runnable {
listener.searchStats(stats);
}
}
public void searchRemoteMessages(final String acctUuid, final String folderName, final String query, final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
if (K9.DEBUG) {
String msg = "searchRemoteMessages ("
+ "acct=" + acctUuid
+ ", folderName = " + folderName
+ ", query = " + query
+ ")";
Log.i(K9.LOG_TAG, msg);
}
threadPool.execute(new Runnable() {
@Override
public void run() {
searchRemoteMessagesSynchronous(acctUuid, folderName, query, requiredFlags, forbiddenFlags, listener);
}
});
}
public void searchRemoteMessagesSynchronous(final String acctUuid, final String folderName, final String query,
final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
final Account acct = Preferences.getPreferences(mApplication.getApplicationContext()).getAccount(acctUuid);
if (listener != null) {
listener.remoteSearchStarted(acct, folderName);
}
List<Message> extraResults = new ArrayList<Message>();
try {
Store remoteStore = acct.getRemoteStore();
LocalStore localStore = acct.getLocalStore();
if (remoteStore == null || localStore == null) {
throw new MessagingException("Could not get store");
}
Folder remoteFolder = remoteStore.getFolder(folderName);
LocalFolder localFolder = localStore.getFolder(folderName);
if (remoteFolder == null || localFolder == null) {
throw new MessagingException("Folder not found");
}
if (listener != null) {
listener.remoteSearchStarted(acct, folderName);
}
List<Message> messages = remoteStore.searchRemoteMessages(query, folderName, requiredFlags, forbiddenFlags);
if (listener != null) {
listener.remoteSearchServerQueryComplete(acct, folderName, messages.size());
}
if (K9.DEBUG) {
Log.i("Remote Search", "Remote search got " + messages.size() + " results");
}
Collections.sort(messages, new UidReverseComparator());
int resultLimit = acct.getRemoteSearchNumResults();
if (resultLimit > 0 && messages.size() > resultLimit) {
extraResults = messages.subList(resultLimit, messages.size());
messages = messages.subList(0, resultLimit);
}
loadSearchResultsSynchronous(messages, localFolder, remoteFolder, listener);
} catch (Exception e) {
if (listener != null) {
listener.remoteSearchFailed(acct, null, e.getMessage());
}
addErrorMessage(acct, null, e);
} finally {
if (listener != null) {
listener.remoteSearchFinished(acct, folderName, 0, extraResults);
}
}
}
public void loadSearchResults(final Account account, final String folderName, final List<Message> messages, final MessagingListener listener) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
Store remoteStore = account.getRemoteStore();
LocalStore localStore = account.getLocalStore();
if (remoteStore == null || localStore == null) {
throw new MessagingException("Could not get store");
}
Folder remoteFolder = remoteStore.getFolder(folderName);
LocalFolder localFolder = localStore.getFolder(folderName);
if (remoteFolder == null || localFolder == null) {
throw new MessagingException("Folder not found");
}
loadSearchResultsSynchronous(messages, localFolder, remoteFolder, listener);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Exception in loadSearchResults: " + e);
addErrorMessage(account, null, e);
}
}
});
}
public void loadSearchResultsSynchronous(List<Message> messages, LocalFolder localFolder, Folder remoteFolder, MessagingListener listener) throws MessagingException {
FetchProfile fp_header = new FetchProfile();
fp_header.add(FetchProfile.Item.FLAGS);
fp_header.add(FetchProfile.Item.ENVELOPE);
FetchProfile fp_structure = new FetchProfile();
fp_structure.add(FetchProfile.Item.STRUCTURE);
int i = 0;
for (Message message : messages) {
i++;
LocalMessage localMsg = localFolder.getMessage(message.getUid());
if (localMsg == null) {
remoteFolder.fetch(new Message [] {message}, fp_header, null);
//fun fact: ImapFolder.fetch can't handle getting STRUCTURE at same time as headers
remoteFolder.fetch(new Message [] {message}, fp_structure, null);
localFolder.appendMessages(new Message [] {message});
localMsg = localFolder.getMessage(message.getUid());
}
if (listener != null) {
listener.remoteSearchAddMessage(remoteFolder.getAccount(), remoteFolder.getName(), localMsg, i, messages.size());
}
}
}
public void loadMoreMessages(Account account, String folder, MessagingListener listener) {
try {
LocalStore localStore = account.getLocalStore();
@ -1193,12 +1364,12 @@ public class MessagingController implements Runnable {
* Reverse the order of the messages. Depending on the server this may get us
* fetch results for newest to oldest. If not, no harm done.
*/
Collections.reverse(unsyncedMessages);
Collections.sort(unsyncedMessages, new UidReverseComparator());
int visibleLimit = localFolder.getVisibleLimit();
int listSize = unsyncedMessages.size();
if ((visibleLimit > 0) && (listSize > visibleLimit)) {
unsyncedMessages = unsyncedMessages.subList(listSize - visibleLimit, listSize);
unsyncedMessages = unsyncedMessages.subList(0, visibleLimit);
}
FetchProfile fp = new FetchProfile();
@ -1933,7 +2104,7 @@ public class MessagingController implements Runnable {
LocalStore localStore = account.getLocalStore();
localFolder = localStore.getFolder(folder);
LocalMessage localMessage = (LocalMessage) localFolder.getMessage(uid);
LocalMessage localMessage = localFolder.getMessage(uid);
if (localMessage == null) {
return;
@ -2811,7 +2982,7 @@ public class MessagingController implements Runnable {
LocalFolder localFolder = localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
LocalMessage message = (LocalMessage)localFolder.getMessage(uid);
LocalMessage message = localFolder.getMessage(uid);
if (message == null
|| message.getId() == 0) {
throw new IllegalArgumentException("Message not found: folder=" + folder + ", uid=" + uid);

View File

@ -1,7 +1,10 @@
package com.fsck.k9.controller;
import java.util.List;
import android.content.Context;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.BaseAccount;
@ -9,8 +12,6 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
import java.util.List;
/**
* Defines the interface that {@link MessagingController} will use to callback to requesters.
*
@ -152,6 +153,50 @@ public class MessagingListener {
public void pendingCommandsFinished(Account account) {}
/**
* Called when a remote search is started
*
* @param acct
* @param folder
*/
public void remoteSearchStarted(Account acct, String folder) {}
/**
* Called when server has responded to our query. Messages have not yet been downloaded.
*
* @param numResults
*/
public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) { }
/**
* Called when a new result message is available for a remote search
* Can assume headers have been downloaded, but potentially not body.
* @param account
* @param folder
* @param message
*/
public void remoteSearchAddMessage(Account account, String folder, Message message, int numDone, int numTotal) { }
/**
* Called when Remote Search is fully complete
*
* @param acct
* @param folder
* @param numResults
*/
public void remoteSearchFinished(Account acct, String folder, int numResults, List<Message> extraResults) {}
/**
* Called when there was a problem with a remote search operation.
*
* @param acct
* @param folder
* @param err
*/
public void remoteSearchFailed(Account acct, String folder, String err) { }
/**
* General notification messages subclasses can override to be notified that the controller
* has completed a command. This is useful for turning off progress indicators that may have

View File

@ -17,7 +17,6 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.helper.DateFormatter;
public class MessageHelper {
@ -43,11 +42,10 @@ public class MessageHelper {
mTodayDateFormat = android.text.format.DateFormat.getTimeFormat(mContext);
}
public void populate(final MessageInfoHolder target, final Message m,
public void populate(final MessageInfoHolder target, final Message message,
final FolderInfoHolder folder, final Account account) {
final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
try {
LocalMessage message = (LocalMessage) m;
target.message = message;
target.compareArrival = message.getInternalDate();
target.compareDate = message.getSentDate();
@ -86,13 +84,16 @@ public class MessageHelper {
target.uid = message.getUid();
target.account = account.getUuid();
target.uri = "email://messages/" + account.getAccountNumber() + "/" + m.getFolder().getName() + "/" + m.getUid();
target.uri = "email://messages/" + account.getAccountNumber() + "/" + message.getFolder().getName() + "/" + message.getUid();
} catch (MessagingException me) {
Log.w(K9.LOG_TAG, "Unable to load message info", me);
}
}
public String formatDate(Date date) {
if (date == null) {
return "";
}
if (Utility.isDateToday(date)) {
return mTodayDateFormat.format(date);
} else {

View File

@ -1,19 +1,18 @@
package com.fsck.k9.mail;
import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.io.IOException;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.mail.filter.CountingOutputStream;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.K9;
public abstract class Message implements Part, Body {
@ -144,6 +143,16 @@ public abstract class Message implements Part, Body {
return getContentType().startsWith(mimeType);
}
public abstract boolean toMe();
public abstract boolean ccMe();
public abstract boolean bccMe();
public abstract long getId();
public abstract String getPreview();
public abstract boolean hasAttachments();
public void delete(String trashFolderName) throws MessagingException {}
/*

View File

@ -1,6 +1,9 @@
package com.fsck.k9.mail;
import java.util.HashMap;
import java.util.List;
import android.app.Application;
import android.content.Context;
@ -8,11 +11,9 @@ import com.fsck.k9.Account;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import java.util.HashMap;
import java.util.List;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.WebDavStore;
/**
* Store is the access point for an email message store. It's location can be
@ -176,4 +177,9 @@ public abstract class Store {
public Account getAccount() {
return mAccount;
}
public List<Message> searchRemoteMessages(String queryString,
String folder, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException {
throw new MessagingException("K-9 does not support remote searching on this account type");
}
}

View File

@ -592,4 +592,28 @@ public class MimeMessage extends Message {
copy(message);
return message;
}
public boolean toMe() {
return false;
}
public boolean ccMe() {
return false;
}
public boolean bccMe() {
return false;
}
public long getId() {
return Long.parseLong(mUid); //or maybe .mMessageId?
}
public String getPreview() {
return "";
}
public boolean hasAttachments() {
return false;
}
}

View File

@ -45,11 +45,15 @@ import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import org.apache.commons.io.IOUtils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
@ -73,13 +77,14 @@ import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Folder.OpenMode;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.filter.FixedLengthInputStream;
import com.fsck.k9.mail.filter.PeekableInputStream;
@ -94,9 +99,6 @@ import com.fsck.k9.mail.store.imap.ImapUtility;
import com.fsck.k9.mail.transport.imap.ImapSettings;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import org.apache.commons.io.IOUtils;
/**
* <pre>
@ -1127,7 +1129,7 @@ public class ImapStore extends Store {
}
ImapFolder iFolder = (ImapFolder)folder;
checkOpen();
checkOpen(); //only need READ access
String[] uids = new String[messages.length];
for (int i = 0, count = messages.length; i < count; i++) {
@ -1253,7 +1255,7 @@ public class ImapStore extends Store {
private int getRemoteMessageCount(String criteria) throws MessagingException {
checkOpen();
checkOpen(); //only need READ access
try {
int count = 0;
int start = 1;
@ -1289,7 +1291,7 @@ public class ImapStore extends Store {
return executeSimpleCommand("UID SEARCH *:*");
}
};
Message[] messages = search(searcher, null);
Message[] messages = search(searcher, null).toArray(EMPTY_MESSAGE_ARRAY);
if (messages.length > 0) {
return Long.parseLong(messages[0].getUid());
}
@ -1338,7 +1340,7 @@ public class ImapStore extends Store {
return executeSimpleCommand(String.format("UID SEARCH %d:%d%s%s", start, end, dateSearchString, includeDeleted ? "" : " NOT DELETED"));
}
};
return search(searcher, listener);
return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY);
}
protected Message[] getMessages(final List<Long> mesgSeqs, final boolean includeDeleted, final MessageRetrievalListener listener)
@ -1348,7 +1350,7 @@ public class ImapStore extends Store {
return executeSimpleCommand(String.format("UID SEARCH %s%s", Utility.combine(mesgSeqs.toArray(), ','), includeDeleted ? "" : " NOT DELETED"));
}
};
return search(searcher, listener);
return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY);
}
protected Message[] getMessagesFromUids(final List<String> mesgUids, final boolean includeDeleted, final MessageRetrievalListener listener)
@ -1358,12 +1360,12 @@ public class ImapStore extends Store {
return executeSimpleCommand(String.format("UID SEARCH UID %s%s", Utility.combine(mesgUids.toArray(), ','), includeDeleted ? "" : " NOT DELETED"));
}
};
return search(searcher, listener);
return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY);
}
private Message[] search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException {
private List<Message> search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException {
checkOpen();
checkOpen(); //only need READ access
ArrayList<Message> messages = new ArrayList<Message>();
try {
ArrayList<Long> uids = new ArrayList<Long>();
@ -1378,8 +1380,12 @@ public class ImapStore extends Store {
}
}
// Sort the uids in numerically ascending order
Collections.sort(uids);
// Sort the uids in numerically decreasing order
// By doing it in decreasing order, we ensure newest messages are dealt with first
// This makes the most sense when a limit is imposed, and also prevents UI from going
// crazy adding stuff at the top.
Collections.sort(uids, Collections.reverseOrder());
for (int i = 0, count = uids.size(); i < count; i++) {
String uid = uids.get(i).toString();
if (listener != null) {
@ -1394,7 +1400,7 @@ public class ImapStore extends Store {
} catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);
}
return messages.toArray(EMPTY_MESSAGE_ARRAY);
return messages;
}
@ -1406,7 +1412,7 @@ public class ImapStore extends Store {
@Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException {
checkOpen();
checkOpen(); //only need READ access
ArrayList<Message> messages = new ArrayList<Message>();
try {
if (uids == null) {
@ -1443,7 +1449,7 @@ public class ImapStore extends Store {
if (messages == null || messages.length == 0) {
return;
}
checkOpen();
checkOpen(); //only need READ access
List<String> uids = new ArrayList<String>(messages.length);
HashMap<String, Message> messageMap = new HashMap<String, Message>();
for (int i = 0, count = messages.length; i < count; i++) {
@ -1568,7 +1574,7 @@ public class ImapStore extends Store {
@Override
public void fetchPart(Message message, Part part, MessageRetrievalListener listener)
throws MessagingException {
checkOpen();
checkOpen(); //only need READ access
String[] parts = part.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
if (parts == null) {
@ -1971,6 +1977,7 @@ public class ImapStore extends Store {
*/
@Override
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
open(OpenMode.READ_WRITE);
checkOpen();
try {
Map<String, String> uidMap = new HashMap<String, String>();
@ -2083,6 +2090,7 @@ public class ImapStore extends Store {
@Override
public void expunge() throws MessagingException {
open(OpenMode.READ_WRITE);
checkOpen();
try {
executeSimpleCommand("EXPUNGE");
@ -2115,6 +2123,7 @@ public class ImapStore extends Store {
@Override
public void setFlags(Flag[] flags, boolean value)
throws MessagingException {
open(OpenMode.READ_WRITE);
checkOpen();
@ -2149,6 +2158,7 @@ public class ImapStore extends Store {
@Override
public void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException {
open(OpenMode.READ_WRITE);
checkOpen();
String[] uids = new String[messages.length];
for (int i = 0, count = messages.length; i < count; i++) {
@ -3415,4 +3425,105 @@ public class ImapStore extends Store {
}
}
@Override
public List<Message> searchRemoteMessages(final String queryString,
final String folderName, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException {
if (!mAccount.allowRemoteSearch()) {
throw new MessagingException("Your settings do not allow remote searching of this account");
}
final ImapFolder folder = (ImapFolder) getFolder(folderName);
if (folder == null) {
throw new MessagingException("Invalid folder specified");
}
try {
folder.open(OpenMode.READ_ONLY);
folder.checkOpen();
ImapSearcher searcher = new ImapSearcher() {
public List<ImapResponse> search() throws IOException, MessagingException {
String imapQuery = "UID SEARCH ";
if (requiredFlags != null) {
for (Flag f : requiredFlags) {
switch (f) {
case DELETED:
imapQuery += "DELETED ";
break;
case SEEN:
imapQuery += "SEEN ";
break;
case ANSWERED:
imapQuery += "ANSWERED ";
break;
case FLAGGED:
imapQuery += "FLAGGED ";
break;
case DRAFT:
imapQuery += "DRAFT ";
break;
case RECENT:
imapQuery += "RECENT ";
break;
}
}
}
if (forbiddenFlags != null) {
for (Flag f : forbiddenFlags) {
switch (f) {
case DELETED:
imapQuery += "UNDELETED ";
break;
case SEEN:
imapQuery += "UNSEEN ";
break;
case ANSWERED:
imapQuery += "UNANSWERED ";
break;
case FLAGGED:
imapQuery += "UNFLAGGED ";
break;
case DRAFT:
imapQuery += "UNDRAFT ";
break;
case RECENT:
imapQuery += "UNRECENT ";
break;
}
}
}
String encodedQry = encodeString(queryString);
if (mAccount.isRemoteSearchFullText()) {
imapQuery += "TEXT " + encodedQry;
} else {
imapQuery += "OR SUBJECT " + encodedQry + " FROM " + encodedQry;
}
return folder.executeSimpleCommand(imapQuery);
}
};
//don't pass listener--we don't want to add messages until we've downloaded them
return folder.search(searcher, null);
} catch (Exception e) {
e.printStackTrace();
throw new MessagingException("Error during search: " + e.toString());
}
}
}

View File

@ -1,7 +1,15 @@
package com.fsck.k9.mail.store;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
@ -17,7 +25,6 @@ import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import com.fsck.k9.helper.HtmlConverter;
import org.apache.commons.io.IOUtils;
import android.app.Application;
@ -36,8 +43,10 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.Account.MessageFormat;
import com.fsck.k9.activity.Search;
import com.fsck.k9.controller.MessageRemovalListener;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
@ -1127,9 +1136,15 @@ public class LocalStore extends Store implements Serializable {
@Override
public void open(final OpenMode mode) throws MessagingException {
if (isOpen()) {
if (isOpen() && (getMode() == mode || mode == OpenMode.READ_ONLY)) {
return;
} else if (isOpen()) {
//previously opened in READ_ONLY and now requesting READ_WRITE
//so close connection and reopen
close();
}
try {
database.execute(false, new DbCallback<Void>() {
@Override
@ -1336,17 +1351,19 @@ public class LocalStore extends Store implements Serializable {
}
public void purgeToVisibleLimit(MessageRemovalListener listener) throws MessagingException {
if (mVisibleLimit == 0) {
return ;
}
open(OpenMode.READ_WRITE);
Message[] messages = getMessages(null, false);
for (int i = mVisibleLimit; i < messages.length; i++) {
if (listener != null) {
listener.messageRemoved(messages[i]);
//don't purge messages while a Search is active since it might throw away search results
if (!Search.isActive()) {
if (mVisibleLimit == 0) {
return ;
}
open(OpenMode.READ_WRITE);
Message[] messages = getMessages(null, false);
for (int i = mVisibleLimit; i < messages.length; i++) {
if (listener != null) {
listener.messageRemoved(messages[i]);
}
messages[i].destroy();
}
messages[i].destroy();
}
}
@ -1831,11 +1848,11 @@ public class LocalStore extends Store implements Serializable {
}
@Override
public Message getMessage(final String uid) throws MessagingException {
public LocalMessage getMessage(final String uid) throws MessagingException {
try {
return database.execute(false, new DbCallback<Message>() {
return database.execute(false, new DbCallback<LocalMessage>() {
@Override
public Message doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
public LocalMessage doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try {
open(OpenMode.READ_WRITE);
LocalMessage message = new LocalMessage(uid, LocalFolder.this);
@ -2110,7 +2127,7 @@ public class LocalStore extends Store implements Serializable {
/*
* Replace an existing message in the database
*/
LocalMessage oldMessage = (LocalMessage) getMessage(uid);
LocalMessage oldMessage = getMessage(uid);
if (oldMessage != null) {
oldMessageId = oldMessage.getId();