mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-23 18:02:15 -05:00
Merge branch 'content_provider'
This commit is contained in:
commit
08615f205f
@ -396,6 +396,11 @@ otherwise it would make K-9 start at the wrong time
|
|||||||
android:readPermission="com.fsck.k9.permission.READ_MESSAGES"
|
android:readPermission="com.fsck.k9.permission.READ_MESSAGES"
|
||||||
android:writePermission="com.fsck.k9.permission.DELETE_MESSAGES"
|
android:writePermission="com.fsck.k9.permission.DELETE_MESSAGES"
|
||||||
/>
|
/>
|
||||||
|
<provider
|
||||||
|
android:name="com.fsck.k9.provider.EmailProvider"
|
||||||
|
android:authorities="org.k9mail.provider.email"
|
||||||
|
android:exported="false"
|
||||||
|
/>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".provider.UnreadWidgetProvider"
|
android:name=".provider.UnreadWidgetProvider"
|
||||||
|
@ -55,14 +55,32 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toRightOf="@+id/chip_wrapper"
|
android:layout_toRightOf="@+id/chip_wrapper"
|
||||||
android:layout_below="@+id/subject"
|
android:layout_below="@+id/subject"
|
||||||
|
android:layout_toLeftOf="@+id/thread_count"
|
||||||
android:layout_marginLeft="1dip"
|
android:layout_marginLeft="1dip"
|
||||||
android:layout_marginBottom="3dip"
|
android:layout_marginBottom="3dip"
|
||||||
android:layout_marginRight="16dip"
|
android:layout_marginRight="3dip"
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:bufferType="spannable"
|
android:bufferType="spannable"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textColor="?android:attr/textColorPrimary" />
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thread_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="6dip"
|
||||||
|
android:layout_marginRight="12dip"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/subject"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:paddingLeft="4dip"
|
||||||
|
android:paddingRight="4dip"
|
||||||
|
android:paddingTop="2dip"
|
||||||
|
android:paddingBottom="2dip"
|
||||||
|
android:background="#50000000"
|
||||||
|
android:focusable="false" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/date"
|
android:id="@+id/date"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -75,7 +93,4 @@
|
|||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textColor="?android:attr/textColorSecondary" />
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -34,9 +34,11 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/set_sort_subject"
|
android:id="@+id/set_sort_subject"
|
||||||
android:title="@string/sort_by_subject"/>
|
android:title="@string/sort_by_subject"/>
|
||||||
|
<!--
|
||||||
<item
|
<item
|
||||||
android:id="@+id/set_sort_sender"
|
android:id="@+id/set_sort_sender"
|
||||||
android:title="@string/sort_by_sender"/>
|
android:title="@string/sort_by_sender"/>
|
||||||
|
-->
|
||||||
<item
|
<item
|
||||||
android:id="@+id/set_sort_flag"
|
android:id="@+id/set_sort_flag"
|
||||||
android:title="@string/sort_by_flag"/>
|
android:title="@string/sort_by_flag"/>
|
||||||
|
@ -1138,6 +1138,7 @@ http://k9mail.googlecode.com/
|
|||||||
<string name="remote_search_sending_query">Sending query to server</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">Fetching %d results</string>
|
||||||
<string name="remote_search_downloading_limited">Fetching %1$d of %2$d results</string>
|
<string name="remote_search_downloading_limited">Fetching %1$d of %2$d results</string>
|
||||||
|
<string name="remote_search_error">Remote search failed</string>
|
||||||
|
|
||||||
<string name="account_settings_search">Search</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">Enable server search</string>
|
||||||
@ -1149,4 +1150,7 @@ http://k9mail.googlecode.com/
|
|||||||
|
|
||||||
<string name="global_settings_background_as_unread_indicator_label">Use background as (un)read indicator</string>
|
<string name="global_settings_background_as_unread_indicator_label">Use background as (un)read indicator</string>
|
||||||
<string name="global_settings_background_as_unread_indicator_summary">Show read and unread messages with different background colors</string>
|
<string name="global_settings_background_as_unread_indicator_summary">Show read and unread messages with different background colors</string>
|
||||||
|
|
||||||
|
<string name="global_settings_threaded_view_label">Threaded view</string>
|
||||||
|
<string name="global_settings_threaded_view_summary">Collapse messages belonging to the same thread</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -150,6 +150,12 @@
|
|||||||
android:summary="@string/global_settings_background_as_unread_indicator_summary"
|
android:summary="@string/global_settings_background_as_unread_indicator_summary"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:persistent="false"
|
||||||
|
android:key="threaded_view"
|
||||||
|
android:title="@string/global_settings_threaded_view_label"
|
||||||
|
android:summary="@string/global_settings_threaded_view_summary" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
@ -94,7 +94,7 @@ public class Account implements BaseAccount {
|
|||||||
SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false),
|
SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false),
|
||||||
SORT_ARRIVAL(R.string.sort_earliest_first, R.string.sort_latest_first, false),
|
SORT_ARRIVAL(R.string.sort_earliest_first, R.string.sort_latest_first, false),
|
||||||
SORT_SUBJECT(R.string.sort_subject_alpha, R.string.sort_subject_re_alpha, true),
|
SORT_SUBJECT(R.string.sort_subject_alpha, R.string.sort_subject_re_alpha, true),
|
||||||
SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true),
|
// SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true),
|
||||||
SORT_UNREAD(R.string.sort_unread_first, R.string.sort_unread_last, true),
|
SORT_UNREAD(R.string.sort_unread_first, R.string.sort_unread_last, true),
|
||||||
SORT_FLAGGED(R.string.sort_flagged_first, R.string.sort_flagged_last, true),
|
SORT_FLAGGED(R.string.sort_flagged_first, R.string.sort_flagged_last, true),
|
||||||
SORT_ATTACHMENT(R.string.sort_attach_first, R.string.sort_unattached_first, true);
|
SORT_ATTACHMENT(R.string.sort_attach_first, R.string.sort_unattached_first, true);
|
||||||
|
@ -206,6 +206,8 @@ public class K9 extends Application {
|
|||||||
private static HashMap<SortType, Boolean> mSortAscending = new HashMap<SortType, Boolean>();
|
private static HashMap<SortType, Boolean> mSortAscending = new HashMap<SortType, Boolean>();
|
||||||
|
|
||||||
private static boolean sUseBackgroundAsUnreadIndicator = true;
|
private static boolean sUseBackgroundAsUnreadIndicator = true;
|
||||||
|
private static boolean sThreadedViewEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The MIME type(s) of attachments we're willing to view.
|
* The MIME type(s) of attachments we're willing to view.
|
||||||
@ -473,6 +475,7 @@ public class K9 extends Application {
|
|||||||
|
|
||||||
editor.putString("attachmentdefaultpath", mAttachmentDefaultPath);
|
editor.putString("attachmentdefaultpath", mAttachmentDefaultPath);
|
||||||
editor.putBoolean("useBackgroundAsUnreadIndicator", sUseBackgroundAsUnreadIndicator);
|
editor.putBoolean("useBackgroundAsUnreadIndicator", sUseBackgroundAsUnreadIndicator);
|
||||||
|
editor.putBoolean("threadedView", sThreadedViewEnabled);
|
||||||
fontSizes.save(editor);
|
fontSizes.save(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,6 +646,7 @@ public class K9 extends Application {
|
|||||||
|
|
||||||
mAttachmentDefaultPath = sprefs.getString("attachmentdefaultpath", Environment.getExternalStorageDirectory().toString());
|
mAttachmentDefaultPath = sprefs.getString("attachmentdefaultpath", Environment.getExternalStorageDirectory().toString());
|
||||||
sUseBackgroundAsUnreadIndicator = sprefs.getBoolean("useBackgroundAsUnreadIndicator", true);
|
sUseBackgroundAsUnreadIndicator = sprefs.getBoolean("useBackgroundAsUnreadIndicator", true);
|
||||||
|
sThreadedViewEnabled = sprefs.getBoolean("threadedView", true);
|
||||||
fontSizes.load(sprefs);
|
fontSizes.load(sprefs);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1138,4 +1142,12 @@ public class K9 extends Application {
|
|||||||
public static synchronized void setUseBackgroundAsUnreadIndicator(boolean enabled) {
|
public static synchronized void setUseBackgroundAsUnreadIndicator(boolean enabled) {
|
||||||
sUseBackgroundAsUnreadIndicator = enabled;
|
sUseBackgroundAsUnreadIndicator = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized boolean isThreadedViewEnabled() {
|
||||||
|
return sThreadedViewEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void setThreadedViewEnabled(boolean enable) {
|
||||||
|
sThreadedViewEnabled = enable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Flag;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a meta-Account that represents one or more accounts with filters on them. The filter specification
|
|
||||||
* is defined by {@link com.fsck.k9.activity.SearchModifier}.
|
|
||||||
*/
|
|
||||||
public class SearchAccount implements BaseAccount, SearchSpecification, Serializable {
|
|
||||||
/**
|
|
||||||
* Create a {@code SearchAccount} instance for the Unified Inbox.
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* A {@link Context} instance that will be used to get localized strings and will be
|
|
||||||
* passed on to the {@code SearchAccount} instance.
|
|
||||||
*
|
|
||||||
* @return The {@link SearchAccount} instance for the Unified Inbox.
|
|
||||||
*/
|
|
||||||
public static SearchAccount createUnifiedInboxAccount(Context context) {
|
|
||||||
SearchAccount unifiedInbox = new SearchAccount(context, true, null, null);
|
|
||||||
unifiedInbox.setDescription(context.getString(R.string.integrated_inbox_title));
|
|
||||||
unifiedInbox.setEmail(context.getString(R.string.integrated_inbox_detail));
|
|
||||||
return unifiedInbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a {@code SearchAccount} instance for the special account "All messages".
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* A {@link Context} instance that will be used to get localized strings and will be
|
|
||||||
* passed on to the {@code SearchAccount} instance.
|
|
||||||
*
|
|
||||||
* @return The {@link SearchAccount} instance for the Unified Inbox.
|
|
||||||
*/
|
|
||||||
public static SearchAccount createAllMessagesAccount(Context context) {
|
|
||||||
SearchAccount allMessages = new SearchAccount(context, false, null, null);
|
|
||||||
allMessages.setDescription(context.getString(R.string.search_all_messages_title));
|
|
||||||
allMessages.setEmail(context.getString(R.string.search_all_messages_detail));
|
|
||||||
return allMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -4388420303235543976L;
|
|
||||||
private Flag[] mRequiredFlags = null;
|
|
||||||
private Flag[] mForbiddenFlags = null;
|
|
||||||
private String email = null;
|
|
||||||
private String description = null;
|
|
||||||
private String query = "";
|
|
||||||
private boolean integrate = false;
|
|
||||||
private String mUuid = null;
|
|
||||||
private boolean builtin = false;
|
|
||||||
private String[] accountUuids = null;
|
|
||||||
private String[] folderNames = null;
|
|
||||||
|
|
||||||
public SearchAccount(Preferences preferences) {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected synchronized void delete(Preferences preferences) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void save(Preferences preferences) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public SearchAccount(Context context, boolean nintegrate, Flag[] requiredFlags, Flag[] forbiddenFlags) {
|
|
||||||
mRequiredFlags = requiredFlags;
|
|
||||||
mForbiddenFlags = forbiddenFlags;
|
|
||||||
integrate = nintegrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Flag[] getRequiredFlags() {
|
|
||||||
return mRequiredFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Flag[] getForbiddenFlags() {
|
|
||||||
return mForbiddenFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIntegrate() {
|
|
||||||
return integrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription(String description) {
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuery() {
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuery(String query) {
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUuid() {
|
|
||||||
if (mUuid == null) {
|
|
||||||
setUuid(UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
return mUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUuid(String nUuid) {
|
|
||||||
mUuid = nUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIntegrate(boolean integrate) {
|
|
||||||
this.integrate = integrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBuiltin() {
|
|
||||||
return builtin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBuiltin(boolean builtin) {
|
|
||||||
this.builtin = builtin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getAccountUuids() {
|
|
||||||
return accountUuids;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccountUuids(String[] accountUuids) {
|
|
||||||
this.accountUuids = accountUuids;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getFolderNames() {
|
|
||||||
return folderNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFolderNames(String[] folderNames) {
|
|
||||||
this.folderNames = folderNames;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
package com.fsck.k9;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Flag;
|
|
||||||
|
|
||||||
public interface SearchSpecification {
|
|
||||||
|
|
||||||
public Flag[] getRequiredFlags();
|
|
||||||
|
|
||||||
public Flag[] getForbiddenFlags();
|
|
||||||
|
|
||||||
public boolean isIntegrate();
|
|
||||||
|
|
||||||
public String getQuery();
|
|
||||||
|
|
||||||
public String[] getAccountUuids();
|
|
||||||
|
|
||||||
public String[] getFolderNames();
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ import com.fsck.k9.FontSizes;
|
|||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.SearchAccount;
|
import com.fsck.k9.search.SearchAccount;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,11 +62,8 @@ import com.fsck.k9.FontSizes;
|
|||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.SearchAccount;
|
|
||||||
import com.fsck.k9.SearchSpecification;
|
|
||||||
import com.fsck.k9.activity.misc.ExtendedAsyncTask;
|
import com.fsck.k9.activity.misc.ExtendedAsyncTask;
|
||||||
import com.fsck.k9.activity.misc.NonConfigurationInstance;
|
import com.fsck.k9.activity.misc.NonConfigurationInstance;
|
||||||
import com.fsck.k9.activity.setup.AccountSettings;
|
|
||||||
import com.fsck.k9.activity.setup.AccountSetupBasics;
|
import com.fsck.k9.activity.setup.AccountSetupBasics;
|
||||||
import com.fsck.k9.activity.setup.Prefs;
|
import com.fsck.k9.activity.setup.Prefs;
|
||||||
import com.fsck.k9.activity.setup.WelcomeMessage;
|
import com.fsck.k9.activity.setup.WelcomeMessage;
|
||||||
@ -80,6 +77,9 @@ import com.fsck.k9.mail.Transport;
|
|||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
import com.fsck.k9.mail.internet.MimeUtility;
|
||||||
import com.fsck.k9.mail.store.StorageManager;
|
import com.fsck.k9.mail.store.StorageManager;
|
||||||
import com.fsck.k9.mail.store.WebDavStore;
|
import com.fsck.k9.mail.store.WebDavStore;
|
||||||
|
import com.fsck.k9.search.LocalSearch;
|
||||||
|
import com.fsck.k9.search.SearchAccount;
|
||||||
|
import com.fsck.k9.search.SearchModifier;
|
||||||
import com.fsck.k9.view.ColorChip;
|
import com.fsck.k9.view.ColorChip;
|
||||||
import com.fsck.k9.preferences.SettingsExporter;
|
import com.fsck.k9.preferences.SettingsExporter;
|
||||||
import com.fsck.k9.preferences.SettingsImportExportException;
|
import com.fsck.k9.preferences.SettingsImportExportException;
|
||||||
@ -126,8 +126,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
|
|
||||||
private AccountsHandler mHandler = new AccountsHandler();
|
private AccountsHandler mHandler = new AccountsHandler();
|
||||||
private AccountsAdapter mAdapter;
|
private AccountsAdapter mAdapter;
|
||||||
private SearchAccount unreadAccount = null;
|
private SearchAccount mAllMessagesAccount = null;
|
||||||
private SearchAccount integratedInboxAccount = null;
|
private SearchAccount mUnifiedInboxAccount = null;
|
||||||
private FontSizes mFontSizes = K9.getFontSizes();
|
private FontSizes mFontSizes = K9.getFontSizes();
|
||||||
|
|
||||||
private MenuItem mRefreshMenuItem;
|
private MenuItem mRefreshMenuItem;
|
||||||
@ -375,7 +375,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
|
|
||||||
boolean startup = intent.getBooleanExtra(EXTRA_STARTUP, true);
|
boolean startup = intent.getBooleanExtra(EXTRA_STARTUP, true);
|
||||||
if (startup && K9.startIntegratedInbox() && !K9.isHideSpecialAccounts()) {
|
if (startup && K9.startIntegratedInbox() && !K9.isHideSpecialAccounts()) {
|
||||||
onOpenAccount(integratedInboxAccount);
|
onOpenAccount(mUnifiedInboxAccount);
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
} else if (startup && accounts.length == 1 && onOpenAccount(accounts[0])) {
|
} else if (startup && accounts.length == 1 && onOpenAccount(accounts[0])) {
|
||||||
@ -424,8 +424,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
* Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
|
* Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
|
||||||
*/
|
*/
|
||||||
private void createSpecialAccounts() {
|
private void createSpecialAccounts() {
|
||||||
integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
|
mUnifiedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
|
||||||
unreadAccount = SearchAccount.createAllMessagesAccount(this);
|
mAllMessagesAccount = SearchAccount.createAllMessagesAccount(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -519,14 +519,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
|
|
||||||
List<BaseAccount> newAccounts;
|
List<BaseAccount> newAccounts;
|
||||||
if (!K9.isHideSpecialAccounts() && accounts.length > 0) {
|
if (!K9.isHideSpecialAccounts() && accounts.length > 0) {
|
||||||
if (integratedInboxAccount == null || unreadAccount == null) {
|
if (mUnifiedInboxAccount == null || mAllMessagesAccount == null) {
|
||||||
createSpecialAccounts();
|
createSpecialAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
newAccounts = new ArrayList<BaseAccount>(accounts.length +
|
newAccounts = new ArrayList<BaseAccount>(accounts.length +
|
||||||
SPECIAL_ACCOUNTS_COUNT);
|
SPECIAL_ACCOUNTS_COUNT);
|
||||||
newAccounts.add(integratedInboxAccount);
|
newAccounts.add(mUnifiedInboxAccount);
|
||||||
newAccounts.add(unreadAccount);
|
newAccounts.add(mAllMessagesAccount);
|
||||||
} else {
|
} else {
|
||||||
newAccounts = new ArrayList<BaseAccount>(accounts.length);
|
newAccounts = new ArrayList<BaseAccount>(accounts.length);
|
||||||
}
|
}
|
||||||
@ -550,7 +550,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
pendingWork.put(account, "true");
|
pendingWork.put(account, "true");
|
||||||
final SearchAccount searchAccount = (SearchAccount)account;
|
final SearchAccount searchAccount = (SearchAccount)account;
|
||||||
|
|
||||||
MessagingController.getInstance(getApplication()).searchLocalMessages(searchAccount, null, new MessagingListener() {
|
MessagingController.getInstance(getApplication())
|
||||||
|
.searchLocalMessages(searchAccount.getRelatedSearch(), new MessagingListener() {
|
||||||
@Override
|
@Override
|
||||||
public void searchStats(AccountStats stats) {
|
public void searchStats(AccountStats stats) {
|
||||||
mListener.accountStatusChanged(searchAccount, stats);
|
mListener.accountStatusChanged(searchAccount, stats);
|
||||||
@ -607,7 +608,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
private boolean onOpenAccount(BaseAccount account) {
|
private boolean onOpenAccount(BaseAccount account) {
|
||||||
if (account instanceof SearchAccount) {
|
if (account instanceof SearchAccount) {
|
||||||
SearchAccount searchAccount = (SearchAccount)account;
|
SearchAccount searchAccount = (SearchAccount)account;
|
||||||
MessageList.actionHandle(this, searchAccount.getDescription(), searchAccount);
|
MessageList.actionDisplaySearch(this, searchAccount.getRelatedSearch(), false, false);
|
||||||
} else {
|
} else {
|
||||||
Account realAccount = (Account)account;
|
Account realAccount = (Account)account;
|
||||||
if (!realAccount.isEnabled()) {
|
if (!realAccount.isEnabled()) {
|
||||||
@ -624,8 +625,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) {
|
if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) {
|
||||||
FolderList.actionHandleAccount(this, realAccount);
|
FolderList.actionHandleAccount(this, realAccount);
|
||||||
} else {
|
} else {
|
||||||
MessageList.actionHandleFolder(this, realAccount, realAccount.getAutoExpandFolderName());
|
LocalSearch search = new LocalSearch(realAccount.getAutoExpandFolderName());
|
||||||
}
|
search.addAllowedFolder(realAccount.getAutoExpandFolderName());
|
||||||
|
search.addAccountUuid(realAccount.getUuid());
|
||||||
|
MessageList.actionDisplaySearch(this, search, false, true);}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1782,49 +1785,20 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
|
final String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
|
||||||
|
LocalSearch search = null;
|
||||||
|
|
||||||
if (account instanceof SearchAccount) {
|
if (account instanceof SearchAccount) {
|
||||||
SearchAccount searchAccount = (SearchAccount)account;
|
search = ((SearchAccount) account).getRelatedSearch().clone();
|
||||||
|
search.setName(description);
|
||||||
MessageList.actionHandle(Accounts.this,
|
|
||||||
description, "", searchAccount.isIntegrate(),
|
|
||||||
combine(searchAccount.getRequiredFlags(), searchModifier.requiredFlags),
|
|
||||||
combine(searchAccount.getForbiddenFlags(), searchModifier.forbiddenFlags));
|
|
||||||
} else {
|
} else {
|
||||||
SearchSpecification searchSpec = new SearchSpecification() {
|
search = new LocalSearch(description);
|
||||||
@Override
|
search.addAccountUuid(account.getUuid());
|
||||||
public String[] getAccountUuids() {
|
|
||||||
return new String[] { account.getUuid() };
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flag[] getForbiddenFlags() {
|
|
||||||
return searchModifier.forbiddenFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getQuery() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flag[] getRequiredFlags() {
|
|
||||||
return searchModifier.requiredFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isIntegrate() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getFolderNames() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
MessageList.actionHandle(Accounts.this, description, searchSpec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
search.allRequiredFlags(searchModifier.requiredFlags);
|
||||||
|
search.allForbiddenFlags(searchModifier.forbiddenFlags);
|
||||||
|
MessageList.actionDisplaySearch(Accounts.this, search, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,6 @@ import com.fsck.k9.FontSizes;
|
|||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.SearchSpecification;
|
|
||||||
import com.fsck.k9.activity.FolderList.FolderListAdapter.FolderListFilter;
|
import com.fsck.k9.activity.FolderList.FolderListAdapter.FolderListFilter;
|
||||||
import com.fsck.k9.activity.misc.ActionBarNavigationSpinner;
|
import com.fsck.k9.activity.misc.ActionBarNavigationSpinner;
|
||||||
import com.fsck.k9.activity.setup.AccountSettings;
|
import com.fsck.k9.activity.setup.AccountSettings;
|
||||||
@ -68,6 +67,9 @@ import com.fsck.k9.mail.Folder;
|
|||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||||
|
import com.fsck.k9.search.LocalSearch;
|
||||||
|
import com.fsck.k9.search.SearchModifier;
|
||||||
|
import com.fsck.k9.search.SearchSpecification;
|
||||||
import com.fsck.k9.service.MailService;
|
import com.fsck.k9.service.MailService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -620,7 +622,10 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onOpenFolder(String folder) {
|
private void onOpenFolder(String folder) {
|
||||||
MessageList.actionHandleFolder(this, mAccount, folder);
|
LocalSearch search = new LocalSearch(folder);
|
||||||
|
search.addAccountUuid(mAccount.getUuid());
|
||||||
|
search.addAllowedFolder(folder);
|
||||||
|
MessageList.actionDisplaySearch(this, search, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCompact(Account account) {
|
private void onCompact(Account account) {
|
||||||
@ -1267,86 +1272,34 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
String description = getString(R.string.search_title,
|
final String description = getString(R.string.search_title,
|
||||||
getString(R.string.message_list_title, account.getDescription(), displayName),
|
getString(R.string.message_list_title, account.getDescription(), displayName),
|
||||||
getString(searchModifier.resId));
|
getString(searchModifier.resId));
|
||||||
|
|
||||||
SearchSpecification searchSpec = new SearchSpecification() {
|
LocalSearch search = new LocalSearch(description);
|
||||||
@Override
|
try {
|
||||||
public String[] getAccountUuids() {
|
search.allRequiredFlags(searchModifier.requiredFlags);
|
||||||
return new String[] { account.getUuid() };
|
search.allForbiddenFlags(searchModifier.forbiddenFlags);
|
||||||
}
|
} catch (Exception e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
@Override
|
e.printStackTrace();
|
||||||
public Flag[] getForbiddenFlags() {
|
}
|
||||||
return searchModifier.forbiddenFlags;
|
search.addAllowedFolder(folderName);
|
||||||
}
|
search.addAccountUuid(account.getUuid());
|
||||||
|
MessageList.actionDisplaySearch(FolderList.this, search, true, false);
|
||||||
@Override
|
|
||||||
public String getQuery() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flag[] getRequiredFlags() {
|
|
||||||
return searchModifier.requiredFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isIntegrate() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getFolderNames() {
|
|
||||||
return new String[] { folderName };
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
MessageList.actionHandle(FolderList.this, description, searchSpec);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Flag[] UNREAD_FLAG_ARRAY = { Flag.SEEN };
|
|
||||||
|
|
||||||
private void openUnreadSearch(Context context, final Account account) {
|
private void openUnreadSearch(Context context, final Account account) {
|
||||||
String description = getString(R.string.search_title, mAccount.getDescription(), getString(R.string.unread_modifier));
|
String description = getString(R.string.search_title, mAccount.getDescription(), getString(R.string.unread_modifier));
|
||||||
|
LocalSearch search = new LocalSearch(description);
|
||||||
SearchSpecification searchSpec = new SearchSpecification() {
|
search.addAccountUuid(account.getUuid());
|
||||||
//interface has no override @Override
|
try {
|
||||||
public String[] getAccountUuids() {
|
search.allRequiredFlags(new Flag[]{Flag.SEEN});
|
||||||
return new String[] { account.getUuid() };
|
} catch (Exception e) {
|
||||||
}
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
//interface has no override @Override
|
}
|
||||||
public Flag[] getForbiddenFlags() {
|
|
||||||
return UNREAD_FLAG_ARRAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
//interface has no override @Override
|
|
||||||
public String getQuery() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flag[] getRequiredFlags() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isIntegrate() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getFolderNames() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
MessageList.actionHandle(context, description, searchSpec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import android.os.Parcelable;
|
|||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.BaseAccount;
|
import com.fsck.k9.BaseAccount;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.SearchSpecification;
|
import com.fsck.k9.search.SearchSpecification;
|
||||||
|
|
||||||
public class LauncherShortcuts extends AccountList {
|
public class LauncherShortcuts extends AccountList {
|
||||||
@Override
|
@Override
|
||||||
@ -31,8 +31,8 @@ public class LauncherShortcuts extends AccountList {
|
|||||||
Intent shortcutIntent = null;
|
Intent shortcutIntent = null;
|
||||||
|
|
||||||
if (account instanceof SearchSpecification) {
|
if (account instanceof SearchSpecification) {
|
||||||
shortcutIntent = MessageList.actionHandleAccountIntent(this, account.getDescription(),
|
shortcutIntent = MessageList.intentDisplaySearch(this, (SearchSpecification) account,
|
||||||
(SearchSpecification) account);
|
false, true, true);
|
||||||
} else {
|
} else {
|
||||||
shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null,
|
shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null,
|
||||||
true);
|
true);
|
||||||
|
@ -24,17 +24,19 @@ import com.fsck.k9.Account.SortType;
|
|||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.SearchSpecification;
|
|
||||||
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
||||||
import com.fsck.k9.activity.setup.AccountSettings;
|
import com.fsck.k9.activity.setup.AccountSettings;
|
||||||
import com.fsck.k9.activity.setup.FolderSettings;
|
import com.fsck.k9.activity.setup.FolderSettings;
|
||||||
import com.fsck.k9.activity.setup.Prefs;
|
import com.fsck.k9.activity.setup.Prefs;
|
||||||
import com.fsck.k9.fragment.MessageListFragment;
|
import com.fsck.k9.fragment.MessageListFragment;
|
||||||
import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener;
|
import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener;
|
||||||
import com.fsck.k9.helper.Utility;
|
|
||||||
import com.fsck.k9.mail.Flag;
|
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.store.StorageManager;
|
import com.fsck.k9.mail.store.StorageManager;
|
||||||
|
import com.fsck.k9.search.LocalSearch;
|
||||||
|
import com.fsck.k9.search.SearchSpecification;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.Attribute;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,86 +46,42 @@ import com.fsck.k9.mail.store.StorageManager;
|
|||||||
*/
|
*/
|
||||||
public class MessageList extends K9FragmentActivity implements MessageListFragmentListener,
|
public class MessageList extends K9FragmentActivity implements MessageListFragmentListener,
|
||||||
OnBackStackChangedListener, OnSwipeGestureListener {
|
OnBackStackChangedListener, OnSwipeGestureListener {
|
||||||
private static final String EXTRA_ACCOUNT = "account";
|
|
||||||
private static final String EXTRA_FOLDER = "folder";
|
// for this activity
|
||||||
|
private static final String EXTRA_SEARCH = "search";
|
||||||
|
private static final String EXTRA_NO_THREADING = "no_threading";
|
||||||
|
|
||||||
|
// used for remote search
|
||||||
private static final String EXTRA_SEARCH_ACCOUNT = "com.fsck.k9.search_account";
|
private static final String EXTRA_SEARCH_ACCOUNT = "com.fsck.k9.search_account";
|
||||||
private static final String EXTRA_SEARCH_FOLDER = "com.fsck.k9.search_folder";
|
private static final String EXTRA_SEARCH_FOLDER = "com.fsck.k9.search_folder";
|
||||||
private static final String EXTRA_QUERY_FLAGS = "queryFlags";
|
|
||||||
private static final String EXTRA_FORBIDDEN_FLAGS = "forbiddenFlags";
|
|
||||||
private static final String EXTRA_INTEGRATE = "integrate";
|
|
||||||
private static final String EXTRA_ACCOUNT_UUIDS = "accountUuids";
|
|
||||||
private static final String EXTRA_FOLDER_NAMES = "folderNames";
|
|
||||||
private static final String EXTRA_TITLE = "title";
|
|
||||||
|
|
||||||
|
public static void actionDisplaySearch(Context context, SearchSpecification search,
|
||||||
public static void actionHandleFolder(Context context, Account account, String folder) {
|
boolean noThreading, boolean newTask) {
|
||||||
Intent intent = new Intent(context, MessageList.class);
|
actionDisplaySearch(context, search, noThreading, newTask, true);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
|
||||||
|
|
||||||
if (folder != null) {
|
|
||||||
intent.putExtra(EXTRA_FOLDER, folder);
|
|
||||||
}
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent actionHandleFolderIntent(Context context, Account account, String folder) {
|
public static void actionDisplaySearch(Context context, SearchSpecification search,
|
||||||
Intent intent = new Intent(context, MessageList.class);
|
boolean noThreading, boolean newTask, boolean clearTop) {
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
context.startActivity(
|
||||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
intentDisplaySearch(context, search, noThreading, newTask, clearTop));
|
||||||
intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
|
||||||
|
|
||||||
if (folder != null) {
|
|
||||||
intent.putExtra(EXTRA_FOLDER, folder);
|
|
||||||
}
|
|
||||||
return intent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void actionHandle(Context context, String title, String queryString, boolean integrate, Flag[] flags, Flag[] forbiddenFlags) {
|
public static Intent intentDisplaySearch(Context context, SearchSpecification search,
|
||||||
|
boolean noThreading, boolean newTask, boolean clearTop) {
|
||||||
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.putExtra(EXTRA_SEARCH, search);
|
||||||
intent.putExtra(SearchManager.QUERY, queryString);
|
intent.putExtra(EXTRA_NO_THREADING, noThreading);
|
||||||
if (flags != null) {
|
|
||||||
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(flags, ','));
|
|
||||||
}
|
|
||||||
if (forbiddenFlags != null) {
|
|
||||||
intent.putExtra(EXTRA_FORBIDDEN_FLAGS, Utility.combine(forbiddenFlags, ','));
|
|
||||||
}
|
|
||||||
intent.putExtra(EXTRA_INTEGRATE, integrate);
|
|
||||||
intent.putExtra(EXTRA_TITLE, title);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (clearTop) {
|
||||||
* Creates and returns an intent that opens Unified Inbox or All Messages screen.
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
*/
|
|
||||||
public static Intent actionHandleAccountIntent(Context context, String title,
|
|
||||||
SearchSpecification searchSpecification) {
|
|
||||||
Intent intent = new Intent(context, MessageList.class);
|
|
||||||
intent.putExtra(SearchManager.QUERY, searchSpecification.getQuery());
|
|
||||||
if (searchSpecification.getRequiredFlags() != null) {
|
|
||||||
intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(searchSpecification.getRequiredFlags(), ','));
|
|
||||||
}
|
}
|
||||||
if (searchSpecification.getForbiddenFlags() != null) {
|
if (newTask) {
|
||||||
intent.putExtra(EXTRA_FORBIDDEN_FLAGS, Utility.combine(searchSpecification.getForbiddenFlags(), ','));
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
}
|
}
|
||||||
intent.putExtra(EXTRA_INTEGRATE, searchSpecification.isIntegrate());
|
|
||||||
intent.putExtra(EXTRA_ACCOUNT_UUIDS, searchSpecification.getAccountUuids());
|
|
||||||
intent.putExtra(EXTRA_FOLDER_NAMES, searchSpecification.getFolderNames());
|
|
||||||
intent.putExtra(EXTRA_TITLE, title);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
|
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void actionHandle(Context context, String title,
|
|
||||||
SearchSpecification searchSpecification) {
|
|
||||||
Intent intent = actionHandleAccountIntent(context, title, searchSpecification);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
|
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
|
||||||
|
|
||||||
@ -131,31 +89,28 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
private TextView mActionBarTitle;
|
private TextView mActionBarTitle;
|
||||||
private TextView mActionBarSubTitle;
|
private TextView mActionBarSubTitle;
|
||||||
private TextView mActionBarUnread;
|
private TextView mActionBarUnread;
|
||||||
private String mTitle;
|
|
||||||
private Menu mMenu;
|
private Menu mMenu;
|
||||||
|
|
||||||
private MessageListFragment mMessageListFragment;
|
private MessageListFragment mMessageListFragment;
|
||||||
|
|
||||||
private Account mAccount;
|
private Account mAccount;
|
||||||
private String mQueryString;
|
|
||||||
private String mFolderName;
|
private String mFolderName;
|
||||||
private Flag[] mQueryFlags;
|
private LocalSearch mSearch;
|
||||||
private Flag[] mForbiddenFlags;
|
private boolean mSingleFolderMode;
|
||||||
private String mSearchAccount = null;
|
private boolean mSingleAccountMode;
|
||||||
private String mSearchFolder = null;
|
|
||||||
private boolean mIntegrate;
|
|
||||||
private String[] mAccountUuids;
|
|
||||||
private String[] mFolderNames;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code true} if the message list should be displayed as flat list (i.e. no threading)
|
||||||
|
* regardless whether or not message threading was enabled in the settings. This is used for
|
||||||
|
* filtered views, e.g. when only displaying the unread messages in a folder.
|
||||||
|
*/
|
||||||
|
private boolean mNoThreading;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.message_list);
|
setContentView(R.layout.message_list);
|
||||||
|
|
||||||
// need this for actionbar initialization
|
|
||||||
mQueryString = getIntent().getStringExtra(SearchManager.QUERY);
|
|
||||||
|
|
||||||
mActionBar = getSupportActionBar();
|
mActionBar = getSupportActionBar();
|
||||||
initializeActionBar();
|
initializeActionBar();
|
||||||
|
|
||||||
@ -171,76 +126,63 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
|
|
||||||
if (mMessageListFragment == null) {
|
if (mMessageListFragment == null) {
|
||||||
FragmentTransaction ft = fragmentManager.beginTransaction();
|
FragmentTransaction ft = fragmentManager.beginTransaction();
|
||||||
if (mQueryString == null) {
|
mMessageListFragment = MessageListFragment.newInstance(mSearch,
|
||||||
mMessageListFragment = MessageListFragment.newInstance(mAccount, mFolderName);
|
(K9.isThreadedViewEnabled() && !mNoThreading));
|
||||||
} else if (mSearchAccount != null) {
|
|
||||||
mMessageListFragment = MessageListFragment.newInstance(mSearchAccount,
|
|
||||||
mSearchFolder, mQueryString, false);
|
|
||||||
} else {
|
|
||||||
mMessageListFragment = MessageListFragment.newInstance(mTitle, mAccountUuids,
|
|
||||||
mFolderNames, mQueryString, mQueryFlags, mForbiddenFlags, mIntegrate);
|
|
||||||
}
|
|
||||||
ft.add(R.id.message_list_container, mMessageListFragment);
|
ft.add(R.id.message_list_container, mMessageListFragment);
|
||||||
ft.commit();
|
ft.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decodeExtras(Intent intent) {
|
private void decodeExtras(Intent intent) {
|
||||||
mQueryString = intent.getStringExtra(SearchManager.QUERY);
|
// check if this intent comes from the system search ( remote )
|
||||||
mFolderName = null;
|
if (intent.getStringExtra(SearchManager.QUERY) != null) {
|
||||||
mSearchAccount = null;
|
|
||||||
mSearchFolder = null;
|
|
||||||
if (mQueryString != null) {
|
|
||||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||||
//Query was received from Search Dialog
|
//Query was received from Search Dialog
|
||||||
|
String query = intent.getStringExtra(SearchManager.QUERY);
|
||||||
|
|
||||||
|
mSearch = new LocalSearch(getString(R.string.search_results));
|
||||||
|
mSearch.setManualSearch(true);
|
||||||
|
mNoThreading = true;
|
||||||
|
|
||||||
|
mSearch.or(new SearchCondition(Searchfield.SENDER, Attribute.CONTAINS, query));
|
||||||
|
mSearch.or(new SearchCondition(Searchfield.SUBJECT, Attribute.CONTAINS, query));
|
||||||
|
mSearch.or(new SearchCondition(Searchfield.MESSAGE_CONTENTS, Attribute.CONTAINS, query));
|
||||||
|
|
||||||
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
|
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
|
||||||
if (appData != null) {
|
if (appData != null) {
|
||||||
mSearchAccount = appData.getString(EXTRA_SEARCH_ACCOUNT);
|
mSearch.addAccountUuid(appData.getString(EXTRA_SEARCH_ACCOUNT));
|
||||||
mSearchFolder = appData.getString(EXTRA_SEARCH_FOLDER);
|
mSearch.addAllowedFolder(appData.getString(EXTRA_SEARCH_FOLDER));
|
||||||
|
} else {
|
||||||
|
mSearch.addAccountUuid(LocalSearch.ALL_ACCOUNTS);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
mSearchAccount = intent.getStringExtra(EXTRA_SEARCH_ACCOUNT);
|
} else {
|
||||||
mSearchFolder = intent.getStringExtra(EXTRA_SEARCH_FOLDER);
|
// regular LocalSearch object was passed
|
||||||
|
mSearch = intent.getParcelableExtra(EXTRA_SEARCH);
|
||||||
|
mNoThreading = intent.getBooleanExtra(EXTRA_NO_THREADING, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] accountUuids = mSearch.getAccountUuids();
|
||||||
|
mSingleAccountMode = (accountUuids.length == 1 && !mSearch.searchAllAccounts());
|
||||||
|
mSingleFolderMode = mSingleAccountMode && (mSearch.getFolderNames().size() == 1);
|
||||||
|
|
||||||
|
if (mSingleAccountMode) {
|
||||||
|
Preferences prefs = Preferences.getPreferences(getApplicationContext());
|
||||||
|
mAccount = prefs.getAccount(accountUuids[0]);
|
||||||
|
|
||||||
|
if (mAccount != null && !mAccount.isAvailable(this)) {
|
||||||
|
Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
|
||||||
|
onAccountUnavailable();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String accountUuid = intent.getStringExtra(EXTRA_ACCOUNT);
|
if (mSingleFolderMode) {
|
||||||
mFolderName = intent.getStringExtra(EXTRA_FOLDER);
|
mFolderName = mSearch.getFolderNames().get(0);
|
||||||
|
|
||||||
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
|
|
||||||
|
|
||||||
if (mAccount != null && !mAccount.isAvailable(this)) {
|
|
||||||
Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
|
|
||||||
onAccountUnavailable();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS);
|
// now we know if we are in single account mode and need a subtitle
|
||||||
if (queryFlags != null) {
|
mActionBarSubTitle.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
|
||||||
String[] flagStrings = queryFlags.split(",");
|
|
||||||
mQueryFlags = new Flag[flagStrings.length];
|
|
||||||
for (int i = 0; i < flagStrings.length; i++) {
|
|
||||||
mQueryFlags[i] = Flag.valueOf(flagStrings[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String forbiddenFlags = intent.getStringExtra(EXTRA_FORBIDDEN_FLAGS);
|
|
||||||
if (forbiddenFlags != null) {
|
|
||||||
String[] flagStrings = forbiddenFlags.split(",");
|
|
||||||
mForbiddenFlags = new Flag[flagStrings.length];
|
|
||||||
for (int i = 0; i < flagStrings.length; i++) {
|
|
||||||
mForbiddenFlags[i] = Flag.valueOf(flagStrings[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mIntegrate = intent.getBooleanExtra(EXTRA_INTEGRATE, false);
|
|
||||||
mAccountUuids = intent.getStringArrayExtra(EXTRA_ACCOUNT_UUIDS);
|
|
||||||
mFolderNames = intent.getStringArrayExtra(EXTRA_FOLDER_NAMES);
|
|
||||||
mTitle = intent.getStringExtra(EXTRA_TITLE);
|
|
||||||
|
|
||||||
// Take the initial folder into account only if we are *not* restoring
|
|
||||||
// the activity already.
|
|
||||||
if (mFolderName == null && mQueryString == null) {
|
|
||||||
mFolderName = mAccount.getAutoExpandFolderName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -276,10 +218,6 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
mActionBarSubTitle = (TextView) customView.findViewById(R.id.actionbar_title_sub);
|
mActionBarSubTitle = (TextView) customView.findViewById(R.id.actionbar_title_sub);
|
||||||
mActionBarUnread = (TextView) customView.findViewById(R.id.actionbar_unread_count);
|
mActionBarUnread = (TextView) customView.findViewById(R.id.actionbar_unread_count);
|
||||||
|
|
||||||
if (mQueryString != null) {
|
|
||||||
mActionBarSubTitle.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
mActionBar.setDisplayHomeAsUpEnabled(true);
|
mActionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,15 +345,9 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
if (fragmentManager.getBackStackEntryCount() > 0) {
|
if (fragmentManager.getBackStackEntryCount() > 0) {
|
||||||
fragmentManager.popBackStack();
|
fragmentManager.popBackStack();
|
||||||
} else if (mIntegrate) {
|
} else if (!mSingleFolderMode || mMessageListFragment.isManualSearch()) {
|
||||||
// If we were in one of the integrated mailboxes (think All Mail or Integrated Inbox), then
|
|
||||||
// go to accounts.
|
|
||||||
onAccounts();
|
|
||||||
} else if (mQueryString != null) {
|
|
||||||
// We did a search of some sort. Go back to wherever the user searched from.
|
|
||||||
onBackPressed();
|
onBackPressed();
|
||||||
} else {
|
} else {
|
||||||
// In a standard message list of a folder. Go to folder list.
|
|
||||||
onShowFolderList();
|
onShowFolderList();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -440,10 +372,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
mMessageListFragment.changeSort(SortType.SORT_SUBJECT);
|
mMessageListFragment.changeSort(SortType.SORT_SUBJECT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.set_sort_sender: {
|
// case R.id.set_sort_sender: {
|
||||||
mMessageListFragment.changeSort(SortType.SORT_SENDER);
|
// mMessageListFragment.changeSort(SortType.SORT_SENDER);
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
case R.id.set_sort_flag: {
|
case R.id.set_sort_flag: {
|
||||||
mMessageListFragment.changeSort(SortType.SORT_FLAGGED);
|
mMessageListFragment.changeSort(SortType.SORT_FLAGGED);
|
||||||
return true;
|
return true;
|
||||||
@ -464,6 +396,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
onEditPrefs();
|
onEditPrefs();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case R.id.account_settings: {
|
||||||
|
onEditAccount();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case R.id.search: {
|
case R.id.search: {
|
||||||
mMessageListFragment.onSearchRequested();
|
mMessageListFragment.onSearchRequested();
|
||||||
return true;
|
return true;
|
||||||
@ -474,7 +410,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mQueryString != null) {
|
if (!mSingleFolderMode) {
|
||||||
// None of the options after this point are "safe" for search results
|
// None of the options after this point are "safe" for search results
|
||||||
//TODO: This is not true for "unread" and "starred" searches in regular folders
|
//TODO: This is not true for "unread" and "starred" searches in regular folders
|
||||||
return false;
|
return false;
|
||||||
@ -491,10 +427,6 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.account_settings: {
|
|
||||||
onEditAccount();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case R.id.expunge: {
|
case R.id.expunge: {
|
||||||
mMessageListFragment.onExpunge();
|
mMessageListFragment.onExpunge();
|
||||||
return true;
|
return true;
|
||||||
@ -539,22 +471,14 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
menu.findItem(R.id.select_all).setVisible(true);
|
menu.findItem(R.id.select_all).setVisible(true);
|
||||||
menu.findItem(R.id.settings).setVisible(true);
|
menu.findItem(R.id.settings).setVisible(true);
|
||||||
|
|
||||||
if (mMessageListFragment.isSearchQuery()) {
|
if (!mSingleAccountMode) {
|
||||||
menu.findItem(R.id.expunge).setVisible(false);
|
menu.findItem(R.id.expunge).setVisible(false);
|
||||||
menu.findItem(R.id.check_mail).setVisible(false);
|
menu.findItem(R.id.check_mail).setVisible(false);
|
||||||
menu.findItem(R.id.send_messages).setVisible(false);
|
menu.findItem(R.id.send_messages).setVisible(false);
|
||||||
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.
|
|
||||||
if (!mMessageListFragment.isRemoteSearch() &&
|
|
||||||
mMessageListFragment.isRemoteSearchAllowed()) {
|
|
||||||
menu.findItem(R.id.search_remote).setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
menu.findItem(R.id.search).setVisible(true);
|
menu.findItem(R.id.folder_settings).setVisible(mSingleFolderMode);
|
||||||
menu.findItem(R.id.folder_settings).setVisible(true);
|
|
||||||
menu.findItem(R.id.account_settings).setVisible(true);
|
menu.findItem(R.id.account_settings).setVisible(true);
|
||||||
|
|
||||||
if (mMessageListFragment.isOutbox()) {
|
if (mMessageListFragment.isOutbox()) {
|
||||||
@ -571,6 +495,14 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
menu.findItem(R.id.expunge).setVisible(false);
|
menu.findItem(R.id.expunge).setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is an explicit local search, show the option to search the cloud.
|
||||||
|
if (!mMessageListFragment.isRemoteSearch() &&
|
||||||
|
mMessageListFragment.isRemoteSearchAllowed()) {
|
||||||
|
menu.findItem(R.id.search_remote).setVisible(true);
|
||||||
|
} else if (!mMessageListFragment.isManualSearch()) {
|
||||||
|
menu.findItem(R.id.search).setVisible(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,8 +604,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showMoreFromSameSender(String senderAddress) {
|
public void showMoreFromSameSender(String senderAddress) {
|
||||||
MessageListFragment fragment = MessageListFragment.newInstance("From " + senderAddress,
|
LocalSearch tmpSearch = new LocalSearch("From " + senderAddress);
|
||||||
null, null, senderAddress, null, null, false);
|
tmpSearch.addAccountUuids(mSearch.getAccountUuids());
|
||||||
|
tmpSearch.and(Searchfield.SENDER, senderAddress, Attribute.CONTAINS);
|
||||||
|
|
||||||
|
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
|
||||||
|
|
||||||
addMessageListFragment(fragment, true);
|
addMessageListFragment(fragment, true);
|
||||||
}
|
}
|
||||||
@ -720,13 +655,6 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remoteSearch(String searchAccount, String searchFolder, String queryString) {
|
|
||||||
MessageListFragment fragment = MessageListFragment.newInstance(searchAccount, searchFolder,
|
|
||||||
queryString, true);
|
|
||||||
mMenu.findItem(R.id.search_remote).setVisible(false);
|
|
||||||
addMessageListFragment(fragment, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addMessageListFragment(MessageListFragment fragment, boolean addToBackStack) {
|
private void addMessageListFragment(MessageListFragment fragment, boolean addToBackStack) {
|
||||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||||
|
|
||||||
@ -754,4 +682,21 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showThread(Account account, String folderName, long threadRootId) {
|
||||||
|
LocalSearch tmpSearch = new LocalSearch();
|
||||||
|
tmpSearch.addAccountUuid(account.getUuid());
|
||||||
|
tmpSearch.and(Searchfield.THREAD_ROOT, String.valueOf(threadRootId), Attribute.EQUALS);
|
||||||
|
tmpSearch.or(new SearchCondition(Searchfield.ID, Attribute.EQUALS, String.valueOf(threadRootId)));
|
||||||
|
|
||||||
|
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
|
||||||
|
addMessageListFragment(fragment, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteSearchStarted() {
|
||||||
|
// Remove action button for remote search
|
||||||
|
configureMenu(mMenu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package com.fsck.k9.activity;
|
|
||||||
|
|
||||||
import com.fsck.k9.R;
|
|
||||||
import com.fsck.k9.mail.Flag;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This enum represents filtering parameters used by {@link com.fsck.k9.SearchAccount}.
|
|
||||||
*/
|
|
||||||
enum SearchModifier {
|
|
||||||
FLAGGED(R.string.flagged_modifier, new Flag[]{Flag.FLAGGED}, null),
|
|
||||||
UNREAD(R.string.unread_modifier, null, new Flag[]{Flag.SEEN});
|
|
||||||
|
|
||||||
final int resId;
|
|
||||||
final Flag[] requiredFlags;
|
|
||||||
final Flag[] forbiddenFlags;
|
|
||||||
|
|
||||||
SearchModifier(int nResId, Flag[] nRequiredFlags, Flag[] nForbiddenFlags) {
|
|
||||||
resId = nResId;
|
|
||||||
requiredFlags = nRequiredFlags;
|
|
||||||
forbiddenFlags = nForbiddenFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -499,6 +499,7 @@ public class AccountSettings extends K9PreferenceActivity {
|
|||||||
// IMAP-specific preferences
|
// IMAP-specific preferences
|
||||||
|
|
||||||
mSearchScreen = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_SEARCH);
|
mSearchScreen = (PreferenceScreen) findPreference(PREFERENCE_SCREEN_SEARCH);
|
||||||
|
|
||||||
mCloudSearchEnabled = (CheckBoxPreference) findPreference(PREFERENCE_CLOUD_SEARCH_ENABLED);
|
mCloudSearchEnabled = (CheckBoxPreference) findPreference(PREFERENCE_CLOUD_SEARCH_ENABLED);
|
||||||
mRemoteSearchNumResults = (ListPreference) findPreference(PREFERENCE_REMOTE_SEARCH_NUM_RESULTS);
|
mRemoteSearchNumResults = (ListPreference) findPreference(PREFERENCE_REMOTE_SEARCH_NUM_RESULTS);
|
||||||
mRemoteSearchNumResults.setOnPreferenceChangeListener(
|
mRemoteSearchNumResults.setOnPreferenceChangeListener(
|
||||||
|
@ -87,8 +87,11 @@ public class Prefs extends K9PreferenceActivity {
|
|||||||
|
|
||||||
private static final String PREFERENCE_ATTACHMENT_DEF_PATH = "attachment_default_path";
|
private static final String PREFERENCE_ATTACHMENT_DEF_PATH = "attachment_default_path";
|
||||||
private static final String PREFERENCE_BACKGROUND_AS_UNREAD_INDICATOR = "messagelist_background_as_unread_indicator";
|
private static final String PREFERENCE_BACKGROUND_AS_UNREAD_INDICATOR = "messagelist_background_as_unread_indicator";
|
||||||
|
private static final String PREFERENCE_THREADED_VIEW = "threaded_view";
|
||||||
|
|
||||||
private static final int ACTIVITY_CHOOSE_FOLDER = 1;
|
private static final int ACTIVITY_CHOOSE_FOLDER = 1;
|
||||||
|
|
||||||
|
|
||||||
private ListPreference mLanguage;
|
private ListPreference mLanguage;
|
||||||
private ListPreference mTheme;
|
private ListPreference mTheme;
|
||||||
private ListPreference mDateFormat;
|
private ListPreference mDateFormat;
|
||||||
@ -128,6 +131,8 @@ public class Prefs extends K9PreferenceActivity {
|
|||||||
private CheckBoxPreference mBatchButtonsFlag;
|
private CheckBoxPreference mBatchButtonsFlag;
|
||||||
private CheckBoxPreference mBatchButtonsUnselect;
|
private CheckBoxPreference mBatchButtonsUnselect;
|
||||||
private CheckBoxPreference mBackgroundAsUnreadIndicator;
|
private CheckBoxPreference mBackgroundAsUnreadIndicator;
|
||||||
|
private CheckBoxPreference mThreadedView;
|
||||||
|
|
||||||
|
|
||||||
public static void actionPrefs(Context context) {
|
public static void actionPrefs(Context context) {
|
||||||
Intent i = new Intent(context, Prefs.class);
|
Intent i = new Intent(context, Prefs.class);
|
||||||
@ -235,6 +240,10 @@ public class Prefs extends K9PreferenceActivity {
|
|||||||
|
|
||||||
mChangeContactNameColor = (CheckBoxPreference)findPreference(PREFERENCE_MESSAGELIST_CONTACT_NAME_COLOR);
|
mChangeContactNameColor = (CheckBoxPreference)findPreference(PREFERENCE_MESSAGELIST_CONTACT_NAME_COLOR);
|
||||||
mChangeContactNameColor.setChecked(K9.changeContactNameColor());
|
mChangeContactNameColor.setChecked(K9.changeContactNameColor());
|
||||||
|
|
||||||
|
mThreadedView = (CheckBoxPreference) findPreference(PREFERENCE_THREADED_VIEW);
|
||||||
|
mThreadedView.setChecked(K9.isThreadedViewEnabled());
|
||||||
|
|
||||||
if (K9.changeContactNameColor()) {
|
if (K9.changeContactNameColor()) {
|
||||||
mChangeContactNameColor.setSummary(R.string.global_settings_registered_name_color_changed);
|
mChangeContactNameColor.setSummary(R.string.global_settings_registered_name_color_changed);
|
||||||
} else {
|
} else {
|
||||||
@ -418,6 +427,7 @@ public class Prefs extends K9PreferenceActivity {
|
|||||||
K9.setMessageListSenderAboveSubject(mSenderAboveSubject.isChecked());
|
K9.setMessageListSenderAboveSubject(mSenderAboveSubject.isChecked());
|
||||||
K9.setShowContactName(mShowContactName.isChecked());
|
K9.setShowContactName(mShowContactName.isChecked());
|
||||||
K9.setUseBackgroundAsUnreadIndicator(mBackgroundAsUnreadIndicator.isChecked());
|
K9.setUseBackgroundAsUnreadIndicator(mBackgroundAsUnreadIndicator.isChecked());
|
||||||
|
K9.setThreadedViewEnabled(mThreadedView.isChecked());
|
||||||
K9.setChangeContactNameColor(mChangeContactNameColor.isChecked());
|
K9.setChangeContactNameColor(mChangeContactNameColor.isChecked());
|
||||||
K9.setMessageViewFixedWidthFont(mFixedWidth.isChecked());
|
K9.setMessageViewFixedWidthFont(mFixedWidth.isChecked());
|
||||||
K9.setMessageViewReturnToList(mReturnToList.isChecked());
|
K9.setMessageViewReturnToList(mReturnToList.isChecked());
|
||||||
|
@ -40,11 +40,9 @@ import com.fsck.k9.K9.Intents;
|
|||||||
import com.fsck.k9.NotificationSetting;
|
import com.fsck.k9.NotificationSetting;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.SearchSpecification;
|
|
||||||
import com.fsck.k9.activity.FolderList;
|
import com.fsck.k9.activity.FolderList;
|
||||||
import com.fsck.k9.activity.MessageList;
|
import com.fsck.k9.activity.MessageList;
|
||||||
import com.fsck.k9.helper.NotificationBuilder;
|
import com.fsck.k9.helper.NotificationBuilder;
|
||||||
import com.fsck.k9.helper.Utility;
|
|
||||||
import com.fsck.k9.helper.power.TracingPowerManager;
|
import com.fsck.k9.helper.power.TracingPowerManager;
|
||||||
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
|
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
|
||||||
import com.fsck.k9.mail.Address;
|
import com.fsck.k9.mail.Address;
|
||||||
@ -70,6 +68,8 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
|||||||
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
|
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
|
||||||
import com.fsck.k9.mail.store.UnavailableAccountException;
|
import com.fsck.k9.mail.store.UnavailableAccountException;
|
||||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||||
|
import com.fsck.k9.search.LocalSearch;
|
||||||
|
import com.fsck.k9.search.SearchSpecification;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -509,235 +509,39 @@ public class MessagingController implements Runnable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List the messages in the local message store for the given folder asynchronously.
|
|
||||||
*
|
|
||||||
* @param account
|
|
||||||
* @param folder
|
|
||||||
* @param listener
|
|
||||||
* @throws MessagingException
|
|
||||||
*/
|
|
||||||
public void listLocalMessages(final Account account, final String folder, final MessagingListener listener) {
|
|
||||||
threadPool.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
listLocalMessagesSynchronous(account, folder, listener);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List the messages in the local message store for the given folder synchronously.
|
|
||||||
*
|
|
||||||
* @param account
|
|
||||||
* @param folder
|
|
||||||
* @param listener
|
|
||||||
* @throws MessagingException
|
|
||||||
*/
|
|
||||||
public void listLocalMessagesSynchronous(final Account account, final String folder, final MessagingListener listener) {
|
|
||||||
|
|
||||||
for (MessagingListener l : getListeners(listener)) {
|
|
||||||
l.listLocalMessagesStarted(account, folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalFolder localFolder = null;
|
|
||||||
MessageRetrievalListener retrievalListener =
|
|
||||||
new MessageRetrievalListener() {
|
|
||||||
List<Message> pendingMessages = new ArrayList<Message>();
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageStarted(String message, int number, int ofTotal) {}
|
|
||||||
@Override
|
|
||||||
public void messageFinished(Message message, int number, int ofTotal) {
|
|
||||||
|
|
||||||
if (!isMessageSuppressed(account, folder, message)) {
|
|
||||||
pendingMessages.add(message);
|
|
||||||
if (pendingMessages.size() > 10) {
|
|
||||||
addPendingMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
for (MessagingListener l : getListeners(listener)) {
|
|
||||||
l.listLocalMessagesRemoveMessage(account, folder, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void messagesFinished(int number) {
|
|
||||||
addPendingMessages();
|
|
||||||
}
|
|
||||||
private void addPendingMessages() {
|
|
||||||
for (MessagingListener l : getListeners(listener)) {
|
|
||||||
l.listLocalMessagesAddMessages(account, folder, pendingMessages);
|
|
||||||
}
|
|
||||||
pendingMessages.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
LocalStore localStore = account.getLocalStore();
|
|
||||||
localFolder = localStore.getFolder(folder);
|
|
||||||
localFolder.open(OpenMode.READ_WRITE);
|
|
||||||
|
|
||||||
//Purging followed by getting requires 2 DB queries.
|
|
||||||
//TODO: Fix getMessages to allow auto-pruning at visible limit?
|
|
||||||
localFolder.purgeToVisibleLimit(null);
|
|
||||||
localFolder.getMessages(
|
|
||||||
retrievalListener,
|
|
||||||
false // Skip deleted messages
|
|
||||||
);
|
|
||||||
if (K9.DEBUG)
|
|
||||||
Log.v(K9.LOG_TAG, "Got ack that callbackRunner finished");
|
|
||||||
|
|
||||||
for (MessagingListener l : getListeners(listener)) {
|
|
||||||
l.listLocalMessagesFinished(account, folder);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
for (MessagingListener l : getListeners(listener)) {
|
|
||||||
l.listLocalMessagesFailed(account, folder, e.getMessage());
|
|
||||||
}
|
|
||||||
addErrorMessage(account, null, e);
|
|
||||||
} finally {
|
|
||||||
closeFolder(localFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void searchLocalMessages(SearchSpecification searchSpecification, final Message[] messages, final MessagingListener listener) {
|
|
||||||
searchLocalMessages(searchSpecification.getAccountUuids(), searchSpecification.getFolderNames(), messages,
|
|
||||||
searchSpecification.getQuery(), searchSpecification.isIntegrate(), searchSpecification.getRequiredFlags(), searchSpecification.getForbiddenFlags(), listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all messages in any local account which match the query 'query'
|
* Find all messages in any local account which match the query 'query'
|
||||||
* @throws MessagingException
|
* @throws MessagingException
|
||||||
*/
|
*/
|
||||||
public void searchLocalMessages(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate,
|
public void searchLocalMessages(final LocalSearch search, final MessagingListener listener) {
|
||||||
final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
|
|
||||||
if (K9.DEBUG) {
|
|
||||||
Log.i(K9.LOG_TAG, "searchLocalMessages ("
|
|
||||||
+ "accountUuids=" + Utility.combine(accountUuids, ',')
|
|
||||||
+ ", folderNames = " + Utility.combine(folderNames, ',')
|
|
||||||
+ ", messages.size() = " + (messages != null ? messages.length : -1)
|
|
||||||
+ ", query = " + query
|
|
||||||
+ ", integrate = " + integrate
|
|
||||||
+ ", requiredFlags = " + Utility.combine(requiredFlags, ',')
|
|
||||||
+ ", forbiddenFlags = " + Utility.combine(forbiddenFlags, ',')
|
|
||||||
+ ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
threadPool.execute(new Runnable() {
|
threadPool.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
searchLocalMessagesSynchronous(accountUuids, folderNames, messages, query, integrate, requiredFlags, forbiddenFlags, listener);
|
searchLocalMessagesSynchronous(search, listener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public void searchLocalMessagesSynchronous(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate, final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
|
|
||||||
|
|
||||||
|
public void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) {
|
||||||
final AccountStats stats = new AccountStats();
|
final AccountStats stats = new AccountStats();
|
||||||
final Set<String> accountUuidsSet = new HashSet<String>();
|
final HashSet<String> uuidSet = new HashSet<String>(Arrays.asList(search.getAccountUuids()));
|
||||||
if (accountUuids != null) {
|
Account[] accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts();
|
||||||
accountUuidsSet.addAll(Arrays.asList(accountUuids));
|
boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS);
|
||||||
}
|
|
||||||
final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext());
|
// for every account we want to search do the query in the localstore
|
||||||
List<LocalFolder> foldersToSearch = null;
|
for (final Account account : accounts) {
|
||||||
boolean displayableOnly = false;
|
|
||||||
boolean noSpecialFolders = true;
|
if (!allAccounts && !uuidSet.contains(account.getUuid())) {
|
||||||
for (final Account account : prefs.getAvailableAccounts()) {
|
|
||||||
if (accountUuids != null && !accountUuidsSet.contains(account.getUuid())) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accountUuids != null && accountUuidsSet.contains(account.getUuid())) {
|
// Collecting statistics of the search result
|
||||||
displayableOnly = true;
|
|
||||||
noSpecialFolders = true;
|
|
||||||
} else if (!integrate && folderNames == null) {
|
|
||||||
Account.Searchable searchableFolders = account.getSearchableFolders();
|
|
||||||
switch (searchableFolders) {
|
|
||||||
case NONE:
|
|
||||||
continue;
|
|
||||||
case DISPLAYABLE:
|
|
||||||
displayableOnly = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<Message> messagesToSearch = null;
|
|
||||||
if (messages != null) {
|
|
||||||
messagesToSearch = new LinkedList<Message>();
|
|
||||||
for (Message message : messages) {
|
|
||||||
if (message.getFolder().getAccount().getUuid().equals(account.getUuid())) {
|
|
||||||
messagesToSearch.add(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (messagesToSearch.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (listener != null) {
|
|
||||||
listener.listLocalMessagesStarted(account, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (integrate || displayableOnly || folderNames != null || noSpecialFolders) {
|
|
||||||
List<LocalFolder> tmpFoldersToSearch = new LinkedList<LocalFolder>();
|
|
||||||
try {
|
|
||||||
LocalStore store = account.getLocalStore();
|
|
||||||
List <? extends Folder > folders = store.getPersonalNamespaces(false);
|
|
||||||
Set<String> folderNameSet = null;
|
|
||||||
if (folderNames != null) {
|
|
||||||
folderNameSet = new HashSet<String>();
|
|
||||||
folderNameSet.addAll(Arrays.asList(folderNames));
|
|
||||||
}
|
|
||||||
for (Folder folder : folders) {
|
|
||||||
LocalFolder localFolder = (LocalFolder)folder;
|
|
||||||
boolean include = true;
|
|
||||||
folder.refresh(prefs);
|
|
||||||
String localFolderName = localFolder.getName();
|
|
||||||
if (integrate) {
|
|
||||||
include = localFolder.isIntegrate();
|
|
||||||
} else {
|
|
||||||
if (folderNameSet != null) {
|
|
||||||
if (!folderNameSet.contains(localFolderName))
|
|
||||||
|
|
||||||
{
|
|
||||||
include = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Never exclude the INBOX (see issue 1817)
|
|
||||||
else if (noSpecialFolders && !localFolderName.equalsIgnoreCase(account.getInboxFolderName()) &&
|
|
||||||
!localFolderName.equals(account.getArchiveFolderName()) && account.isSpecialFolder(localFolderName)) {
|
|
||||||
include = false;
|
|
||||||
} else if (displayableOnly && modeMismatch(account.getFolderDisplayMode(), folder.getDisplayClass())) {
|
|
||||||
include = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (include) {
|
|
||||||
tmpFoldersToSearch.add(localFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tmpFoldersToSearch.size() < 1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foldersToSearch = tmpFoldersToSearch;
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
Log.e(K9.LOG_TAG, "Unable to restrict search folders in Account " + account.getDescription() + ", searching all", me);
|
|
||||||
addErrorMessage(account, null, me);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
|
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
|
||||||
@Override
|
@Override
|
||||||
public void messageStarted(String message, int number, int ofTotal) {}
|
public void messageStarted(String message, int number, int ofTotal) {}
|
||||||
@Override
|
@Override
|
||||||
|
public void messagesFinished(int number) {}
|
||||||
|
@Override
|
||||||
public void messageFinished(Message message, int number, int ofTotal) {
|
public void messageFinished(Message message, int number, int ofTotal) {
|
||||||
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
|
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
|
||||||
List<Message> messages = new ArrayList<Message>();
|
List<Message> messages = new ArrayList<Message>();
|
||||||
@ -749,22 +553,18 @@ public class MessagingController implements Runnable {
|
|||||||
listener.listLocalMessagesAddMessages(account, null, messages);
|
listener.listLocalMessagesAddMessages(account, null, messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void messagesFinished(int number) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
// alert everyone the search has started
|
||||||
String[] queryFields = {"html_content", "subject", "sender_list"};
|
if (listener != null) {
|
||||||
LocalStore localStore = account.getLocalStore();
|
listener.listLocalMessagesStarted(account, null);
|
||||||
localStore.searchForMessages(retrievalListener, queryFields
|
}
|
||||||
, query, foldersToSearch,
|
|
||||||
messagesToSearch == null ? null : messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY),
|
|
||||||
requiredFlags, forbiddenFlags);
|
|
||||||
|
|
||||||
|
// build and do the query in the localstore
|
||||||
|
try {
|
||||||
|
LocalStore localStore = account.getLocalStore();
|
||||||
|
localStore.searchForMessages(retrievalListener, search);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.listLocalMessagesFailed(account, null, e.getMessage());
|
listener.listLocalMessagesFailed(account, null, e.getMessage());
|
||||||
@ -776,6 +576,8 @@ public class MessagingController implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// publish the total search statistics
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.searchStats(stats);
|
listener.searchStats(stats);
|
||||||
}
|
}
|
||||||
@ -824,24 +626,28 @@ public class MessagingController implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Message> messages = remoteFolder.search(query, requiredFlags, forbiddenFlags);
|
List<Message> messages = remoteFolder.search(query, requiredFlags, forbiddenFlags);
|
||||||
if (listener != null) {
|
|
||||||
listener.remoteSearchServerQueryComplete(acct, folderName, messages.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (K9.DEBUG) {
|
if (K9.DEBUG) {
|
||||||
Log.i("Remote Search", "Remote search got " + messages.size() + " results");
|
Log.i("Remote Search", "Remote search got " + messages.size() + " results");
|
||||||
}
|
}
|
||||||
|
|
||||||
Collections.sort(messages, new UidReverseComparator());
|
// There's no need to fetch messages already completely downloaded
|
||||||
|
List<Message> remoteMessages = localFolder.extractNewMessages(messages);
|
||||||
|
messages.clear();
|
||||||
|
|
||||||
|
if (listener != null) {
|
||||||
int resultLimit = acct.getRemoteSearchNumResults();
|
listener.remoteSearchServerQueryComplete(acct, folderName, remoteMessages.size());
|
||||||
if (resultLimit > 0 && messages.size() > resultLimit) {
|
|
||||||
extraResults = messages.subList(resultLimit, messages.size());
|
|
||||||
messages = messages.subList(0, resultLimit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSearchResultsSynchronous(messages, localFolder, remoteFolder, listener);
|
Collections.sort(remoteMessages, new UidReverseComparator());
|
||||||
|
|
||||||
|
int resultLimit = acct.getRemoteSearchNumResults();
|
||||||
|
if (resultLimit > 0 && remoteMessages.size() > resultLimit) {
|
||||||
|
extraResults = remoteMessages.subList(resultLimit, remoteMessages.size());
|
||||||
|
remoteMessages = remoteMessages.subList(0, resultLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSearchResultsSynchronous(remoteMessages, localFolder, remoteFolder, listener);
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -2753,20 +2559,39 @@ public class MessagingController implements Runnable {
|
|||||||
processPendingCommands(account);
|
processPendingCommands(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlag(
|
public void setFlag(final List<Message> messages, final Flag flag, final boolean newState) {
|
||||||
final Message[] messages,
|
|
||||||
final Flag flag,
|
|
||||||
final boolean newState) {
|
|
||||||
actOnMessages(messages, new MessageActor() {
|
actOnMessages(messages, new MessageActor() {
|
||||||
@Override
|
@Override
|
||||||
public void act(final Account account, final Folder folder,
|
public void act(final Account account, final Folder folder,
|
||||||
final List<Message> messages) {
|
final List<Message> accountMessages) {
|
||||||
setFlag(account, folder.getName(), messages.toArray(EMPTY_MESSAGE_ARRAY), flag,
|
|
||||||
|
setFlag(account, folder.getName(), accountMessages.toArray(EMPTY_MESSAGE_ARRAY), flag,
|
||||||
newState);
|
newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlagForThreads(final List<Message> messages, final Flag flag,
|
||||||
|
final boolean newState) {
|
||||||
|
|
||||||
|
actOnMessages(messages, new MessageActor() {
|
||||||
|
@Override
|
||||||
|
public void act(final Account account, final Folder folder,
|
||||||
|
final List<Message> accountMessages) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Message> messagesInThreads = collectMessagesInThreads(account,
|
||||||
|
accountMessages);
|
||||||
|
|
||||||
|
setFlag(account, folder.getName(),
|
||||||
|
messagesInThreads.toArray(EMPTY_MESSAGE_ARRAY), flag, newState);
|
||||||
|
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
addErrorMessage(account, "Something went wrong in setFlagForThreads()", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3023,14 +2848,15 @@ public class MessagingController implements Runnable {
|
|||||||
false, true)) {
|
false, true)) {
|
||||||
if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) {
|
if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) {
|
||||||
message.setFlag(Flag.SEEN, true);
|
message.setFlag(Flag.SEEN, true);
|
||||||
setFlag(new Message[] { message }, Flag.SEEN, true);
|
setFlag(Collections.singletonList((Message) message),
|
||||||
|
Flag.SEEN, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!message.isSet(Flag.SEEN)) {
|
if (!message.isSet(Flag.SEEN)) {
|
||||||
message.setFlag(Flag.SEEN, true);
|
message.setFlag(Flag.SEEN, true);
|
||||||
setFlag(new Message[] { message }, Flag.SEEN, true);
|
setFlag(Collections.singletonList((Message) message), Flag.SEEN, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (MessagingListener l : getListeners(listener)) {
|
for (MessagingListener l : getListeners(listener)) {
|
||||||
@ -3247,8 +3073,11 @@ public class MessagingController implements Runnable {
|
|||||||
builder.setContentTitle(mApplication.getString(R.string.notification_bg_send_title));
|
builder.setContentTitle(mApplication.getString(R.string.notification_bg_send_title));
|
||||||
builder.setContentText(account.getDescription());
|
builder.setContentText(account.getDescription());
|
||||||
|
|
||||||
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account,
|
LocalSearch search = new LocalSearch(account.getInboxFolderName());
|
||||||
account.getInboxFolderName());
|
search.addAllowedFolder(account.getInboxFolderName());
|
||||||
|
search.addAccountUuid(account.getUuid());
|
||||||
|
Intent intent = MessageList.intentDisplaySearch(mApplication, search, false, true, true);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
|
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
|
||||||
builder.setContentIntent(pi);
|
builder.setContentIntent(pi);
|
||||||
|
|
||||||
@ -3330,8 +3159,11 @@ public class MessagingController implements Runnable {
|
|||||||
mApplication.getString(R.string.notification_bg_title_separator) +
|
mApplication.getString(R.string.notification_bg_title_separator) +
|
||||||
folder.getName());
|
folder.getName());
|
||||||
|
|
||||||
Intent intent = MessageList.actionHandleFolderIntent(mApplication, account,
|
LocalSearch search = new LocalSearch(account.getInboxFolderName());
|
||||||
account.getInboxFolderName());
|
search.addAllowedFolder(account.getInboxFolderName());
|
||||||
|
search.addAccountUuid(account.getUuid());
|
||||||
|
Intent intent = MessageList.intentDisplaySearch(mApplication, search, false, true, true);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
|
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
|
||||||
builder.setContentIntent(pi);
|
builder.setContentIntent(pi);
|
||||||
|
|
||||||
@ -3597,40 +3429,90 @@ public class MessagingController implements Runnable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void moveMessages(final Account account, final String srcFolder, final Message[] messages, final String destFolder,
|
public void moveMessages(final Account account, final String srcFolder,
|
||||||
final MessagingListener listener) {
|
final List<Message> messages, final String destFolder,
|
||||||
|
final MessagingListener listener) {
|
||||||
|
|
||||||
for (Message message : messages) {
|
for (Message message : messages) {
|
||||||
suppressMessage(account, srcFolder, message);
|
suppressMessage(account, srcFolder, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
putBackground("moveMessages", null, new Runnable() {
|
putBackground("moveMessages", null, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
moveOrCopyMessageSynchronous(account, srcFolder, messages, destFolder, false, listener);
|
moveOrCopyMessageSynchronous(account, srcFolder, messages, destFolder, false,
|
||||||
|
listener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void moveMessage(final Account account, final String srcFolder, final Message message, final String destFolder,
|
public void moveMessagesInThread(final Account account, final String srcFolder,
|
||||||
final MessagingListener listener) {
|
final List<Message> messages, final String destFolder) {
|
||||||
moveMessages(account, srcFolder, new Message[] { message }, destFolder, listener);
|
|
||||||
|
for (Message message : messages) {
|
||||||
|
suppressMessage(account, srcFolder, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
putBackground("moveMessagesInThread", null, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
List<Message> messagesInThreads = collectMessagesInThreads(account, messages);
|
||||||
|
moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destFolder,
|
||||||
|
false, null);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
addErrorMessage(account, "Exception while moving messages", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyMessages(final Account account, final String srcFolder, final Message[] messages, final String destFolder,
|
public void moveMessage(final Account account, final String srcFolder, final Message message,
|
||||||
final MessagingListener listener) {
|
final String destFolder, final MessagingListener listener) {
|
||||||
|
|
||||||
|
moveMessages(account, srcFolder, Collections.singletonList(message), destFolder, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyMessages(final Account account, final String srcFolder,
|
||||||
|
final List<Message> messages, final String destFolder,
|
||||||
|
final MessagingListener listener) {
|
||||||
|
|
||||||
putBackground("copyMessages", null, new Runnable() {
|
putBackground("copyMessages", null, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
moveOrCopyMessageSynchronous(account, srcFolder, messages, destFolder, true, listener);
|
moveOrCopyMessageSynchronous(account, srcFolder, messages, destFolder, true,
|
||||||
|
listener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public void copyMessage(final Account account, final String srcFolder, final Message message, final String destFolder,
|
|
||||||
final MessagingListener listener) {
|
public void copyMessagesInThread(final Account account, final String srcFolder,
|
||||||
copyMessages(account, srcFolder, new Message[] { message }, destFolder, listener);
|
final List<Message> messages, final String destFolder) {
|
||||||
|
|
||||||
|
putBackground("copyMessagesInThread", null, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
List<Message> messagesInThreads = collectMessagesInThreads(account, messages);
|
||||||
|
moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destFolder,
|
||||||
|
true, null);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
addErrorMessage(account, "Exception while copying messages", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder, final Message[] inMessages,
|
public void copyMessage(final Account account, final String srcFolder, final Message message,
|
||||||
final String destFolder, final boolean isCopy, MessagingListener listener) {
|
final String destFolder, final MessagingListener listener) {
|
||||||
|
|
||||||
|
copyMessages(account, srcFolder, Collections.singletonList(message), destFolder, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder,
|
||||||
|
final List<Message> inMessages, final String destFolder, final boolean isCopy,
|
||||||
|
MessagingListener listener) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, String> uidMap = new HashMap<String, String>();
|
Map<String, String> uidMap = new HashMap<String, String>();
|
||||||
Store localStore = account.getLocalStore();
|
Store localStore = account.getLocalStore();
|
||||||
@ -3741,7 +3623,7 @@ public class MessagingController implements Runnable {
|
|||||||
if (uid != null) {
|
if (uid != null) {
|
||||||
Message message = localFolder.getMessage(uid);
|
Message message = localFolder.getMessage(uid);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
deleteMessages(new Message[] { message }, null);
|
deleteMessages(Collections.singletonList(message), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
@ -3751,20 +3633,72 @@ public class MessagingController implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteMessages(final Message[] messages, final MessagingListener listener) {
|
public void deleteThreads(final List<Message> messages) {
|
||||||
actOnMessages(messages, new MessageActor() {
|
actOnMessages(messages, new MessageActor() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void act(final Account account, final Folder folder,
|
public void act(final Account account, final Folder folder,
|
||||||
final List<Message> messages) {
|
final List<Message> accountMessages) {
|
||||||
for (Message message : messages) {
|
|
||||||
|
for (Message message : accountMessages) {
|
||||||
|
suppressMessage(account, folder.getName(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
putBackground("deleteThreads", null, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
deleteThreadsSynchronous(account, folder.getName(), accountMessages);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteThreadsSynchronous(Account account, String folderName,
|
||||||
|
List<Message> messages) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Message> messagesToDelete = collectMessagesInThreads(account, messages);
|
||||||
|
|
||||||
|
deleteMessagesSynchronous(account, folderName,
|
||||||
|
messagesToDelete.toArray(EMPTY_MESSAGE_ARRAY), null);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
Log.e(K9.LOG_TAG, "Something went wrong while deleting threads", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Message> collectMessagesInThreads(Account account, List<Message> messages)
|
||||||
|
throws MessagingException {
|
||||||
|
|
||||||
|
LocalStore localStore = account.getLocalStore();
|
||||||
|
|
||||||
|
List<Message> messagesInThreads = new ArrayList<Message>();
|
||||||
|
for (Message message : messages) {
|
||||||
|
long rootId = ((LocalMessage) message).getRootId();
|
||||||
|
long threadId = (rootId == -1) ? message.getId() : rootId;
|
||||||
|
|
||||||
|
Message[] messagesInThread = localStore.getMessagesInThread(threadId);
|
||||||
|
Collections.addAll(messagesInThreads, messagesInThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messagesInThreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteMessages(final List<Message> messages, final MessagingListener listener) {
|
||||||
|
actOnMessages(messages, new MessageActor() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void act(final Account account, final Folder folder,
|
||||||
|
final List<Message> accountMessages) {
|
||||||
|
for (Message message : accountMessages) {
|
||||||
suppressMessage(account, folder.getName(), message);
|
suppressMessage(account, folder.getName(), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
putBackground("deleteMessages", null, new Runnable() {
|
putBackground("deleteMessages", null, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
deleteMessagesSynchronous(account, folder.getName(), messages.toArray(EMPTY_MESSAGE_ARRAY), listener);
|
deleteMessagesSynchronous(account, folder.getName(),
|
||||||
|
accountMessages.toArray(EMPTY_MESSAGE_ARRAY), listener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -5024,7 +4958,7 @@ public class MessagingController implements Runnable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void actOnMessages(Message[] messages, MessageActor actor) {
|
private void actOnMessages(List<Message> messages, MessageActor actor) {
|
||||||
Map<Account, Map<Folder, List<Message>>> accountMap = new HashMap<Account, Map<Folder, List<Message>>>();
|
Map<Account, Map<Folder, List<Message>>> accountMap = new HashMap<Account, Map<Folder, List<Message>>>();
|
||||||
|
|
||||||
for (Message message : messages) {
|
for (Message message : messages) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
package com.fsck.k9.fragment;
|
package com.fsck.k9.fragment;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -274,7 +275,7 @@ public class MessageViewFragment extends SherlockFragment implements OnClickList
|
|||||||
mMenu.findItem(R.id.delete).setEnabled(false);
|
mMenu.findItem(R.id.delete).setEnabled(false);
|
||||||
Message messageToDelete = mMessage;
|
Message messageToDelete = mMessage;
|
||||||
mFragmentListener.showNextMessageOrReturn();
|
mFragmentListener.showNextMessageOrReturn();
|
||||||
mController.deleteMessages(new Message[] {messageToDelete}, null);
|
mController.deleteMessages(Collections.singletonList(messageToDelete), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
463
src/com/fsck/k9/helper/MergeCursor.java
Normal file
463
src/com/fsck/k9/helper/MergeCursor.java
Normal file
@ -0,0 +1,463 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The K-9 Dog Walkers
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.fsck.k9.helper;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.database.CharArrayBuffer;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.DataSetObserver;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used to combine multiple {@link Cursor}s into one.
|
||||||
|
*/
|
||||||
|
public class MergeCursor implements Cursor {
|
||||||
|
/**
|
||||||
|
* List of the cursors combined in this object.
|
||||||
|
*/
|
||||||
|
protected final Cursor[] mCursors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active cursor.
|
||||||
|
*/
|
||||||
|
protected Cursor mActiveCursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the currently active cursor in {@link #mCursors}.
|
||||||
|
*
|
||||||
|
* @see #mActiveCursor
|
||||||
|
*/
|
||||||
|
protected int mActiveCursorIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cursor's current position.
|
||||||
|
*/
|
||||||
|
protected int mPosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to cache the value of {@link #getCount()}.
|
||||||
|
*/
|
||||||
|
private int mCount = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The comparator that is used to decide how the individual cursors are merged.
|
||||||
|
*/
|
||||||
|
private final Comparator<Cursor> mComparator;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param cursors
|
||||||
|
* The list of cursors this {@code MultiCursor} should combine.
|
||||||
|
* @param comparator
|
||||||
|
* A comparator that is used to decide in what order the individual cursors are merged.
|
||||||
|
*/
|
||||||
|
public MergeCursor(Cursor[] cursors, Comparator<Cursor> comparator) {
|
||||||
|
mCursors = cursors.clone();
|
||||||
|
mComparator = comparator;
|
||||||
|
|
||||||
|
resetCursors();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetCursors() {
|
||||||
|
mActiveCursorIndex = -1;
|
||||||
|
mActiveCursor = null;
|
||||||
|
mPosition = -1;
|
||||||
|
|
||||||
|
for (int i = 0, len = mCursors.length; i < len; i++) {
|
||||||
|
Cursor cursor = mCursors[i];
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.moveToPosition(-1);
|
||||||
|
|
||||||
|
if (mActiveCursor == null) {
|
||||||
|
mActiveCursorIndex = i;
|
||||||
|
mActiveCursor = mCursors[mActiveCursorIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
|
||||||
|
mActiveCursor.copyStringToBuffer(columnIndex, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deactivate() {
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlob(int columnIndex) {
|
||||||
|
return mActiveCursor.getBlob(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return mActiveCursor.getColumnCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndex(String columnName) {
|
||||||
|
return mActiveCursor.getColumnIndex(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
|
||||||
|
return mActiveCursor.getColumnIndexOrThrow(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex) {
|
||||||
|
return mActiveCursor.getColumnName(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getColumnNames() {
|
||||||
|
return mActiveCursor.getColumnNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
// CursorLoaders seem to call getCount() a lot. So we're caching the aggregated count.
|
||||||
|
if (mCount == -1) {
|
||||||
|
int count = 0;
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
if (cursor != null) {
|
||||||
|
count += cursor.getCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDouble(int columnIndex) {
|
||||||
|
return mActiveCursor.getDouble(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloat(int columnIndex) {
|
||||||
|
return mActiveCursor.getFloat(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(int columnIndex) {
|
||||||
|
return mActiveCursor.getInt(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(int columnIndex) {
|
||||||
|
return mActiveCursor.getLong(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPosition() {
|
||||||
|
return mPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getShort(int columnIndex) {
|
||||||
|
return mActiveCursor.getShort(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(int columnIndex) {
|
||||||
|
return mActiveCursor.getString(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(11)
|
||||||
|
@Override
|
||||||
|
public int getType(int columnIndex) {
|
||||||
|
return mActiveCursor.getType(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getWantsAllOnMoveCalls() {
|
||||||
|
return mActiveCursor.getWantsAllOnMoveCalls();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAfterLast() {
|
||||||
|
int count = getCount();
|
||||||
|
if (count == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mPosition == count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBeforeFirst() {
|
||||||
|
if (getCount() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mPosition == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return mActiveCursor.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFirst() {
|
||||||
|
if (getCount() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mPosition == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLast() {
|
||||||
|
int count = getCount();
|
||||||
|
if (count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mPosition == (count - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNull(int columnIndex) {
|
||||||
|
return mActiveCursor.isNull(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean move(int offset) {
|
||||||
|
return moveToPosition(mPosition + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToFirst() {
|
||||||
|
return moveToPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToLast() {
|
||||||
|
return moveToPosition(getCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToNext() {
|
||||||
|
int count = getCount();
|
||||||
|
if (mPosition == count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPosition == count - 1) {
|
||||||
|
mActiveCursor.moveToNext();
|
||||||
|
mPosition++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int smallest = -1;
|
||||||
|
for (int i = 0, len = mCursors.length; i < len; i++) {
|
||||||
|
if (mCursors[i] == null || mCursors[i].getCount() == 0 || mCursors[i].isLast()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smallest == -1) {
|
||||||
|
smallest = i;
|
||||||
|
mCursors[smallest].moveToNext();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor left = mCursors[smallest];
|
||||||
|
Cursor right = mCursors[i];
|
||||||
|
|
||||||
|
right.moveToNext();
|
||||||
|
|
||||||
|
int result = mComparator.compare(left, right);
|
||||||
|
if (result > 0) {
|
||||||
|
smallest = i;
|
||||||
|
left.moveToPrevious();
|
||||||
|
} else {
|
||||||
|
right.moveToPrevious();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mPosition++;
|
||||||
|
if (smallest != -1) {
|
||||||
|
mActiveCursorIndex = smallest;
|
||||||
|
mActiveCursor = mCursors[mActiveCursorIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToPosition(int position) {
|
||||||
|
// Make sure position isn't past the end of the cursor
|
||||||
|
final int count = getCount();
|
||||||
|
if (position >= count) {
|
||||||
|
mPosition = count;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure position isn't before the beginning of the cursor
|
||||||
|
if (position < 0) {
|
||||||
|
mPosition = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for no-op moves, and skip the rest of the work for them
|
||||||
|
if (position == mPosition) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position > mPosition) {
|
||||||
|
for (int i = 0, end = position - mPosition; i < end; i++) {
|
||||||
|
if (!moveToNext()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0, end = mPosition - position; i < end; i++) {
|
||||||
|
if (!moveToPrevious()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToPrevious() {
|
||||||
|
if (mPosition < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mActiveCursor.moveToPrevious();
|
||||||
|
|
||||||
|
if (mPosition == 0) {
|
||||||
|
mPosition = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int greatest = -1;
|
||||||
|
for (int i = 0, len = mCursors.length; i < len; i++) {
|
||||||
|
if (mCursors[i] == null || mCursors[i].isBeforeFirst()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (greatest == -1) {
|
||||||
|
greatest = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor left = mCursors[greatest];
|
||||||
|
Cursor right = mCursors[i];
|
||||||
|
|
||||||
|
int result = mComparator.compare(left, right);
|
||||||
|
if (result <= 0) {
|
||||||
|
greatest = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mPosition--;
|
||||||
|
if (greatest != -1) {
|
||||||
|
mActiveCursorIndex = greatest;
|
||||||
|
mActiveCursor = mCursors[mActiveCursorIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerContentObserver(ContentObserver observer) {
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
cursor.registerContentObserver(observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerDataSetObserver(DataSetObserver observer) {
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
cursor.registerDataSetObserver(observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public boolean requery() {
|
||||||
|
boolean success = true;
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
success &= cursor.requery();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotificationUri(ContentResolver cr, Uri uri) {
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
cursor.setNotificationUri(cr, uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterContentObserver(ContentObserver observer) {
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
cursor.unregisterContentObserver(observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||||
|
for (Cursor cursor : mCursors) {
|
||||||
|
cursor.unregisterDataSetObserver(observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle getExtras() {
|
||||||
|
throw new RuntimeException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle respond(Bundle extras) {
|
||||||
|
throw new RuntimeException("Not implemented");
|
||||||
|
}
|
||||||
|
}
|
83
src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
Normal file
83
src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package com.fsck.k9.helper;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
|
||||||
|
public class MergeCursorWithUniqueId extends MergeCursor {
|
||||||
|
private static final int SHIFT = 48;
|
||||||
|
private static final long MAX_ID = (1L << SHIFT) - 1;
|
||||||
|
private static final long MAX_CURSORS = 1L << (63 - SHIFT);
|
||||||
|
|
||||||
|
private int mColumnCount = -1;
|
||||||
|
private int mIdColumnIndex = -1;
|
||||||
|
|
||||||
|
|
||||||
|
public MergeCursorWithUniqueId(Cursor[] cursors, Comparator<Cursor> comparator) {
|
||||||
|
super(cursors, comparator);
|
||||||
|
|
||||||
|
if (cursors.length > MAX_CURSORS) {
|
||||||
|
throw new IllegalArgumentException("This class only supports up to " +
|
||||||
|
MAX_CURSORS + " cursors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
if (mColumnCount == -1) {
|
||||||
|
mColumnCount = super.getColumnCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mColumnCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndex(String columnName) {
|
||||||
|
if ("_id".equals(columnName)) {
|
||||||
|
return getUniqueIdColumnIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getColumnIndexOrThrow(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
|
||||||
|
if ("_id".equals(columnName)) {
|
||||||
|
return getUniqueIdColumnIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getColumnIndexOrThrow(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(int columnIndex) {
|
||||||
|
if (columnIndex == getUniqueIdColumnIndex()) {
|
||||||
|
long id = getPerCursorId();
|
||||||
|
if (id > MAX_ID) {
|
||||||
|
throw new RuntimeException("Sorry, " + this.getClass().getName() +
|
||||||
|
" can only handle '_id' values up to " + SHIFT + " bits.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (((long) mActiveCursorIndex) << SHIFT) + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getLong(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getUniqueIdColumnIndex() {
|
||||||
|
if (mColumnCount == -1) {
|
||||||
|
mColumnCount = super.getColumnCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mColumnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getPerCursorId() {
|
||||||
|
if (mIdColumnIndex == -1) {
|
||||||
|
mIdColumnIndex = super.getColumnIndexOrThrow("_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getLong(mIdColumnIndex);
|
||||||
|
}
|
||||||
|
}
|
@ -105,4 +105,28 @@ public class MessageHelper {
|
|||||||
mDateFormat = DateFormatter.getDateFormat(mContext);
|
mDateFormat = DateFormatter.getDateFormat(mContext);
|
||||||
mTodayDateFormat = android.text.format.DateFormat.getTimeFormat(mContext);
|
mTodayDateFormat = android.text.format.DateFormat.getTimeFormat(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CharSequence getDisplayName(Account account, Address[] fromAddrs, Address[] toAddrs) {
|
||||||
|
final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
|
||||||
|
|
||||||
|
CharSequence displayName;
|
||||||
|
if (fromAddrs.length > 0 && account.isAnIdentity(fromAddrs[0])) {
|
||||||
|
CharSequence to = Address.toFriendly(toAddrs, contactHelper);
|
||||||
|
displayName = new SpannableStringBuilder(
|
||||||
|
mContext.getString(R.string.message_to_label)).append(to);
|
||||||
|
} else {
|
||||||
|
displayName = Address.toFriendly(fromAddrs, contactHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean toMe(Account account, Address[] toAddrs) {
|
||||||
|
for (Address address : toAddrs) {
|
||||||
|
if (account.isAnIdentity(address)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
|
||||||
package com.fsck.k9.helper;
|
package com.fsck.k9.helper;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
|
import android.os.Build;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@ -18,7 +20,10 @@ import java.io.File;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -666,4 +671,57 @@ public class Utility {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Pattern MESSAGE_ID = Pattern.compile("<" +
|
||||||
|
"(?:" +
|
||||||
|
"[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+" +
|
||||||
|
"(?:\\.[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+)*" +
|
||||||
|
"|" +
|
||||||
|
"\"(?:[^\\\\\"]|\\\\.)*\"" +
|
||||||
|
")" +
|
||||||
|
"@" +
|
||||||
|
"(?:" +
|
||||||
|
"[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+" +
|
||||||
|
"(?:\\.[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+)*" +
|
||||||
|
"|" +
|
||||||
|
"\\[(?:[^\\\\\\]]|\\\\.)*\\]" +
|
||||||
|
")" +
|
||||||
|
">");
|
||||||
|
|
||||||
|
public static List<String> extractMessageIds(final String text) {
|
||||||
|
List<String> messageIds = new ArrayList<String>();
|
||||||
|
Matcher matcher = MESSAGE_ID.matcher(text);
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
while (matcher.find(start)) {
|
||||||
|
String messageId = text.substring(matcher.start(), matcher.end());
|
||||||
|
messageIds.add(messageId);
|
||||||
|
start = matcher.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String extractMessageId(final String text) {
|
||||||
|
Matcher matcher = MESSAGE_ID.matcher(text);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
return text.substring(matcher.start(), matcher.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public static String[] copyOf(String[] original, int newLength) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 9) {
|
||||||
|
return Arrays.copyOf(original, newLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] newArray = new String[newLength];
|
||||||
|
int copyLength = (original.length >= newLength) ? newLength : original.length;
|
||||||
|
System.arraycopy(original, 0, newArray, 0, copyLength);
|
||||||
|
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,10 +143,6 @@ 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 boolean fromMe();
|
|
||||||
public abstract long getId();
|
public abstract long getId();
|
||||||
|
|
||||||
public abstract String getPreview();
|
public abstract String getPreview();
|
||||||
@ -197,8 +193,6 @@ public abstract class Message implements Part, Body {
|
|||||||
|
|
||||||
public void destroy() throws MessagingException {}
|
public void destroy() throws MessagingException {}
|
||||||
|
|
||||||
public abstract void saveChanges() throws MessagingException;
|
|
||||||
|
|
||||||
public abstract void setEncoding(String encoding) throws UnavailableStorageException;
|
public abstract void setEncoding(String encoding) throws UnavailableStorageException;
|
||||||
|
|
||||||
public abstract void setCharset(String charset) throws MessagingException;
|
public abstract void setCharset(String charset) throws MessagingException;
|
||||||
@ -213,11 +207,6 @@ public abstract class Message implements Part, Body {
|
|||||||
return mReference;
|
return mReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equalsReference(MessageReference ref) {
|
|
||||||
MessageReference tmpReference = makeMessageReference();
|
|
||||||
return tmpReference.equals(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long calculateSize() {
|
public long calculateSize() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import org.apache.james.mime4j.MimeException;
|
import org.apache.james.mime4j.MimeException;
|
||||||
import org.apache.james.mime4j.dom.field.DateTimeField;
|
import org.apache.james.mime4j.dom.field.DateTimeField;
|
||||||
import org.apache.james.mime4j.dom.field.ParsedField;
|
|
||||||
import org.apache.james.mime4j.field.DefaultFieldParser;
|
import org.apache.james.mime4j.field.DefaultFieldParser;
|
||||||
import org.apache.james.mime4j.io.EOLConvertingInputStream;
|
import org.apache.james.mime4j.io.EOLConvertingInputStream;
|
||||||
import org.apache.james.mime4j.parser.ContentHandler;
|
import org.apache.james.mime4j.parser.ContentHandler;
|
||||||
@ -23,7 +22,6 @@ import org.apache.james.mime4j.parser.MimeStreamParser;
|
|||||||
import org.apache.james.mime4j.stream.BodyDescriptor;
|
import org.apache.james.mime4j.stream.BodyDescriptor;
|
||||||
import org.apache.james.mime4j.stream.Field;
|
import org.apache.james.mime4j.stream.Field;
|
||||||
import org.apache.james.mime4j.stream.MimeConfig;
|
import org.apache.james.mime4j.stream.MimeConfig;
|
||||||
import org.apache.james.mime4j.stream.RawField;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.Address;
|
import com.fsck.k9.mail.Address;
|
||||||
import com.fsck.k9.mail.Body;
|
import com.fsck.k9.mail.Body;
|
||||||
@ -345,11 +343,6 @@ public class MimeMessage extends Message {
|
|||||||
setHeader("References", references);
|
setHeader("References", references);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveChanges() throws MessagingException {
|
|
||||||
throw new MessagingException("saveChanges not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Body getBody() {
|
public Body getBody() {
|
||||||
return mBody;
|
return mBody;
|
||||||
@ -593,22 +586,6 @@ public class MimeMessage extends Message {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean toMe() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean ccMe() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean bccMe() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean fromMe() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
return Long.parseLong(mUid); //or maybe .mMessageId?
|
return Long.parseLong(mUid); //or maybe .mMessageId?
|
||||||
}
|
}
|
||||||
|
@ -1478,7 +1478,7 @@ public class ImapStore extends Store {
|
|||||||
fetchFields.add("INTERNALDATE");
|
fetchFields.add("INTERNALDATE");
|
||||||
fetchFields.add("RFC822.SIZE");
|
fetchFields.add("RFC822.SIZE");
|
||||||
fetchFields.add("BODY.PEEK[HEADER.FIELDS (date subject from content-type to cc " +
|
fetchFields.add("BODY.PEEK[HEADER.FIELDS (date subject from content-type to cc " +
|
||||||
"reply-to message-id " + K9.IDENTITY_HEADER + ")]");
|
"reply-to message-id references " + K9.IDENTITY_HEADER + ")]");
|
||||||
}
|
}
|
||||||
if (fp.contains(FetchProfile.Item.STRUCTURE)) {
|
if (fp.contains(FetchProfile.Item.STRUCTURE)) {
|
||||||
fetchFields.add("BODYSTRUCTURE");
|
fetchFields.add("BODYSTRUCTURE");
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -49,7 +49,7 @@ public class LockableDatabase {
|
|||||||
* Workaround exception wrapper used to keep the inner exception generated
|
* Workaround exception wrapper used to keep the inner exception generated
|
||||||
* in a {@link DbCallback}.
|
* in a {@link DbCallback}.
|
||||||
*/
|
*/
|
||||||
protected static class WrappedException extends RuntimeException {
|
public static class WrappedException extends RuntimeException {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -222,6 +222,9 @@ public class GlobalSettings {
|
|||||||
s.put("useBackgroundAsUnreadIndicator", Settings.versions(
|
s.put("useBackgroundAsUnreadIndicator", Settings.versions(
|
||||||
new V(19, new BooleanSetting(true))
|
new V(19, new BooleanSetting(true))
|
||||||
));
|
));
|
||||||
|
s.put("threadedView", Settings.versions(
|
||||||
|
new V(20, new BooleanSetting(true))
|
||||||
|
));
|
||||||
|
|
||||||
SETTINGS = Collections.unmodifiableMap(s);
|
SETTINGS = Collections.unmodifiableMap(s);
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ public class Settings {
|
|||||||
*
|
*
|
||||||
* @see SettingsExporter
|
* @see SettingsExporter
|
||||||
*/
|
*/
|
||||||
public static final int VERSION = 19;
|
public static final int VERSION = 20;
|
||||||
|
|
||||||
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,
|
||||||
|
579
src/com/fsck/k9/provider/EmailProvider.java
Normal file
579
src/com/fsck/k9/provider/EmailProvider.java
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
package com.fsck.k9.provider;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fsck.k9.Account;
|
||||||
|
import com.fsck.k9.Preferences;
|
||||||
|
import com.fsck.k9.helper.StringUtils;
|
||||||
|
import com.fsck.k9.helper.Utility;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
import com.fsck.k9.mail.store.LocalStore;
|
||||||
|
import com.fsck.k9.mail.store.LockableDatabase;
|
||||||
|
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
|
||||||
|
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
|
||||||
|
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||||
|
import com.fsck.k9.search.SqlQueryBuilder;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.CursorWrapper;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content Provider used to display the message list etc.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* For now this content provider is for internal use only. In the future we may allow third-party
|
||||||
|
* apps to access K-9 Mail content using this content provider.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* TODO:
|
||||||
|
* - add support for account list and folder list
|
||||||
|
*/
|
||||||
|
public class EmailProvider extends ContentProvider {
|
||||||
|
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
|
|
||||||
|
public static final String AUTHORITY = "org.k9mail.provider.email";
|
||||||
|
|
||||||
|
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constants that are used for the URI matching.
|
||||||
|
*/
|
||||||
|
private static final int MESSAGE_BASE = 0;
|
||||||
|
private static final int MESSAGES = MESSAGE_BASE;
|
||||||
|
private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
|
||||||
|
//private static final int MESSAGES_THREAD = MESSAGE_BASE + 2;
|
||||||
|
|
||||||
|
|
||||||
|
private static final String MESSAGES_TABLE = "messages";
|
||||||
|
|
||||||
|
private static final String[] MESSAGES_COLUMNS = {
|
||||||
|
MessageColumns.ID,
|
||||||
|
MessageColumns.UID,
|
||||||
|
MessageColumns.INTERNAL_DATE,
|
||||||
|
MessageColumns.SUBJECT,
|
||||||
|
MessageColumns.DATE,
|
||||||
|
MessageColumns.MESSAGE_ID,
|
||||||
|
MessageColumns.SENDER_LIST,
|
||||||
|
MessageColumns.TO_LIST,
|
||||||
|
MessageColumns.CC_LIST,
|
||||||
|
MessageColumns.BCC_LIST,
|
||||||
|
MessageColumns.REPLY_TO_LIST,
|
||||||
|
MessageColumns.FLAGS,
|
||||||
|
MessageColumns.ATTACHMENT_COUNT,
|
||||||
|
MessageColumns.FOLDER_ID,
|
||||||
|
MessageColumns.PREVIEW,
|
||||||
|
MessageColumns.THREAD_ROOT,
|
||||||
|
MessageColumns.THREAD_PARENT,
|
||||||
|
InternalMessageColumns.DELETED,
|
||||||
|
InternalMessageColumns.EMPTY,
|
||||||
|
InternalMessageColumns.TEXT_CONTENT,
|
||||||
|
InternalMessageColumns.HTML_CONTENT,
|
||||||
|
InternalMessageColumns.MIME_TYPE
|
||||||
|
};
|
||||||
|
|
||||||
|
static {
|
||||||
|
UriMatcher matcher = sUriMatcher;
|
||||||
|
|
||||||
|
matcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
|
||||||
|
matcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
|
||||||
|
//matcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SpecialColumns {
|
||||||
|
public static final String ACCOUNT_UUID = "account_uuid";
|
||||||
|
|
||||||
|
public static final String FOLDER_NAME = "name";
|
||||||
|
public static final String INTEGRATE = "integrate";
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MessageColumns {
|
||||||
|
public static final String ID = "id";
|
||||||
|
public static final String UID = "uid";
|
||||||
|
public static final String INTERNAL_DATE = "internal_date";
|
||||||
|
public static final String SUBJECT = "subject";
|
||||||
|
public static final String DATE = "date";
|
||||||
|
public static final String MESSAGE_ID = "message_id";
|
||||||
|
public static final String SENDER_LIST = "sender_list";
|
||||||
|
public static final String TO_LIST = "to_list";
|
||||||
|
public static final String CC_LIST = "cc_list";
|
||||||
|
public static final String BCC_LIST = "bcc_list";
|
||||||
|
public static final String REPLY_TO_LIST = "reply_to_list";
|
||||||
|
public static final String FLAGS = "flags";
|
||||||
|
public static final String ATTACHMENT_COUNT = "attachment_count";
|
||||||
|
public static final String FOLDER_ID = "folder_id";
|
||||||
|
public static final String PREVIEW = "preview";
|
||||||
|
public static final String THREAD_ROOT = "thread_root";
|
||||||
|
public static final String THREAD_PARENT = "thread_parent";
|
||||||
|
public static final String THREAD_COUNT = "thread_count";
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface InternalMessageColumns extends MessageColumns {
|
||||||
|
public static final String DELETED = "deleted";
|
||||||
|
public static final String EMPTY = "empty";
|
||||||
|
public static final String TEXT_CONTENT = "text_content";
|
||||||
|
public static final String HTML_CONTENT = "html_content";
|
||||||
|
public static final String MIME_TYPE = "mime_type";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Preferences mPreferences;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType(Uri uri) {
|
||||||
|
throw new RuntimeException("not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||||
|
String sortOrder) {
|
||||||
|
|
||||||
|
int match = sUriMatcher.match(uri);
|
||||||
|
if (match < 0) {
|
||||||
|
throw new IllegalArgumentException("Unknown URI: " + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentResolver contentResolver = getContext().getContentResolver();
|
||||||
|
Cursor cursor = null;
|
||||||
|
switch (match) {
|
||||||
|
case MESSAGES:
|
||||||
|
case MESSAGES_THREADED: {
|
||||||
|
List<String> segments = uri.getPathSegments();
|
||||||
|
String accountUuid = segments.get(1);
|
||||||
|
|
||||||
|
List<String> dbColumnNames = new ArrayList<String>(projection.length);
|
||||||
|
Map<String, String> specialColumns = new HashMap<String, String>();
|
||||||
|
for (String columnName : projection) {
|
||||||
|
if (SpecialColumns.ACCOUNT_UUID.equals(columnName)) {
|
||||||
|
specialColumns.put(SpecialColumns.ACCOUNT_UUID, accountUuid);
|
||||||
|
} else {
|
||||||
|
dbColumnNames.add(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] dbProjection = dbColumnNames.toArray(new String[0]);
|
||||||
|
|
||||||
|
if (match == MESSAGES) {
|
||||||
|
cursor = getMessages(accountUuid, dbProjection, selection, selectionArgs,
|
||||||
|
sortOrder);
|
||||||
|
} else if (match == MESSAGES_THREADED) {
|
||||||
|
cursor = getThreadedMessages(accountUuid, dbProjection, selection,
|
||||||
|
selectionArgs, sortOrder);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.setNotificationUri(contentResolver, uri);
|
||||||
|
|
||||||
|
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
|
||||||
|
specialColumns);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||||
|
throw new RuntimeException("not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
|
throw new RuntimeException("not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
|
throw new RuntimeException("not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Cursor getMessages(String accountUuid, final String[] projection,
|
||||||
|
final String selection, final String[] selectionArgs, final String sortOrder) {
|
||||||
|
|
||||||
|
Account account = getAccount(accountUuid);
|
||||||
|
LockableDatabase database = getDatabase(account);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return database.execute(false, new DbCallback<Cursor>() {
|
||||||
|
@Override
|
||||||
|
public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
|
||||||
|
UnavailableStorageException {
|
||||||
|
|
||||||
|
String where;
|
||||||
|
if (StringUtils.isNullOrEmpty(selection)) {
|
||||||
|
where = InternalMessageColumns.DELETED + "=0 AND (" +
|
||||||
|
InternalMessageColumns.EMPTY + " IS NULL OR " +
|
||||||
|
InternalMessageColumns.EMPTY + "!=1)";
|
||||||
|
} else {
|
||||||
|
where = "(" + selection + ") AND " +
|
||||||
|
InternalMessageColumns.DELETED + "=0 AND (" +
|
||||||
|
InternalMessageColumns.EMPTY + " IS NULL OR " +
|
||||||
|
InternalMessageColumns.EMPTY + "!=1)";
|
||||||
|
}
|
||||||
|
|
||||||
|
final Cursor cursor;
|
||||||
|
//TODO: check projection and selection for folder columns
|
||||||
|
if (Utility.arrayContains(projection, SpecialColumns.FOLDER_NAME)) {
|
||||||
|
StringBuilder query = new StringBuilder();
|
||||||
|
query.append("SELECT ");
|
||||||
|
boolean first = true;
|
||||||
|
for (String columnName : projection) {
|
||||||
|
if (!first) {
|
||||||
|
query.append(",");
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MessageColumns.ID.equals(columnName)) {
|
||||||
|
query.append("m.");
|
||||||
|
query.append(MessageColumns.ID);
|
||||||
|
query.append(" AS ");
|
||||||
|
query.append(MessageColumns.ID);
|
||||||
|
} else {
|
||||||
|
query.append(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query.append(" FROM messages m " +
|
||||||
|
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
|
||||||
|
"WHERE ");
|
||||||
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
||||||
|
"m.", where));
|
||||||
|
query.append(" ORDER BY ");
|
||||||
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
||||||
|
"m.", sortOrder));
|
||||||
|
|
||||||
|
cursor = db.rawQuery(query.toString(), selectionArgs);
|
||||||
|
} else {
|
||||||
|
cursor = db.query(MESSAGES_TABLE, projection, where, selectionArgs, null,
|
||||||
|
null, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (UnavailableStorageException e) {
|
||||||
|
throw new RuntimeException("Storage not available", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Cursor getThreadedMessages(String accountUuid, final String[] projection,
|
||||||
|
final String selection, final String[] selectionArgs, final String sortOrder) {
|
||||||
|
|
||||||
|
Account account = getAccount(accountUuid);
|
||||||
|
LockableDatabase database = getDatabase(account);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return database.execute(false, new DbCallback<Cursor>() {
|
||||||
|
@Override
|
||||||
|
public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
|
||||||
|
UnavailableStorageException {
|
||||||
|
|
||||||
|
StringBuilder query = new StringBuilder();
|
||||||
|
query.append("SELECT ");
|
||||||
|
boolean first = true;
|
||||||
|
for (String columnName : projection) {
|
||||||
|
if (!first) {
|
||||||
|
query.append(",");
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MessageColumns.DATE.equals(columnName)) {
|
||||||
|
query.append("MAX(m.date) AS " + MessageColumns.DATE);
|
||||||
|
} else if (MessageColumns.THREAD_COUNT.equals(columnName)) {
|
||||||
|
query.append("COUNT(h.id) AS " + MessageColumns.THREAD_COUNT);
|
||||||
|
} else if (SpecialColumns.FOLDER_NAME.equals(columnName)) {
|
||||||
|
query.append("f." + SpecialColumns.FOLDER_NAME + " AS " +
|
||||||
|
SpecialColumns.FOLDER_NAME);
|
||||||
|
} else if (SpecialColumns.INTEGRATE.equals(columnName)) {
|
||||||
|
query.append("f." + SpecialColumns.INTEGRATE + " AS " +
|
||||||
|
SpecialColumns.INTEGRATE);
|
||||||
|
} else {
|
||||||
|
query.append("m.");
|
||||||
|
query.append(columnName);
|
||||||
|
query.append(" AS ");
|
||||||
|
query.append(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query.append(
|
||||||
|
" FROM messages h JOIN messages m " +
|
||||||
|
"ON (h.id = m.thread_root OR h.id = m.id) ");
|
||||||
|
|
||||||
|
//TODO: check projection and selection for folder columns
|
||||||
|
if (Utility.arrayContains(projection, SpecialColumns.FOLDER_NAME)) {
|
||||||
|
query.append("LEFT JOIN folders f ON (m.folder_id = f.id) ");
|
||||||
|
}
|
||||||
|
|
||||||
|
query.append(
|
||||||
|
"WHERE " +
|
||||||
|
"(h.deleted = 0 AND m.deleted = 0 AND " +
|
||||||
|
"(m.empty IS NULL OR m.empty != 1) AND " +
|
||||||
|
"h.thread_root IS NULL) ");
|
||||||
|
|
||||||
|
if (!StringUtils.isNullOrEmpty(selection)) {
|
||||||
|
query.append("AND (");
|
||||||
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
||||||
|
"h.", selection));
|
||||||
|
query.append(") ");
|
||||||
|
}
|
||||||
|
|
||||||
|
query.append("GROUP BY h.id");
|
||||||
|
|
||||||
|
if (!StringUtils.isNullOrEmpty(sortOrder)) {
|
||||||
|
query.append(" ORDER BY ");
|
||||||
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
||||||
|
"m.", sortOrder));
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.rawQuery(query.toString(), selectionArgs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (UnavailableStorageException e) {
|
||||||
|
throw new RuntimeException("Storage not available", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Account getAccount(String accountUuid) {
|
||||||
|
if (mPreferences == null) {
|
||||||
|
Context appContext = getContext().getApplicationContext();
|
||||||
|
mPreferences = Preferences.getPreferences(appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
Account account = mPreferences.getAccount(accountUuid);
|
||||||
|
|
||||||
|
if (account == null) {
|
||||||
|
throw new IllegalArgumentException("Unknown account: " + accountUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LockableDatabase getDatabase(Account account) {
|
||||||
|
LocalStore localStore;
|
||||||
|
try {
|
||||||
|
localStore = account.getLocalStore();
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
throw new RuntimeException("Couldn't get LocalStore", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return localStore.getDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is needed to make {@link CursorAdapter} work with our database schema.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* {@code CursorAdapter} requires a column named {@code "_id"} containing a stable id. We use
|
||||||
|
* the column name {@code "id"} as primary key in all our tables. So this {@link CursorWrapper}
|
||||||
|
* maps all queries for {@code "_id"} to {@code "id"}.
|
||||||
|
* </p><p>
|
||||||
|
* Please note that this only works for the returned {@code Cursor}. When querying the content
|
||||||
|
* provider you still need to use {@link MessageColumns#ID}.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
static class IdTrickeryCursor extends CursorWrapper {
|
||||||
|
public IdTrickeryCursor(Cursor cursor) {
|
||||||
|
super(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndex(String columnName) {
|
||||||
|
if ("_id".equals(columnName)) {
|
||||||
|
return super.getColumnIndex("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getColumnIndex(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndexOrThrow(String columnName) {
|
||||||
|
if ("_id".equals(columnName)) {
|
||||||
|
return super.getColumnIndexOrThrow("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getColumnIndexOrThrow(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SpecialColumnsCursor extends CursorWrapper {
|
||||||
|
private int[] mColumnMapping;
|
||||||
|
private String[] mSpecialColumnValues;
|
||||||
|
private String[] mColumnNames;
|
||||||
|
|
||||||
|
public SpecialColumnsCursor(Cursor cursor, String[] allColumnNames,
|
||||||
|
Map<String, String> specialColumns) {
|
||||||
|
super(cursor);
|
||||||
|
|
||||||
|
mColumnNames = allColumnNames;
|
||||||
|
mColumnMapping = new int[allColumnNames.length];
|
||||||
|
mSpecialColumnValues = new String[specialColumns.size()];
|
||||||
|
for (int i = 0, columnIndex = 0, specialColumnCount = 0, len = allColumnNames.length;
|
||||||
|
i < len; i++) {
|
||||||
|
|
||||||
|
String columnName = allColumnNames[i];
|
||||||
|
|
||||||
|
if (specialColumns.containsKey(columnName)) {
|
||||||
|
// This is a special column name, so save the value in mSpecialColumnValues
|
||||||
|
mSpecialColumnValues[specialColumnCount] = specialColumns.get(columnName);
|
||||||
|
|
||||||
|
// Write the index into mSpecialColumnValues negated into mColumnMapping
|
||||||
|
mColumnMapping[i] = -(specialColumnCount + 1);
|
||||||
|
specialColumnCount++;
|
||||||
|
} else {
|
||||||
|
mColumnMapping[i] = columnIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlob(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getBlob(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return mColumnMapping.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndex(String columnName) {
|
||||||
|
for (int i = 0, len = mColumnNames.length; i < len; i++) {
|
||||||
|
if (mColumnNames[i].equals(columnName)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getColumnIndex(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
|
||||||
|
int index = getColumnIndex(columnName);
|
||||||
|
|
||||||
|
if (index == -1) {
|
||||||
|
throw new IllegalArgumentException("Unknown column name");
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int columnIndex) {
|
||||||
|
return mColumnNames[columnIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getColumnNames() {
|
||||||
|
return mColumnNames.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDouble(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getDouble(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloat(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getFloat(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getInt(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getLong(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getShort(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getShort(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
return mSpecialColumnValues[-realColumnIndex - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getString(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(11)
|
||||||
|
@Override
|
||||||
|
public int getType(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
return FIELD_TYPE_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getType(realColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNull(int columnIndex) {
|
||||||
|
int realColumnIndex = mColumnMapping[columnIndex];
|
||||||
|
if (realColumnIndex < 0) {
|
||||||
|
return (mSpecialColumnValues[-realColumnIndex - 1] == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.isNull(realColumnIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,23 +21,22 @@ import com.fsck.k9.Account;
|
|||||||
import com.fsck.k9.AccountStats;
|
import com.fsck.k9.AccountStats;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
import com.fsck.k9.SearchAccount;
|
|
||||||
import com.fsck.k9.activity.FolderInfoHolder;
|
import com.fsck.k9.activity.FolderInfoHolder;
|
||||||
import com.fsck.k9.activity.MessageInfoHolder;
|
import com.fsck.k9.activity.MessageInfoHolder;
|
||||||
import com.fsck.k9.activity.MessageList;
|
|
||||||
import com.fsck.k9.controller.MessagingController;
|
import com.fsck.k9.controller.MessagingController;
|
||||||
import com.fsck.k9.controller.MessagingListener;
|
import com.fsck.k9.controller.MessagingListener;
|
||||||
import com.fsck.k9.fragment.MessageListFragment;
|
|
||||||
import com.fsck.k9.helper.MessageHelper;
|
import com.fsck.k9.helper.MessageHelper;
|
||||||
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.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.store.LocalStore;
|
import com.fsck.k9.mail.store.LocalStore;
|
||||||
|
import com.fsck.k9.search.SearchAccount;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
@ -302,14 +301,13 @@ public class MessageProvider extends ContentProvider {
|
|||||||
final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext());
|
final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext());
|
||||||
final MessagingController msgController = MessagingController.getInstance(K9.app);
|
final MessagingController msgController = MessagingController.getInstance(K9.app);
|
||||||
|
|
||||||
msgController.searchLocalMessages(integratedInboxAccount, null,
|
msgController.searchLocalMessages(integratedInboxAccount.getRelatedSearch(),
|
||||||
new MesssageInfoHolderRetrieverListener(queue));
|
new MesssageInfoHolderRetrieverListener(queue));
|
||||||
|
|
||||||
final List<MessageInfoHolder> holders = queue.take();
|
final List<MessageInfoHolder> holders = queue.take();
|
||||||
|
|
||||||
// TODO add sort order parameter
|
// TODO add sort order parameter
|
||||||
Collections.sort(holders, new MessageListFragment.ReverseComparator<MessageInfoHolder>(
|
Collections.sort(holders, new ReverseDateComparator());
|
||||||
new MessageListFragment.DateComparator()));
|
|
||||||
|
|
||||||
final String[] projectionToUse;
|
final String[] projectionToUse;
|
||||||
if (projection == null) {
|
if (projection == null) {
|
||||||
@ -1025,7 +1023,8 @@ public class MessageProvider extends ContentProvider {
|
|||||||
|
|
||||||
// launch command to delete the message
|
// launch command to delete the message
|
||||||
if ((myAccount != null) && (msg != null)) {
|
if ((myAccount != null) && (msg != null)) {
|
||||||
MessagingController.getInstance(K9.app).deleteMessages(new Message[] { msg }, null);
|
MessagingController controller = MessagingController.getInstance(K9.app);
|
||||||
|
controller.deleteMessages(Collections.singletonList(msg), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME return the actual number of deleted messages
|
// FIXME return the actual number of deleted messages
|
||||||
@ -1123,4 +1122,16 @@ public class MessageProvider extends ContentProvider {
|
|||||||
mUriMatcher.addURI(AUTHORITY, handler.getPath(), code);
|
mUriMatcher.addURI(AUTHORITY, handler.getPath(), code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ReverseDateComparator implements Comparator<MessageInfoHolder> {
|
||||||
|
@Override
|
||||||
|
public int compare(MessageInfoHolder object2, MessageInfoHolder object1) {
|
||||||
|
if (object1.compareDate == null) {
|
||||||
|
return (object2.compareDate == null ? 0 : 1);
|
||||||
|
} else if (object2.compareDate == null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return object1.compareDate.compareTo(object2.compareDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import com.fsck.k9.R;
|
|||||||
import com.fsck.k9.activity.UnreadWidgetConfiguration;
|
import com.fsck.k9.activity.UnreadWidgetConfiguration;
|
||||||
import com.fsck.k9.activity.FolderList;
|
import com.fsck.k9.activity.FolderList;
|
||||||
import com.fsck.k9.activity.MessageList;
|
import com.fsck.k9.activity.MessageList;
|
||||||
|
import com.fsck.k9.search.LocalSearch;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.appwidget.AppWidgetManager;
|
import android.appwidget.AppWidgetManager;
|
||||||
@ -62,8 +63,11 @@ public class UnreadWidgetProvider extends AppWidgetProvider {
|
|||||||
clickIntent = FolderList.actionHandleAccountIntent(context, account, null,
|
clickIntent = FolderList.actionHandleAccountIntent(context, account, null,
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
clickIntent = MessageList.actionHandleFolderIntent(context, account,
|
LocalSearch search = new LocalSearch(account.getAutoExpandFolderName());
|
||||||
account.getAutoExpandFolderName());
|
search.addAllowedFolder(account.getAutoExpandFolderName());
|
||||||
|
search.addAccountUuid(account.getUuid());
|
||||||
|
clickIntent = MessageList.intentDisplaySearch(context, search, false, true,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||||
}
|
}
|
||||||
|
431
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
431
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.fsck.k9.search.SearchSpecification.Attribute;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class stores search conditions. It's basically a boolean expression binary tree.
|
||||||
|
* The output will be SQL queries ( obtained by traversing inorder ).
|
||||||
|
*
|
||||||
|
* TODO removing conditions from the tree
|
||||||
|
* TODO implement NOT as a node again
|
||||||
|
*/
|
||||||
|
public class ConditionsTreeNode implements Parcelable {
|
||||||
|
|
||||||
|
public enum Operator {
|
||||||
|
AND, OR, CONDITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionsTreeNode mLeft;
|
||||||
|
public ConditionsTreeNode mRight;
|
||||||
|
public ConditionsTreeNode mParent;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If mValue isn't CONDITION then mCondition contains a real
|
||||||
|
* condition, otherwise it's null.
|
||||||
|
*/
|
||||||
|
public Operator mValue;
|
||||||
|
public SearchCondition mCondition;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used for storing and retrieving the tree to/from the database.
|
||||||
|
* The algorithm is called "modified preorder tree traversal".
|
||||||
|
*/
|
||||||
|
public int mLeftMPTTMarker;
|
||||||
|
public int mRightMPTTMarker;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Static Helpers to restore a tree from a database cursor
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Builds a condition tree starting from a database cursor. The cursor
|
||||||
|
* should point to rows representing the nodes of the tree.
|
||||||
|
*
|
||||||
|
* @param cursor Cursor pointing to the first of a bunch or rows. Each rows
|
||||||
|
* should contains 1 tree node.
|
||||||
|
* @return A condition tree.
|
||||||
|
*/
|
||||||
|
public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
|
||||||
|
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||||
|
ConditionsTreeNode tmp = null;
|
||||||
|
|
||||||
|
// root node
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
tmp = buildNodeFromRow(cursor);
|
||||||
|
stack.push(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// other nodes
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
tmp = buildNodeFromRow(cursor);
|
||||||
|
if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker) {
|
||||||
|
stack.peek().mLeft = tmp;
|
||||||
|
stack.push(tmp);
|
||||||
|
} else {
|
||||||
|
while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
stack.peek().mRight = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a single database row to a single condition node.
|
||||||
|
*
|
||||||
|
* @param cursor Cursor pointing to the row we want to convert.
|
||||||
|
* @return A single ConditionsTreeNode
|
||||||
|
*/
|
||||||
|
private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
|
||||||
|
ConditionsTreeNode result = null;
|
||||||
|
SearchCondition condition = null;
|
||||||
|
|
||||||
|
Operator tmpValue = ConditionsTreeNode.Operator.valueOf(cursor.getString(5));
|
||||||
|
|
||||||
|
if (tmpValue == Operator.CONDITION) {
|
||||||
|
condition = new SearchCondition(Searchfield.valueOf(cursor.getString(0)),
|
||||||
|
Attribute.valueOf(cursor.getString(2)), cursor.getString(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new ConditionsTreeNode(condition);
|
||||||
|
result.mValue = tmpValue;
|
||||||
|
result.mLeftMPTTMarker = cursor.getInt(3);
|
||||||
|
result.mRightMPTTMarker = cursor.getInt(4);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Constructors
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
public ConditionsTreeNode(SearchCondition condition) {
|
||||||
|
mParent = null;
|
||||||
|
mCondition = condition;
|
||||||
|
mValue = Operator.CONDITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionsTreeNode(ConditionsTreeNode parent, Operator op) {
|
||||||
|
mParent = parent;
|
||||||
|
mValue = op;
|
||||||
|
mCondition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ ConditionsTreeNode cloneTree() {
|
||||||
|
if (mParent != null) {
|
||||||
|
throw new IllegalStateException("Can't call cloneTree() for a non-root node");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionsTreeNode copy = new ConditionsTreeNode(mCondition.clone());
|
||||||
|
|
||||||
|
copy.mLeftMPTTMarker = mLeftMPTTMarker;
|
||||||
|
copy.mRightMPTTMarker = mRightMPTTMarker;
|
||||||
|
|
||||||
|
copy.mLeft = (mLeft == null) ? null : mLeft.cloneNode(copy);
|
||||||
|
copy.mRight = (mRight == null) ? null : mRight.cloneNode(copy);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConditionsTreeNode cloneNode(ConditionsTreeNode parent) {
|
||||||
|
ConditionsTreeNode copy = new ConditionsTreeNode(parent, mValue);
|
||||||
|
|
||||||
|
copy.mCondition = mCondition.clone();
|
||||||
|
copy.mLeftMPTTMarker = mLeftMPTTMarker;
|
||||||
|
copy.mRightMPTTMarker = mRightMPTTMarker;
|
||||||
|
|
||||||
|
copy.mLeft = (mLeft == null) ? null : mLeft.cloneNode(copy);
|
||||||
|
copy.mRight = (mRight == null) ? null : mRight.cloneNode(copy);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public modifiers
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Adds the expression as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param expr Expression to 'AND' with.
|
||||||
|
* @return New top AND node.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
|
||||||
|
return add(expr, Operator.AND);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method.
|
||||||
|
* Adds the provided condition as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'AND' with.
|
||||||
|
* @return New top AND node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return and(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the expression as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param expr Expression to 'OR' with.
|
||||||
|
* @return New top OR node.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
|
||||||
|
return add(expr, Operator.OR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method.
|
||||||
|
* Adds the provided condition as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'OR' with.
|
||||||
|
* @return New top OR node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return or(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This applies the MPTT labeling to the subtree of which this node
|
||||||
|
* is the root node.
|
||||||
|
*
|
||||||
|
* For a description on MPTT see:
|
||||||
|
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||||
|
*/
|
||||||
|
public void applyMPTTLabel() {
|
||||||
|
applyMPTTLabel(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public accessors
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Returns the condition stored in this node.
|
||||||
|
* @return Condition stored in the node.
|
||||||
|
*/
|
||||||
|
public SearchCondition getCondition() {
|
||||||
|
return mCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a set of all the leaves in the tree.
|
||||||
|
* @return Set of all the leaves.
|
||||||
|
*/
|
||||||
|
public HashSet<ConditionsTreeNode> getLeafSet() {
|
||||||
|
HashSet<ConditionsTreeNode> leafSet = new HashSet<ConditionsTreeNode>();
|
||||||
|
return getLeafSet(leafSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all the nodes in the subtree of which this node
|
||||||
|
* is the root. The list contains the nodes in a pre traversal order.
|
||||||
|
*
|
||||||
|
* @return List of all nodes in subtree in preorder.
|
||||||
|
*/
|
||||||
|
public List<ConditionsTreeNode> preorder() {
|
||||||
|
ArrayList<ConditionsTreeNode> result = new ArrayList<ConditionsTreeNode>();
|
||||||
|
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||||
|
stack.push(this);
|
||||||
|
|
||||||
|
while (!stack.isEmpty()) {
|
||||||
|
ConditionsTreeNode current = stack.pop();
|
||||||
|
|
||||||
|
if (current.mLeft != null) {
|
||||||
|
stack.push(current.mLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.mRight != null) {
|
||||||
|
stack.push(current.mRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Private class logic
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Adds two new ConditionTreeNodes, one for the operator and one for the
|
||||||
|
* new condition. The current node will end up on the same level as the
|
||||||
|
* one provided in the arguments, they will be siblings. Their common
|
||||||
|
* parent node will be one containing the operator provided in the arguments.
|
||||||
|
* The method will update all the required references so the tree ends up in
|
||||||
|
* a valid state.
|
||||||
|
*
|
||||||
|
* This method only supports node arguments with a null parent node.
|
||||||
|
*
|
||||||
|
* @param Node to add.
|
||||||
|
* @param Operator that will connect the new node with this one.
|
||||||
|
* @return New parent node, containing the operator.
|
||||||
|
* @throws Exception Throws when the provided new node does not have a null parent.
|
||||||
|
*/
|
||||||
|
private ConditionsTreeNode add(ConditionsTreeNode node, Operator op) throws Exception {
|
||||||
|
if (node.mParent != null) {
|
||||||
|
throw new Exception("Can only add new expressions from root node down.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
|
||||||
|
tmpNode.mLeft = this;
|
||||||
|
tmpNode.mRight = node;
|
||||||
|
|
||||||
|
if (mParent != null) {
|
||||||
|
mParent.updateChild(this, tmpNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mParent = tmpNode;
|
||||||
|
node.mParent = tmpNode;
|
||||||
|
|
||||||
|
return tmpNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that replaces a child of the current node with a new node.
|
||||||
|
* If the provided old child node was the left one, left will be replaced with
|
||||||
|
* the new one. Same goes for the right one.
|
||||||
|
*
|
||||||
|
* @param oldChild Old child node to be replaced.
|
||||||
|
* @param newChild New child node.
|
||||||
|
*/
|
||||||
|
private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
|
||||||
|
// we can compare objects id's because this is the desired behaviour in this case
|
||||||
|
if (mLeft == oldChild) {
|
||||||
|
mLeft = newChild;
|
||||||
|
} else if (mRight == oldChild) {
|
||||||
|
mRight = newChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive function to gather all the leaves in the subtree of which
|
||||||
|
* this node is the root.
|
||||||
|
*
|
||||||
|
* @param leafSet Leafset that's being built.
|
||||||
|
* @return Set of leaves being completed.
|
||||||
|
*/
|
||||||
|
private HashSet<ConditionsTreeNode> getLeafSet(HashSet<ConditionsTreeNode> leafSet) {
|
||||||
|
if (mLeft == null && mRight == null) {
|
||||||
|
// if we ended up in a leaf, add ourself and return
|
||||||
|
leafSet.add(this);
|
||||||
|
return leafSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we didn't end up in a leaf
|
||||||
|
if (mLeft != null) {
|
||||||
|
mLeft.getLeafSet(leafSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRight != null) {
|
||||||
|
mRight.getLeafSet(leafSet);
|
||||||
|
}
|
||||||
|
return leafSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This applies the MPTT labeling to the subtree of which this node
|
||||||
|
* is the root node.
|
||||||
|
*
|
||||||
|
* For a description on MPTT see:
|
||||||
|
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||||
|
*/
|
||||||
|
private int applyMPTTLabel(int label) {
|
||||||
|
mLeftMPTTMarker = label;
|
||||||
|
|
||||||
|
if (mLeft != null) {
|
||||||
|
label = mLeft.applyMPTTLabel(label += 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRight != null) {
|
||||||
|
label = mRight.applyMPTTLabel(label += 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
++label;
|
||||||
|
mRightMPTTMarker = label;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Parcelable
|
||||||
|
//
|
||||||
|
// This whole class has to be parcelable because it's passed
|
||||||
|
// on through intents.
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mValue.ordinal());
|
||||||
|
dest.writeParcelable(mCondition, flags);
|
||||||
|
dest.writeParcelable(mLeft, flags);
|
||||||
|
dest.writeParcelable(mRight, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<ConditionsTreeNode> CREATOR =
|
||||||
|
new Parcelable.Creator<ConditionsTreeNode>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionsTreeNode createFromParcel(Parcel in) {
|
||||||
|
return new ConditionsTreeNode(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionsTreeNode[] newArray(int size) {
|
||||||
|
return new ConditionsTreeNode[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private ConditionsTreeNode(Parcel in) {
|
||||||
|
mValue = Operator.values()[in.readInt()];
|
||||||
|
mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||||
|
mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||||
|
mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||||
|
mParent = null;
|
||||||
|
|
||||||
|
if (mLeft != null) {
|
||||||
|
mLeft.mParent = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRight != null) {
|
||||||
|
mRight.mParent = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
428
src/com/fsck/k9/search/LocalSearch.java
Normal file
428
src/com/fsck/k9/search/LocalSearch.java
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a local search.
|
||||||
|
*
|
||||||
|
* Removing conditions could be done through matching there unique id in the leafset and then
|
||||||
|
* removing them from the tree.
|
||||||
|
*
|
||||||
|
* TODO implement a complete addAllowedFolder method
|
||||||
|
* TODO conflicting conditions check on add
|
||||||
|
* TODO duplicate condition checking?
|
||||||
|
* TODO assign each node a unique id that's used to retrieve it from the leaveset and remove.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LocalSearch implements SearchSpecification {
|
||||||
|
|
||||||
|
private String mName;
|
||||||
|
private boolean mPredefined;
|
||||||
|
private boolean mManualSearch = false;
|
||||||
|
|
||||||
|
// since the uuid isn't in the message table it's not in the tree neither
|
||||||
|
private HashSet<String> mAccountUuids = new HashSet<String>();
|
||||||
|
private ConditionsTreeNode mConditions = null;
|
||||||
|
private HashSet<ConditionsTreeNode> mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Constructors
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Use this only if the search won't be saved. Saved searches need
|
||||||
|
* a name!
|
||||||
|
*/
|
||||||
|
public LocalSearch() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public LocalSearch(String name) {
|
||||||
|
this.mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this constructor when you know what you'r doing. Normally it's only used
|
||||||
|
* when restoring these search objects from the database.
|
||||||
|
*
|
||||||
|
* @param name Name of the search
|
||||||
|
* @param searchConditions SearchConditions, may contains flags and folders
|
||||||
|
* @param accounts Relative Account's uuid's
|
||||||
|
* @param predefined Is this a predefined search or a user created one?
|
||||||
|
*/
|
||||||
|
protected LocalSearch(String name, ConditionsTreeNode searchConditions,
|
||||||
|
String accounts, boolean predefined) {
|
||||||
|
this(name);
|
||||||
|
mConditions = searchConditions;
|
||||||
|
mPredefined = predefined;
|
||||||
|
mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||||
|
if (mConditions != null) {
|
||||||
|
mLeafSet.addAll(mConditions.getLeafSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize accounts
|
||||||
|
if (accounts != null) {
|
||||||
|
for (String account : accounts.split(",")) {
|
||||||
|
mAccountUuids.add(account);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// impossible but still not unrecoverable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalSearch clone() {
|
||||||
|
ConditionsTreeNode conditions = (mConditions == null) ? null : mConditions.cloneTree();
|
||||||
|
|
||||||
|
LocalSearch copy = new LocalSearch(mName, conditions, null, mPredefined);
|
||||||
|
copy.mManualSearch = mManualSearch;
|
||||||
|
copy.mAccountUuids = new HashSet<String>(mAccountUuids);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public manipulation methods
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* Sets the name of the saved search. If one existed it will
|
||||||
|
* be overwritten.
|
||||||
|
*
|
||||||
|
* @param name Name to be set.
|
||||||
|
*/
|
||||||
|
public void setName(String name) {
|
||||||
|
this.mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new account to the search. When no accounts are
|
||||||
|
* added manually we search all accounts on the device.
|
||||||
|
*
|
||||||
|
* @param uuid Uuid of the account to be added.
|
||||||
|
*/
|
||||||
|
public void addAccountUuid(String uuid) {
|
||||||
|
if (uuid.equals(ALL_ACCOUNTS)) {
|
||||||
|
mAccountUuids.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mAccountUuids.add(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds all the account uuids in the provided array to
|
||||||
|
* be matched by the seach.
|
||||||
|
*
|
||||||
|
* @param accountUuids
|
||||||
|
*/
|
||||||
|
public void addAccountUuids(String[] accountUuids) {
|
||||||
|
for (String acc : accountUuids) {
|
||||||
|
addAccountUuid(acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an account UUID from the current search.
|
||||||
|
*
|
||||||
|
* @param uuid Account UUID to remove.
|
||||||
|
* @return True if removed, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean removeAccountUuid(String uuid) {
|
||||||
|
return mAccountUuids.remove(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided node as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param field Message table field to match against.
|
||||||
|
* @param string Value to look for.
|
||||||
|
* @param contains Attribute to use when matching.
|
||||||
|
*
|
||||||
|
* @throws IllegalConditionException
|
||||||
|
*/
|
||||||
|
public void and(Searchfield field, String value, Attribute attribute) {
|
||||||
|
and(new SearchCondition(field, attribute, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided condition as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'AND' with.
|
||||||
|
* @return New top AND node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return and(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided node as the second argument of an AND
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param node Node to 'AND' with.
|
||||||
|
* @return New top AND node, new root.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode and(ConditionsTreeNode node) throws Exception {
|
||||||
|
mLeafSet.addAll(node.getLeafSet());
|
||||||
|
|
||||||
|
if (mConditions == null) {
|
||||||
|
mConditions = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
mConditions = mConditions.and(node);
|
||||||
|
return mConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided condition as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param condition Condition to 'OR' with.
|
||||||
|
* @return New top OR node, new root.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(SearchCondition condition) {
|
||||||
|
try {
|
||||||
|
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||||
|
return or(tmp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// impossible
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided node as the second argument of an OR
|
||||||
|
* clause to this node.
|
||||||
|
*
|
||||||
|
* @param node Node to 'OR' with.
|
||||||
|
* @return New top OR node, new root.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode or(ConditionsTreeNode node) throws Exception {
|
||||||
|
mLeafSet.addAll(node.getLeafSet());
|
||||||
|
|
||||||
|
if (mConditions == null) {
|
||||||
|
mConditions = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
mConditions = mConditions.or(node);
|
||||||
|
return mConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the flags to this node as required flags. The
|
||||||
|
* provided flags will be combined using AND with the root.
|
||||||
|
*
|
||||||
|
* @param requiredFlags Array of required flags.
|
||||||
|
*/
|
||||||
|
public void allRequiredFlags(Flag[] requiredFlags) {
|
||||||
|
if (requiredFlags != null) {
|
||||||
|
for (Flag f : requiredFlags) {
|
||||||
|
and(new SearchCondition(Searchfield.FLAG, Attribute.CONTAINS, f.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the flags to this node as forbidden flags. The
|
||||||
|
* provided flags will be combined using AND with the root.
|
||||||
|
*
|
||||||
|
* @param forbiddenFlags Array of forbidden flags.
|
||||||
|
*/
|
||||||
|
public void allForbiddenFlags(Flag[] forbiddenFlags) {
|
||||||
|
if (forbiddenFlags != null) {
|
||||||
|
for (Flag f : forbiddenFlags) {
|
||||||
|
and(new SearchCondition(Searchfield.FLAG, Attribute.NOT_CONTAINS, f.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* FOR NOW: And the folder with the root.
|
||||||
|
*
|
||||||
|
* Add the folder as another folder to search in. The folder
|
||||||
|
* will be added AND to the root if no 'folder subtree' was found.
|
||||||
|
* Otherwise the folder will be added OR to that tree.
|
||||||
|
*
|
||||||
|
* @param name Name of the folder to add.
|
||||||
|
*/
|
||||||
|
public void addAllowedFolder(String name) {
|
||||||
|
/*
|
||||||
|
* TODO find folder sub-tree
|
||||||
|
* - do and on root of it & rest of search
|
||||||
|
* - do or between folder nodes
|
||||||
|
*/
|
||||||
|
mConditions = and(new SearchCondition(Searchfield.FOLDER, Attribute.EQUALS, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO make this more advanced!
|
||||||
|
* This is a temporarely solution that does NOT WORK for
|
||||||
|
* real searches because of possible extra conditions to a folder requirement.
|
||||||
|
*/
|
||||||
|
public List<String> getFolderNames() {
|
||||||
|
ArrayList<String> results = new ArrayList<String>();
|
||||||
|
for (ConditionsTreeNode node : mLeafSet) {
|
||||||
|
if (node.mCondition.field == Searchfield.FOLDER &&
|
||||||
|
node.mCondition.attribute == Attribute.EQUALS) {
|
||||||
|
results.add(node.mCondition.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the leafset of the related condition tree.
|
||||||
|
*
|
||||||
|
* @return All the leaf conditions as a set.
|
||||||
|
*/
|
||||||
|
public Set<ConditionsTreeNode> getLeafSet() {
|
||||||
|
return mLeafSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Public accesor methods
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* TODO THIS HAS TO GO!!!!
|
||||||
|
* very dirty fix for remotesearch support atm
|
||||||
|
*/
|
||||||
|
public String getRemoteSearchArguments() {
|
||||||
|
Set<ConditionsTreeNode> leafSet = getLeafSet();
|
||||||
|
if (leafSet == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ConditionsTreeNode node : leafSet) {
|
||||||
|
if (node.getCondition().field == Searchfield.SUBJECT ||
|
||||||
|
node.getCondition().field == Searchfield.SENDER ) {
|
||||||
|
return node.getCondition().value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the saved search.
|
||||||
|
*
|
||||||
|
* @return Name of the search.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return (mName == null) ? "" : mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this search was hard coded and shipped with K-9
|
||||||
|
*
|
||||||
|
* @return True is search was shipped with K-9
|
||||||
|
*/
|
||||||
|
public boolean isPredefined() {
|
||||||
|
return mPredefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isManualSearch() {
|
||||||
|
return mManualSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setManualSearch(boolean manualSearch) {
|
||||||
|
mManualSearch = manualSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the account uuids that this search will try to
|
||||||
|
* match against.
|
||||||
|
*
|
||||||
|
* @return Array of account uuids.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String[] getAccountUuids() {
|
||||||
|
if (mAccountUuids.size() == 0) {
|
||||||
|
return new String[] { SearchSpecification.ALL_ACCOUNTS };
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] tmp = new String[mAccountUuids.size()];
|
||||||
|
mAccountUuids.toArray(tmp);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not to search all accounts.
|
||||||
|
*
|
||||||
|
* @return {@code true} if all accounts should be searched.
|
||||||
|
*/
|
||||||
|
public boolean searchAllAccounts() {
|
||||||
|
return (mAccountUuids.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the condition tree.
|
||||||
|
*
|
||||||
|
* @return The root node of the related conditions tree.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ConditionsTreeNode getConditions() {
|
||||||
|
return mConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// Parcelable
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(mName);
|
||||||
|
dest.writeByte((byte) (mPredefined ? 1 : 0));
|
||||||
|
dest.writeByte((byte) (mManualSearch ? 1 : 0));
|
||||||
|
dest.writeStringList(new ArrayList<String>(mAccountUuids));
|
||||||
|
dest.writeParcelable(mConditions, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<LocalSearch> CREATOR =
|
||||||
|
new Parcelable.Creator<LocalSearch>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalSearch createFromParcel(Parcel in) {
|
||||||
|
return new LocalSearch(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalSearch[] newArray(int size) {
|
||||||
|
return new LocalSearch[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public LocalSearch(Parcel in) {
|
||||||
|
mName = in.readString();
|
||||||
|
mPredefined = (in.readByte() == 1);
|
||||||
|
mManualSearch = (in.readByte() == 1);
|
||||||
|
mAccountUuids.addAll(in.createStringArrayList());
|
||||||
|
mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
|
||||||
|
mLeafSet = (mConditions == null) ? null : mConditions.getLeafSet();
|
||||||
|
}
|
||||||
|
}
|
91
src/com/fsck/k9/search/SearchAccount.java
Normal file
91
src/com/fsck/k9/search/SearchAccount.java
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.fsck.k9.BaseAccount;
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.Attribute;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is basically a wrapper around a LocalSearch. It allows to expose it as
|
||||||
|
* an account. This is a meta-account containing all the e-mail that matches the search.
|
||||||
|
*/
|
||||||
|
public class SearchAccount implements BaseAccount {
|
||||||
|
|
||||||
|
// create the all messages search ( all accounts is default when none specified )
|
||||||
|
public static SearchAccount createAllMessagesAccount(Context context) {
|
||||||
|
String name = context.getString(R.string.search_all_messages_title);
|
||||||
|
LocalSearch tmpSearch = new LocalSearch(name);
|
||||||
|
return new SearchAccount(tmpSearch, name,
|
||||||
|
context.getString(R.string.search_all_messages_detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create the unified inbox meta account ( all accounts is default when none specified )
|
||||||
|
public static SearchAccount createUnifiedInboxAccount(Context context) {
|
||||||
|
String name = context.getString(R.string.integrated_inbox_title);
|
||||||
|
LocalSearch tmpSearch = new LocalSearch(name);
|
||||||
|
tmpSearch.and(Searchfield.INTEGRATE, "1", Attribute.EQUALS);
|
||||||
|
return new SearchAccount(tmpSearch, name,
|
||||||
|
context.getString(R.string.integrated_inbox_detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mEmail;
|
||||||
|
private String mDescription;
|
||||||
|
private LocalSearch mSearch;
|
||||||
|
private String mFakeUuid;
|
||||||
|
|
||||||
|
public SearchAccount(LocalSearch search, String description, String email)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
|
if (search == null) {
|
||||||
|
throw new IllegalArgumentException("Provided LocalSearch was null");
|
||||||
|
}
|
||||||
|
|
||||||
|
mSearch = search;
|
||||||
|
mDescription = description;
|
||||||
|
mEmail = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized String getEmail() {
|
||||||
|
return mEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setEmail(String email) {
|
||||||
|
this.mEmail = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return mDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.mDescription = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalSearch getRelatedSearch() {
|
||||||
|
return mSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This will only be used when accessed as an Account. If that
|
||||||
|
* is the case we don't want to return the uuid of a real account since
|
||||||
|
* this is posing as a fake meta-account. If this object is accesed as
|
||||||
|
* a Search then methods from LocalSearch will be called which do handle
|
||||||
|
* things nice.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getUuid() {
|
||||||
|
if (mFakeUuid == null) {
|
||||||
|
mFakeUuid = UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
return mFakeUuid;
|
||||||
|
}
|
||||||
|
}
|
23
src/com/fsck/k9/search/SearchModifier.java
Normal file
23
src/com/fsck/k9/search/SearchModifier.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.mail.Flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum represents filtering parameters used by {@link com.fsck.k9.search.SearchAccount}.
|
||||||
|
*/
|
||||||
|
public enum SearchModifier {
|
||||||
|
FLAGGED(R.string.flagged_modifier, new Flag[] { Flag.FLAGGED }, null),
|
||||||
|
UNREAD(R.string.unread_modifier, null, new Flag[] { Flag.SEEN });
|
||||||
|
|
||||||
|
public final int resId;
|
||||||
|
public final Flag[] requiredFlags;
|
||||||
|
public final Flag[] forbiddenFlags;
|
||||||
|
|
||||||
|
SearchModifier(int nResId, Flag[] nRequiredFlags, Flag[] nForbiddenFlags) {
|
||||||
|
resId = nResId;
|
||||||
|
requiredFlags = nRequiredFlags;
|
||||||
|
forbiddenFlags = nForbiddenFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
163
src/com/fsck/k9/search/SearchSpecification.java
Normal file
163
src/com/fsck/k9/search/SearchSpecification.java
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public interface SearchSpecification extends Parcelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the uuids of accounts this search acts on.
|
||||||
|
* @return Array of uuids.
|
||||||
|
*/
|
||||||
|
public String[] getAccountUuids();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the search's name if it was named.
|
||||||
|
* @return Name of the search.
|
||||||
|
*/
|
||||||
|
public String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root node of the condition tree accompanying
|
||||||
|
* the search.
|
||||||
|
*
|
||||||
|
* @return Root node of conditions tree.
|
||||||
|
*/
|
||||||
|
public ConditionsTreeNode getConditions();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some meta names for certain conditions.
|
||||||
|
*/
|
||||||
|
public static final String ALL_ACCOUNTS = "allAccounts";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// ATTRIBUTE enum
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
public enum Attribute {
|
||||||
|
CONTAINS,
|
||||||
|
NOT_CONTAINS,
|
||||||
|
|
||||||
|
EQUALS,
|
||||||
|
NOT_EQUALS,
|
||||||
|
|
||||||
|
STARTSWITH,
|
||||||
|
NOT_STARTSWITH,
|
||||||
|
|
||||||
|
ENDSWITH,
|
||||||
|
NOT_ENDSWITH
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// SEARCHFIELD enum
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/*
|
||||||
|
* Using an enum in order to have more robust code. Users ( & coders )
|
||||||
|
* are prevented from passing illegal fields. No database overhead
|
||||||
|
* when invalid fields passed.
|
||||||
|
*
|
||||||
|
* By result, only the fields in here are searchable.
|
||||||
|
*
|
||||||
|
* Fields not in here at this moment ( and by effect not searchable ):
|
||||||
|
* id, html_content, internal_date, message_id,
|
||||||
|
* preview, mime_type
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum Searchfield {
|
||||||
|
SUBJECT,
|
||||||
|
DATE,
|
||||||
|
UID,
|
||||||
|
FLAG,
|
||||||
|
SENDER,
|
||||||
|
TO,
|
||||||
|
CC,
|
||||||
|
FOLDER,
|
||||||
|
BCC,
|
||||||
|
REPLY_TO,
|
||||||
|
MESSAGE_CONTENTS,
|
||||||
|
ATTACHMENT_COUNT,
|
||||||
|
DELETED,
|
||||||
|
THREAD_ROOT,
|
||||||
|
ID,
|
||||||
|
INTEGRATE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// SearchCondition class
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
/**
|
||||||
|
* This class represents 1 value for a certain search field. One
|
||||||
|
* value consists of three things:
|
||||||
|
* an attribute: equals, starts with, contains,...
|
||||||
|
* a searchfield: date, flags, sender, subject,...
|
||||||
|
* a value: "apple", "jesse",..
|
||||||
|
*
|
||||||
|
* @author dzan
|
||||||
|
*/
|
||||||
|
public class SearchCondition implements Parcelable {
|
||||||
|
public final String value;
|
||||||
|
public final Attribute attribute;
|
||||||
|
public final Searchfield field;
|
||||||
|
|
||||||
|
public SearchCondition(Searchfield field, Attribute attribute, String value) {
|
||||||
|
this.value = value;
|
||||||
|
this.attribute = attribute;
|
||||||
|
this.field = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchCondition(Parcel in) {
|
||||||
|
this.value = in.readString();
|
||||||
|
this.attribute = Attribute.values()[in.readInt()];
|
||||||
|
this.field = Searchfield.values()[in.readInt()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchCondition clone() {
|
||||||
|
return new SearchCondition(field, attribute, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toHumanString() {
|
||||||
|
return field.toString() + attribute.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof SearchCondition) {
|
||||||
|
SearchCondition tmp = (SearchCondition) o;
|
||||||
|
if (tmp.attribute == attribute &&
|
||||||
|
tmp.field == field &&
|
||||||
|
tmp.value.equals(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(value);
|
||||||
|
dest.writeInt(attribute.ordinal());
|
||||||
|
dest.writeInt(field.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<SearchCondition> CREATOR =
|
||||||
|
new Parcelable.Creator<SearchCondition>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchCondition createFromParcel(Parcel in) {
|
||||||
|
return new SearchCondition(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchCondition[] newArray(int size) {
|
||||||
|
return new SearchCondition[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
232
src/com/fsck/k9/search/SqlQueryBuilder.java
Normal file
232
src/com/fsck/k9/search/SqlQueryBuilder.java
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
package com.fsck.k9.search;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fsck.k9.Account;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
import com.fsck.k9.mail.Folder.OpenMode;
|
||||||
|
import com.fsck.k9.mail.store.LocalStore;
|
||||||
|
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||||
|
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
||||||
|
|
||||||
|
|
||||||
|
public class SqlQueryBuilder {
|
||||||
|
public static void buildWhereClause(Account account, ConditionsTreeNode node,
|
||||||
|
StringBuilder query, List<String> selectionArgs) {
|
||||||
|
buildWhereClauseInternal(account, node, query, selectionArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void buildWhereClauseInternal(Account account, ConditionsTreeNode node,
|
||||||
|
StringBuilder query, List<String> selectionArgs) {
|
||||||
|
if (node == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.mLeft == null && node.mRight == null) {
|
||||||
|
SearchCondition condition = node.mCondition;
|
||||||
|
switch (condition.field) {
|
||||||
|
case FOLDER: {
|
||||||
|
String folderName = condition.value;
|
||||||
|
long folderId = getFolderId(account, folderName);
|
||||||
|
query.append("folder_id = ?");
|
||||||
|
selectionArgs.add(Long.toString(folderId));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
appendCondition(condition, query, selectionArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query.append("(");
|
||||||
|
buildWhereClauseInternal(account, node.mLeft, query, selectionArgs);
|
||||||
|
query.append(") ");
|
||||||
|
query.append(node.mValue.name());
|
||||||
|
query.append(" (");
|
||||||
|
buildWhereClauseInternal(account, node.mRight, query, selectionArgs);
|
||||||
|
query.append(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendCondition(SearchCondition condition, StringBuilder query,
|
||||||
|
List<String> selectionArgs) {
|
||||||
|
query.append(getColumnName(condition));
|
||||||
|
appendExprRight(condition, query, selectionArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getFolderId(Account account, String folderName) {
|
||||||
|
long folderId = 0;
|
||||||
|
try {
|
||||||
|
LocalStore localStore = account.getLocalStore();
|
||||||
|
LocalFolder folder = localStore.getFolder(folderName);
|
||||||
|
folder.open(OpenMode.READ_ONLY);
|
||||||
|
folderId = folder.getId();
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
//FIXME
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getColumnName(SearchCondition condition) {
|
||||||
|
String columnName = null;
|
||||||
|
switch (condition.field) {
|
||||||
|
case ATTACHMENT_COUNT: {
|
||||||
|
columnName = "attachment_count";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BCC: {
|
||||||
|
columnName = "bcc_list";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CC: {
|
||||||
|
columnName = "cc_list";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DATE: {
|
||||||
|
columnName = "date";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DELETED: {
|
||||||
|
columnName = "deleted";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FLAG: {
|
||||||
|
columnName = "flags";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FOLDER: {
|
||||||
|
columnName = "folder_id";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ID: {
|
||||||
|
columnName = "id";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MESSAGE_CONTENTS: {
|
||||||
|
columnName = "text_content";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case REPLY_TO: {
|
||||||
|
columnName = "reply_to_list";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SENDER: {
|
||||||
|
columnName = "sender_list";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SUBJECT: {
|
||||||
|
columnName = "subject";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case THREAD_ROOT: {
|
||||||
|
columnName = "thread_root";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TO: {
|
||||||
|
columnName = "to_list";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UID: {
|
||||||
|
columnName = "uid";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case INTEGRATE: {
|
||||||
|
columnName = "integrate";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnName == null) {
|
||||||
|
throw new RuntimeException("Unhandled case");
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendExprRight(SearchCondition condition, StringBuilder query,
|
||||||
|
List<String> selectionArgs) {
|
||||||
|
String value = condition.value;
|
||||||
|
Searchfield field = condition.field;
|
||||||
|
|
||||||
|
query.append(" ");
|
||||||
|
String selectionArg = null;
|
||||||
|
switch (condition.attribute) {
|
||||||
|
case NOT_CONTAINS:
|
||||||
|
query.append("NOT ");
|
||||||
|
//$FALL-THROUGH$
|
||||||
|
case CONTAINS: {
|
||||||
|
query.append("LIKE ?");
|
||||||
|
selectionArg = "%" + value + "%";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NOT_STARTSWITH:
|
||||||
|
query.append("NOT ");
|
||||||
|
//$FALL-THROUGH$
|
||||||
|
case STARTSWITH: {
|
||||||
|
query.append("LIKE ?");
|
||||||
|
selectionArg = "%" + value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NOT_ENDSWITH:
|
||||||
|
query.append("NOT ");
|
||||||
|
//$FALL-THROUGH$
|
||||||
|
case ENDSWITH: {
|
||||||
|
query.append("LIKE ?");
|
||||||
|
selectionArg = value + "%";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NOT_EQUALS: {
|
||||||
|
if (isNumberColumn(field)) {
|
||||||
|
query.append("!= ?");
|
||||||
|
} else {
|
||||||
|
query.append("NOT LIKE ?");
|
||||||
|
}
|
||||||
|
selectionArg = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EQUALS: {
|
||||||
|
if (isNumberColumn(field)) {
|
||||||
|
query.append("= ?");
|
||||||
|
} else {
|
||||||
|
query.append("LIKE ?");
|
||||||
|
}
|
||||||
|
selectionArg = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectionArg == null) {
|
||||||
|
throw new RuntimeException("Unhandled case");
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionArgs.add(selectionArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNumberColumn(Searchfield field) {
|
||||||
|
switch (field) {
|
||||||
|
case ATTACHMENT_COUNT:
|
||||||
|
case DATE:
|
||||||
|
case DELETED:
|
||||||
|
case FOLDER:
|
||||||
|
case ID:
|
||||||
|
case INTEGRATE:
|
||||||
|
case THREAD_ROOT: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String addPrefixToSelection(String[] columnNames, String prefix, String selection) {
|
||||||
|
String result = selection;
|
||||||
|
for (String columnName : columnNames) {
|
||||||
|
result = result.replaceAll("\\b" + columnName + "\\b", prefix + columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user