Merge branch 'imapsearch'
* imapsearch: Change settings version to 18 to match what's currrently on master. Handle aborted imap searches by nuking in-progress connections. Move IMAP search into the Folder level. Remove duplicate notification on remote search start. Rename variables changed PREFERENCE_CLOUD_SEARCH_ENABLED from "cloud_search_enabled" to "remote_search_enabled" in activity/setup/AccountSettings.java to resolve FC. Add cloud search icon to local search result screen. Implement pull-to-remote-search. Log remote search exceptions in addition to toasting them. Add settings export for remote search settings. Whitespace; no functional changes. Handle implicit vs. explicit searches in ActionBar home button behavior. Whitespace fix; no functional changes. Add remote search actionbar icons. IMAP Search: log exceptions on remote search, properly dispatch MessageList changes. modified loadMessageForView() to dowload message if neither X_DOWNLOADED_FULL nor X_DOWNLOADED_PARTIAL. Add remote IMAP search support. Conflicts: res/menu/message_list_option.xml res/values/attrs.xml res/values/themes.xml src/com/fsck/k9/activity/MessageList.java src/com/fsck/k9/preferences/Settings.java
4551
images/drawable-src/ic_action_remote_search.ai
Normal file
BIN
res/drawable-hdpi/ic_action_remote_search_dark.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-hdpi/ic_action_remote_search_light.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-ldpi/ic_action_remote_search_dark.png
Normal file
After Width: | Height: | Size: 533 B |
BIN
res/drawable-ldpi/ic_action_remote_search_light.png
Normal file
After Width: | Height: | Size: 650 B |
BIN
res/drawable-mdpi/ic_action_remote_search_dark.png
Normal file
After Width: | Height: | Size: 801 B |
BIN
res/drawable-mdpi/ic_action_remote_search_light.png
Normal file
After Width: | Height: | Size: 963 B |
BIN
res/drawable-xhdpi/ic_action_remote_search_dark.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/drawable-xhdpi/ic_action_remote_search_light.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
|
@ -39,4 +39,15 @@
|
|||
android:textColor="?android:attr/textColorTertiary"
|
||||
android:textSize="36sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/actionbar_remote_search"
|
||||
android:gravity="center_vertical"
|
||||
android:focusable="false"
|
||||
android:layout_marginRight="3dip"
|
||||
android:background="?attr/iconActionRemoteSearch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/action_remote_search"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -1,6 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/search_remote"
|
||||
android:icon="?attr/iconActionRemoteSearch"
|
||||
android:showAsAction="always"
|
||||
android:title="@string/action_remote_search"
|
||||
android:visible="false"
|
||||
/>
|
||||
<item
|
||||
android:id="@+id/check_mail"
|
||||
android:alphabeticShortcut="r"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<attr name="iconActionUnflag" format="reference" />
|
||||
<attr name="iconActionMarkAsRead" format="reference" />
|
||||
<attr name="iconActionMarkAsUnread" format="reference" />
|
||||
<attr name="iconActionRemoteSearch" format="reference" />
|
||||
<attr name="iconMenuAdd" format="reference" />
|
||||
<attr name="iconMenuAttachment" format="reference" />
|
||||
<attr name="iconMenuClear" format="reference" />
|
||||
|
|
|
@ -1122,4 +1122,28 @@ 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_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">Server search result limit</string>
|
||||
<string name="account_settings_remote_search_num_summary">Search will stop after <xliff:g id="num_results">%s</xliff:g> results have been found.</string>
|
||||
<string name="account_settings_remote_search">Remote folder searching</string>
|
||||
<string name="account_settings_remote_search_full_text">Include body text in server search</string>
|
||||
<string name="account_settings_remote_search_full_text_summary">Full text searches can be slow.</string>
|
||||
<string name="remote_search_sending_query">Sending query to server</string>
|
||||
<string name="remote_search_downloading">Fetching %d results</string>
|
||||
<string name="remote_search_downloading_limited">Fetching %1$d of %2$d results</string>
|
||||
|
||||
<string name="account_settings_search">Search</string>
|
||||
<string name="account_settings_remote_search_enabled">Enable server search</string>
|
||||
<string name="account_settings_remote_search_enabled_summary">Search messages on the server in addition to those on your device</string>
|
||||
<string name="action_remote_search">Search messages on server</string>
|
||||
<string name="pull_to_refresh_remote_search_from_local_search_pull">Pull to search server…</string>
|
||||
<string name="pull_to_refresh_remote_search_from_local_search_release">Release to search server…</string>
|
||||
</resources>
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<item name="iconActionUnflag">@drawable/ic_action_unflag_light</item>
|
||||
<item name="iconActionMarkAsRead">@drawable/ic_action_mark_as_read_light</item>
|
||||
<item name="iconActionMarkAsUnread">@drawable/ic_action_mark_as_unread_light</item>
|
||||
<item name="iconActionRemoteSearch">@drawable/ic_action_remote_search_light</item>
|
||||
<item name="iconMenuAdd">@drawable/ic_menu_add</item>
|
||||
<item name="iconMenuAttachment">@drawable/ic_menu_attachment</item>
|
||||
<item name="iconMenuClear">@drawable/ic_menu_clear</item>
|
||||
|
@ -67,6 +68,7 @@
|
|||
<item name="iconActionUnflag">@drawable/ic_action_unflag_dark</item>
|
||||
<item name="iconActionMarkAsRead">@drawable/ic_action_mark_as_read_dark</item>
|
||||
<item name="iconActionMarkAsUnread">@drawable/ic_action_mark_as_unread_dark</item>
|
||||
<item name="iconActionRemoteSearch">@drawable/ic_action_remote_search_dark</item>
|
||||
<item name="iconMenuAdd">@drawable/ic_menu_add</item>
|
||||
<item name="iconMenuAttachment">@drawable/ic_menu_attachment</item>
|
||||
<item name="iconMenuClear">@drawable/ic_menu_clear</item>
|
||||
|
|
|
@ -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,30 @@
|
|||
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen android:title="@string/account_settings_search" android:key="search">
|
||||
<CheckBoxPreference
|
||||
android:key="remote_search_enabled"
|
||||
android:title="@string/account_settings_remote_search_enabled"
|
||||
android:summary="@string/account_settings_remote_search_enabled_summary"
|
||||
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="remote_search_enabled"/>
|
||||
|
||||
<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="remote_search_enabled"/>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen
|
||||
android:title="@string/account_settings_crypto"
|
||||
android:key="crypto">
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -289,6 +305,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;
|
||||
|
@ -456,6 +475,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);
|
||||
|
@ -706,6 +729,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);
|
||||
|
@ -1623,6 +1649,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;
|
||||
}
|
||||
|
@ -1693,4 +1735,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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -7,9 +7,12 @@ import java.util.EnumMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.SearchManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
@ -25,15 +28,9 @@ import android.text.style.ForegroundColorSpan;
|
|||
import android.text.style.StyleSpan;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.*;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
|
@ -75,9 +72,10 @@ import com.fsck.k9.helper.Utility;
|
|||
import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Folder;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.Store;
|
||||
import com.fsck.k9.mail.store.ImapStore;
|
||||
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;
|
||||
|
@ -177,7 +175,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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -186,7 +190,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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -227,7 +237,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";
|
||||
|
@ -287,6 +299,10 @@ 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 Future mRemoteSearchFuture = null;
|
||||
private boolean mIntegrate = false;
|
||||
private String[] mAccountUuids = null;
|
||||
private String[] mFolderNames = null;
|
||||
|
@ -365,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);
|
||||
|
@ -394,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.
|
||||
|
@ -483,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) {
|
||||
|
@ -498,7 +519,9 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
|||
|
||||
private void refreshTitle() {
|
||||
setWindowTitle();
|
||||
setWindowProgress();
|
||||
if (!mRemoteSearch) {
|
||||
setWindowProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private void setWindowProgress() {
|
||||
|
@ -599,7 +622,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, ','));
|
||||
}
|
||||
|
@ -617,7 +640,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(), ','));
|
||||
}
|
||||
|
@ -644,8 +667,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;
|
||||
}
|
||||
|
@ -667,7 +707,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);
|
||||
|
||||
|
@ -699,7 +739,30 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
|||
}
|
||||
|
||||
private void initializeMessageList(Intent intent, boolean create) {
|
||||
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)) {
|
||||
|
@ -708,8 +771,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) {
|
||||
|
@ -745,8 +808,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);
|
||||
|
@ -806,26 +869,54 @@ 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);
|
||||
mSenderAboveSubject = K9.messageListSenderAboveSubject();
|
||||
|
||||
// TODO Add support for pull to fresh on searches.
|
||||
if(mQueryString == null) {
|
||||
final Preferences prefs = Preferences.getPreferences(getApplicationContext());
|
||||
|
||||
boolean allowRemoteSearch = false;
|
||||
if (mSearchAccount != null) {
|
||||
final Account searchAccount = prefs.getAccount(mSearchAccount);
|
||||
if (searchAccount != null) {
|
||||
allowRemoteSearch = searchAccount.allowRemoteSearch();
|
||||
}
|
||||
}
|
||||
|
||||
if (mQueryString == null) {
|
||||
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
||||
checkMail(mAccount, mFolderName);
|
||||
}
|
||||
});
|
||||
} else if (allowRemoteSearch && !mRemoteSearch && !mIntegrate) {
|
||||
// mQueryString != null is implied if we get this far.
|
||||
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
||||
mPullToRefreshView.onRefreshComplete();
|
||||
onRemoteSearchRequested(true);
|
||||
}
|
||||
});
|
||||
mPullToRefreshView.setPullLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_pull));
|
||||
mPullToRefreshView.setReleaseLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_release));
|
||||
} else {
|
||||
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
|
||||
}
|
||||
|
||||
mController.addListener(mAdapter.mListener);
|
||||
|
||||
//Cancel pending new mail notifications when we open an account
|
||||
Account[] accountsWithNotification;
|
||||
|
||||
Preferences prefs = Preferences.getPreferences(getApplicationContext());
|
||||
Account account = getCurrentAccount(prefs);
|
||||
|
||||
if (account != null) {
|
||||
|
@ -844,8 +935,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
|
||||
mRemoteSearchFuture = 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()) {
|
||||
|
@ -856,7 +951,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();
|
||||
|
@ -866,27 +960,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);
|
||||
}
|
||||
|
||||
|
@ -1021,6 +1117,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 {
|
||||
|
@ -1161,6 +1258,47 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
|||
AccountSettings.actionSettings(this, mAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
final Bundle appData = new Bundle();
|
||||
if (fromLocalSearch) {
|
||||
appData.putString(EXTRA_SEARCH_ACCOUNT, mSearchAccount);
|
||||
appData.putString(EXTRA_SEARCH_FOLDER, mSearchFolder);
|
||||
} else {
|
||||
appData.putString(EXTRA_SEARCH_ACCOUNT, mAccount.getUuid());
|
||||
appData.putString(EXTRA_SEARCH_FOLDER, mCurrentFolder.name);
|
||||
}
|
||||
appData.putBoolean(EXTRA_REMOTE_SEARCH, true);
|
||||
|
||||
final Intent intent = new Intent(Intent.ACTION_SEARCH);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(SearchManager.QUERY, mQueryString);
|
||||
intent.putExtra(SearchManager.APP_DATA, appData);
|
||||
intent.setComponent(new ComponentName(context.getApplicationContext(), MessageList.class));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
// If this search was started from a MessageList of a single folder, pass along that folder info
|
||||
// so that we can enable remote search.
|
||||
if (mAccount != null && mCurrentFolder != null) {
|
||||
final Bundle appData = new Bundle();
|
||||
appData.putString(EXTRA_SEARCH_ACCOUNT, mAccount.getUuid());
|
||||
appData.putString(EXTRA_SEARCH_FOLDER, mCurrentFolder.name);
|
||||
startSearch(null, false, appData, false);
|
||||
} else {
|
||||
// TODO Handle the case where we're searching from within a search result.
|
||||
startSearch(null, false, null, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void changeSort(SortType sortType) {
|
||||
Boolean sortAscending = (mSortType == sortType) ? !mSortAscending : null;
|
||||
changeSort(sortType, sortAscending);
|
||||
|
@ -1357,7 +1495,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
|||
}
|
||||
|
||||
private void onToggleFlag(MessageInfoHolder messageInfo) {
|
||||
LocalMessage message = messageInfo.message;
|
||||
Message message = messageInfo.message;
|
||||
Folder folder = message.getFolder();
|
||||
Account account = folder.getAccount();
|
||||
String folderName = folder.getName();
|
||||
|
@ -1376,6 +1514,36 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
|||
mController.sendPendingMessages(account, mAdapter.mListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to do some special clean up when leaving a remote search result screen. If no remote search is
|
||||
* in progress, this method does nothing special.
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// If we represent a remote search, then kill that before going back.
|
||||
if(mSearchAccount != null && mSearchFolder != null && mRemoteSearchFuture != null) {
|
||||
try {
|
||||
Log.i(K9.LOG_TAG, "Remote search in progress, attempting to abort...");
|
||||
// Canceling the future stops any message fetches in progress.
|
||||
final boolean cancelSuccess = mRemoteSearchFuture.cancel(true); // mayInterruptIfRunning = true
|
||||
if (!cancelSuccess) {
|
||||
Log.e(K9.LOG_TAG, "Could not cancel remote search future.");
|
||||
}
|
||||
// Closing the folder will kill off the connection if we're mid-search.
|
||||
final Account searchAccount = Preferences.getPreferences(this).getAccount(mSearchAccount);
|
||||
final Store remoteStore = searchAccount.getRemoteStore();
|
||||
final Folder remoteFolder = remoteStore.getFolder(mSearchFolder);
|
||||
remoteFolder.close();
|
||||
// Send a remoteSearchFinished() message for good measure.
|
||||
mAdapter.mListener.remoteSearchFinished(searchAccount, mSearchFolder, 0, null);
|
||||
} catch (Exception e) {
|
||||
// Since the user is going back, log and squash any exceptions.
|
||||
Log.e(K9.LOG_TAG, "Could not abort remote search before going back", e);
|
||||
}
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
|
@ -1438,6 +1606,10 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
|||
onEditPrefs();
|
||||
return true;
|
||||
}
|
||||
case R.id.search_remote: {
|
||||
onRemoteSearchRequested(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mQueryString != null) {
|
||||
|
@ -1602,12 +1774,27 @@ 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);
|
||||
menu.findItem(R.id.folder_settings).setVisible(false);
|
||||
menu.findItem(R.id.account_settings).setVisible(false);
|
||||
// If this is an explicit local search, show the option to search the cloud.
|
||||
|
||||
final Preferences prefs = Preferences.getPreferences(getApplicationContext());
|
||||
|
||||
boolean allowRemoteSearch = false;
|
||||
if (mSearchAccount != null) {
|
||||
final Account searchAccount = prefs.getAccount(mSearchAccount);
|
||||
if (searchAccount != null) {
|
||||
allowRemoteSearch = searchAccount.allowRemoteSearch();
|
||||
}
|
||||
}
|
||||
|
||||
if (allowRemoteSearch && mQueryString != null && !mRemoteSearch && !mIntegrate && mSearchFolder != null) {
|
||||
menu.findItem(R.id.search_remote).setVisible(true);
|
||||
}
|
||||
} else {
|
||||
if (mCurrentFolder != null && mCurrentFolder.name.equals(mAccount.getOutboxFolderName())) {
|
||||
menu.findItem(R.id.check_mail).setVisible(false);
|
||||
|
@ -1663,8 +1850,63 @@ 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() {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
mHandler.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();
|
||||
|
@ -2374,26 +2616,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
@ -48,10 +58,12 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||
|
||||
private static final int ACTIVITY_MANAGE_IDENTITIES = 2;
|
||||
|
||||
private static final String PREFERENCE_SCREEN_MAIN = "main";
|
||||
private static final String PREFERENCE_SCREEN_COMPOSING = "composing";
|
||||
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_SEARCH = "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 +113,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_CLOUD_SEARCH_ENABLED = "remote_search_enabled";
|
||||
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";
|
||||
|
@ -116,6 +132,7 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||
private boolean mIsPushCapable = false;
|
||||
private boolean mIsExpungeCapable = false;
|
||||
|
||||
private PreferenceScreen mMainScreen;
|
||||
private PreferenceScreen mComposingScreen;
|
||||
|
||||
private EditTextPreference mAccountDescription;
|
||||
|
@ -162,6 +179,12 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||
private ListPreference mCryptoApp;
|
||||
private CheckBoxPreference mCryptoAutoSignature;
|
||||
private CheckBoxPreference mCryptoAutoEncrypt;
|
||||
|
||||
private PreferenceScreen mSearchScreen;
|
||||
private CheckBoxPreference mCloudSearchEnabled;
|
||||
private ListPreference mRemoteSearchNumResults;
|
||||
private CheckBoxPreference mRemoteSearchFullText;
|
||||
|
||||
private ListPreference mLocalStorageProvider;
|
||||
private ListPreference mArchiveFolder;
|
||||
private ListPreference mDraftsFolder;
|
||||
|
@ -195,6 +218,8 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||
|
||||
addPreferencesFromResource(R.xml.account_settings_preferences);
|
||||
|
||||
mMainScreen = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_MAIN);
|
||||
|
||||
mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
|
||||
mAccountDescription.setSummary(mAccount.getDescription());
|
||||
mAccountDescription.setText(mAccount.getDescription());
|
||||
|
@ -397,7 +422,7 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||
|
||||
if (!mAccount.isSearchByDateCapable()) {
|
||||
((PreferenceScreen) findPreference(PREFERENCE_SCREEN_INCOMING)).removePreference(mMessageAge);
|
||||
} else {
|
||||
} else {
|
||||
mMessageAge.setValue(String.valueOf(mAccount.getMaximumPolledMessageAge()));
|
||||
mMessageAge.setSummary(mMessageAge.getEntry());
|
||||
mMessageAge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
|
@ -471,14 +496,33 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// IMAP-specific preferences
|
||||
|
||||
mSearchScreen = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_SEARCH);
|
||||
mCloudSearchEnabled = (CheckBoxPreference) findPreference(PREFERENCE_CLOUD_SEARCH_ENABLED);
|
||||
mRemoteSearchNumResults = (ListPreference) findPreference(PREFERENCE_REMOTE_SEARCH_NUM_RESULTS);
|
||||
mRemoteSearchNumResults.setOnPreferenceChangeListener(
|
||||
new OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference pref, Object newVal) {
|
||||
updateRemoteSearchLimit((String)newVal);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
updateRemoteSearchLimit(mRemoteSearchNumResults.getValue());
|
||||
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());
|
||||
|
||||
mCloudSearchEnabled.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 +560,9 @@ 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));
|
||||
mMainScreen.removePreference(mSearchScreen);
|
||||
}
|
||||
|
||||
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
|
||||
|
@ -745,11 +790,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(mCloudSearchEnabled.isChecked());
|
||||
mAccount.setRemoteSearchNumResults(Integer.parseInt(mRemoteSearchNumResults.getValue()));
|
||||
mAccount.setRemoteSearchFullText(mRemoteSearchFullText.isChecked());
|
||||
}
|
||||
|
||||
if (!mIsMoveCapable) {
|
||||
|
@ -776,6 +824,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) {
|
||||
|
@ -923,6 +972,20 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||
Integer.parseInt(mAccountVibrateTimes.getValue())), -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote search result limit summary contains the current limit. On load or change, update this value.
|
||||
* @param maxResults Search limit to update the summary with.
|
||||
*/
|
||||
private void updateRemoteSearchLimit(String maxResults) {
|
||||
if (maxResults != null) {
|
||||
if (maxResults.equals("0")) {
|
||||
maxResults = getString(R.string.account_settings_remote_search_num_results_entries_all);
|
||||
}
|
||||
|
||||
mRemoteSearchNumResults.setSummary(String.format(getString(R.string.account_settings_remote_search_num_summary), maxResults));
|
||||
}
|
||||
}
|
||||
|
||||
private class PopulateFolderPrefsTask extends AsyncTask<Void, Void, Void> {
|
||||
List <? extends Folder > folders = new LinkedList<LocalFolder>();
|
||||
String[] allFolderValues;
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
|
||||
package com.fsck.k9.controller;
|
||||
|
||||
import java.io.CharArrayWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
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.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
@ -31,11 +36,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 +53,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 +64,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 +131,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 +543,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 +581,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 +780,142 @@ public class MessagingController implements Runnable {
|
|||
listener.searchStats(stats);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Future 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);
|
||||
}
|
||||
|
||||
return threadPool.submit(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");
|
||||
}
|
||||
|
||||
List<Message> messages = remoteFolder.search(query, 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 (Thread.currentThread().isInterrupted()) {
|
||||
Log.i(K9.LOG_TAG, "Caught exception on aborted remote search; safe to ignore.", e);
|
||||
} else {
|
||||
Log.e(K9.LOG_TAG, "Could not complete remote search", 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 {
|
||||
final FetchProfile header = new FetchProfile();
|
||||
header.add(FetchProfile.Item.FLAGS);
|
||||
header.add(FetchProfile.Item.ENVELOPE);
|
||||
final FetchProfile structure = new FetchProfile();
|
||||
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}, header, null);
|
||||
//fun fact: ImapFolder.fetch can't handle getting STRUCTURE at same time as headers
|
||||
remoteFolder.fetch(new Message [] {message}, 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 +1359,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 +2099,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;
|
||||
|
@ -2725,78 +2891,111 @@ public class MessagingController implements Runnable {
|
|||
put("loadMessageForViewRemote", listener, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Folder remoteFolder = null;
|
||||
LocalFolder localFolder = null;
|
||||
try {
|
||||
LocalStore localStore = account.getLocalStore();
|
||||
localFolder = localStore.getFolder(folder);
|
||||
localFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
Message message = localFolder.getMessage(uid);
|
||||
|
||||
if (message.isSet(Flag.X_DOWNLOADED_FULL)) {
|
||||
/*
|
||||
* If the message has been synchronized since we were called we'll
|
||||
* just hand it back cause it's ready to go.
|
||||
*/
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
localFolder.fetch(new Message[] { message }, fp, null);
|
||||
} else {
|
||||
/*
|
||||
* At this point the message is not available, so we need to download it
|
||||
* fully if possible.
|
||||
*/
|
||||
|
||||
Store remoteStore = account.getRemoteStore();
|
||||
remoteFolder = remoteStore.getFolder(folder);
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// Get the remote message and fully download it
|
||||
Message remoteMessage = remoteFolder.getMessage(uid);
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
||||
|
||||
// Store the message locally and load the stored message into memory
|
||||
localFolder.appendMessages(new Message[] { remoteMessage });
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
message = localFolder.getMessage(uid);
|
||||
localFolder.fetch(new Message[] { message }, fp, null);
|
||||
|
||||
// Mark that this message is now fully synched
|
||||
if (account.isMarkMessageAsReadOnView()) {
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
}
|
||||
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
}
|
||||
|
||||
// now that we have the full message, refresh the headers
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewHeadersAvailable(account, folder, uid, message);
|
||||
}
|
||||
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewBodyAvailable(account, folder, uid, message);
|
||||
}
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewFinished(account, folder, uid, message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewFailed(account, folder, uid, e);
|
||||
}
|
||||
addErrorMessage(account, null, e);
|
||||
|
||||
} finally {
|
||||
closeFolder(remoteFolder);
|
||||
closeFolder(localFolder);
|
||||
}
|
||||
}//run
|
||||
loadMessageForViewRemoteSynchronous(account, folder, uid, listener, false, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean loadMessageForViewRemoteSynchronous(final Account account, final String folder,
|
||||
final String uid, final MessagingListener listener, final boolean force,
|
||||
final boolean loadPartialFromSearch) {
|
||||
Folder remoteFolder = null;
|
||||
LocalFolder localFolder = null;
|
||||
try {
|
||||
LocalStore localStore = account.getLocalStore();
|
||||
localFolder = localStore.getFolder(folder);
|
||||
localFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
Message message = localFolder.getMessage(uid);
|
||||
|
||||
if (uid.startsWith(K9.LOCAL_UID_PREFIX)) {
|
||||
Log.w(K9.LOG_TAG, "Message has local UID so cannot download fully.");
|
||||
// ASH move toast
|
||||
android.widget.Toast.makeText(mApplication,
|
||||
"Message has local UID so cannot download fully",
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
// TODO: Using X_DOWNLOADED_FULL is wrong because it's only a partial message. But
|
||||
// one we can't download completely. Maybe add a new flag; X_PARTIAL_MESSAGE ?
|
||||
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false);
|
||||
}
|
||||
/* commented out because this was pulled from another unmerged branch:
|
||||
} else if (localFolder.isLocalOnly() && !force) {
|
||||
Log.w(K9.LOG_TAG, "Message in local-only folder so cannot download fully.");
|
||||
// ASH move toast
|
||||
android.widget.Toast.makeText(mApplication,
|
||||
"Message in local-only folder so cannot download fully",
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false);
|
||||
}*/
|
||||
|
||||
if (message.isSet(Flag.X_DOWNLOADED_FULL)) {
|
||||
/*
|
||||
* If the message has been synchronized since we were called we'll
|
||||
* just hand it back cause it's ready to go.
|
||||
*/
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
localFolder.fetch(new Message[] { message }, fp, null);
|
||||
} else {
|
||||
/*
|
||||
* At this point the message is not available, so we need to download it
|
||||
* fully if possible.
|
||||
*/
|
||||
|
||||
Store remoteStore = account.getRemoteStore();
|
||||
remoteFolder = remoteStore.getFolder(folder);
|
||||
remoteFolder.open(OpenMode.READ_WRITE);
|
||||
|
||||
// Get the remote message and fully download it
|
||||
Message remoteMessage = remoteFolder.getMessage(uid);
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
|
||||
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null);
|
||||
|
||||
// Store the message locally and load the stored message into memory
|
||||
localFolder.appendMessages(new Message[] { remoteMessage });
|
||||
if (loadPartialFromSearch) {
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
}
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
message = localFolder.getMessage(uid);
|
||||
localFolder.fetch(new Message[] { message }, fp, null);
|
||||
|
||||
// Mark that this message is now fully synched
|
||||
if (account.isMarkMessageAsReadOnView()) {
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
}
|
||||
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
}
|
||||
|
||||
// now that we have the full message, refresh the headers
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewHeadersAvailable(account, folder, uid, message);
|
||||
}
|
||||
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewBodyAvailable(account, folder, uid, message);
|
||||
}
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewFinished(account, folder, uid, message);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadMessageForViewFailed(account, folder, uid, e);
|
||||
}
|
||||
addErrorMessage(account, null, e);
|
||||
return false;
|
||||
} finally {
|
||||
closeFolder(remoteFolder);
|
||||
closeFolder(localFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadMessageForView(final Account account, final String folder, final String uid,
|
||||
final MessagingListener listener) {
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
|
@ -2811,12 +3010,25 @@ 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);
|
||||
}
|
||||
if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) {
|
||||
// IMAP search results will usually need to be downloaded before viewing.
|
||||
// TODO: limit by account.getMaximumAutoDownloadMessageSize().
|
||||
if (!message.isSet(Flag.X_DOWNLOADED_FULL) &&
|
||||
!message.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
|
||||
if (loadMessageForViewRemoteSynchronous(account, folder, uid, listener,
|
||||
false, true)) {
|
||||
if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) {
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
setFlag(new Message[] { message }, Flag.SEEN, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!message.isSet(Flag.SEEN)) {
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
setFlag(new Message[] { message }, Flag.SEEN, true);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.fsck.k9.mail;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.util.Log;
|
||||
|
@ -226,4 +227,9 @@ public abstract class Folder {
|
|||
public Account getAccount() {
|
||||
return mAccount;
|
||||
}
|
||||
|
||||
public List<Message> search(String queryString, final Flag[] requiredFlags, final Flag[] forbiddenFlags)
|
||||
throws MessagingException {
|
||||
throw new MessagingException("K-9 does not support searches on this folder type");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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,6 @@ public abstract class Store {
|
|||
public Account getAccount() {
|
||||
return mAccount;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
@ -818,7 +820,7 @@ public class ImapStore extends Store {
|
|||
private volatile boolean mExists;
|
||||
private ImapStore store = null;
|
||||
Map<Long, String> msgSeqUidMap = new ConcurrentHashMap<Long, String>();
|
||||
|
||||
private boolean mInSearch = false;
|
||||
|
||||
public ImapFolder(ImapStore nStore, String name) {
|
||||
super(nStore.getAccount());
|
||||
|
@ -998,7 +1000,13 @@ public class ImapStore extends Store {
|
|||
}
|
||||
|
||||
synchronized (this) {
|
||||
releaseConnection(mConnection);
|
||||
// If we are mid-search and we get a close request, we gotta trash the connection.
|
||||
if (mInSearch && mConnection != null) {
|
||||
Log.i(K9.LOG_TAG, "IMAP search was aborted, shutting down connection.");
|
||||
mConnection.close();
|
||||
} else {
|
||||
releaseConnection(mConnection);
|
||||
}
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
|
@ -1127,7 +1135,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++) {
|
||||
|
@ -1251,7 +1259,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;
|
||||
|
@ -1287,7 +1295,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());
|
||||
}
|
||||
|
@ -1336,7 +1344,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)
|
||||
|
@ -1346,7 +1354,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)
|
||||
|
@ -1356,12 +1364,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>();
|
||||
|
@ -1376,8 +1384,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) {
|
||||
|
@ -1392,7 +1404,7 @@ public class ImapStore extends Store {
|
|||
} catch (IOException ioe) {
|
||||
throw ioExceptionHandler(mConnection, ioe);
|
||||
}
|
||||
return messages.toArray(EMPTY_MESSAGE_ARRAY);
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1404,7 +1416,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) {
|
||||
|
@ -1441,7 +1453,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++) {
|
||||
|
@ -1566,7 +1578,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) {
|
||||
|
@ -1969,6 +1981,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>();
|
||||
|
@ -2081,6 +2094,7 @@ public class ImapStore extends Store {
|
|||
|
||||
@Override
|
||||
public void expunge() throws MessagingException {
|
||||
open(OpenMode.READ_WRITE);
|
||||
checkOpen();
|
||||
try {
|
||||
executeSimpleCommand("EXPUNGE");
|
||||
|
@ -2113,6 +2127,7 @@ public class ImapStore extends Store {
|
|||
@Override
|
||||
public void setFlags(Flag[] flags, boolean value)
|
||||
throws MessagingException {
|
||||
open(OpenMode.READ_WRITE);
|
||||
checkOpen();
|
||||
|
||||
|
||||
|
@ -2147,6 +2162,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++) {
|
||||
|
@ -2201,6 +2217,108 @@ public class ImapStore extends Store {
|
|||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the remote ImapFolder.
|
||||
* @param queryString String to query for.
|
||||
* @param requiredFlags Mandatory flags
|
||||
* @param forbiddenFlags Flags to exclude
|
||||
* @return List of messages found
|
||||
* @throws MessagingException On any error.
|
||||
*/
|
||||
@Override
|
||||
public List<Message> search(final String queryString, final Flag[] requiredFlags, final Flag[] forbiddenFlags)
|
||||
throws MessagingException {
|
||||
|
||||
if (!mAccount.allowRemoteSearch()) {
|
||||
throw new MessagingException("Your settings do not allow remote searching of this account");
|
||||
}
|
||||
|
||||
// Setup the searcher
|
||||
final 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
final String encodedQry = encodeString(queryString);
|
||||
if (mAccount.isRemoteSearchFullText()) {
|
||||
imapQuery += "TEXT " + encodedQry;
|
||||
} else {
|
||||
imapQuery += "OR SUBJECT " + encodedQry + " FROM " + encodedQry;
|
||||
}
|
||||
return executeSimpleCommand(imapQuery);
|
||||
}
|
||||
};
|
||||
|
||||
// Execute the search
|
||||
try {
|
||||
open(OpenMode.READ_ONLY);
|
||||
checkOpen();
|
||||
|
||||
mInSearch = true;
|
||||
// don't pass listener--we don't want to add messages until we've downloaded them
|
||||
return search(searcher, null);
|
||||
} finally {
|
||||
mInSearch = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3412,5 +3530,4 @@ public class ImapStore extends Store {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -215,6 +215,16 @@ public class AccountSettings {
|
|||
new V(1, new IntegerResourceSetting(5,
|
||||
R.array.account_settings_vibrate_times_label))
|
||||
));
|
||||
s.put("allowRemoteSearch", Settings.versions(
|
||||
new V(18, new BooleanSetting(false))
|
||||
));
|
||||
s.put("remoteSearchNumResults", Settings.versions(
|
||||
new V(18, new IntegerResourceSetting(Account.DEFAULT_REMOTE_SEARCH_NUM_RESULTS,
|
||||
R.array.account_settings_remote_search_num_results_values))
|
||||
));
|
||||
s.put("remoteSearchFullText", Settings.versions(
|
||||
new V(18, new BooleanSetting(false))
|
||||
));
|
||||
|
||||
SETTINGS = Collections.unmodifiableMap(s);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public class Settings {
|
|||
*
|
||||
* @see SettingsExporter
|
||||
*/
|
||||
public static final int VERSION = 17;
|
||||
public static final int VERSION = 18;
|
||||
|
||||
public static Map<String, Object> validate(int version, Map<String,
|
||||
TreeMap<Integer, SettingsDescription>> settings,
|
||||
|
|