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:textColor="?android:attr/textColorTertiary"
|
||||||
android:textSize="36sp" />
|
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>
|
</LinearLayout>
|
|
@ -1,6 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
<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
|
<item
|
||||||
android:id="@+id/check_mail"
|
android:id="@+id/check_mail"
|
||||||
android:alphabeticShortcut="r"
|
android:alphabeticShortcut="r"
|
||||||
|
|
|
@ -668,6 +668,26 @@
|
||||||
<item>HTML</item>
|
<item>HTML</item>
|
||||||
<item>AUTO</item>
|
<item>AUTO</item>
|
||||||
</string-array>
|
</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">
|
<string-array name="global_settings_notification_hide_subject_entries">
|
||||||
<item name="1">@string/global_settings_notification_hide_subject_never</item>
|
<item name="1">@string/global_settings_notification_hide_subject_never</item>
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
<attr name="iconActionUnflag" format="reference" />
|
<attr name="iconActionUnflag" format="reference" />
|
||||||
<attr name="iconActionMarkAsRead" format="reference" />
|
<attr name="iconActionMarkAsRead" format="reference" />
|
||||||
<attr name="iconActionMarkAsUnread" format="reference" />
|
<attr name="iconActionMarkAsUnread" format="reference" />
|
||||||
|
<attr name="iconActionRemoteSearch" format="reference" />
|
||||||
<attr name="iconMenuAdd" format="reference" />
|
<attr name="iconMenuAdd" format="reference" />
|
||||||
<attr name="iconMenuAttachment" format="reference" />
|
<attr name="iconMenuAttachment" format="reference" />
|
||||||
<attr name="iconMenuClear" 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_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="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>
|
</resources>
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
<item name="iconActionUnflag">@drawable/ic_action_unflag_light</item>
|
<item name="iconActionUnflag">@drawable/ic_action_unflag_light</item>
|
||||||
<item name="iconActionMarkAsRead">@drawable/ic_action_mark_as_read_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="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="iconMenuAdd">@drawable/ic_menu_add</item>
|
||||||
<item name="iconMenuAttachment">@drawable/ic_menu_attachment</item>
|
<item name="iconMenuAttachment">@drawable/ic_menu_attachment</item>
|
||||||
<item name="iconMenuClear">@drawable/ic_menu_clear</item>
|
<item name="iconMenuClear">@drawable/ic_menu_clear</item>
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
<item name="iconActionUnflag">@drawable/ic_action_unflag_dark</item>
|
<item name="iconActionUnflag">@drawable/ic_action_unflag_dark</item>
|
||||||
<item name="iconActionMarkAsRead">@drawable/ic_action_mark_as_read_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="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="iconMenuAdd">@drawable/ic_menu_add</item>
|
||||||
<item name="iconMenuAttachment">@drawable/ic_menu_attachment</item>
|
<item name="iconMenuAttachment">@drawable/ic_menu_attachment</item>
|
||||||
<item name="iconMenuClear">@drawable/ic_menu_clear</item>
|
<item name="iconMenuClear">@drawable/ic_menu_clear</item>
|
||||||
|
|
|
@ -23,7 +23,9 @@
|
||||||
can be displayed after the device has been rotated.
|
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
|
<PreferenceScreen
|
||||||
android:title="@string/account_settings_general_title"
|
android:title="@string/account_settings_general_title"
|
||||||
|
@ -460,6 +462,30 @@
|
||||||
|
|
||||||
</PreferenceScreen>
|
</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
|
<PreferenceScreen
|
||||||
android:title="@string/account_settings_crypto"
|
android:title="@string/account_settings_crypto"
|
||||||
android:key="crypto">
|
android:key="crypto">
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
|
|
||||||
package com.fsck.k9;
|
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.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.ConnectivityManager;
|
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_QUOTED_TEXT_SHOWN = true;
|
||||||
public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
|
public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false;
|
||||||
public static final boolean DEFAULT_STRIP_SIGNATURE = true;
|
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 ACCOUNT_DESCRIPTION_KEY = "description";
|
||||||
public static final String STORE_URI_KEY = "storeUri";
|
public static final String STORE_URI_KEY = "storeUri";
|
||||||
|
@ -186,6 +199,9 @@ public class Account implements BaseAccount {
|
||||||
private boolean mCryptoAutoEncrypt;
|
private boolean mCryptoAutoEncrypt;
|
||||||
private boolean mMarkMessageAsReadOnView;
|
private boolean mMarkMessageAsReadOnView;
|
||||||
private boolean mAlwaysShowCcBcc;
|
private boolean mAlwaysShowCcBcc;
|
||||||
|
private boolean mAllowRemoteSearch;
|
||||||
|
private boolean mRemoteSearchFullText;
|
||||||
|
private int mRemoteSearchNumResults;
|
||||||
|
|
||||||
private CryptoProvider mCryptoProvider = null;
|
private CryptoProvider mCryptoProvider = null;
|
||||||
|
|
||||||
|
@ -289,6 +305,9 @@ public class Account implements BaseAccount {
|
||||||
mCryptoApp = Apg.NAME;
|
mCryptoApp = Apg.NAME;
|
||||||
mCryptoAutoSignature = false;
|
mCryptoAutoSignature = false;
|
||||||
mCryptoAutoEncrypt = false;
|
mCryptoAutoEncrypt = false;
|
||||||
|
mAllowRemoteSearch = false;
|
||||||
|
mRemoteSearchFullText = false;
|
||||||
|
mRemoteSearchNumResults = DEFAULT_REMOTE_SEARCH_NUM_RESULTS;
|
||||||
mEnabled = true;
|
mEnabled = true;
|
||||||
mMarkMessageAsReadOnView = true;
|
mMarkMessageAsReadOnView = true;
|
||||||
mAlwaysShowCcBcc = false;
|
mAlwaysShowCcBcc = false;
|
||||||
|
@ -456,6 +475,10 @@ public class Account implements BaseAccount {
|
||||||
mCryptoApp = prefs.getString(mUuid + ".cryptoApp", Apg.NAME);
|
mCryptoApp = prefs.getString(mUuid + ".cryptoApp", Apg.NAME);
|
||||||
mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false);
|
mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false);
|
||||||
mCryptoAutoEncrypt = prefs.getBoolean(mUuid + ".cryptoAutoEncrypt", 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);
|
mEnabled = prefs.getBoolean(mUuid + ".enabled", true);
|
||||||
mMarkMessageAsReadOnView = prefs.getBoolean(mUuid + ".markMessageAsReadOnView", true);
|
mMarkMessageAsReadOnView = prefs.getBoolean(mUuid + ".markMessageAsReadOnView", true);
|
||||||
mAlwaysShowCcBcc = prefs.getBoolean(mUuid + ".alwaysShowCcBcc", false);
|
mAlwaysShowCcBcc = prefs.getBoolean(mUuid + ".alwaysShowCcBcc", false);
|
||||||
|
@ -706,6 +729,9 @@ public class Account implements BaseAccount {
|
||||||
editor.putString(mUuid + ".cryptoApp", mCryptoApp);
|
editor.putString(mUuid + ".cryptoApp", mCryptoApp);
|
||||||
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
|
editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature);
|
||||||
editor.putBoolean(mUuid + ".cryptoAutoEncrypt", mCryptoAutoEncrypt);
|
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 + ".enabled", mEnabled);
|
||||||
editor.putBoolean(mUuid + ".markMessageAsReadOnView", mMarkMessageAsReadOnView);
|
editor.putBoolean(mUuid + ".markMessageAsReadOnView", mMarkMessageAsReadOnView);
|
||||||
editor.putBoolean(mUuid + ".alwaysShowCcBcc", mAlwaysShowCcBcc);
|
editor.putBoolean(mUuid + ".alwaysShowCcBcc", mAlwaysShowCcBcc);
|
||||||
|
@ -1623,6 +1649,22 @@ public class Account implements BaseAccount {
|
||||||
mCryptoAutoEncrypt = cryptoAutoEncrypt;
|
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() {
|
public String getInboxFolderName() {
|
||||||
return mInboxFolderName;
|
return mInboxFolderName;
|
||||||
}
|
}
|
||||||
|
@ -1693,4 +1735,13 @@ public class Account implements BaseAccount {
|
||||||
public synchronized void setAlwaysShowCcBcc(boolean show) {
|
public synchronized void setAlwaysShowCcBcc(boolean show) {
|
||||||
mAlwaysShowCcBcc = 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 java.util.Date;
|
||||||
import com.fsck.k9.helper.MessageHelper;
|
import com.fsck.k9.helper.MessageHelper;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
import com.fsck.k9.mail.Message;
|
||||||
|
|
||||||
public class MessageInfoHolder {
|
public class MessageInfoHolder {
|
||||||
public String date;
|
public String date;
|
||||||
|
@ -19,7 +19,7 @@ public class MessageInfoHolder {
|
||||||
public boolean forwarded;
|
public boolean forwarded;
|
||||||
public boolean flagged;
|
public boolean flagged;
|
||||||
public boolean dirty;
|
public boolean dirty;
|
||||||
public LocalMessage message;
|
public Message message;
|
||||||
public FolderInfoHolder folder;
|
public FolderInfoHolder folder;
|
||||||
public boolean selected;
|
public boolean selected;
|
||||||
public String account;
|
public String account;
|
||||||
|
|
|
@ -7,9 +7,12 @@ import java.util.EnumMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.app.SearchManager;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
|
@ -25,15 +28,9 @@ import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.ContextMenu;
|
import android.view.*;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
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.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
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.Flag;
|
||||||
import com.fsck.k9.mail.Folder;
|
import com.fsck.k9.mail.Folder;
|
||||||
import com.fsck.k9.mail.Message;
|
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;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
|
||||||
import com.fsck.k9.mail.store.StorageManager;
|
import com.fsck.k9.mail.store.StorageManager;
|
||||||
import com.handmark.pulltorefresh.library.PullToRefreshBase;
|
import com.handmark.pulltorefresh.library.PullToRefreshBase;
|
||||||
import com.handmark.pulltorefresh.library.PullToRefreshListView;
|
import com.handmark.pulltorefresh.library.PullToRefreshListView;
|
||||||
|
@ -177,7 +175,13 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
|
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
|
@Override
|
||||||
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
|
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_ACCOUNT = "account";
|
||||||
private static final String EXTRA_FOLDER = "folder";
|
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_QUERY_FLAGS = "queryFlags";
|
||||||
private static final String EXTRA_FORBIDDEN_FLAGS = "forbiddenFlags";
|
private static final String EXTRA_FORBIDDEN_FLAGS = "forbiddenFlags";
|
||||||
private static final String EXTRA_INTEGRATE = "integrate";
|
private static final String EXTRA_INTEGRATE = "integrate";
|
||||||
|
@ -287,6 +299,10 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
private String mQueryString;
|
private String mQueryString;
|
||||||
private Flag[] mQueryFlags = null;
|
private Flag[] mQueryFlags = null;
|
||||||
private Flag[] mForbiddenFlags = 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 boolean mIntegrate = false;
|
||||||
private String[] mAccountUuids = null;
|
private String[] mAccountUuids = null;
|
||||||
private String[] mFolderNames = 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_REFRESH_TITLE = 5;
|
||||||
private static final int ACTION_PROGRESS = 6;
|
private static final int ACTION_PROGRESS = 6;
|
||||||
|
|
||||||
|
|
||||||
public void removeMessage(MessageReference messageReference) {
|
public void removeMessage(MessageReference messageReference) {
|
||||||
android.os.Message msg = android.os.Message.obtain(this, ACTION_REMOVE_MESSAGE,
|
android.os.Message msg = android.os.Message.obtain(this, ACTION_REMOVE_MESSAGE,
|
||||||
messageReference);
|
messageReference);
|
||||||
|
@ -394,6 +409,15 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
sendMessage(msg);
|
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) {
|
public void changeMessageUid(final MessageReference ref, final String newUid) {
|
||||||
// Instead of explicitly creating a container to be able to pass both arguments in a
|
// Instead of explicitly creating a container to be able to pass both arguments in a
|
||||||
// Message we post a Runnable to the message queue.
|
// Message we post a Runnable to the message queue.
|
||||||
|
@ -483,10 +507,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the comparator chain
|
return new ComparatorChain<MessageInfoHolder>(chain);
|
||||||
final Comparator<MessageInfoHolder> chainComparator = new ComparatorChain<MessageInfoHolder>(chain);
|
|
||||||
|
|
||||||
return chainComparator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void folderLoading(String folder, boolean loading) {
|
private void folderLoading(String folder, boolean loading) {
|
||||||
|
@ -498,7 +519,9 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
|
|
||||||
private void refreshTitle() {
|
private void refreshTitle() {
|
||||||
setWindowTitle();
|
setWindowTitle();
|
||||||
setWindowProgress();
|
if (!mRemoteSearch) {
|
||||||
|
setWindowProgress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void 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) {
|
public static void actionHandle(Context context, String title, String queryString, boolean integrate, Flag[] flags, Flag[] forbiddenFlags) {
|
||||||
Intent intent = new Intent(context, MessageList.class);
|
Intent intent = new Intent(context, MessageList.class);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
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) {
|
if (flags != null) {
|
||||||
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(flags, ','));
|
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,
|
public static Intent actionHandleAccountIntent(Context context, String title,
|
||||||
SearchSpecification searchSpecification) {
|
SearchSpecification searchSpecification) {
|
||||||
Intent intent = new Intent(context, MessageList.class);
|
Intent intent = new Intent(context, MessageList.class);
|
||||||
intent.putExtra(EXTRA_QUERY, searchSpecification.getQuery());
|
intent.putExtra(SearchManager.QUERY, searchSpecification.getQuery());
|
||||||
if (searchSpecification.getRequiredFlags() != null) {
|
if (searchSpecification.getRequiredFlags() != null) {
|
||||||
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(searchSpecification.getRequiredFlags(), ','));
|
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(searchSpecification.getRequiredFlags(), ','));
|
||||||
}
|
}
|
||||||
|
@ -644,8 +667,25 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (view == mFooterView) {
|
if (view == mFooterView) {
|
||||||
if (mCurrentFolder != null) {
|
if (mCurrentFolder != null && !mRemoteSearch) {
|
||||||
mController.loadMoreMessages(mAccount, mFolderName, mAdapter.mListener);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -667,7 +707,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
mActionBarProgressView = getLayoutInflater().inflate(R.layout.actionbar_indeterminate_progress_actionview, null);
|
mActionBarProgressView = getLayoutInflater().inflate(R.layout.actionbar_indeterminate_progress_actionview, null);
|
||||||
|
|
||||||
// need this for actionbar initialization
|
// need this for actionbar initialization
|
||||||
mQueryString = getIntent().getStringExtra(EXTRA_QUERY);
|
mQueryString = getIntent().getStringExtra(SearchManager.QUERY);
|
||||||
|
|
||||||
mPullToRefreshView = (PullToRefreshListView) findViewById(R.id.message_list);
|
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) {
|
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);
|
String accountUuid = intent.getStringExtra(EXTRA_ACCOUNT);
|
||||||
|
mFolderName = intent.getStringExtra(EXTRA_FOLDER);
|
||||||
|
|
||||||
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
|
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
|
||||||
|
|
||||||
if (mAccount != null && !mAccount.isAvailable(this)) {
|
if (mAccount != null && !mAccount.isAvailable(this)) {
|
||||||
|
@ -708,8 +771,8 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mFolderName = intent.getStringExtra(EXTRA_FOLDER);
|
|
||||||
mQueryString = intent.getStringExtra(EXTRA_QUERY);
|
|
||||||
|
|
||||||
String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS);
|
String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS);
|
||||||
if (queryFlags != null) {
|
if (queryFlags != null) {
|
||||||
|
@ -745,8 +808,8 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount);
|
mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide "Load up to x more" footer for search views
|
// Hide "Load up to x more" footer for local search views
|
||||||
mFooterView.setVisibility((mQueryString != null) ? View.GONE : View.VISIBLE);
|
mFooterView.setVisibility((mQueryString != null && !mRemoteSearch) ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
mController = MessagingController.getInstance(getApplication());
|
mController = MessagingController.getInstance(getApplication());
|
||||||
mListView.setAdapter(mAdapter);
|
mListView.setAdapter(mAdapter);
|
||||||
|
@ -806,26 +869,54 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
onAccountUnavailable();
|
onAccountUnavailable();
|
||||||
return;
|
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);
|
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
|
||||||
mSenderAboveSubject = K9.messageListSenderAboveSubject();
|
mSenderAboveSubject = K9.messageListSenderAboveSubject();
|
||||||
|
|
||||||
// TODO Add support for pull to fresh on searches.
|
final Preferences prefs = Preferences.getPreferences(getApplicationContext());
|
||||||
if(mQueryString == null) {
|
|
||||||
|
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>() {
|
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
|
||||||
@Override
|
@Override
|
||||||
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
|
||||||
checkMail(mAccount, mFolderName);
|
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 {
|
} else {
|
||||||
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
|
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
mController.addListener(mAdapter.mListener);
|
mController.addListener(mAdapter.mListener);
|
||||||
|
|
||||||
|
//Cancel pending new mail notifications when we open an account
|
||||||
Account[] accountsWithNotification;
|
Account[] accountsWithNotification;
|
||||||
|
|
||||||
Preferences prefs = Preferences.getPreferences(getApplicationContext());
|
|
||||||
Account account = getCurrentAccount(prefs);
|
Account account = getCurrentAccount(prefs);
|
||||||
|
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
|
@ -844,8 +935,12 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
mController.notifyAccountCancel(this, accountWithNotification);
|
mController.notifyAccountCancel(this, accountWithNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (mAdapter.isEmpty()) {
|
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);
|
mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener);
|
||||||
// Hide the archive button if we don't have an archive folder.
|
// Hide the archive button if we don't have an archive folder.
|
||||||
if (!mAccount.hasArchiveFolder()) {
|
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.
|
// Don't show the archive button if this is a search.
|
||||||
// mBatchArchiveButton.setVisibility(View.GONE);
|
// mBatchArchiveButton.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// reread the selected date format preference in case it has changed
|
// reread the selected date format preference in case it has changed
|
||||||
mMessageHelper.refresh();
|
mMessageHelper.refresh();
|
||||||
|
@ -866,27 +960,29 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (mFolderName != null) {
|
if (!mRemoteSearch) {
|
||||||
mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener);
|
if (mFolderName != null) {
|
||||||
} else if (mQueryString != null) {
|
mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener);
|
||||||
mController.searchLocalMessagesSynchronous(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, 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();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mAdapter.pruneDirtyMessages();
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
restoreListState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mAccount != null && mFolderName != null) {
|
if (mAccount != null && mFolderName != null && !mRemoteSearch) {
|
||||||
mController.getFolderUnreadMessageCount(mAccount, mFolderName, mAdapter.mListener);
|
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;
|
boolean retval = true;
|
||||||
int position = mListView.getSelectedItemPosition();
|
int position = mListView.getSelectedItemPosition();
|
||||||
try {
|
try {
|
||||||
|
@ -1161,6 +1258,47 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
AccountSettings.actionSettings(this, mAccount);
|
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) {
|
private void changeSort(SortType sortType) {
|
||||||
Boolean sortAscending = (mSortType == sortType) ? !mSortAscending : null;
|
Boolean sortAscending = (mSortType == sortType) ? !mSortAscending : null;
|
||||||
changeSort(sortType, sortAscending);
|
changeSort(sortType, sortAscending);
|
||||||
|
@ -1357,7 +1495,7 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onToggleFlag(MessageInfoHolder messageInfo) {
|
private void onToggleFlag(MessageInfoHolder messageInfo) {
|
||||||
LocalMessage message = messageInfo.message;
|
Message message = messageInfo.message;
|
||||||
Folder folder = message.getFolder();
|
Folder folder = message.getFolder();
|
||||||
Account account = folder.getAccount();
|
Account account = folder.getAccount();
|
||||||
String folderName = folder.getName();
|
String folderName = folder.getName();
|
||||||
|
@ -1376,6 +1514,36 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
mController.sendPendingMessages(account, mAdapter.mListener);
|
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
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
int itemId = item.getItemId();
|
int itemId = item.getItemId();
|
||||||
|
@ -1438,6 +1606,10 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
onEditPrefs();
|
onEditPrefs();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case R.id.search_remote: {
|
||||||
|
onRemoteSearchRequested(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mQueryString != null) {
|
if (mQueryString != null) {
|
||||||
|
@ -1602,12 +1774,27 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
|
||||||
if (mQueryString != null) {
|
if (mQueryString != null || mIntegrate) {
|
||||||
menu.findItem(R.id.expunge).setVisible(false);
|
menu.findItem(R.id.expunge).setVisible(false);
|
||||||
menu.findItem(R.id.check_mail).setVisible(false);
|
menu.findItem(R.id.check_mail).setVisible(false);
|
||||||
menu.findItem(R.id.send_messages).setVisible(false);
|
menu.findItem(R.id.send_messages).setVisible(false);
|
||||||
menu.findItem(R.id.folder_settings).setVisible(false);
|
menu.findItem(R.id.folder_settings).setVisible(false);
|
||||||
menu.findItem(R.id.account_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 {
|
} else {
|
||||||
if (mCurrentFolder != null && mCurrentFolder.name.equals(mAccount.getOutboxFolderName())) {
|
if (mCurrentFolder != null && mCurrentFolder.name.equals(mAccount.getOutboxFolderName())) {
|
||||||
menu.findItem(R.id.check_mail).setVisible(false);
|
menu.findItem(R.id.check_mail).setVisible(false);
|
||||||
|
@ -1663,8 +1850,63 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
private final List<MessageInfoHolder> mMessages =
|
private final List<MessageInfoHolder> mMessages =
|
||||||
Collections.synchronizedList(new ArrayList<MessageInfoHolder>());
|
Collections.synchronizedList(new ArrayList<MessageInfoHolder>());
|
||||||
|
|
||||||
|
public List<Message> mExtraSearchResults;
|
||||||
|
|
||||||
private final ActivityListener mListener = new ActivityListener() {
|
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
|
@Override
|
||||||
public void informUserOfStatus() {
|
public void informUserOfStatus() {
|
||||||
mHandler.refreshTitle();
|
mHandler.refreshTitle();
|
||||||
|
@ -2374,26 +2616,41 @@ public class MessageList extends K9ListActivity implements OnItemClickListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFooterView() {
|
private void updateFooterView() {
|
||||||
FooterViewHolder holder = (FooterViewHolder) mFooterView.getTag();
|
|
||||||
|
|
||||||
if (mCurrentFolder != null && mAccount != null) {
|
if (mCurrentFolder != null && mAccount != null) {
|
||||||
if (mCurrentFolder.loading) {
|
if (mCurrentFolder.loading) {
|
||||||
holder.main.setText(getString(R.string.status_loading_more));
|
final boolean showProgress = true;
|
||||||
holder.progress.setVisibility(ProgressBar.VISIBLE);
|
updateFooter(getString(R.string.status_loading_more), showProgress);
|
||||||
} else {
|
} else {
|
||||||
|
String message;
|
||||||
if (!mCurrentFolder.lastCheckFailed) {
|
if (!mCurrentFolder.lastCheckFailed) {
|
||||||
if (mAccount.getDisplayCount() == 0) {
|
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 {
|
} 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 {
|
} 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 {
|
} 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;
|
package com.fsck.k9.activity;
|
||||||
import com.fsck.k9.activity.MessageList;
|
|
||||||
|
|
||||||
|
|
||||||
public class Search extends 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;
|
package com.fsck.k9.activity.setup;
|
||||||
|
|
||||||
import android.app.Dialog;
|
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.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Vibrator;
|
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 android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
import java.util.Iterator;
|
import android.widget.CheckBox;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.Account.FolderMode;
|
import com.fsck.k9.Account.FolderMode;
|
||||||
|
@ -24,18 +35,17 @@ import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.NotificationSetting;
|
import com.fsck.k9.NotificationSetting;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.mail.Folder;
|
|
||||||
import com.fsck.k9.activity.ChooseFolder;
|
import com.fsck.k9.activity.ChooseFolder;
|
||||||
import com.fsck.k9.activity.ChooseIdentity;
|
import com.fsck.k9.activity.ChooseIdentity;
|
||||||
import com.fsck.k9.activity.ColorPickerDialog;
|
import com.fsck.k9.activity.ColorPickerDialog;
|
||||||
import com.fsck.k9.activity.K9PreferenceActivity;
|
import com.fsck.k9.activity.K9PreferenceActivity;
|
||||||
import com.fsck.k9.activity.ManageIdentities;
|
import com.fsck.k9.activity.ManageIdentities;
|
||||||
import com.fsck.k9.crypto.Apg;
|
import com.fsck.k9.crypto.Apg;
|
||||||
|
import com.fsck.k9.mail.Folder;
|
||||||
import com.fsck.k9.mail.Store;
|
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.LocalStore.LocalFolder;
|
||||||
|
import com.fsck.k9.mail.store.StorageManager;
|
||||||
|
import com.fsck.k9.service.MailService;
|
||||||
|
|
||||||
|
|
||||||
public class AccountSettings extends K9PreferenceActivity {
|
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 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_COMPOSING = "composing";
|
||||||
private static final String PREFERENCE_SCREEN_INCOMING = "incoming_prefs";
|
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_PUSH_ADVANCED = "push_advanced";
|
||||||
private static final String PREFERENCE_SCREEN_NOTIFICATIONS = "notifications";
|
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_DESCRIPTION = "account_description";
|
||||||
private static final String PREFERENCE_MARK_MESSAGE_AS_READ_ON_VIEW = "mark_message_as_read_on_view";
|
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_APP = "crypto_app";
|
||||||
private static final String PREFERENCE_CRYPTO_AUTO_SIGNATURE = "crypto_auto_signature";
|
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_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_LOCAL_STORAGE_PROVIDER = "local_storage_provider";
|
||||||
private static final String PREFERENCE_CATEGORY_FOLDERS = "folders";
|
private static final String PREFERENCE_CATEGORY_FOLDERS = "folders";
|
||||||
private static final String PREFERENCE_ARCHIVE_FOLDER = "archive_folder";
|
private static final String PREFERENCE_ARCHIVE_FOLDER = "archive_folder";
|
||||||
|
@ -116,6 +132,7 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
private boolean mIsPushCapable = false;
|
private boolean mIsPushCapable = false;
|
||||||
private boolean mIsExpungeCapable = false;
|
private boolean mIsExpungeCapable = false;
|
||||||
|
|
||||||
|
private PreferenceScreen mMainScreen;
|
||||||
private PreferenceScreen mComposingScreen;
|
private PreferenceScreen mComposingScreen;
|
||||||
|
|
||||||
private EditTextPreference mAccountDescription;
|
private EditTextPreference mAccountDescription;
|
||||||
|
@ -162,6 +179,12 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
private ListPreference mCryptoApp;
|
private ListPreference mCryptoApp;
|
||||||
private CheckBoxPreference mCryptoAutoSignature;
|
private CheckBoxPreference mCryptoAutoSignature;
|
||||||
private CheckBoxPreference mCryptoAutoEncrypt;
|
private CheckBoxPreference mCryptoAutoEncrypt;
|
||||||
|
|
||||||
|
private PreferenceScreen mSearchScreen;
|
||||||
|
private CheckBoxPreference mCloudSearchEnabled;
|
||||||
|
private ListPreference mRemoteSearchNumResults;
|
||||||
|
private CheckBoxPreference mRemoteSearchFullText;
|
||||||
|
|
||||||
private ListPreference mLocalStorageProvider;
|
private ListPreference mLocalStorageProvider;
|
||||||
private ListPreference mArchiveFolder;
|
private ListPreference mArchiveFolder;
|
||||||
private ListPreference mDraftsFolder;
|
private ListPreference mDraftsFolder;
|
||||||
|
@ -195,6 +218,8 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
|
|
||||||
addPreferencesFromResource(R.xml.account_settings_preferences);
|
addPreferencesFromResource(R.xml.account_settings_preferences);
|
||||||
|
|
||||||
|
mMainScreen = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_MAIN);
|
||||||
|
|
||||||
mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
|
mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
|
||||||
mAccountDescription.setSummary(mAccount.getDescription());
|
mAccountDescription.setSummary(mAccount.getDescription());
|
||||||
mAccountDescription.setText(mAccount.getDescription());
|
mAccountDescription.setText(mAccount.getDescription());
|
||||||
|
@ -397,7 +422,7 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
|
|
||||||
if (!mAccount.isSearchByDateCapable()) {
|
if (!mAccount.isSearchByDateCapable()) {
|
||||||
((PreferenceScreen) findPreference(PREFERENCE_SCREEN_INCOMING)).removePreference(mMessageAge);
|
((PreferenceScreen) findPreference(PREFERENCE_SCREEN_INCOMING)).removePreference(mMessageAge);
|
||||||
} else {
|
} else {
|
||||||
mMessageAge.setValue(String.valueOf(mAccount.getMaximumPolledMessageAge()));
|
mMessageAge.setValue(String.valueOf(mAccount.getMaximumPolledMessageAge()));
|
||||||
mMessageAge.setSummary(mMessageAge.getEntry());
|
mMessageAge.setSummary(mMessageAge.getEntry());
|
||||||
mMessageAge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mMessageAge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@ -471,14 +496,33 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMAP-specific preferences
|
// 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);
|
mPushPollOnConnect = (CheckBoxPreference) findPreference(PREFERENCE_PUSH_POLL_ON_CONNECT);
|
||||||
mIdleRefreshPeriod = (ListPreference) findPreference(PREFERENCE_IDLE_REFRESH_PERIOD);
|
mIdleRefreshPeriod = (ListPreference) findPreference(PREFERENCE_IDLE_REFRESH_PERIOD);
|
||||||
mMaxPushFolders = (ListPreference) findPreference(PREFERENCE_MAX_PUSH_FOLDERS);
|
mMaxPushFolders = (ListPreference) findPreference(PREFERENCE_MAX_PUSH_FOLDERS);
|
||||||
if (mIsPushCapable) {
|
if (mIsPushCapable) {
|
||||||
mPushPollOnConnect.setChecked(mAccount.isPushPollOnConnect());
|
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.setValue(String.valueOf(mAccount.getIdleRefreshMinutes()));
|
||||||
mIdleRefreshPeriod.setSummary(mIdleRefreshPeriod.getEntry());
|
mIdleRefreshPeriod.setSummary(mIdleRefreshPeriod.getEntry());
|
||||||
mIdleRefreshPeriod.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mIdleRefreshPeriod.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@ -516,8 +560,9 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
PreferenceScreen incomingPrefs = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_INCOMING);
|
PreferenceScreen incomingPrefs = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_INCOMING);
|
||||||
incomingPrefs.removePreference( (PreferenceScreen) findPreference(PREFERENCE_SCREEN_PUSH_ADVANCED));
|
incomingPrefs.removePreference((PreferenceScreen) findPreference(PREFERENCE_SCREEN_PUSH_ADVANCED));
|
||||||
incomingPrefs.removePreference( (ListPreference) findPreference(PREFERENCE_PUSH_MODE));
|
incomingPrefs.removePreference((ListPreference) findPreference(PREFERENCE_PUSH_MODE));
|
||||||
|
mMainScreen.removePreference(mSearchScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
|
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
|
||||||
|
@ -745,11 +790,14 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
mAccount.setTrashFolderName(mTrashFolder.getValue());
|
mAccount.setTrashFolderName(mTrashFolder.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//IMAP stuff
|
||||||
if (mIsPushCapable) {
|
if (mIsPushCapable) {
|
||||||
mAccount.setPushPollOnConnect(mPushPollOnConnect.isChecked());
|
mAccount.setPushPollOnConnect(mPushPollOnConnect.isChecked());
|
||||||
mAccount.setIdleRefreshMinutes(Integer.parseInt(mIdleRefreshPeriod.getValue()));
|
mAccount.setIdleRefreshMinutes(Integer.parseInt(mIdleRefreshPeriod.getValue()));
|
||||||
mAccount.setMaxPushFolders(Integer.parseInt(mMaxPushFolders.getValue()));
|
mAccount.setMaxPushFolders(Integer.parseInt(mMaxPushFolders.getValue()));
|
||||||
|
mAccount.setAllowRemoteSearch(mCloudSearchEnabled.isChecked());
|
||||||
|
mAccount.setRemoteSearchNumResults(Integer.parseInt(mRemoteSearchNumResults.getValue()));
|
||||||
|
mAccount.setRemoteSearchFullText(mRemoteSearchFullText.isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mIsMoveCapable) {
|
if (!mIsMoveCapable) {
|
||||||
|
@ -776,6 +824,7 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
|
|
||||||
mAccount.setShowPictures(Account.ShowPictures.valueOf(mAccountShowPictures.getValue()));
|
mAccount.setShowPictures(Account.ShowPictures.valueOf(mAccountShowPictures.getValue()));
|
||||||
|
|
||||||
|
//IMAP specific stuff
|
||||||
if (mIsPushCapable) {
|
if (mIsPushCapable) {
|
||||||
boolean needsPushRestart = mAccount.setFolderPushMode(Account.FolderMode.valueOf(mPushMode.getValue()));
|
boolean needsPushRestart = mAccount.setFolderPushMode(Account.FolderMode.valueOf(mPushMode.getValue()));
|
||||||
if (mAccount.getFolderPushMode() != FolderMode.NONE) {
|
if (mAccount.getFolderPushMode() != FolderMode.NONE) {
|
||||||
|
@ -923,6 +972,20 @@ public class AccountSettings extends K9PreferenceActivity {
|
||||||
Integer.parseInt(mAccountVibrateTimes.getValue())), -1);
|
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> {
|
private class PopulateFolderPrefsTask extends AsyncTask<Void, Void, Void> {
|
||||||
List <? extends Folder > folders = new LinkedList<LocalFolder>();
|
List <? extends Folder > folders = new LinkedList<LocalFolder>();
|
||||||
String[] allFolderValues;
|
String[] allFolderValues;
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
|
|
||||||
package com.fsck.k9.controller;
|
package com.fsck.k9.controller;
|
||||||
|
|
||||||
import java.io.CharArrayWriter;
|
import java.io.CharArrayWriter;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.Date;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
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.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@ -31,11 +36,11 @@ import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.AccountStats;
|
import com.fsck.k9.AccountStats;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.K9.NotificationHideSubject;
|
import com.fsck.k9.K9.NotificationHideSubject;
|
||||||
|
import com.fsck.k9.K9.Intents;
|
||||||
import com.fsck.k9.NotificationSetting;
|
import com.fsck.k9.NotificationSetting;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.SearchSpecification;
|
import com.fsck.k9.SearchSpecification;
|
||||||
import com.fsck.k9.K9.Intents;
|
|
||||||
import com.fsck.k9.activity.FolderList;
|
import com.fsck.k9.activity.FolderList;
|
||||||
import com.fsck.k9.activity.MessageList;
|
import com.fsck.k9.activity.MessageList;
|
||||||
import com.fsck.k9.helper.NotificationBuilder;
|
import com.fsck.k9.helper.NotificationBuilder;
|
||||||
|
@ -48,8 +53,8 @@ import com.fsck.k9.mail.Flag;
|
||||||
import com.fsck.k9.mail.Folder;
|
import com.fsck.k9.mail.Folder;
|
||||||
import com.fsck.k9.mail.Folder.FolderType;
|
import com.fsck.k9.mail.Folder.FolderType;
|
||||||
import com.fsck.k9.mail.Folder.OpenMode;
|
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;
|
||||||
|
import com.fsck.k9.mail.Message.RecipientType;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.Part;
|
import com.fsck.k9.mail.Part;
|
||||||
import com.fsck.k9.mail.PushReceiver;
|
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.MimeMessage;
|
||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
import com.fsck.k9.mail.internet.MimeUtility;
|
||||||
import com.fsck.k9.mail.internet.TextBody;
|
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.LocalStore;
|
||||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
||||||
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
|
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
|
||||||
|
import com.fsck.k9.mail.store.UnavailableAccountException;
|
||||||
|
import com.fsck.k9.mail.store.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_MARK_ALL_AS_READ = "com.fsck.k9.MessagingController.markAllAsRead";
|
||||||
private static final String PENDING_COMMAND_EXPUNGE = "com.fsck.k9.MessagingController.expunge";
|
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
|
* Maximum number of unsynced messages to store at once
|
||||||
*/
|
*/
|
||||||
|
@ -516,7 +543,7 @@ public class MessagingController implements Runnable {
|
||||||
l.listLocalMessagesStarted(account, folder);
|
l.listLocalMessagesStarted(account, folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
Folder localFolder = null;
|
LocalFolder localFolder = null;
|
||||||
MessageRetrievalListener retrievalListener =
|
MessageRetrievalListener retrievalListener =
|
||||||
new MessageRetrievalListener() {
|
new MessageRetrievalListener() {
|
||||||
List<Message> pendingMessages = new ArrayList<Message>();
|
List<Message> pendingMessages = new ArrayList<Message>();
|
||||||
|
@ -554,10 +581,13 @@ public class MessagingController implements Runnable {
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Store localStore = account.getLocalStore();
|
LocalStore localStore = account.getLocalStore();
|
||||||
localFolder = localStore.getFolder(folder);
|
localFolder = localStore.getFolder(folder);
|
||||||
localFolder.open(OpenMode.READ_WRITE);
|
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(
|
localFolder.getMessages(
|
||||||
retrievalListener,
|
retrievalListener,
|
||||||
false // Skip deleted messages
|
false // Skip deleted messages
|
||||||
|
@ -750,6 +780,142 @@ public class MessagingController implements Runnable {
|
||||||
listener.searchStats(stats);
|
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) {
|
public void loadMoreMessages(Account account, String folder, MessagingListener listener) {
|
||||||
try {
|
try {
|
||||||
LocalStore localStore = account.getLocalStore();
|
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
|
* 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.
|
* fetch results for newest to oldest. If not, no harm done.
|
||||||
*/
|
*/
|
||||||
Collections.reverse(unsyncedMessages);
|
Collections.sort(unsyncedMessages, new UidReverseComparator());
|
||||||
int visibleLimit = localFolder.getVisibleLimit();
|
int visibleLimit = localFolder.getVisibleLimit();
|
||||||
int listSize = unsyncedMessages.size();
|
int listSize = unsyncedMessages.size();
|
||||||
|
|
||||||
if ((visibleLimit > 0) && (listSize > visibleLimit)) {
|
if ((visibleLimit > 0) && (listSize > visibleLimit)) {
|
||||||
unsyncedMessages = unsyncedMessages.subList(listSize - visibleLimit, listSize);
|
unsyncedMessages = unsyncedMessages.subList(0, visibleLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchProfile fp = new FetchProfile();
|
FetchProfile fp = new FetchProfile();
|
||||||
|
@ -1933,7 +2099,7 @@ public class MessagingController implements Runnable {
|
||||||
|
|
||||||
LocalStore localStore = account.getLocalStore();
|
LocalStore localStore = account.getLocalStore();
|
||||||
localFolder = localStore.getFolder(folder);
|
localFolder = localStore.getFolder(folder);
|
||||||
LocalMessage localMessage = (LocalMessage) localFolder.getMessage(uid);
|
LocalMessage localMessage = localFolder.getMessage(uid);
|
||||||
|
|
||||||
if (localMessage == null) {
|
if (localMessage == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -2725,78 +2891,111 @@ public class MessagingController implements Runnable {
|
||||||
put("loadMessageForViewRemote", listener, new Runnable() {
|
put("loadMessageForViewRemote", listener, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Folder remoteFolder = null;
|
loadMessageForViewRemoteSynchronous(account, folder, uid, listener, false, false);
|
||||||
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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
public void loadMessageForView(final Account account, final String folder, final String uid,
|
||||||
final MessagingListener listener) {
|
final MessagingListener listener) {
|
||||||
for (MessagingListener l : getListeners(listener)) {
|
for (MessagingListener l : getListeners(listener)) {
|
||||||
|
@ -2811,12 +3010,25 @@ public class MessagingController implements Runnable {
|
||||||
LocalFolder localFolder = localStore.getFolder(folder);
|
LocalFolder localFolder = localStore.getFolder(folder);
|
||||||
localFolder.open(OpenMode.READ_WRITE);
|
localFolder.open(OpenMode.READ_WRITE);
|
||||||
|
|
||||||
LocalMessage message = (LocalMessage)localFolder.getMessage(uid);
|
LocalMessage message = localFolder.getMessage(uid);
|
||||||
if (message == null
|
if (message == null
|
||||||
|| message.getId() == 0) {
|
|| message.getId() == 0) {
|
||||||
throw new IllegalArgumentException("Message not found: folder=" + folder + ", uid=" + uid);
|
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);
|
message.setFlag(Flag.SEEN, true);
|
||||||
setFlag(new Message[] { message }, Flag.SEEN, true);
|
setFlag(new Message[] { message }, Flag.SEEN, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
|
||||||
package com.fsck.k9.controller;
|
package com.fsck.k9.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.AccountStats;
|
import com.fsck.k9.AccountStats;
|
||||||
import com.fsck.k9.BaseAccount;
|
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.Message;
|
||||||
import com.fsck.k9.mail.Part;
|
import com.fsck.k9.mail.Part;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the interface that {@link MessagingController} will use to callback to requesters.
|
* Defines the interface that {@link MessagingController} will use to callback to requesters.
|
||||||
*
|
*
|
||||||
|
@ -152,6 +153,50 @@ public class MessagingListener {
|
||||||
public void pendingCommandsFinished(Account account) {}
|
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
|
* 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
|
* 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.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.Message.RecipientType;
|
import com.fsck.k9.mail.Message.RecipientType;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
|
||||||
import com.fsck.k9.helper.DateFormatter;
|
import com.fsck.k9.helper.DateFormatter;
|
||||||
|
|
||||||
public class MessageHelper {
|
public class MessageHelper {
|
||||||
|
@ -43,11 +42,10 @@ public class MessageHelper {
|
||||||
mTodayDateFormat = android.text.format.DateFormat.getTimeFormat(mContext);
|
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 FolderInfoHolder folder, final Account account) {
|
||||||
final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
|
final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
|
||||||
try {
|
try {
|
||||||
LocalMessage message = (LocalMessage) m;
|
|
||||||
target.message = message;
|
target.message = message;
|
||||||
target.compareArrival = message.getInternalDate();
|
target.compareArrival = message.getInternalDate();
|
||||||
target.compareDate = message.getSentDate();
|
target.compareDate = message.getSentDate();
|
||||||
|
@ -86,13 +84,16 @@ public class MessageHelper {
|
||||||
target.uid = message.getUid();
|
target.uid = message.getUid();
|
||||||
|
|
||||||
target.account = account.getUuid();
|
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) {
|
} catch (MessagingException me) {
|
||||||
Log.w(K9.LOG_TAG, "Unable to load message info", me);
|
Log.w(K9.LOG_TAG, "Unable to load message info", me);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public String formatDate(Date date) {
|
public String formatDate(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
if (Utility.isDateToday(date)) {
|
if (Utility.isDateToday(date)) {
|
||||||
return mTodayDateFormat.format(date);
|
return mTodayDateFormat.format(date);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.fsck.k9.mail;
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -226,4 +227,9 @@ public abstract class Folder {
|
||||||
public Account getAccount() {
|
public Account getAccount() {
|
||||||
return mAccount;
|
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;
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.activity.MessageReference;
|
import com.fsck.k9.activity.MessageReference;
|
||||||
import com.fsck.k9.mail.filter.CountingOutputStream;
|
import com.fsck.k9.mail.filter.CountingOutputStream;
|
||||||
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
|
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
|
||||||
|
|
||||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||||
import com.fsck.k9.K9;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class Message implements Part, Body {
|
public abstract class Message implements Part, Body {
|
||||||
|
@ -144,6 +143,16 @@ public abstract class Message implements Part, Body {
|
||||||
return getContentType().startsWith(mimeType);
|
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 {}
|
public void delete(String trashFolderName) throws MessagingException {}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
|
||||||
package com.fsck.k9.mail;
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
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.ImapStore;
|
||||||
import com.fsck.k9.mail.store.LocalStore;
|
import com.fsck.k9.mail.store.LocalStore;
|
||||||
import com.fsck.k9.mail.store.Pop3Store;
|
import com.fsck.k9.mail.store.Pop3Store;
|
||||||
import com.fsck.k9.mail.store.WebDavStore;
|
|
||||||
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
||||||
|
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||||
import java.util.HashMap;
|
import com.fsck.k9.mail.store.WebDavStore;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store is the access point for an email message store. It's location can be
|
* 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() {
|
public Account getAccount() {
|
||||||
return mAccount;
|
return mAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -592,4 +592,28 @@ public class MimeMessage extends Message {
|
||||||
copy(message);
|
copy(message);
|
||||||
return 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.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
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.SSLContext;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
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.FetchProfile;
|
||||||
import com.fsck.k9.mail.Flag;
|
import com.fsck.k9.mail.Flag;
|
||||||
import com.fsck.k9.mail.Folder;
|
import com.fsck.k9.mail.Folder;
|
||||||
|
import com.fsck.k9.mail.Folder.OpenMode;
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.Part;
|
import com.fsck.k9.mail.Part;
|
||||||
import com.fsck.k9.mail.PushReceiver;
|
import com.fsck.k9.mail.PushReceiver;
|
||||||
import com.fsck.k9.mail.Pusher;
|
import com.fsck.k9.mail.Pusher;
|
||||||
import com.fsck.k9.mail.Store;
|
|
||||||
import com.fsck.k9.mail.ServerSettings;
|
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.EOLConvertingOutputStream;
|
||||||
import com.fsck.k9.mail.filter.FixedLengthInputStream;
|
import com.fsck.k9.mail.filter.FixedLengthInputStream;
|
||||||
import com.fsck.k9.mail.filter.PeekableInputStream;
|
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.fsck.k9.mail.transport.imap.ImapSettings;
|
||||||
import com.jcraft.jzlib.JZlib;
|
import com.jcraft.jzlib.JZlib;
|
||||||
import com.jcraft.jzlib.ZOutputStream;
|
import com.jcraft.jzlib.ZOutputStream;
|
||||||
import java.util.zip.Inflater;
|
|
||||||
import java.util.zip.InflaterInputStream;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
|
@ -818,7 +820,7 @@ public class ImapStore extends Store {
|
||||||
private volatile boolean mExists;
|
private volatile boolean mExists;
|
||||||
private ImapStore store = null;
|
private ImapStore store = null;
|
||||||
Map<Long, String> msgSeqUidMap = new ConcurrentHashMap<Long, String>();
|
Map<Long, String> msgSeqUidMap = new ConcurrentHashMap<Long, String>();
|
||||||
|
private boolean mInSearch = false;
|
||||||
|
|
||||||
public ImapFolder(ImapStore nStore, String name) {
|
public ImapFolder(ImapStore nStore, String name) {
|
||||||
super(nStore.getAccount());
|
super(nStore.getAccount());
|
||||||
|
@ -998,7 +1000,13 @@ public class ImapStore extends Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (this) {
|
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;
|
mConnection = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1127,7 +1135,7 @@ public class ImapStore extends Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImapFolder iFolder = (ImapFolder)folder;
|
ImapFolder iFolder = (ImapFolder)folder;
|
||||||
checkOpen();
|
checkOpen(); //only need READ access
|
||||||
|
|
||||||
String[] uids = new String[messages.length];
|
String[] uids = new String[messages.length];
|
||||||
for (int i = 0, count = messages.length; i < count; i++) {
|
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 {
|
private int getRemoteMessageCount(String criteria) throws MessagingException {
|
||||||
checkOpen();
|
checkOpen(); //only need READ access
|
||||||
try {
|
try {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int start = 1;
|
int start = 1;
|
||||||
|
@ -1287,7 +1295,7 @@ public class ImapStore extends Store {
|
||||||
return executeSimpleCommand("UID SEARCH *:*");
|
return executeSimpleCommand("UID SEARCH *:*");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Message[] messages = search(searcher, null);
|
Message[] messages = search(searcher, null).toArray(EMPTY_MESSAGE_ARRAY);
|
||||||
if (messages.length > 0) {
|
if (messages.length > 0) {
|
||||||
return Long.parseLong(messages[0].getUid());
|
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 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)
|
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 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)
|
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 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>();
|
ArrayList<Message> messages = new ArrayList<Message>();
|
||||||
try {
|
try {
|
||||||
ArrayList<Long> uids = new ArrayList<Long>();
|
ArrayList<Long> uids = new ArrayList<Long>();
|
||||||
|
@ -1376,8 +1384,12 @@ public class ImapStore extends Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the uids in numerically ascending order
|
// Sort the uids in numerically decreasing order
|
||||||
Collections.sort(uids);
|
// 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++) {
|
for (int i = 0, count = uids.size(); i < count; i++) {
|
||||||
String uid = uids.get(i).toString();
|
String uid = uids.get(i).toString();
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
|
@ -1392,7 +1404,7 @@ public class ImapStore extends Store {
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw ioExceptionHandler(mConnection, ioe);
|
throw ioExceptionHandler(mConnection, ioe);
|
||||||
}
|
}
|
||||||
return messages.toArray(EMPTY_MESSAGE_ARRAY);
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1404,7 +1416,7 @@ public class ImapStore extends Store {
|
||||||
@Override
|
@Override
|
||||||
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
checkOpen();
|
checkOpen(); //only need READ access
|
||||||
ArrayList<Message> messages = new ArrayList<Message>();
|
ArrayList<Message> messages = new ArrayList<Message>();
|
||||||
try {
|
try {
|
||||||
if (uids == null) {
|
if (uids == null) {
|
||||||
|
@ -1441,7 +1453,7 @@ public class ImapStore extends Store {
|
||||||
if (messages == null || messages.length == 0) {
|
if (messages == null || messages.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checkOpen();
|
checkOpen(); //only need READ access
|
||||||
List<String> uids = new ArrayList<String>(messages.length);
|
List<String> uids = new ArrayList<String>(messages.length);
|
||||||
HashMap<String, Message> messageMap = new HashMap<String, Message>();
|
HashMap<String, Message> messageMap = new HashMap<String, Message>();
|
||||||
for (int i = 0, count = messages.length; i < count; i++) {
|
for (int i = 0, count = messages.length; i < count; i++) {
|
||||||
|
@ -1566,7 +1578,7 @@ public class ImapStore extends Store {
|
||||||
@Override
|
@Override
|
||||||
public void fetchPart(Message message, Part part, MessageRetrievalListener listener)
|
public void fetchPart(Message message, Part part, MessageRetrievalListener listener)
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
checkOpen();
|
checkOpen(); //only need READ access
|
||||||
|
|
||||||
String[] parts = part.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
|
String[] parts = part.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
|
||||||
if (parts == null) {
|
if (parts == null) {
|
||||||
|
@ -1969,6 +1981,7 @@ public class ImapStore extends Store {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
|
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
|
||||||
|
open(OpenMode.READ_WRITE);
|
||||||
checkOpen();
|
checkOpen();
|
||||||
try {
|
try {
|
||||||
Map<String, String> uidMap = new HashMap<String, String>();
|
Map<String, String> uidMap = new HashMap<String, String>();
|
||||||
|
@ -2081,6 +2094,7 @@ public class ImapStore extends Store {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void expunge() throws MessagingException {
|
public void expunge() throws MessagingException {
|
||||||
|
open(OpenMode.READ_WRITE);
|
||||||
checkOpen();
|
checkOpen();
|
||||||
try {
|
try {
|
||||||
executeSimpleCommand("EXPUNGE");
|
executeSimpleCommand("EXPUNGE");
|
||||||
|
@ -2113,6 +2127,7 @@ public class ImapStore extends Store {
|
||||||
@Override
|
@Override
|
||||||
public void setFlags(Flag[] flags, boolean value)
|
public void setFlags(Flag[] flags, boolean value)
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
|
open(OpenMode.READ_WRITE);
|
||||||
checkOpen();
|
checkOpen();
|
||||||
|
|
||||||
|
|
||||||
|
@ -2147,6 +2162,7 @@ public class ImapStore extends Store {
|
||||||
@Override
|
@Override
|
||||||
public void setFlags(Message[] messages, Flag[] flags, boolean value)
|
public void setFlags(Message[] messages, Flag[] flags, boolean value)
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
|
open(OpenMode.READ_WRITE);
|
||||||
checkOpen();
|
checkOpen();
|
||||||
String[] uids = new String[messages.length];
|
String[] uids = new String[messages.length];
|
||||||
for (int i = 0, count = messages.length; i < count; i++) {
|
for (int i = 0, count = messages.length; i < count; i++) {
|
||||||
|
@ -2201,6 +2217,108 @@ public class ImapStore extends Store {
|
||||||
}
|
}
|
||||||
return id;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
|
|
||||||
package com.fsck.k9.mail.store;
|
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.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -17,7 +25,6 @@ import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.fsck.k9.helper.HtmlConverter;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
@ -36,8 +43,10 @@ import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.Account.MessageFormat;
|
import com.fsck.k9.Account.MessageFormat;
|
||||||
|
import com.fsck.k9.activity.Search;
|
||||||
import com.fsck.k9.controller.MessageRemovalListener;
|
import com.fsck.k9.controller.MessageRemovalListener;
|
||||||
import com.fsck.k9.controller.MessageRetrievalListener;
|
import com.fsck.k9.controller.MessageRetrievalListener;
|
||||||
|
import com.fsck.k9.helper.HtmlConverter;
|
||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.Utility;
|
||||||
import com.fsck.k9.mail.Address;
|
import com.fsck.k9.mail.Address;
|
||||||
import com.fsck.k9.mail.Body;
|
import com.fsck.k9.mail.Body;
|
||||||
|
@ -1127,9 +1136,15 @@ public class LocalStore extends Store implements Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void open(final OpenMode mode) throws MessagingException {
|
public void open(final OpenMode mode) throws MessagingException {
|
||||||
if (isOpen()) {
|
|
||||||
|
if (isOpen() && (getMode() == mode || mode == OpenMode.READ_ONLY)) {
|
||||||
return;
|
return;
|
||||||
|
} else if (isOpen()) {
|
||||||
|
//previously opened in READ_ONLY and now requesting READ_WRITE
|
||||||
|
//so close connection and reopen
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.execute(false, new DbCallback<Void>() {
|
database.execute(false, new DbCallback<Void>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -1336,17 +1351,19 @@ public class LocalStore extends Store implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void purgeToVisibleLimit(MessageRemovalListener listener) throws MessagingException {
|
public void purgeToVisibleLimit(MessageRemovalListener listener) throws MessagingException {
|
||||||
if (mVisibleLimit == 0) {
|
//don't purge messages while a Search is active since it might throw away search results
|
||||||
return ;
|
if (!Search.isActive()) {
|
||||||
}
|
if (mVisibleLimit == 0) {
|
||||||
open(OpenMode.READ_WRITE);
|
return ;
|
||||||
Message[] messages = getMessages(null, false);
|
}
|
||||||
for (int i = mVisibleLimit; i < messages.length; i++) {
|
open(OpenMode.READ_WRITE);
|
||||||
if (listener != null) {
|
Message[] messages = getMessages(null, false);
|
||||||
listener.messageRemoved(messages[i]);
|
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
|
@Override
|
||||||
public Message getMessage(final String uid) throws MessagingException {
|
public LocalMessage getMessage(final String uid) throws MessagingException {
|
||||||
try {
|
try {
|
||||||
return database.execute(false, new DbCallback<Message>() {
|
return database.execute(false, new DbCallback<LocalMessage>() {
|
||||||
@Override
|
@Override
|
||||||
public Message doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
public LocalMessage doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
||||||
try {
|
try {
|
||||||
open(OpenMode.READ_WRITE);
|
open(OpenMode.READ_WRITE);
|
||||||
LocalMessage message = new LocalMessage(uid, LocalFolder.this);
|
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
|
* Replace an existing message in the database
|
||||||
*/
|
*/
|
||||||
LocalMessage oldMessage = (LocalMessage) getMessage(uid);
|
LocalMessage oldMessage = getMessage(uid);
|
||||||
|
|
||||||
if (oldMessage != null) {
|
if (oldMessage != null) {
|
||||||
oldMessageId = oldMessage.getId();
|
oldMessageId = oldMessage.getId();
|
||||||
|
|
|
@ -215,6 +215,16 @@ public class AccountSettings {
|
||||||
new V(1, new IntegerResourceSetting(5,
|
new V(1, new IntegerResourceSetting(5,
|
||||||
R.array.account_settings_vibrate_times_label))
|
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);
|
SETTINGS = Collections.unmodifiableMap(s);
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class Settings {
|
||||||
*
|
*
|
||||||
* @see SettingsExporter
|
* @see SettingsExporter
|
||||||
*/
|
*/
|
||||||
public static final int VERSION = 17;
|
public static final int VERSION = 18;
|
||||||
|
|
||||||
public static Map<String, Object> validate(int version, Map<String,
|
public static Map<String, Object> validate(int version, Map<String,
|
||||||
TreeMap<Integer, SettingsDescription>> settings,
|
TreeMap<Integer, SettingsDescription>> settings,
|
||||||
|
|