- @string/account_setup_options_mail_check_frequency_never
+ - @string/account_setup_options_mail_check_frequency_1min
- @string/account_setup_options_mail_check_frequency_5min
- @string/account_setup_options_mail_check_frequency_10min
- @string/account_setup_options_mail_check_frequency_15min
@@ -27,6 +28,7 @@
- -1
+ - 1
- 5
- 10
- 15
@@ -47,4 +49,69 @@
- 50
- 100
+
+
+ - @string/account_settings_folder_display_mode_all
+ - @string/account_settings_folder_display_mode_first_class
+ - @string/account_settings_folder_display_mode_first_and_second_class
+ - @string/account_settings_folder_display_mode_not_second_class
+
+
+
+ - ALL
+ - FIRST_CLASS
+ - FIRST_AND_SECOND_CLASS
+ - NOT_SECOND_CLASS
+
+
+
+ - @string/account_settings_folder_sync_mode_all
+ - @string/account_settings_folder_sync_mode_first_class
+ - @string/account_settings_folder_sync_mode_first_and_second_class
+ - @string/account_settings_folder_sync_mode_not_second_class
+
+
+
+ - ALL
+ - FIRST_CLASS
+ - FIRST_AND_SECOND_CLASS
+ - NOT_SECOND_CLASS
+
+
+
+ - @string/folder_settings_folder_display_mode_normal
+ - @string/folder_settings_folder_display_mode_first_class
+ - @string/folder_settings_folder_display_mode_second_class
+
+
+
+ - NONE
+ - FIRST_CLASS
+ - SECOND_CLASS
+
+
+
+ - @string/folder_settings_folder_sync_mode_normal
+ - @string/folder_settings_folder_sync_mode_first_class
+ - @string/folder_settings_folder_sync_mode_second_class
+
+
+
+ - NONE
+ - FIRST_CLASS
+ - SECOND_CLASS
+
+
+
+ - @string/account_setup_incoming_delete_policy_never_label
+ - @string/account_setup_incoming_delete_policy_delete_label
+ - @string/account_setup_incoming_delete_policy_markread_label
+
+
+
+ - 0
+ - 2
+ - 3
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 625620785..74c88db58 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -31,16 +31,23 @@
Save as draft
Retry
Refresh
+ Mark all messages as read
Add account
Compose
Search
Preferences
Open
Account settings
+ Folder settings
Remove account
+ Clear pending actions (danger!)
+
Accounts
Read
Mark as read
+ Forward (alternate)
+ Choose sender
+
Mark as unread
Move to
Folders
@@ -53,6 +60,7 @@
About
Account options
+ Folder options
(No subject)
@@ -69,8 +77,11 @@
in %d accounts
Message not sent
- Checking email: %s
+ Checking email: %s:%s
Checking email
+ Sending email: %s
+ Sending email
+ :
Inbox
Outbox
@@ -78,6 +89,8 @@
Drafts
Trash
Sent
+
+ No more messages
Welcome to K-9 Mail setup. K-9 is an open source email client for Android based on the standard Android Mail client.
@@ -86,6 +99,8 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
* Better performance
* Email signatures
* Bcc-to-self
+ * Folder subscriptions
+ * All folder synchronization
* Return-address configuration
* Keyboard shortcuts
* Better IMAP support
@@ -142,6 +157,7 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
Message deleted.
Message discarded.
Message saved as draft.
+ Message could not be deleted.
About %s
Version: %s
@@ -189,10 +205,13 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
SSL (always)
TLS (if available)
TLS (always)
- Delete email from server:
- Never
+
+ When I delete a message
+ Do not delete on server
After 7 days
- When I delete from Inbox
+ Delete from server
+ Mark as read on server
+
IMAP path prefix
Optional
WebDav(Exchange) path prefix
@@ -219,9 +238,11 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
WebDav(Exchange) before SMTP
Account options
+
Email checking frequency
Never
+ Every minute
Every 5 minutes
Every 10 minutes
Every 15 minutes
@@ -229,7 +250,6 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
Every hour
Send email from this account by default.
Notify me when email arrives.
- Play a sound when email arrives.
Number of emails to display
@@ -248,14 +268,41 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
Default account
Default account
Send email from this account by default
- Your email address
Email notifications
+ Your email address
Notify in status bar when email arrives
- Ringtone notifications
- Play a sound when email arrives
Show combined Inbox
+
+ Display and synchronization
+
Email check frequency
+ 2nd class check frequency
+
Number of emails to display
+
+ Folder display mode
+ All
+ Only 1st Class folders
+ 1st and 2nd Class folders
+ All except 2nd Class folders
+
+ Folder sync mode
+ All
+ Only 1st Class folders
+ 1st and 2nd Class folders
+ All except 2nd Class folders
+
+ Folder settings
+ Folder display class
+ None
+ 1st Class
+ 2nd Class
+
+ Folder sync class
+ Same as display class
+ 1st Class
+ 2nd Class
+
Incoming settings
Configure the incoming email server
Outgoing settings
@@ -295,7 +342,9 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based
your correct email address and password, you may not have a paid
\"Plus\" account. Please launch the Web browser to gain access to
these mail accounts.
+
Unrecognized Certificate
Accept Key
Reject Key
+
diff --git a/res/xml/account_settings_preferences.xml b/res/xml/account_settings_preferences.xml
index 9927a0a17..f0bc3961a 100644
--- a/res/xml/account_settings_preferences.xml
+++ b/res/xml/account_settings_preferences.xml
@@ -26,12 +26,10 @@
android:summary=""
android:dialogTitle="@string/account_settings_description_label" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -54,11 +86,6 @@
android:title="@string/account_settings_notify_label"
android:defaultValue="true"
android:summary="@string/account_settings_notify_summary" />
-
-
-
-
-
-
+
diff --git a/res/xml/folder_settings_preferences.xml b/res/xml/folder_settings_preferences.xml
new file mode 100644
index 000000000..a4137cb6d
--- /dev/null
+++ b/res/xml/folder_settings_preferences.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/android/email/Account.java b/src/com/android/email/Account.java
index 08d5fa102..72037a057 100644
--- a/src/com/android/email/Account.java
+++ b/src/com/android/email/Account.java
@@ -5,9 +5,17 @@ import java.io.Serializable;
import java.util.Arrays;
import java.util.UUID;
+import com.android.email.mail.Folder;
+import com.android.email.mail.MessagingException;
+import com.android.email.mail.Store;
+import com.android.email.mail.store.LocalStore;
+import com.android.email.mail.store.LocalStore.LocalFolder;
+
+import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
+import android.util.Log;
/**
* Account stores all of the settings for a single account defined by the user. It is able to save
@@ -17,6 +25,7 @@ public class Account implements Serializable {
public static final int DELETE_POLICY_NEVER = 0;
public static final int DELETE_POLICY_7DAYS = 1;
public static final int DELETE_POLICY_ON_DELETE = 2;
+ public static final int DELETE_POLICY_MARK_AS_READ = 3;
private static final long serialVersionUID = 2975156672298625121L;
@@ -33,14 +42,19 @@ public class Account implements Serializable {
int mDisplayCount;
long mLastAutomaticCheckTime;
boolean mNotifyNewMail;
- boolean mNotifyRingtone;
String mDraftsFolderName;
String mSentFolderName;
String mTrashFolderName;
String mOutboxFolderName;
+ FolderMode mFolderDisplayMode;
+ FolderMode mFolderSyncMode;
int mAccountNumber;
boolean mVibrate;
String mRingtoneUri;
+
+ public enum FolderMode {
+ ALL, FIRST_CLASS, FIRST_AND_SECOND_CLASS, NOT_SECOND_CLASS;
+ }
/**
*
@@ -59,9 +73,10 @@ public class Account implements Serializable {
mDisplayCount = -1;
mAccountNumber = -1;
mNotifyNewMail = true;
- mNotifyRingtone = false;
mSignature = "Sent from my Android phone with K-9. Please excuse my brevity.";
mVibrate = false;
+ mFolderDisplayMode = FolderMode.ALL;
+ mFolderSyncMode = FolderMode.ALL;
mRingtoneUri = "content://settings/system/notification_sound";
}
@@ -91,8 +106,6 @@ public class Account implements Serializable {
+ ".lastAutomaticCheckTime", 0);
mNotifyNewMail = preferences.mSharedPreferences.getBoolean(mUuid + ".notifyNewMail",
false);
- mNotifyRingtone = preferences.mSharedPreferences.getBoolean(mUuid + ".notifyRingtone",
- false);
mDeletePolicy = preferences.mSharedPreferences.getInt(mUuid + ".deletePolicy", 0);
mDraftsFolderName = preferences.mSharedPreferences.getString(mUuid + ".draftsFolderName",
"Drafts");
@@ -106,6 +119,26 @@ public class Account implements Serializable {
mVibrate = preferences.mSharedPreferences.getBoolean(mUuid + ".vibrate", false);
mRingtoneUri = preferences.mSharedPreferences.getString(mUuid + ".ringtone",
"content://settings/system/notification_sound");
+ try
+ {
+ mFolderDisplayMode = FolderMode.valueOf(preferences.mSharedPreferences.getString(mUuid + ".folderDisplayMode",
+ FolderMode.NOT_SECOND_CLASS.name()));
+ }
+ catch (Exception e)
+ {
+ mFolderDisplayMode = FolderMode.ALL;
+ }
+
+ try
+ {
+ mFolderSyncMode = FolderMode.valueOf(preferences.mSharedPreferences.getString(mUuid + ".folderSyncMode",
+ FolderMode.FIRST_CLASS.name()));
+ }
+ catch (Exception e)
+ {
+ mFolderSyncMode = FolderMode.ALL;
+ }
+
}
public String getUuid() {
@@ -218,6 +251,9 @@ public class Account implements Serializable {
editor.remove(mUuid + ".accountNumber");
editor.remove(mUuid + ".vibrate");
editor.remove(mUuid + ".ringtone");
+ editor.remove(mUuid + ".lastFullSync");
+ editor.remove(mUuid + ".folderDisplayMode");
+ editor.remove(mUuid + ".folderSyncMode");
editor.commit();
}
@@ -269,7 +305,6 @@ public class Account implements Serializable {
editor.putInt(mUuid + ".displayCount", mDisplayCount);
editor.putLong(mUuid + ".lastAutomaticCheckTime", mLastAutomaticCheckTime);
editor.putBoolean(mUuid + ".notifyNewMail", mNotifyNewMail);
- editor.putBoolean(mUuid + ".notifyRingtone", mNotifyRingtone);
editor.putInt(mUuid + ".deletePolicy", mDeletePolicy);
editor.putString(mUuid + ".draftsFolderName", mDraftsFolderName);
editor.putString(mUuid + ".sentFolderName", mSentFolderName);
@@ -278,6 +313,9 @@ public class Account implements Serializable {
editor.putInt(mUuid + ".accountNumber", mAccountNumber);
editor.putBoolean(mUuid + ".vibrate", mVibrate);
editor.putString(mUuid + ".ringtone", mRingtoneUri);
+ editor.putString(mUuid + ".folderDisplayMode", mFolderDisplayMode.name());
+ editor.putString(mUuid + ".folderSyncMode", mFolderSyncMode.name());
+
editor.commit();
}
@@ -303,6 +341,49 @@ public class Account implements Serializable {
public int getAutomaticCheckIntervalMinutes() {
return mAutomaticCheckIntervalMinutes;
}
+
+ public int getUnreadMessageCount(Context context, Application application) throws MessagingException
+ {
+ int unreadMessageCount = 0;
+ LocalStore localStore = (LocalStore) Store.getInstance(
+ getLocalStoreUri(),
+ application);
+ Account.FolderMode aMode = getFolderDisplayMode();
+ Preferences prefs = Preferences.getPreferences(context);
+ for (LocalFolder folder : localStore.getPersonalNamespaces())
+ {
+ folder.refresh(prefs);
+ Folder.FolderClass fMode = folder.getDisplayClass();
+
+ if (folder.getName().equals(getTrashFolderName()) == false &&
+ folder.getName().equals(getDraftsFolderName()) == false &&
+ folder.getName().equals(getOutboxFolderName()) == false &&
+ folder.getName().equals(getSentFolderName()) == false &&
+ folder.getName().equals(getErrorFolderName()) == false)
+ {
+ if (aMode == Account.FolderMode.FIRST_CLASS &&
+ fMode != Folder.FolderClass.FIRST_CLASS)
+ {
+ continue;
+ }
+ if (aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
+ fMode != Folder.FolderClass.FIRST_CLASS &&
+ fMode != Folder.FolderClass.SECOND_CLASS)
+ {
+ continue;
+ }
+ if (aMode == Account.FolderMode.NOT_SECOND_CLASS &&
+ fMode == Folder.FolderClass.SECOND_CLASS)
+ {
+ continue;
+ }
+ unreadMessageCount += folder.getUnreadMessageCount();
+ }
+ }
+
+ return unreadMessageCount;
+
+ }
public int getDisplayCount() {
if (mDisplayCount == -1) {
@@ -337,15 +418,6 @@ public class Account implements Serializable {
this.mLastAutomaticCheckTime = lastAutomaticCheckTime;
}
- public boolean isNotifyRingtone() {
- return mNotifyRingtone;
- }
-
- public void setNotifyRingtone(boolean notifyRingtone) {
- this.mNotifyRingtone = notifyRingtone;
- }
-
-
public boolean isNotifyNewMail() {
return mNotifyNewMail;
}
@@ -373,6 +445,11 @@ public class Account implements Serializable {
public String getSentFolderName() {
return mSentFolderName;
}
+
+ public String getErrorFolderName()
+ {
+ return Email.ERROR_FOLDER_NAME;
+ }
public void setSentFolderName(String sentFolderName) {
mSentFolderName = sentFolderName;
@@ -405,4 +482,25 @@ public class Account implements Serializable {
}
return super.equals(o);
}
+
+ public FolderMode getFolderDisplayMode()
+ {
+ return mFolderDisplayMode;
+ }
+
+ public void setFolderDisplayMode(FolderMode displayMode)
+ {
+ mFolderDisplayMode = displayMode;
+ }
+
+ public FolderMode getFolderSyncMode()
+ {
+ return mFolderSyncMode;
+ }
+
+ public void setFolderSyncMode(FolderMode syncMode)
+ {
+ mFolderSyncMode = syncMode;
+ }
+
}
diff --git a/src/com/android/email/Email.java b/src/com/android/email/Email.java
index fa925660b..75ff998e2 100644
--- a/src/com/android/email/Email.java
+++ b/src/com/android/email/Email.java
@@ -20,6 +20,14 @@ public class Email extends Application {
public static Application app = null;
public static File tempDirectory;
public static final String LOG_TAG = "k9";
+
+ /**
+ * Some log messages can be sent to a file, so that the logs
+ * can be read using unprivileged access (eg. Terminal Emulator)
+ * on the phone, without adb. Set to null to disable
+ */
+ public static final String logFile = null;
+ //public static final String logFile = "/sdcard/k9mail/debug.log";
/**
* If this is enabled there will be additional logging information sent to
@@ -33,6 +41,12 @@ public class Email extends Application {
*/
public static boolean DEBUG_SENSITIVE = false;
+ /**
+ * Can create messages containing stack traces that can be forwarded
+ * to the development team.
+ */
+ public static boolean ENABLE_ERROR_FOLDER = true;
+ public static String ERROR_FOLDER_NAME = "K9mail-errors";
/**
* The MIME type(s) of attachments we're willing to send. At the moment it is not possible
@@ -41,7 +55,7 @@ public class Email extends Application {
* with Intent.ACTION_SEND.
*/
public static final String[] ACCEPTABLE_ATTACHMENT_SEND_TYPES = new String[] {
- "*/*",
+ "*/*"
};
/**
@@ -103,7 +117,7 @@ public class Email extends Application {
/**
* Max time (in millis) the wake lock will be held for when background sync is happening
*/
- public static final int WAKE_LOCK_TIMEOUT = 30000;
+ public static final int WAKE_LOCK_TIMEOUT = 600000;
/**
* LED color used for the new email notitication
@@ -120,8 +134,10 @@ public class Email extends Application {
*/
public static final int NOTIFICATION_LED_OFF_TIME = 2000;
- public static final int NEW_EMAIL_NOTIFICATION_ID = 1;
- public static final int FETCHING_EMAIL_NOTIFICATION_ID = 2;
+ // Must not conflict with an account number
+ public static final int FETCHING_EMAIL_NOTIFICATION_ID = -4;
+ public static final int FETCHING_EMAIL_NOTIFICATION_MULTI_ACCOUNT_ID = -1;
+ public static final int FETCHING_EMAIL_NOTIFICATION_NO_ACCOUNT = -2;
/**
* Called throughout the application when the number of accounts has changed. This method
diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java
index ff40d340a..eb7f0827c 100644
--- a/src/com/android/email/MessagingController.java
+++ b/src/com/android/email/MessagingController.java
@@ -1,13 +1,23 @@
package com.android.email;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import android.app.Application;
import android.app.Notification;
@@ -20,6 +30,8 @@ import android.util.Config;
import android.util.Log;
import com.android.email.activity.FolderMessageList;
+import com.android.email.mail.Address;
+import com.android.email.mail.Body;
import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag;
import com.android.email.mail.Folder;
@@ -31,8 +43,9 @@ import com.android.email.mail.Store;
import com.android.email.mail.Transport;
import com.android.email.mail.Folder.FolderType;
import com.android.email.mail.Folder.OpenMode;
-import com.android.email.mail.internet.MimeHeader;
+import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.internet.MimeUtility;
+import com.android.email.mail.internet.TextBody;
import com.android.email.mail.store.LocalStore;
import com.android.email.mail.store.LocalStore.LocalFolder;
import com.android.email.mail.store.LocalStore.LocalMessage;
@@ -69,7 +82,8 @@ public class MessagingController implements Runnable {
*
* So 25k gives good performance and a reasonable data footprint. Sounds good to me.
*/
- private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024);
+ // Daniel I. Applebaum Changing to 5k for faster syncing
+ private static final int MAX_SMALL_MESSAGE_SIZE = (5 * 1024);
private static final String PENDING_COMMAND_TRASH =
"com.android.email.MessagingController.trash";
@@ -77,19 +91,68 @@ public class MessagingController implements Runnable {
"com.android.email.MessagingController.markRead";
private static final String PENDING_COMMAND_APPEND =
"com.android.email.MessagingController.append";
+ private static final String PENDING_COMMAND_MARK_ALL_AS_READ =
+ "com.android.email.MessagingController.markAllAsRead";
+ private static final String PENDING_COMMAND_CLEAR_ALL_PENDING =
+ "com.android.email.MessagingController.clearAllPending";
+
private static MessagingController inst = null;
private BlockingQueue mCommands = new LinkedBlockingQueue();
+ private BlockingQueue backCommands = new LinkedBlockingQueue();
+
private Thread mThread;
- private HashSet mListeners = new HashSet();
+ //private Set mListeners = Collections.synchronizedSet(new HashSet());
+ private Set mListeners = new CopyOnWriteArraySet();
+
private boolean mBusy;
private Application mApplication;
+
+
private MessagingController(Application application) {
mApplication = application;
mThread = new Thread(this);
mThread.start();
}
+
+ public void log(String logmess)
+ {
+ Log.d(Email.LOG_TAG, logmess);
+ if (Email.logFile != null)
+ {
+ FileOutputStream fos = null;
+ try
+ {
+ File logFile = new File(Email.logFile);
+ fos = new FileOutputStream(logFile, true);
+ PrintStream ps = new PrintStream(fos);
+ ps.println(new Date() + ":" + Email.LOG_TAG + ":" + logmess);
+ ps.flush();
+ ps.close();
+ fos.flush();
+ fos.close();
+ }
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "Unable to log message '" + logmess + "'", e);
+ }
+ finally
+ {
+ if (fos != null)
+ {
+ try
+ {
+ fos.close();
+ }
+ catch (Exception e)
+ {
+
+ }
+ }
+ }
+ }
+ }
/**
* Gets or creates the singleton instance of MessagingController. Application is used to
@@ -112,13 +175,25 @@ public class MessagingController implements Runnable {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
- Command command = mCommands.take();
- if (command.listener == null || mListeners.contains(command.listener)) {
- mBusy = true;
- command.runnable.run();
- for (MessagingListener l : mListeners) {
- l.controllerCommandCompleted(mCommands.size() > 0);
- }
+ Command command = mCommands.poll();
+ if (command == null)
+ {
+ command = backCommands.poll();
+ }
+ if (command == null)
+ {
+ command = mCommands.poll(1, TimeUnit.SECONDS);
+ }
+
+ if (command != null)
+ {
+ if (command.listener == null || mListeners.contains(command.listener)) {
+ mBusy = true;
+ command.runnable.run();
+ for (MessagingListener l : mListeners) {
+ l.controllerCommandCompleted(mCommands.size() > 0);
+ }
+ }
}
}
catch (Exception e) {
@@ -142,6 +217,20 @@ public class MessagingController implements Runnable {
throw new Error(ie);
}
}
+
+ private void putBackground(String description, MessagingListener listener, Runnable runnable) {
+ try {
+ Command command = new Command();
+ command.listener = listener;
+ command.runnable = runnable;
+ command.description = description;
+ backCommands.put(command);
+ }
+ catch (InterruptedException ie) {
+ throw new Error(ie);
+ }
+ }
+
public void addListener(MessagingListener listener) {
mListeners.add(listener);
@@ -150,6 +239,11 @@ public class MessagingController implements Runnable {
public void removeListener(MessagingListener listener) {
mListeners.remove(listener);
}
+
+ public Set getListeners()
+ {
+ return mListeners;
+ }
/**
* Lists folders that are available locally and remotely. This method calls
@@ -168,7 +262,7 @@ public class MessagingController implements Runnable {
final Account account,
boolean refreshRemote,
MessagingListener listener) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listFoldersStarted(account);
}
try {
@@ -179,18 +273,18 @@ public class MessagingController implements Runnable {
doRefreshRemote(account, listener);
return;
}
-
- for (MessagingListener l : mListeners) {
- l.listFolders(account, localFolders);
- }
- }
- catch (Exception e) {
- for (MessagingListener l : mListeners) {
- l.listFoldersFailed(account, e.getMessage());
- return;
+ for (MessagingListener l : getListeners()) {
+ l.listFolders(account, localFolders);
}
}
- for (MessagingListener l : mListeners) {
+ catch (Exception e) {
+ for (MessagingListener l : getListeners()) {
+ l.listFoldersFailed(account, e.getMessage());
+ }
+ addErrorMessage(account, e);
+ return;
+ }
+ for (MessagingListener l : getListeners()) {
l.listFoldersFinished(account);
}
}
@@ -203,15 +297,17 @@ public class MessagingController implements Runnable {
Folder[] remoteFolders = store.getPersonalNamespaces();
- Store localStore = Store.getInstance(
+ LocalStore localStore = (LocalStore)Store.getInstance(
account.getLocalStoreUri(),
mApplication);
HashSet remoteFolderNames = new HashSet();
for (int i = 0, count = remoteFolders.length; i < count; i++) {
- Folder localFolder = localStore.getFolder(remoteFolders[i].getName());
+ LocalFolder localFolder = localStore.getFolder(remoteFolders[i].getName());
if (!localFolder.exists()) {
-
+ // TODO: if the localFolder is inbox, set to 1st Class
localFolder.create(FolderType.HOLDS_MESSAGES, account.getDisplayCount());
+ localFolder.setDisplayClass(Folder.FolderClass.FIRST_CLASS);
+ localFolder.save(Preferences.getPreferences(mApplication));
}
remoteFolderNames.add(remoteFolders[i].getName());
}
@@ -227,7 +323,8 @@ public class MessagingController implements Runnable {
localFolderName.equals(account.getTrashFolderName()) ||
localFolderName.equals(account.getOutboxFolderName()) ||
localFolderName.equals(account.getDraftsFolderName()) ||
- localFolderName.equals(account.getSentFolderName())) {
+ localFolderName.equals(account.getSentFolderName()) ||
+ localFolderName.equals(account.getErrorFolderName())) {
continue;
}
if (!remoteFolderNames.contains(localFolder.getName())) {
@@ -237,17 +334,18 @@ public class MessagingController implements Runnable {
localFolders = localStore.getPersonalNamespaces();
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listFolders(account, localFolders);
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listFoldersFinished(account);
}
}
catch (Exception e) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listFoldersFailed(account, "");
}
+ addErrorMessage(account, e);
}
}
});
@@ -266,7 +364,7 @@ public class MessagingController implements Runnable {
*/
public void listLocalMessages(final Account account, final String folder,
MessagingListener listener) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listLocalMessagesStarted(account, folder);
}
@@ -281,17 +379,18 @@ public class MessagingController implements Runnable {
messages.add(message);
}
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listLocalMessages(account, folder, messages.toArray(new Message[0]));
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listLocalMessagesFinished(account, folder);
}
}
catch (Exception e) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.listLocalMessagesFailed(account, folder, e.getMessage());
}
+ addErrorMessage(account, e);
}
}
@@ -306,6 +405,8 @@ public class MessagingController implements Runnable {
synchronizeMailbox(account, folder, listener);
}
catch (MessagingException me) {
+ addErrorMessage(account, me);
+
throw new RuntimeException("Unable to set visible limit on folder", me);
}
}
@@ -318,6 +419,8 @@ public class MessagingController implements Runnable {
localStore.resetVisibleLimits(account.getDisplayCount());
}
catch (MessagingException e) {
+ addErrorMessage(account, e);
+
Log.e(Email.LOG_TAG, "Unable to reset visible limits", e);
}
}
@@ -327,22 +430,10 @@ public class MessagingController implements Runnable {
* Start background synchronization of the specified folder.
* @param account
* @param folder
- * @param numNewestMessagesToKeep Specifies the number of messages that should be
- * considered as part of the window of available messages. This number effectively limits
- * the user's view into the mailbox to the newest (numNewestMessagesToKeep) messages.
* @param listener
*/
public void synchronizeMailbox(final Account account, final String folder,
MessagingListener listener) {
- /*
- * We don't ever sync the Outbox.
- */
- if (folder.equals(account.getOutboxFolderName())) {
- return;
- }
- for (MessagingListener l : mListeners) {
- l.synchronizeMailboxStarted(account, folder);
- }
put("synchronizeMailbox", listener, new Runnable() {
public void run() {
synchronizeMailboxSynchronous(account, folder);
@@ -355,27 +446,57 @@ public class MessagingController implements Runnable {
* by synchronizeMailbox.
* @param account
* @param folder
- * @param numNewestMessagesToKeep Specifies the number of messages that should be
- * considered as part of the window of available messages. This number effectively limits
- * the user's view into the mailbox to the newest (numNewestMessagesToKeep) messages.
- * @param listener
*
* TODO Break this method up into smaller chunks.
*/
public void synchronizeMailboxSynchronous(final Account account, final String folder) {
- for (MessagingListener l : mListeners) {
+ /*
+ * We don't ever sync the Outbox.
+ */
+ if (folder.equals(account.getOutboxFolderName())) {
+ return;
+ }
+ if (account.getErrorFolderName().equals(folder)){
+ return;
+ }
+ String debugLine = "Synchronizing folder " + account.getDescription() + ":" + folder;
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, debugLine);
+ }
+ log(debugLine);
+ for (MessagingListener l : getListeners()) {
l.synchronizeMailboxStarted(account, folder);
}
+ LocalFolder tLocalFolder = null;
+ Exception commandException = null;
try {
- processPendingCommandsSynchronous(account);
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to process pending commands for folder " +
+ account.getDescription() + ":" + folder);
+ }
+ try
+ {
+ processPendingCommandsSynchronous(account);
+ }
+ catch (Exception e)
+ {
+ addErrorMessage(account, e);
+
+ Log.e(Email.LOG_TAG, "Failure processing command, but allow message sync attempt", e);
+ commandException = e;
+ }
/*
* Get the message list from the local store and create an index of
* the uids within the list.
*/
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to get local folder " + folder);
+ }
final LocalStore localStore =
(LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication);
- final LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
+ tLocalFolder = (LocalFolder) localStore.getFolder(folder);
+ final LocalFolder localFolder = tLocalFolder;
localFolder.open(OpenMode.READ_WRITE);
Message[] localMessages = localFolder.getMessages(null);
HashMap localUidMap = new HashMap();
@@ -383,7 +504,15 @@ public class MessagingController implements Runnable {
localUidMap.put(message.getUid(), message);
}
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to get remote store for " + folder);
+ }
+
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication);
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to get remote folder " + folder);
+ }
+
Folder remoteFolder = remoteStore.getFolder(folder);
/*
@@ -398,9 +527,10 @@ public class MessagingController implements Runnable {
folder.equals(account.getDraftsFolderName())) {
if (!remoteFolder.exists()) {
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.synchronizeMailboxFinished(account, folder, 0, 0);
}
+ Log.i(Email.LOG_TAG, "Done synchronizing folder " + folder);
return;
}
}
@@ -428,6 +558,9 @@ public class MessagingController implements Runnable {
/*
* Open the remote folder. This pre-loads certain metadata like message count.
*/
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to open remote folder " + folder);
+ }
remoteFolder.open(OpenMode.READ_WRITE);
@@ -438,17 +571,36 @@ public class MessagingController implements Runnable {
int visibleLimit = localFolder.getVisibleLimit();
- Message[] remoteMessages = new Message[0];
+ Message[] remoteMessageArray = new Message[0];
+ final ArrayList remoteMessages = new ArrayList();
final ArrayList unsyncedMessages = new ArrayList();
HashMap remoteUidMap = new HashMap();
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Remote message count for folder " + folder + " is " +
+ remoteMessageCount);
+ }
+
if (remoteMessageCount > 0) {
/*
* Message numbers start at 1.
*/
int remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1;
int remoteEnd = remoteMessageCount;
- remoteMessages = remoteFolder.getMessages(remoteStart, remoteEnd, null);
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to get messages " + remoteStart + " through "
+ + remoteEnd + " for folder " + folder);
+ }
+
+ remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteEnd, null);
+ for (Message thisMess : remoteMessageArray)
+ {
+ remoteMessages.add(thisMess);
+ }
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Got messages for folder " + folder);
+ }
+
for (Message message : remoteMessages) {
remoteUidMap.put(message.getUid(), message);
}
@@ -464,13 +616,16 @@ public class MessagingController implements Runnable {
* local store, or messages that are in the local store but failed to download
* on the last sync. These are the new messages that we will download.
*/
- for (Message message : remoteMessages) {
+ Iterator iter = remoteMessages.iterator();
+ while (iter.hasNext()) {
+ Message message = iter.next();
Message localMessage = localUidMap.get(message.getUid());
if (localMessage == null ||
(!localMessage.isSet(Flag.DELETED) &&
!localMessage.isSet(Flag.X_DOWNLOADED_FULL) &&
!localMessage.isSet(Flag.X_DOWNLOADED_PARTIAL))) {
unsyncedMessages.add(message);
+ iter.remove();
}
}
}
@@ -513,10 +668,19 @@ s * critical data as fast as possible, and then we'll fill in the de
fp.add(FetchProfile.Item.FLAGS);
}
fp.add(FetchProfile.Item.ENVELOPE);
+
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to sync unsynced messages for folder " + folder);
+ }
+
remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp,
new MessageRetrievalListener() {
public void messageFinished(Message message, int number, int ofTotal) {
try {
+ if (!message.isSet(Flag.SEEN)) {
+ newMessages.add(message);
+ }
+
// Store the new message locally
localFolder.appendMessages(new Message[] {
message
@@ -531,51 +695,61 @@ s * critical data as fast as possible, and then we'll fill in the de
* (POP) may not be able to give us headers for
* ENVELOPE, only size.
*/
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.synchronizeMailboxNewMessage(account, folder,
localFolder.getMessage(message.getUid()));
}
}
- if (!message.isSet(Flag.SEEN)) {
- newMessages.add(message);
- }
}
catch (Exception e) {
Log.e(Email.LOG_TAG,
"Error while storing downloaded message.",
e);
+ addErrorMessage(account, e);
+
}
}
public void messageStarted(String uid, int number, int ofTotal) {
}
});
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Synced unsynced messages for folder " + folder);
+ }
+
}
- FetchProfile fp;
-
/*
* Refresh the flags for any messages in the local store that we didn't just
* download.
*/
if (remoteFolder.supportsFetchingFlags()) {
- fp = new FetchProfile();
- fp.add(FetchProfile.Item.FLAGS);
- remoteFolder.fetch(remoteMessages, fp, null);
- for (Message remoteMessage : remoteMessages) {
- Message localMessage = localFolder.getMessage(remoteMessage.getUid());
- if (localMessage == null) {
- continue;
- }
- if (remoteMessage.isSet(Flag.SEEN) != localMessage.isSet(Flag.SEEN)) {
- localMessage.setFlag(Flag.SEEN, remoteMessage.isSet(Flag.SEEN));
- for (MessagingListener l : mListeners) {
- l.synchronizeMailboxNewMessage(account, folder, localMessage);
+
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: About to sync remote messages for folder " + folder);
+ }
+
+ FetchProfile fp = new FetchProfile();
+ fp.add(FetchProfile.Item.FLAGS);
+ remoteFolder.fetch(remoteMessages.toArray(new Message[0]), fp, null);
+ for (Message remoteMessage : remoteMessages) {
+ Message localMessage = localFolder.getMessage(remoteMessage.getUid());
+ if (localMessage == null) {
+ continue;
+ }
+ if (!remoteMessage.isSet(Flag.X_NO_SEEN_INFO) && remoteMessage.isSet(Flag.SEEN) != localMessage.isSet(Flag.SEEN)) {
+ localMessage.setFlag(Flag.SEEN, remoteMessage.isSet(Flag.SEEN));
+ for (MessagingListener l : getListeners()) {
+ l.synchronizeMailboxNewMessage(account, folder, localMessage);
}
}
- }
+ }
}
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Synced remote messages for folder " + folder);
+ }
+
/*
* Get and store the unread message count.
@@ -595,7 +769,7 @@ s * critical data as fast as possible, and then we'll fill in the de
for (Message localMessage : localMessages) {
if (remoteUidMap.get(localMessage.getUid()) == null) {
localMessage.setFlag(Flag.X_DESTROYED, true);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
}
}
@@ -619,13 +793,24 @@ s * critical data as fast as possible, and then we'll fill in the de
smallMessages.add(message);
}
}
+
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Have " + largeMessages.size() + " large messages and "
+ + smallMessages.size() + " small messages to fetch for folder " + folder);
+ }
+
+
/*
* Grab the content of the small messages first. This is going to
* be very fast and at very worst will be a single up of a few bytes and a single
* download of 625k.
*/
- fp = new FetchProfile();
+ FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY);
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Fetching small messages for folder " + folder);
+ }
+
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]),
fp, new MessageRetrievalListener() {
public void messageFinished(Message message, int number, int ofTotal) {
@@ -641,7 +826,7 @@ s * critical data as fast as possible, and then we'll fill in the de
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
// Update the listener with what we've found
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.synchronizeMailboxNewMessage(
account,
folder,
@@ -649,19 +834,28 @@ s * critical data as fast as possible, and then we'll fill in the de
}
}
catch (MessagingException me) {
+ addErrorMessage(account, me);
+ Log.e(Email.LOG_TAG, "SYNC: fetch small messages", me);
}
}
public void messageStarted(String uid, int number, int ofTotal) {
}
});
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Done fetching small messages for folder " + folder);
+ }
/*
* Now do the large messages that require more round trips.
*/
fp.clear();
fp.add(FetchProfile.Item.STRUCTURE);
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Fetching large messages for folder " + folder);
+ }
+
remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]),
fp, null);
for (Message message : largeMessages) {
@@ -726,39 +920,102 @@ s * critical data as fast as possible, and then we'll fill in the de
}
// Update the listener with what we've found
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.synchronizeMailboxNewMessage(
account,
folder,
localFolder.getMessage(message.getUid()));
}
}
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "SYNC: Done fetching large messages for folder " + folder);
+ }
/*
* Notify listeners that we're finally done.
*/
- for (MessagingListener l : mListeners) {
- l.synchronizeMailboxFinished(
- account,
- folder,
- remoteFolder.getMessageCount(), newMessages.size());
- }
-
+
+ localFolder.setLastChecked(System.currentTimeMillis());
+ localFolder.setStatus(null);
+
remoteFolder.close(false);
localFolder.close(false);
+ if (Config.LOGD) {
+ log( "Done synchronizing folder " +
+ account.getDescription() + ":" + folder + " @ " + new Date() +
+ " with " + newMessages.size() + " new messages");
+ }
+
+
+ for (MessagingListener l : getListeners()) {
+ l.synchronizeMailboxFinished(
+ account,
+ folder,
+ remoteMessageCount, newMessages.size());
+ }
+
+ if (commandException != null)
+ {
+ String rootMessage = getRootCauseMessage(commandException);
+ localFolder.setStatus(rootMessage);
+ for (MessagingListener l : getListeners()) {
+ l.synchronizeMailboxFailed(
+ account,
+ folder,
+ rootMessage);
+ }
+ }
+
+
}
catch (Exception e) {
- if (Config.LOGV) {
- Log.v(Email.LOG_TAG, "synchronizeMailbox", e);
- }
- for (MessagingListener l : mListeners) {
+ Log.e(Email.LOG_TAG, "synchronizeMailbox", e);
+ // If we don't set the last checked, it can try too often during
+ // failure conditions
+ String rootMessage = getRootCauseMessage(e);
+ if (tLocalFolder != null)
+ {
+ try
+ {
+ tLocalFolder.setStatus(rootMessage);
+ tLocalFolder.setLastChecked(System.currentTimeMillis());
+ tLocalFolder.close(false);
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Could not set last checked on folder " + account.getDescription() + ":" +
+ tLocalFolder.getName(), e);
+ }
+ }
+
+ for (MessagingListener l : getListeners()) {
l.synchronizeMailboxFailed(
account,
folder,
- e.getMessage());
+ rootMessage);
}
+ addErrorMessage(account, e);
+ log("Failed synchronizing folder " +
+ account.getDescription() + ":" + folder + " @ " + new Date());
+
}
+
+ }
+
+ private String getRootCauseMessage(Throwable t)
+ {
+ Throwable rootCause = t;
+ Throwable nextCause = rootCause;
+ do
+ {
+ nextCause = rootCause.getCause();
+ if (nextCause != null)
+ {
+ rootCause = nextCause;
+ }
+ } while (nextCause != null);
+ return rootCause.getMessage();
}
private void queuePendingCommand(Account account, PendingCommand command) {
@@ -769,6 +1026,8 @@ s * critical data as fast as possible, and then we'll fill in the de
localStore.addPendingCommand(command);
}
catch (Exception e) {
+ addErrorMessage(account, e);
+
throw new RuntimeException("Unable to enqueue pending command", e);
}
}
@@ -783,6 +1042,8 @@ s * critical data as fast as possible, and then we'll fill in the de
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "processPendingCommands", me);
}
+ addErrorMessage(account, me);
+
/*
* Ignore any exceptions from the commands. Commands will be processed
* on the next round.
@@ -797,22 +1058,37 @@ s * critical data as fast as possible, and then we'll fill in the de
account.getLocalStoreUri(),
mApplication);
ArrayList commands = localStore.getPendingCommands();
- for (PendingCommand command : commands) {
- /*
- * We specifically do not catch any exceptions here. If a command fails it is
- * most likely due to a server or IO error and it must be retried before any
- * other command processes. This maintains the order of the commands.
- */
- if (PENDING_COMMAND_APPEND.equals(command.command)) {
- processPendingAppend(command, account);
- }
- else if (PENDING_COMMAND_MARK_READ.equals(command.command)) {
- processPendingMarkRead(command, account);
- }
- else if (PENDING_COMMAND_TRASH.equals(command.command)) {
- processPendingTrash(command, account);
- }
- localStore.removePendingCommand(command);
+ PendingCommand processingCommand = null;
+ try
+ {
+ for (PendingCommand command : commands) {
+ processingCommand = command;
+ /*
+ * We specifically do not catch any exceptions here. If a command fails it is
+ * most likely due to a server or IO error and it must be retried before any
+ * other command processes. This maintains the order of the commands.
+ */
+ if (PENDING_COMMAND_APPEND.equals(command.command)) {
+ processPendingAppend(command, account);
+ }
+ else if (PENDING_COMMAND_MARK_READ.equals(command.command)) {
+ processPendingMarkRead(command, account);
+ }
+ else if (PENDING_COMMAND_MARK_ALL_AS_READ.equals(command.command)) {
+ processPendingMarkAllAsRead(command, account);
+ }
+ else if (PENDING_COMMAND_TRASH.equals(command.command)) {
+ processPendingTrash(command, account);
+ }
+ localStore.removePendingCommand(command);
+ }
+ }
+ catch (MessagingException me)
+ {
+ addErrorMessage(account, me);
+
+ Log.e(Email.LOG_TAG, "Could not process command " + processingCommand, me);
+ throw me;
}
}
@@ -830,9 +1106,16 @@ s * critical data as fast as possible, and then we'll fill in the de
*/
private void processPendingAppend(PendingCommand command, Account account)
throws MessagingException {
+
+
String folder = command.arguments[0];
String uid = command.arguments[1];
+ if (account.getErrorFolderName().equals(folder))
+ {
+ return;
+ }
+
LocalStore localStore = (LocalStore) Store.getInstance(
account.getLocalStoreUri(),
mApplication);
@@ -842,7 +1125,7 @@ s * critical data as fast as possible, and then we'll fill in the de
if (localMessage == null) {
return;
}
-
+
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication);
Folder remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists()) {
@@ -862,6 +1145,31 @@ s * critical data as fast as possible, and then we'll fill in the de
}
if (remoteMessage == null) {
+ if (localMessage.isSet(Flag.X_REMOTE_COPY_STARTED))
+ {
+ Log.w(Email.LOG_TAG, "Local message with uid " + localMessage.getUid() +
+ " has flag " + Flag.X_REMOTE_COPY_STARTED + " already set, checking for remote message with " +
+ " same message id");
+ String rUid = remoteFolder.getUidFromMessageId(localMessage);
+ if (rUid != null)
+ {
+ Log.w(Email.LOG_TAG, "Local message has flag " + Flag.X_REMOTE_COPY_STARTED + " already set, and there is a remote message with " +
+ " uid " + rUid + ", assuming message was already copied and aborting this copy");
+
+ String oldUid = localMessage.getUid();
+ localMessage.setUid(rUid);
+ localFolder.changeUid(localMessage);
+ for (MessagingListener l : getListeners()) {
+ l.messageUidChanged(account, folder, oldUid, localMessage.getUid());
+ }
+ return;
+ }
+ else
+ {
+ Log.w(Email.LOG_TAG, "No remote message with message-id found, proceeding with append");
+ }
+ }
+
/*
* If the message does not exist remotely we just upload it and then
* update our local copy with the new uid.
@@ -870,9 +1178,11 @@ s * critical data as fast as possible, and then we'll fill in the de
fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] { localMessage }, fp, null);
String oldUid = localMessage.getUid();
+ localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true);
remoteFolder.appendMessages(new Message[] { localMessage });
+
localFolder.changeUid(localMessage);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, folder, oldUid, localMessage.getUid());
}
}
@@ -905,12 +1215,16 @@ s * critical data as fast as possible, and then we'll fill in the de
fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] { localMessage }, fp, null);
String oldUid = localMessage.getUid();
+
+ localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true);
+
remoteFolder.appendMessages(new Message[] { localMessage });
localFolder.changeUid(localMessage);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, folder, oldUid, localMessage.getUid());
}
remoteMessage.setFlag(Flag.DELETED, true);
+ remoteFolder.expunge();
}
}
}
@@ -927,42 +1241,43 @@ s * critical data as fast as possible, and then we'll fill in the de
String folder = command.arguments[0];
String uid = command.arguments[1];
+ if (account.getErrorFolderName().equals(folder))
+ {
+ return;
+ }
+
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication);
Folder remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists()) {
+ Log.w(Email.LOG_TAG, "processingPendingTrash: remoteFolder " + folder + " does not exist");
return;
}
remoteFolder.open(OpenMode.READ_WRITE);
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
+ Log.w(Email.LOG_TAG, "processingPendingTrash: could not open remoteFolder " + folder + " read/write");
return;
}
-
+
Message remoteMessage = null;
if (!uid.startsWith("Local")
&& !uid.contains("-")) {
remoteMessage = remoteFolder.getMessage(uid);
}
if (remoteMessage == null) {
+ Log.w(Email.LOG_TAG, "processingPendingTrash: remoteMessage " + uid + " does not exist");
+
return;
}
-
- Folder remoteTrashFolder = remoteStore.getFolder(account.getTrashFolderName());
- /*
- * Attempt to copy the remote message to the remote trash folder.
- */
- if (!remoteTrashFolder.exists()) {
- /*
- * If the remote trash folder doesn't exist we try to create it.
- */
- remoteTrashFolder.create(FolderType.HOLDS_MESSAGES);
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "processingPendingTrash: remote trash folder = " + account.getTrashFolderName());
}
-
- if (remoteTrashFolder.exists()) {
- remoteFolder.copyMessages(new Message[] { remoteMessage }, remoteTrashFolder);
- }
-
- remoteMessage.setFlag(Flag.DELETED, true);
- remoteFolder.expunge();
+
+ remoteMessage.delete(account.getTrashFolderName());
+
+ remoteFolder.close(true);
+
+
}
/**
@@ -975,6 +1290,12 @@ s * critical data as fast as possible, and then we'll fill in the de
throws MessagingException {
String folder = command.arguments[0];
String uid = command.arguments[1];
+
+ if (account.getErrorFolderName().equals(folder))
+ {
+ return;
+ }
+
boolean read = Boolean.parseBoolean(command.arguments[2]);
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication);
@@ -996,6 +1317,129 @@ s * critical data as fast as possible, and then we'll fill in the de
}
remoteMessage.setFlag(Flag.SEEN, read);
}
+
+ private void processPendingMarkAllAsRead(PendingCommand command, Account account) throws MessagingException {
+ String folder = command.arguments[0];
+
+ Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
+ LocalFolder localFolder = (LocalFolder)localStore.getFolder(folder);
+ localFolder.open(OpenMode.READ_WRITE);
+ Message[] messages = localFolder.getMessages(null);
+ for (Message message : messages)
+ {
+ if (message.isSet(Flag.SEEN) == false)
+ {
+ message.setFlag(Flag.SEEN, true);
+ }
+ }
+ localFolder.setUnreadMessageCount(0);
+ for (MessagingListener l : getListeners()) {
+ l.folderStatusChanged(account, folder);
+ }
+ try
+ {
+ if (account.getErrorFolderName().equals(folder))
+ {
+ return;
+ }
+
+ Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication);
+ Folder remoteFolder = remoteStore.getFolder(folder);
+
+ if (!remoteFolder.exists()) {
+ return;
+ }
+ remoteFolder.open(OpenMode.READ_WRITE);
+ if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
+ return;
+ }
+
+ remoteFolder.setFlags(new Flag[] { Flag.SEEN }, true);
+ remoteFolder.close(false);
+ }
+ catch (UnsupportedOperationException uoe)
+ {
+ Log.w(Email.LOG_TAG, "Could not mark all server-side as read because store doesn't support operation", uoe);
+ }
+ finally
+ {
+ localFolder.close(false);
+ }
+
+ }
+
+ static long uidfill = 0;
+ static AtomicBoolean loopCatch = new AtomicBoolean();
+ public void addErrorMessage(Account account, Throwable t)
+ {
+ if (Email.ENABLE_ERROR_FOLDER == false)
+ {
+ return;
+ }
+ if (loopCatch.compareAndSet(false, true) == false)
+ {
+ return;
+ }
+ try
+ {
+ if (t == null)
+ {
+ return;
+ }
+
+ String rootCauseMessage = getRootCauseMessage(t);
+ log("Error" + "'" + rootCauseMessage + "'");
+
+ Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
+ LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName());
+ if (localFolder.exists() == false)
+ {
+ localFolder.create(Folder.FolderType.HOLDS_MESSAGES);
+ }
+ Message[] messages = new Message[1];
+ Message message = new MimeMessage();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos);
+ t.printStackTrace(ps);
+ ps.close();
+ message.setBody(new TextBody(baos.toString()));
+ message.setFlag(Flag.X_DOWNLOADED_FULL, true);
+ message.setSubject(rootCauseMessage);
+
+ long nowTime = System.currentTimeMillis();
+ Date nowDate = new Date(nowTime);
+ message.setInternalDate(nowDate);
+ message.setSentDate(nowDate);
+ message.setFrom(new Address(account.getEmail(), "K9mail internal"));
+ messages[0] = message;
+
+ localFolder.appendMessages(messages);
+
+ localFolder.deleteMessagesOlderThan(nowTime - (15 * 60 * 1000));
+
+ }
+ catch (Throwable it)
+ {
+ Log.e(Email.LOG_TAG, "Could not save error message to " + account.getErrorFolderName(), it);
+ }
+ finally
+ {
+ loopCatch.set(false);
+ }
+ }
+
+
+ public void markAllMessagesRead(final Account account, final String folder)
+ {
+ Log.v(Email.LOG_TAG, "Marking all messages in " + account.getDescription() + ":" + folder + " as read");
+ List args = new ArrayList();
+ args.add(folder);
+ PendingCommand command = new PendingCommand();
+ command.command = PENDING_COMMAND_MARK_ALL_AS_READ;
+ command.arguments = args.toArray(new String[0]);
+ queuePendingCommand(account, command);
+ processPendingCommands(account);
+ }
/**
* Mark the message with the given account, folder and uid either Seen or not Seen.
@@ -1016,6 +1460,12 @@ s * critical data as fast as possible, and then we'll fill in the de
Message message = localFolder.getMessage(uid);
message.setFlag(Flag.SEEN, seen);
+
+ if (account.getErrorFolderName().equals(folder))
+ {
+ return;
+ }
+
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_MARK_READ;
command.arguments = new String[] { folder, uid, Boolean.toString(seen) };
@@ -1023,9 +1473,26 @@ s * critical data as fast as possible, and then we'll fill in the de
processPendingCommands(account);
}
catch (MessagingException me) {
+ addErrorMessage(account, me);
+
throw new RuntimeException(me);
}
}
+
+ public void clearAllPending(final Account account)
+ {
+ try
+ {
+ Log.w(Email.LOG_TAG, "Clearing pending commands!");
+ LocalStore localStore = (LocalStore)Store.getInstance(account.getLocalStoreUri(), mApplication);
+ localStore.removePendingCommands();
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Unable to clear pending command", me);
+ addErrorMessage(account, me);
+ }
+ }
private void loadMessageForViewRemote(final Account account, final String folder,
final String uid, MessagingListener listener) {
@@ -1048,10 +1515,10 @@ s * critical data as fast as possible, and then we'll fill in the de
fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] { message }, fp, null);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewBodyAvailable(account, folder, uid, message);
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewFinished(account, folder, uid, message);
}
localFolder.close(false);
@@ -1086,19 +1553,21 @@ s * critical data as fast as possible, and then we'll fill in the de
// Mark that this message is now fully synched
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewBodyAvailable(account, folder, uid, message);
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewFinished(account, folder, uid, message);
}
remoteFolder.close(false);
localFolder.close(false);
}
catch (Exception e) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewFailed(account, folder, uid, e.getMessage());
}
+ addErrorMessage(account, e);
+
}
}
});
@@ -1106,7 +1575,7 @@ s * critical data as fast as possible, and then we'll fill in the de
public void loadMessageForView(final Account account, final String folder, final String uid,
MessagingListener listener) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewStarted(account, folder, uid);
}
try {
@@ -1116,7 +1585,7 @@ s * critical data as fast as possible, and then we'll fill in the de
Message message = localFolder.getMessage(uid);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewHeadersAvailable(account, folder, uid, message);
}
@@ -1137,19 +1606,29 @@ s * critical data as fast as possible, and then we'll fill in the de
message
}, fp, null);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewBodyAvailable(account, folder, uid, message);
}
+ if (listener != null)
+ {
+ listener.loadMessageForViewBodyAvailable(account, folder, uid, message);
+ }
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewFinished(account, folder, uid, message);
}
+ if (listener != null)
+ {
+ listener.loadMessageForViewFinished(account, folder, uid, message);
+ }
localFolder.close(false);
}
catch (Exception e) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadMessageForViewFailed(account, folder, uid, e.getMessage());
}
+ addErrorMessage(account, e);
+
}
}
@@ -1172,11 +1651,11 @@ s * critical data as fast as possible, and then we'll fill in the de
*/
try {
if (part.getBody() != null) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadAttachmentStarted(account, message, part, tag, false);
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadAttachmentFinished(account, message, part, tag);
}
return;
@@ -1189,7 +1668,7 @@ s * critical data as fast as possible, and then we'll fill in the de
*/
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadAttachmentStarted(account, message, part, tag, true);
}
@@ -1222,7 +1701,7 @@ s * critical data as fast as possible, and then we'll fill in the de
remoteFolder.fetch(new Message[] { message }, fp, null);
localFolder.updateMessage((LocalMessage)message);
localFolder.close(false);
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadAttachmentFinished(account, message, part, tag);
}
}
@@ -1230,9 +1709,11 @@ s * critical data as fast as possible, and then we'll fill in the de
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "", me);
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.loadAttachmentFailed(account, message, part, tag, me.getMessage());
}
+ addErrorMessage(account, me);
+
}
}
});
@@ -1252,6 +1733,10 @@ s * critical data as fast as possible, and then we'll fill in the de
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
LocalFolder localFolder =
(LocalFolder) localStore.getFolder(account.getOutboxFolderName());
+ if (!localFolder.exists())
+ {
+ localFolder.create(Folder.FolderType.HOLDS_MESSAGES);
+ }
localFolder.open(OpenMode.READ_WRITE);
localFolder.appendMessages(new Message[] {
message
@@ -1262,9 +1747,11 @@ s * critical data as fast as possible, and then we'll fill in the de
sendPendingMessages(account, null);
}
catch (Exception e) {
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
// TODO general failed
}
+ addErrorMessage(account, e);
+
}
}
@@ -1297,6 +1784,9 @@ s * critical data as fast as possible, and then we'll fill in the de
if (!localFolder.exists()) {
return;
}
+ for (MessagingListener l : getListeners()) {
+ l.sendPendingMessagesStarted(account);
+ }
localFolder.open(OpenMode.READ_WRITE);
Message[] localMessages = localFolder.getMessages(null);
@@ -1337,9 +1827,27 @@ s * critical data as fast as possible, and then we'll fill in the de
}
catch (Exception e) {
message.setFlag(Flag.X_SEND_FAILED, true);
+ Log.e(Email.LOG_TAG, "Failed to send message", e);
+ for (MessagingListener l : getListeners()) {
+ l.synchronizeMailboxFailed(
+ account,
+ localFolder.getName(),
+ getRootCauseMessage(e));
+ }
+ addErrorMessage(account, e);
+
}
}
catch (Exception e) {
+ Log.e(Email.LOG_TAG, "Failed to fetch message for sending", e);
+ for (MessagingListener l : getListeners()) {
+ l.synchronizeMailboxFailed(
+ account,
+ localFolder.getName(),
+ getRootCauseMessage(e));
+ }
+ addErrorMessage(account, e);
+
/*
* We ignore this exception because a future refresh will retry this
* message.
@@ -1350,14 +1858,16 @@ s * critical data as fast as possible, and then we'll fill in the de
if (localFolder.getMessageCount() == 0) {
localFolder.delete(false);
}
- for (MessagingListener l : mListeners) {
+ for (MessagingListener l : getListeners()) {
l.sendPendingMessagesCompleted(account);
}
}
catch (Exception e) {
- for (MessagingListener l : mListeners) {
- // TODO general failed
+ for (MessagingListener l : getListeners()) {
+ l.sendPendingMessagesFailed(account);
}
+ addErrorMessage(account, e);
+
}
}
@@ -1378,10 +1888,20 @@ s * critical data as fast as possible, and then we'll fill in the de
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
Folder localFolder = localStore.getFolder(folder);
Folder localTrashFolder = localStore.getFolder(account.getTrashFolderName());
-
- localFolder.copyMessages(new Message[] { message }, localTrashFolder);
- message.setFlag(Flag.DELETED, true);
-
+ if (localTrashFolder.exists() == false)
+ {
+ localTrashFolder.create(Folder.FolderType.HOLDS_MESSAGES);
+ }
+ if (localTrashFolder.exists() == true)
+ {
+ localFolder.copyMessages(new Message[] { message }, localTrashFolder);
+ message.setFlag(Flag.DELETED, true);
+ }
+
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "Delete policy for account " + account.getDescription() + " is " + account.getDeletePolicy());
+ }
if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) {
PendingCommand command = new PendingCommand();
command.command = PENDING_COMMAND_TRASH;
@@ -1389,8 +1909,25 @@ s * critical data as fast as possible, and then we'll fill in the de
queuePendingCommand(account, command);
processPendingCommands(account);
}
+ else if (account.getDeletePolicy() == Account.DELETE_POLICY_MARK_AS_READ)
+ {
+ PendingCommand command = new PendingCommand();
+ command.command = PENDING_COMMAND_MARK_READ;
+ command.arguments = new String[] { folder, message.getUid(), Boolean.toString(true) };
+ queuePendingCommand(account, command);
+ processPendingCommands(account);
+ }
+ else
+ {
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server");
+ }
+ }
}
catch (MessagingException me) {
+ addErrorMessage(account, me);
+
throw new RuntimeException("Error deleting message from local store.", me);
}
}
@@ -1398,17 +1935,24 @@ s * critical data as fast as possible, and then we'll fill in the de
public void emptyTrash(final Account account, MessagingListener listener) {
put("emptyTrash", listener, new Runnable() {
public void run() {
- // TODO IMAP
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
Folder localFolder = localStore.getFolder(account.getTrashFolderName());
localFolder.open(OpenMode.READ_WRITE);
- Message[] messages = localFolder.getMessages(null);
- localFolder.setFlags(messages, new Flag[] {
- Flag.DELETED
- }, true);
+ localFolder.setFlags(new Flag[] { Flag.DELETED }, true);
localFolder.close(true);
- for (MessagingListener l : mListeners) {
+
+ Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication);
+
+ Folder remoteFolder = remoteStore.getFolder(account.getTrashFolderName());
+ if (remoteFolder.exists())
+ {
+ remoteFolder.open(OpenMode.READ_WRITE);
+ remoteFolder.setFlags(new Flag [] { Flag.DELETED }, true);
+ remoteFolder.close(true);
+ }
+
+ for (MessagingListener l : getListeners()) {
l.emptyTrashCompleted(account);
}
}
@@ -1417,11 +1961,63 @@ s * critical data as fast as possible, and then we'll fill in the de
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "emptyTrash");
}
+ addErrorMessage(account, e);
}
}
});
}
+ public void sendAlternate(final Context context, Account account, Message message)
+ {
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "About to load message " + account.getDescription() + ":" + message.getFolder().getName()
+ + ":" + message.getUid() + " for sendAlternate");
+ }
+ loadMessageForView(account, message.getFolder().getName(),
+ message.getUid(), new MessagingListener()
+ {
+ @Override
+ public void loadMessageForViewBodyAvailable(Account account, String folder, String uid,
+ Message message)
+ {
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "Got message " + account.getDescription() + ":" + folder
+ + ":" + message.getUid() + " for sendAlternate");
+ }
+
+ try
+ {
+ Intent msg=new Intent(Intent.ACTION_SEND);
+ String quotedText = null;
+ Part part = MimeUtility.findFirstPartByMimeType(message,
+ "text/plain");
+ if (part == null) {
+ part = MimeUtility.findFirstPartByMimeType(message, "text/html");
+ }
+ if (part != null) {
+ quotedText = MimeUtility.getTextFromPart(part);
+ }
+ if (quotedText != null)
+ {
+ msg.putExtra(Intent.EXTRA_TEXT, quotedText);
+ }
+ msg.putExtra(Intent.EXTRA_SUBJECT, "Fwd: " + message.getSubject());
+ msg.setType("text/plain");
+ context.startActivity(Intent.createChooser(msg, context.getString(R.string.send_alternate_chooser_title)));
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Unable to send email through alternate program", me);
+ }
+ }
+ });
+
+ }
+
+
+
/**
* Checks mail for one or multiple accounts. If account is null all accounts
* are checked.
@@ -1432,58 +2028,205 @@ s * critical data as fast as possible, and then we'll fill in the de
*/
public void checkMail(final Context context, final Account account,
final MessagingListener listener) {
- for (MessagingListener l : mListeners) {
+
+
+ for (MessagingListener l : getListeners()) {
l.checkMailStarted(context, account);
}
put("checkMail", listener, new Runnable() {
public void run() {
- NotificationManager notifMgr = (NotificationManager)context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- try {
- Account[] accounts;
- if (account != null) {
- accounts = new Account[] {
- account
- };
- } else {
- accounts = Preferences.getPreferences(context).getAccounts();
- }
- for (Account account : accounts) {
- //We do the math in seconds and not millis
- //since timers are not that accurate
- long now = (long)Math.floor(System.currentTimeMillis() / 1000);
- long autoCheckIntervalTime = account.getAutomaticCheckIntervalMinutes() * 60;
- long lastAutoCheckTime = (long)Math.ceil(account.getLastAutomaticCheckTime() / 1000);
- if (autoCheckIntervalTime>0
- && (now-lastAutoCheckTime)>autoCheckIntervalTime) {
- Notification notif = new Notification(R.drawable.ic_menu_refresh, context.getString(R.string.notification_bg_sync_ticker, account.getDescription()), System.currentTimeMillis());
- Intent intent = FolderMessageList.actionHandleAccountIntent(context, account, Email.INBOX);
- PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0);
- notif.setLatestEventInfo(context, context.getString(R.string.notification_bg_sync_title), account.getDescription(), pi);
+
+ final NotificationManager notifMgr = (NotificationManager)context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ try
+ {
+ Log.i(Email.LOG_TAG, "Starting mail check");
+ Preferences prefs = Preferences.getPreferences(context);
+
+ Account[] accounts;
+ if (account != null) {
+ accounts = new Account[] {
+ account
+ };
+ } else {
+ accounts = prefs.getAccounts();
+ }
+
+ for (final Account account : accounts) {
+ final long accountInterval = account.getAutomaticCheckIntervalMinutes() * 60 * 1000;
+ if (accountInterval <= 0)
+ {
+ if (Config.LOGV || true)
+ {
+ Log.v(Email.LOG_TAG, "Skipping synchronizing account " + account.getDescription());
+ }
+
+ continue;
+ }
+
+ if (Config.LOGV || true)
+ {
+ Log.v(Email.LOG_TAG, "Synchronizing account " + account.getDescription());
+ }
+ putBackground("sendPending " + account.getDescription(), null, new Runnable() {
+ public void run() {
+ Notification notif = new Notification(R.drawable.ic_menu_refresh,
+ context.getString(R.string.notification_bg_send_ticker, account.getDescription()), System.currentTimeMillis());
+ Intent intent = FolderMessageList.actionHandleAccountIntent(context, account, Email.INBOX);
+ PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0);
+ notif.setLatestEventInfo(context, context.getString(R.string.notification_bg_send_title),
+ account.getDescription() , pi);
notif.flags = Notification.FLAG_ONGOING_EVENT;
notifMgr.notify(Email.FETCHING_EMAIL_NOTIFICATION_ID, notif);
-
+ try
+ {
sendPendingMessagesSynchronous(account);
- synchronizeMailboxSynchronous(account, Email.INBOX);
- //This saves the last auto check time even if sync fails
- //TODO: Listen for both send and sync success and failures
- //and only save last auto check time is not errors
- account.setLastAutomaticCheckTime(now*1000);
- account.save(Preferences.getPreferences(context));
+ }
+ finally {
+ notifMgr.cancel(Email.FETCHING_EMAIL_NOTIFICATION_ID);
+ }
}
- }
- for (MessagingListener l : mListeners) {
- l.checkMailFinished(context, account);
- }
- }
- catch (Exception e) {
- for (MessagingListener l : mListeners) {
- l.checkMailFailed(context, account, e.getMessage());
- }
- }
- finally {
- notifMgr.cancel(Email.FETCHING_EMAIL_NOTIFICATION_ID);
- }
+ }
+ );
+ try
+ {
+ Account.FolderMode aDisplayMode = account.getFolderDisplayMode();
+ Account.FolderMode aSyncMode = account.getFolderSyncMode();
+
+ Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
+ for (final Folder folder : localStore.getPersonalNamespaces())
+ {
+
+ folder.open(Folder.OpenMode.READ_WRITE);
+ folder.refresh(prefs);
+
+ Folder.FolderClass fDisplayMode = folder.getDisplayClass();
+ Folder.FolderClass fSyncMode = folder.getSyncClass();
+
+ if ((aDisplayMode == Account.FolderMode.FIRST_CLASS &&
+ fDisplayMode != Folder.FolderClass.FIRST_CLASS)
+ || (aDisplayMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
+ fDisplayMode != Folder.FolderClass.FIRST_CLASS &&
+ fDisplayMode != Folder.FolderClass.SECOND_CLASS)
+ || (aDisplayMode == Account.FolderMode.NOT_SECOND_CLASS &&
+ fDisplayMode == Folder.FolderClass.SECOND_CLASS))
+ {
+ // Never sync a folder that isn't displayed
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "Not syncing folder " + folder.getName() +
+ " which is in display mode " + fDisplayMode + " while account is in display mode " + aDisplayMode);
+ }
+
+ continue;
+ }
+
+ if ((aSyncMode == Account.FolderMode.FIRST_CLASS &&
+ fSyncMode != Folder.FolderClass.FIRST_CLASS)
+ || (aSyncMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
+ fSyncMode != Folder.FolderClass.FIRST_CLASS &&
+ fSyncMode != Folder.FolderClass.SECOND_CLASS)
+ || (aSyncMode == Account.FolderMode.NOT_SECOND_CLASS &&
+ fSyncMode == Folder.FolderClass.SECOND_CLASS))
+ {
+ // Do not sync folders in the wrong class
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "Not syncing folder " + folder.getName() +
+ " which is in sync mode " + fSyncMode + " while account is in sync mode " + aSyncMode);
+ }
+
+ continue;
+ }
+
+
+
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "Folder " + folder.getName() + " was last synced @ " +
+ new Date(folder.getLastChecked()));
+ }
+
+ if (folder.getLastChecked() >
+ (System.currentTimeMillis() - accountInterval))
+ {
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "Not syncing folder " + folder.getName()
+ + ", previously synced @ " + new Date(folder.getLastChecked())
+ + " which would be too recent for the account period");
+ }
+
+ continue;
+ }
+ putBackground("sync" + folder.getName(), null, new Runnable() {
+ public void run() {
+ try {
+ // In case multiple Commands get enqueued, don't run more than
+ // once
+ final LocalStore localStore =
+ (LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication);
+ LocalFolder tLocalFolder = (LocalFolder) localStore.getFolder(folder.getName());
+ tLocalFolder.open(Folder.OpenMode.READ_WRITE);
+
+ if (tLocalFolder.getLastChecked() >
+ (System.currentTimeMillis() - accountInterval))
+ {
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, "Not running Command for folder " + folder.getName()
+ + ", previously synced @ " + new Date(folder.getLastChecked())
+ + " which would be too recent for the account period");
+ }
+ return;
+ }
+ Notification notif = new Notification(R.drawable.ic_menu_refresh,
+ context.getString(R.string.notification_bg_sync_ticker, account.getDescription(), folder.getName()),
+ System.currentTimeMillis());
+ Intent intent = FolderMessageList.actionHandleAccountIntent(context, account, Email.INBOX);
+ PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0);
+ notif.setLatestEventInfo(context, context.getString(R.string.notification_bg_sync_title), account.getDescription()
+ + context.getString(R.string.notification_bg_title_separator) + folder.getName(), pi);
+ notif.flags = Notification.FLAG_ONGOING_EVENT;
+ notifMgr.notify(Email.FETCHING_EMAIL_NOTIFICATION_ID, notif);
+ try
+ {
+ synchronizeMailboxSynchronous(account, folder.getName());
+ }
+
+ finally {
+ notifMgr.cancel(Email.FETCHING_EMAIL_NOTIFICATION_ID);
+ }
+ }
+ catch (Exception e)
+ {
+
+ Log.e(Email.LOG_TAG, "Exception while processing folder " +
+ account.getDescription() + ":" + folder.getName(), e);
+ addErrorMessage(account, e);
+ }
+ }
+ }
+ );
+ }
+ }
+ catch (MessagingException e) {
+ Log.e(Email.LOG_TAG, "Unable to synchronize account " + account.getName(), e);
+ addErrorMessage(account, e);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "Unable to synchronize mail", e);
+ addErrorMessage(account, e);
+ }
+ putBackground("finalize sync", null, new Runnable() {
+ public void run() {
+
+ Log.i(Email.LOG_TAG, "Finished mail sync");
+
+ for (MessagingListener l : getListeners()) {
+ l.checkMailFinished(context, account);
+ }
+ }
+ }
+ );
}
});
}
@@ -1493,6 +2236,10 @@ s * critical data as fast as possible, and then we'll fill in the de
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
LocalFolder localFolder =
(LocalFolder) localStore.getFolder(account.getDraftsFolderName());
+ if (!localFolder.exists())
+ {
+ localFolder.create(Folder.FolderType.HOLDS_MESSAGES);
+ }
localFolder.open(OpenMode.READ_WRITE);
localFolder.appendMessages(new Message[] {
message
@@ -1510,6 +2257,7 @@ s * critical data as fast as possible, and then we'll fill in the de
}
catch (MessagingException e) {
Log.e(Email.LOG_TAG, "Unable to save message as draft.", e);
+ addErrorMessage(account, e);
}
}
diff --git a/src/com/android/email/MessagingListener.java b/src/com/android/email/MessagingListener.java
index f367c6e0b..52557ffd3 100644
--- a/src/com/android/email/MessagingListener.java
+++ b/src/com/android/email/MessagingListener.java
@@ -79,16 +79,26 @@ public class MessagingListener {
public void checkMailFinished(Context context, Account account) {
}
-
+
public void checkMailFailed(Context context, Account account, String reason) {
}
+ public void sendPendingMessagesStarted(Account account) {
+ }
+
public void sendPendingMessagesCompleted(Account account) {
}
+
+ public void sendPendingMessagesFailed(Account account) {
+ }
+
public void emptyTrashCompleted(Account account) {
}
+ public void folderStatusChanged(Account account, String folderName) {
+ }
+
public void messageUidChanged(Account account, String folder, String oldUid, String newUid) {
}
diff --git a/src/com/android/email/Preferences.java b/src/com/android/email/Preferences.java
index 24fa17ee0..f57839879 100644
--- a/src/com/android/email/Preferences.java
+++ b/src/com/android/email/Preferences.java
@@ -12,7 +12,7 @@ import android.util.Log;
public class Preferences {
private static Preferences preferences;
- SharedPreferences mSharedPreferences;
+ public SharedPreferences mSharedPreferences;
private Preferences(Context context) {
mSharedPreferences = context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE);
diff --git a/src/com/android/email/activity/Accounts.java b/src/com/android/email/activity/Accounts.java
index 5701c6c6e..6a2364520 100644
--- a/src/com/android/email/activity/Accounts.java
+++ b/src/com/android/email/activity/Accounts.java
@@ -37,6 +37,7 @@ import com.android.email.R;
import com.android.email.activity.setup.AccountSettings;
import com.android.email.activity.setup.AccountSetupBasics;
import com.android.email.activity.setup.AccountSetupCheckSettings;
+import com.android.email.mail.Folder;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Store;
import com.android.email.mail.store.LocalStore;
@@ -85,7 +86,7 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
NotificationManager notifMgr = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
- notifMgr.cancel(1);
+ notifMgr.cancelAll();
refresh();
}
@@ -106,6 +107,16 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
private void onRefresh() {
MessagingController.getInstance(getApplication()).checkMail(this, null, null);
}
+
+ private void onClearCommands(Account account) {
+ MessagingController.getInstance(getApplication()).clearAllPending(account);
+ }
+
+ private void onEmptyTrash(Account account)
+ {
+ MessagingController.getInstance(getApplication()).emptyTrash(account, null);
+ }
+
private void onCompose() {
Account defaultAccount =
@@ -183,6 +194,12 @@ public class Accounts extends ListActivity implements OnItemClickListener, OnCli
case R.id.open:
onOpenAccount(account);
break;
+ case R.id.clear_pending:
+ onClearCommands(account);
+ break;
+ case R.id.empty_trash:
+ onEmptyTrash(account);
+ break;
}
return true;
}
@@ -286,6 +303,7 @@ getPackageManager().getPackageInfo(getPackageName(), 0);
}
return super.onKeyDown(keyCode, event);
}
+
class AccountsAdapter extends ArrayAdapter {
public AccountsAdapter(Account[] accounts) {
@@ -317,13 +335,7 @@ getPackageManager().getPackageInfo(getPackageName(), 0);
}
int unreadMessageCount = 0;
try {
- LocalStore localStore = (LocalStore) Store.getInstance(
- account.getLocalStoreUri(),
- getApplication());
- LocalFolder localFolder = (LocalFolder) localStore.getFolder(Email.INBOX);
- if (localFolder.exists()) {
- unreadMessageCount = localFolder.getUnreadMessageCount();
- }
+ unreadMessageCount = account.getUnreadMessageCount(Accounts.this, getApplication());
}
catch (MessagingException me) {
/*
diff --git a/src/com/android/email/activity/FolderMessageList.java b/src/com/android/email/activity/FolderMessageList.java
index f67d2b959..1dad9ccd6 100644
--- a/src/com/android/email/activity/FolderMessageList.java
+++ b/src/com/android/email/activity/FolderMessageList.java
@@ -1,6 +1,7 @@
package com.android.email.activity;
import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -42,50 +43,56 @@ import com.android.email.Preferences;
import com.android.email.activity.FolderMessageList.FolderMessageListAdapter.FolderInfoHolder;
import com.android.email.activity.FolderMessageList.FolderMessageListAdapter.MessageInfoHolder;
import com.android.email.activity.setup.AccountSettings;
+import com.android.email.activity.setup.FolderSettings;
import com.android.email.mail.Address;
import com.android.email.mail.Flag;
import com.android.email.mail.Folder;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
+import com.android.email.mail.Part;
+import com.android.email.mail.Store;
import com.android.email.mail.Message.RecipientType;
+import com.android.email.mail.internet.MimeUtility;
+import com.android.email.mail.store.LocalStore.LocalFolder;
import com.android.email.mail.store.LocalStore.LocalMessage;
import com.android.email.mail.store.LocalStore;
/**
- * FolderMessageList is the primary user interface for the program. This Activity shows
- * a two level list of the Account's folders and each folder's messages. From this
- * Activity the user can perform all standard message operations.
+ * FolderMessageList is the primary user interface for the program. This
+ * Activity shows a two level list of the Account's folders and each folder's
+ * messages. From this Activity the user can perform all standard message
+ * operations.
*
*
- * TODO some things that are slowing us down:
- * Need a way to remove state such as progress bar and per folder progress on
- * resume if the command has completed.
+ * TODO some things that are slowing us down: Need a way to remove state such as
+ * progress bar and per folder progress on resume if the command has completed.
*
- * TODO
- * Break out seperate functions for:
- * refresh local folders
- * refresh remote folders
- * refresh open folder local messages
- * refresh open folder remote messages
+ * TODO Break out seperate functions for: refresh local folders refresh remote
+ * folders refresh open folder local messages refresh open folder remote
+ * messages
*
- * And don't refresh remote folders ever unless the user runs a refresh. Maybe not even then.
+ * And don't refresh remote folders ever unless the user runs a refresh. Maybe
+ * not even then.
*/
-public class FolderMessageList extends ExpandableListActivity {
+public class FolderMessageList extends ExpandableListActivity
+{
private static final String EXTRA_ACCOUNT = "account";
+
private static final String EXTRA_CLEAR_NOTIFICATION = "clearNotification";
+
private static final String EXTRA_INITIAL_FOLDER = "initialFolder";
- private static final String STATE_KEY_LIST =
- "com.android.email.activity.folderlist_expandableListState";
- private static final String STATE_KEY_EXPANDED_GROUP =
- "com.android.email.activity.folderlist_expandedGroup";
- private static final String STATE_KEY_EXPANDED_GROUP_SELECTION =
- "com.android.email.activity.folderlist_expandedGroupSelection";
+ private static final String STATE_KEY_LIST = "com.android.email.activity.folderlist_expandableListState";
- private static final int UPDATE_FOLDER_ON_EXPAND_INTERVAL_MS = (1000 * 60 * 3);
+ private static final String STATE_KEY_EXPANDED_GROUP = "com.android.email.activity.folderlist_expandedGroup";
- private static final int[] colorChipResIds = new int[] {
- R.drawable.appointment_indicator_leftside_1,
+ private static final String STATE_KEY_EXPANDED_GROUP_SELECTION = "com.android.email.activity.folderlist_expandedGroupSelection";
+
+ // private static final int UPDATE_FOLDER_ON_EXPAND_INTERVAL_MS = (1000 * 60 *
+ // 3);
+
+ private static final int[] colorChipResIds = new int[]
+ { R.drawable.appointment_indicator_leftside_1,
R.drawable.appointment_indicator_leftside_2,
R.drawable.appointment_indicator_leftside_3,
R.drawable.appointment_indicator_leftside_4,
@@ -105,43 +112,84 @@ public class FolderMessageList extends ExpandableListActivity {
R.drawable.appointment_indicator_leftside_18,
R.drawable.appointment_indicator_leftside_19,
R.drawable.appointment_indicator_leftside_20,
- R.drawable.appointment_indicator_leftside_21,
- };
+ R.drawable.appointment_indicator_leftside_21, };
private ExpandableListView mListView;
+
private int colorChipResId;
private FolderMessageListAdapter mAdapter;
+
private LayoutInflater mInflater;
+
private Account mAccount;
+
/**
- * Stores the name of the folder that we want to open as soon as possible after load. It is
- * set to null once the folder has been opened once.
+ * Stores the name of the folder that we want to open as soon as possible
+ * after load. It is set to null once the folder has been opened once.
*/
private String mInitialFolder;
- private DateFormat mDateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
- private DateFormat mTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
+ private int mExpandedGroup = -1;
- private int mExpandedGroup = -1;
private boolean mRestoringState;
private boolean mRefreshRemote;
private FolderMessageListHandler mHandler = new FolderMessageListHandler();
- class FolderMessageListHandler extends Handler {
- private static final int MSG_PROGRESS = 2;
- private static final int MSG_DATA_CHANGED = 3;
- private static final int MSG_EXPAND_GROUP = 5;
- private static final int MSG_FOLDER_LOADING = 7;
- private static final int MSG_REMOVE_MESSAGE = 11;
- private static final int MSG_SYNC_MESSAGES = 13;
- private static final int MSG_FOLDER_STATUS = 17;
+ private DateFormat dateFormat = null;
+ private DateFormat timeFormat = null;
+
+ private DateFormat getDateFormat()
+ {
+ if (dateFormat == null)
+ {
+ dateFormat = android.pim.DateFormat.getDateFormat(getApplication());
+ }
+ return dateFormat;
+ }
+
+ private DateFormat getTimeFormat()
+ {
+ if (timeFormat == null)
+ {
+ timeFormat = android.pim.DateFormat.getTimeFormat(getApplication());
+ }
+ return timeFormat;
+ }
+
+ private void clearFormats()
+ {
+ dateFormat = null;
+ timeFormat = null;
+ }
+
+ class FolderMessageListHandler extends Handler
+ {
+ private static final int MSG_PROGRESS = 2;
+
+ private static final int MSG_DATA_CHANGED = 3;
+
+ private static final int MSG_EXPAND_GROUP = 5;
+
+ private static final int MSG_FOLDER_LOADING = 7;
+
+ private static final int MSG_REMOVE_MESSAGE = 11;
+
+ private static final int MSG_SYNC_MESSAGES = 13;
+
+ //private static final int MSG_FOLDER_STATUS = 17;
+
+ private static final int MSG_FOLDER_SYNCING = 18;
+
+ private static final int MSG_SENDING_OUTBOX = 19;
@Override
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
+ public void handleMessage(android.os.Message msg)
+ {
+ switch (msg.what)
+ {
case MSG_PROGRESS:
setProgressBarIndeterminateVisibility(msg.arg1 != 0);
break;
@@ -152,66 +200,104 @@ public class FolderMessageList extends ExpandableListActivity {
mListView.expandGroup(msg.arg1);
break;
/*
- * The following functions modify the state of the adapter's underlying list and
- * must be run here, in the main thread, so that notifyDataSetChanged is run
- * before any further requests are made to the adapter.
+ * The following functions modify the state of the adapter's underlying
+ * list and must be run here, in the main thread, so that
+ * notifyDataSetChanged is run before any further requests are made to
+ * the adapter.
*/
- case MSG_FOLDER_LOADING: {
+ case MSG_FOLDER_LOADING:
+ {
FolderInfoHolder folder = mAdapter.getFolder((String) msg.obj);
- if (folder != null) {
+ if (folder != null)
+ {
folder.loading = msg.arg1 != 0;
mAdapter.notifyDataSetChanged();
}
break;
}
- case MSG_REMOVE_MESSAGE: {
+ case MSG_REMOVE_MESSAGE:
+ {
FolderInfoHolder folder = (FolderInfoHolder) ((Object[]) msg.obj)[0];
MessageInfoHolder message = (MessageInfoHolder) ((Object[]) msg.obj)[1];
folder.messages.remove(message);
mAdapter.notifyDataSetChanged();
break;
}
- case MSG_SYNC_MESSAGES: {
+ case MSG_SYNC_MESSAGES:
+ {
FolderInfoHolder folder = (FolderInfoHolder) ((Object[]) msg.obj)[0];
Message[] messages = (Message[]) ((Object[]) msg.obj)[1];
folder.messages.clear();
- for (Message message : messages) {
+ for (Message message : messages)
+ {
mAdapter.addOrUpdateMessage(folder, message, false, false);
}
Collections.sort(folder.messages);
mAdapter.notifyDataSetChanged();
break;
}
- case MSG_FOLDER_STATUS: {
+// case MSG_FOLDER_STATUS:
+// {
+// String folderName = (String) ((Object[]) msg.obj)[0];
+// String status = (String) ((Object[]) msg.obj)[1];
+// FolderInfoHolder folder = mAdapter.getFolder(folderName);
+// if (folder != null)
+// {
+// folder.status = status;
+// mAdapter.notifyDataSetChanged();
+// }
+// break;
+// }
+ case MSG_FOLDER_SYNCING:
+ {
String folderName = (String) ((Object[]) msg.obj)[0];
- String status = (String) ((Object[]) msg.obj)[1];
- FolderInfoHolder folder = mAdapter.getFolder(folderName);
- if (folder != null) {
- folder.status = status;
- mAdapter.notifyDataSetChanged();
+ String dispString;
+ dispString = mAccount.getDescription();
+ if (folderName != null)
+ {
+ dispString += " (" + getString(R.string.status_loading)
+ + folderName + ")";
}
+ setTitle(dispString);
break;
}
+ case MSG_SENDING_OUTBOX:
+ {
+ boolean sending = (msg.arg1 != 0);
+ String dispString;
+ dispString = mAccount.getDescription();
+ if (sending)
+ {
+ dispString += " (" + getString(R.string.status_sending) + ")";
+ }
+ setTitle(dispString);
+ break;
+ }
default:
super.handleMessage(msg);
}
}
- public void synchronizeMessages(FolderInfoHolder folder, Message[] messages) {
+ public void synchronizeMessages(FolderInfoHolder folder, Message[] messages)
+ {
android.os.Message msg = new android.os.Message();
msg.what = MSG_SYNC_MESSAGES;
- msg.obj = new Object[] { folder, messages };
+ msg.obj = new Object[]
+ { folder, messages };
sendMessage(msg);
}
- public void removeMessage(FolderInfoHolder folder, MessageInfoHolder message) {
+ public void removeMessage(FolderInfoHolder folder, MessageInfoHolder message)
+ {
android.os.Message msg = new android.os.Message();
msg.what = MSG_REMOVE_MESSAGE;
- msg.obj = new Object[] { folder, message };
+ msg.obj = new Object[]
+ { folder, message };
sendMessage(msg);
}
- public void folderLoading(String folder, boolean loading) {
+ public void folderLoading(String folder, boolean loading)
+ {
android.os.Message msg = new android.os.Message();
msg.what = MSG_FOLDER_LOADING;
msg.arg1 = loading ? 1 : 0;
@@ -219,100 +305,149 @@ public class FolderMessageList extends ExpandableListActivity {
sendMessage(msg);
}
- public void progress(boolean progress) {
+ public void progress(boolean progress)
+ {
android.os.Message msg = new android.os.Message();
msg.what = MSG_PROGRESS;
msg.arg1 = progress ? 1 : 0;
sendMessage(msg);
}
- public void dataChanged() {
+ public void dataChanged()
+ {
sendEmptyMessage(MSG_DATA_CHANGED);
}
- public void expandGroup(int groupPosition) {
+ public void expandGroup(int groupPosition)
+ {
android.os.Message msg = new android.os.Message();
msg.what = MSG_EXPAND_GROUP;
msg.arg1 = groupPosition;
sendMessage(msg);
}
- public void folderStatus(String folder, String status) {
+// public void folderStatus(String folder, String status)
+// {
+// android.os.Message msg = new android.os.Message();
+// msg.what = MSG_FOLDER_STATUS;
+// msg.obj = new String[]
+// { folder, status };
+// sendMessage(msg);
+// }
+
+ public void folderSyncing(String folder)
+ {
android.os.Message msg = new android.os.Message();
- msg.what = MSG_FOLDER_STATUS;
- msg.obj = new String[] { folder, status };
+ msg.what = MSG_FOLDER_SYNCING;
+ msg.obj = new String[]
+ { folder };
sendMessage(msg);
}
+
+ public void sendingOutbox(boolean sending)
+ {
+ android.os.Message msg = new android.os.Message();
+ msg.what = MSG_SENDING_OUTBOX;
+ msg.arg1 = sending ? 1 : 0;
+ sendMessage(msg);
}
+ }
/**
- * This class is responsible for reloading the list of local messages for a given folder,
- * notifying the adapter that the message have been loaded and queueing up a remote
- * update of the folder.
+ * This class is responsible for reloading the list of local messages for a
+ * given folder, notifying the adapter that the message have been loaded and
+ * queueing up a remote update of the folder.
*/
- class FolderUpdateWorker implements Runnable {
+ class FolderUpdateWorker implements Runnable
+ {
String mFolder;
+
boolean mSynchronizeRemote;
/**
- * Create a worker for the given folder and specifying whether the
- * worker should synchronize the remote folder or just the local one.
+ * Create a worker for the given folder and specifying whether the worker
+ * should synchronize the remote folder or just the local one.
+ *
* @param folder
* @param synchronizeRemote
*/
- public FolderUpdateWorker(String folder, boolean synchronizeRemote) {
+ public FolderUpdateWorker(String folder, boolean synchronizeRemote)
+ {
mFolder = folder;
mSynchronizeRemote = synchronizeRemote;
}
- public void run() {
+ public void run()
+ {
// Lower our priority
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Synchronously load the list of local messages
MessagingController.getInstance(getApplication()).listLocalMessages(
- mAccount,
- mFolder,
- mAdapter.mListener);
- if (mSynchronizeRemote) {
+ mAccount, mFolder, mAdapter.mListener);
+ try
+ {
+ Store localStore = Store.getInstance(mAccount.getLocalStoreUri(),
+ getApplication());
+ LocalFolder localFolder = (LocalFolder) localStore.getFolder(mFolder);
+ if (localFolder.getMessageCount() == 0 && localFolder.getLastChecked() <= 0)
+ {
+ mSynchronizeRemote = true;
+ }
+ } catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG,
+ "Unable to get count of local messages for folder " + mFolder, me);
+ }
+
+ if (mSynchronizeRemote)
+ {
// Tell the MessagingController to run a remote update of this folder
// at it's leisure
MessagingController.getInstance(getApplication()).synchronizeMailbox(
- mAccount,
- mFolder,
- mAdapter.mListener);
+ mAccount, mFolder, mAdapter.mListener);
}
}
}
- public static void actionHandleAccount(Context context, Account account, String initialFolder) {
+ public static void actionHandleAccount(Context context, Account account,
+ String initialFolder)
+ {
Intent intent = new Intent(context, FolderMessageList.class);
intent.putExtra(EXTRA_ACCOUNT, account);
- if (initialFolder != null) {
+ if (initialFolder != null)
+ {
intent.putExtra(EXTRA_INITIAL_FOLDER, initialFolder);
}
context.startActivity(intent);
}
- public static void actionHandleAccount(Context context, Account account) {
+ public static void actionHandleAccount(Context context, Account account)
+ {
actionHandleAccount(context, account, null);
}
- public static Intent actionHandleAccountIntent(Context context, Account account, String initialFolder) {
+ public static Intent actionHandleAccountIntent(Context context,
+ Account account, String initialFolder)
+ {
Intent intent = new Intent(context, FolderMessageList.class);
intent.putExtra(EXTRA_ACCOUNT, account);
intent.putExtra(EXTRA_CLEAR_NOTIFICATION, true);
- if (initialFolder != null) {
+ if (initialFolder != null)
+ {
intent.putExtra(EXTRA_INITIAL_FOLDER, initialFolder);
}
return intent;
}
- public static Intent actionHandleAccountIntent(Context context, Account account) {
+ public static Intent actionHandleAccountIntent(Context context,
+ Account account)
+ {
return actionHandleAccountIntent(context, account, null);
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(Bundle savedInstanceState)
+ {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
@@ -323,7 +458,8 @@ public class FolderMessageList extends ExpandableListActivity {
registerForContextMenu(mListView);
/*
- * We manually save and restore the list's state because our adapter is slow.
+ * We manually save and restore the list's state because our adapter is
+ * slow.
*/
mListView.setSaveEnabled(false);
@@ -335,28 +471,33 @@ public class FolderMessageList extends ExpandableListActivity {
Intent intent = getIntent();
mAccount = (Account)intent.getSerializableExtra(EXTRA_ACCOUNT);
- // Take the initial folder into account only if we are *not* restoring the activity already
- if (savedInstanceState == null) {
+ // Take the initial folder into account only if we are *not* restoring the
+ // activity already
+ if (savedInstanceState == null)
+ {
mInitialFolder = intent.getStringExtra(EXTRA_INITIAL_FOLDER);
}
/*
- * Since the color chip is always the same color for a given account we just cache the id
- * of the chip right here.
+ * Since the color chip is always the same color for a given account we just
+ * cache the id of the chip right here.
*/
- colorChipResId = colorChipResIds[mAccount.getAccountNumber() % colorChipResIds.length];
+ colorChipResId = colorChipResIds[mAccount.getAccountNumber()
+ % colorChipResIds.length];
mAdapter = new FolderMessageListAdapter();
final Object previousData = getLastNonConfigurationInstance();
- if (previousData != null) {
+ if (previousData != null)
+ {
//noinspection unchecked
mAdapter.mFolders = (ArrayList) previousData;
}
setListAdapter(mAdapter);
- if (savedInstanceState != null) {
+ if (savedInstanceState != null)
+ {
mRestoringState = true;
onRestoreListState(savedInstanceState);
mRestoringState = false;
@@ -365,88 +506,101 @@ public class FolderMessageList extends ExpandableListActivity {
setTitle(mAccount.getDescription());
}
- private void onRestoreListState(Bundle savedInstanceState) {
- final int expandedGroup = savedInstanceState.getInt(STATE_KEY_EXPANDED_GROUP, -1);
- if (expandedGroup >= 0 && mAdapter.getGroupCount() > expandedGroup) {
+ private void onRestoreListState(Bundle savedInstanceState)
+ {
+ final int expandedGroup = savedInstanceState.getInt(
+ STATE_KEY_EXPANDED_GROUP, -1);
+ if (expandedGroup >= 0 && mAdapter.getGroupCount() > expandedGroup)
+ {
mListView.expandGroup(expandedGroup);
- long selectedChild = savedInstanceState.getLong(STATE_KEY_EXPANDED_GROUP_SELECTION, -1);
- if (selectedChild != ExpandableListView.PACKED_POSITION_VALUE_NULL) {
+ long selectedChild = savedInstanceState.getLong(
+ STATE_KEY_EXPANDED_GROUP_SELECTION, -1);
+ if (selectedChild != ExpandableListView.PACKED_POSITION_VALUE_NULL)
+ {
mListView.setSelection(mListView.getFlatListPosition(selectedChild));
}
}
- mListView.onRestoreInstanceState(savedInstanceState.getParcelable(STATE_KEY_LIST));
+ mListView.onRestoreInstanceState(savedInstanceState
+ .getParcelable(STATE_KEY_LIST));
}
@Override
- public Object onRetainNonConfigurationInstance() {
+ public Object onRetainNonConfigurationInstance()
+ {
return mAdapter.mFolders;
}
@Override
- public void onPause() {
+ public void onPause()
+ {
super.onPause();
- MessagingController.getInstance(getApplication()).removeListener(mAdapter.mListener);
+ MessagingController.getInstance(getApplication()).removeListener(
+ mAdapter.mListener);
}
/**
- * On resume we refresh the folder list (in the background) and we refresh the messages
- * for any folder that is currently open. This guarantees that things like unread message
- * count and read status are updated.
+ * On resume we refresh the folder list (in the background) and we refresh the
+ * messages for any folder that is currently open. This guarantees that things
+ * like unread message count and read status are updated.
*/
@Override
- public void onResume() {
+ public void onResume()
+ {
super.onResume();
+ clearFormats();
- NotificationManager notifMgr = (NotificationManager)
- getSystemService(Context.NOTIFICATION_SERVICE);
- notifMgr.cancel(Email.NEW_EMAIL_NOTIFICATION_ID);
-
- MessagingController.getInstance(getApplication()).addListener(mAdapter.mListener);
+ MessagingController.getInstance(getApplication()).addListener(
+ mAdapter.mListener);
mAccount.refresh(Preferences.getPreferences(this));
onRefresh(false);
+
+ NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notifMgr.cancel(mAccount.getAccountNumber());
+
}
@Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(Bundle outState)
+ {
super.onSaveInstanceState(outState);
outState.putParcelable(STATE_KEY_LIST, mListView.onSaveInstanceState());
outState.putInt(STATE_KEY_EXPANDED_GROUP, mExpandedGroup);
- outState.putLong(STATE_KEY_EXPANDED_GROUP_SELECTION, mListView.getSelectedPosition());
+ outState.putLong(STATE_KEY_EXPANDED_GROUP_SELECTION, mListView
+ .getSelectedPosition());
}
@Override
- public void onGroupCollapse(int groupPosition) {
+ public void onGroupCollapse(int groupPosition)
+ {
super.onGroupCollapse(groupPosition);
mExpandedGroup = -1;
}
@Override
- public void onGroupExpand(int groupPosition) {
+ public void onGroupExpand(int groupPosition)
+ {
super.onGroupExpand(groupPosition);
- if (mExpandedGroup != -1) {
+ if (mExpandedGroup != -1)
+ {
mListView.collapseGroup(mExpandedGroup);
}
mExpandedGroup = groupPosition;
- if (!mRestoringState) {
+ if (!mRestoringState)
+ {
/*
* Scroll the selected item to the top of the screen.
*/
- int position = mListView.getFlatListPosition(
- ExpandableListView.getPackedPositionForGroup(groupPosition));
+ int position = mListView.getFlatListPosition(ExpandableListView
+ .getPackedPositionForGroup(groupPosition));
mListView.setSelectionFromTop(position, 0);
}
- final FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(groupPosition);
- /*
- * We'll only do a hard refresh of a particular folder every 3 minutes or if the user
- * specifically asks for a refresh.
- */
- if (System.currentTimeMillis() - folder.lastChecked
- > UPDATE_FOLDER_ON_EXPAND_INTERVAL_MS) {
- folder.lastChecked = System.currentTimeMillis();
- // TODO: If the previous thread is already running, we should cancel it
- new Thread(new FolderUpdateWorker(folder.name, true)).start();
+ final FolderInfoHolder folder = (FolderInfoHolder) mAdapter
+ .getGroup(groupPosition);
+ if (folder.messages.size() == 0)
+ {
+ new Thread(new FolderUpdateWorker(folder.name, false)).start();
}
}
@@ -458,30 +612,34 @@ public class FolderMessageList extends ExpandableListActivity {
case KeyEvent.KEYCODE_C: { onCompose(); return true;}
case KeyEvent.KEYCODE_Q: { onAccounts(); return true; }
case KeyEvent.KEYCODE_S: { onEditAccount(); return true; }
- case KeyEvent.KEYCODE_L: {
- long lastAutoCheckTime = mAccount.getLastAutomaticCheckTime();
- Toast.makeText(this, (new Date(lastAutoCheckTime)).toString(), Toast.LENGTH_LONG).show();
- return true;
- }
}//switch
+
+ long packedPosition = mListView.getSelectedPosition();
+ int group = ExpandableListView
+ .getPackedPositionGroup(packedPosition);
+ int child = ExpandableListView
+ .getPackedPositionChild(packedPosition);
- //Shortcuts that only work when a message is selected
- int group = mListView.getPackedPositionGroup(mListView.getSelectedId());
- int item =(mListView.getSelectedItemPosition() -1 );
- // Guard against hitting delete on group names
try {
- MessageInfoHolder message = (MessageInfoHolder) mAdapter.getChild(group, item);
- switch (keyCode) {
- case KeyEvent.KEYCODE_DEL: { onDelete(message); return true;}
- case KeyEvent.KEYCODE_D: { onDelete(message); return true;}
- case KeyEvent.KEYCODE_F: { onForward(message); return true;}
- case KeyEvent.KEYCODE_A: { onReplyAll(message); return true; }
- case KeyEvent.KEYCODE_R: { onReply(message); return true; }
- }
+ if (group >= 0 && child >= 0)
+ {
+
+ MessageInfoHolder message = (MessageInfoHolder) mAdapter.getChild(group, child);
+ if (message != null)
+ {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DEL: { onDelete(message); return true;}
+ case KeyEvent.KEYCODE_D: { onDelete(message); return true;}
+ case KeyEvent.KEYCODE_F: { onForward(message); return true;}
+ case KeyEvent.KEYCODE_A: { onReplyAll(message); return true; }
+ case KeyEvent.KEYCODE_R: { onReply(message); return true; }
+ }
+ }
+ }
}
finally {
- return super.onKeyDown(keyCode, event);
- }
+ return super.onKeyDown(keyCode, event);
+ }
}//onKeyDown
@@ -492,125 +650,162 @@ public class FolderMessageList extends ExpandableListActivity {
if (folder.outbox) {
return false;
}
- if (childPosition == folder.messages.size() && !folder.loading) {
- if (folder.status == null) {
+ if (childPosition == folder.messages.size() && !folder.loading)
+ {
+ if (folder.status == null)
+ {
MessagingController.getInstance(getApplication()).loadMoreMessages(
- mAccount,
- folder.name,
- mAdapter.mListener);
+ mAccount, folder.name, mAdapter.mListener);
return false;
- }
- else {
+ } else
+ {
MessagingController.getInstance(getApplication()).synchronizeMailbox(
- mAccount,
- folder.name,
- mAdapter.mListener);
+ mAccount, folder.name, mAdapter.mListener);
return false;
}
- }
- else if (childPosition >= folder.messages.size()) {
+ } else if (childPosition >= folder.messages.size())
+ {
return false;
}
- MessageInfoHolder message = (MessageInfoHolder) mAdapter.getChild(groupPosition, childPosition);
+ MessageInfoHolder message = (MessageInfoHolder) mAdapter.getChild(
+ groupPosition, childPosition);
onOpenMessage(folder, message);
return true;
}
- private void onRefresh(final boolean forceRemote) {
- if (forceRemote) {
+ private void onRefresh(final boolean forceRemote)
+ {
+ if (forceRemote)
+ {
mRefreshRemote = true;
}
- new Thread() {
- public void run() {
+ new Thread()
+ {
+ public void run()
+ {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- MessagingController.getInstance(getApplication()).listFolders(
- mAccount,
- forceRemote,
- mAdapter.mListener);
- if (forceRemote) {
- MessagingController.getInstance(getApplication()).sendPendingMessages(
- mAccount,
- null);
+ MessagingController.getInstance(getApplication()).listFolders(mAccount,
+ forceRemote, mAdapter.mListener);
+ if (forceRemote)
+ {
+ MessagingController.getInstance(getApplication())
+ .sendPendingMessages(mAccount, null);
}
}
}.start();
}
- private void onOpenMessage(FolderInfoHolder folder, MessageInfoHolder message) {
+ private void onOpenMessage(FolderInfoHolder folder, MessageInfoHolder message)
+ {
/*
- * We set read=true here for UI performance reasons. The actual value will get picked up
- * on the refresh when the Activity is resumed but that may take a second or so and we
- * don't want this to show and then go away.
- * I've gone back and forth on this, and this gives a better UI experience, so I am
- * putting it back in.
+ * We set read=true here for UI performance reasons. The actual value will
+ * get picked up on the refresh when the Activity is resumed but that may
+ * take a second or so and we don't want this to show and then go away. I've
+ * gone back and forth on this, and this gives a better UI experience, so I
+ * am putting it back in.
*/
- if (!message.read) {
+ if (!message.read)
+ {
message.read = true;
mHandler.dataChanged();
}
- if (folder.name.equals(mAccount.getDraftsFolderName())) {
+ if (folder.name.equals(mAccount.getDraftsFolderName()))
+ {
MessageCompose.actionEditDraft(this, mAccount, message.message);
- }
- else {
+ } else
+ {
ArrayList folderUids = new ArrayList();
- for (MessageInfoHolder holder : folder.messages) {
+ for (MessageInfoHolder holder : folder.messages)
+ {
folderUids.add(holder.uid);
}
- MessageView.actionView(this, mAccount, folder.name, message.uid, folderUids);
+ MessageView.actionView(this, mAccount, folder.name, message.uid,
+ folderUids);
}
}
- private void onEditAccount() {
+ private void onEditAccount()
+ {
AccountSettings.actionSettings(this, mAccount);
}
- private void onAccounts() {
+ private void onEditFolder(Account account, String folderName)
+ {
+ FolderSettings.actionSettings(this, account, folderName);
+ }
+
+
+ private void onAccounts()
+ {
startActivity(new Intent(this, Accounts.class));
finish();
}
- private void onCompose() {
+ private void onCompose()
+ {
MessageCompose.actionCompose(this, mAccount);
}
- private void onDelete(MessageInfoHolder holder) {
- MessagingController.getInstance(getApplication()).deleteMessage(
- mAccount,
- holder.message.getFolder().getName(),
- holder.message,
- null);
+ private void onDelete(MessageInfoHolder holder)
+ {
+
+ MessagingController.getInstance(getApplication()).deleteMessage(mAccount,
+ holder.message.getFolder().getName(), holder.message, null);
mAdapter.removeMessage(holder.message.getFolder().getName(), holder.uid);
- Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT)
+ .show();
+ onRefresh(false);
}
- private void onReply(MessageInfoHolder holder) {
+ private void onReply(MessageInfoHolder holder)
+ {
MessageCompose.actionReply(this, mAccount, holder.message, false);
}
- private void onReplyAll(MessageInfoHolder holder) {
+ private void onReplyAll(MessageInfoHolder holder)
+ {
MessageCompose.actionReply(this, mAccount, holder.message, true);
}
- private void onForward(MessageInfoHolder holder) {
+ private void onForward(MessageInfoHolder holder)
+ {
MessageCompose.actionForward(this, mAccount, holder.message);
}
- private void onToggleRead(MessageInfoHolder holder) {
- MessagingController.getInstance(getApplication()).markMessageRead(
- mAccount,
- holder.message.getFolder().getName(),
- holder.uid,
- !holder.read);
+ private void onMarkAllAsRead(Account account, FolderInfoHolder folder)
+ {
+ MessagingController.getInstance(getApplication()).markAllMessagesRead(mAccount,
+ folder.name);
+
+ for (MessageInfoHolder holder : folder.messages){
+ holder.read = true;
+ }
+
+ onRefresh(false);
+ }
+
+ private void onEmptyTrash(Account account)
+ {
+ MessagingController.getInstance(getApplication()).emptyTrash(account, null);
+ }
+
+
+ private void onToggleRead(MessageInfoHolder holder)
+ {
+ MessagingController.getInstance(getApplication()).markMessageRead(mAccount,
+ holder.message.getFolder().getName(), holder.uid, !holder.read);
holder.read = !holder.read;
onRefresh(false);
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ switch (item.getItemId())
+ {
case R.id.refresh:
onRefresh(true);
return true;
@@ -623,31 +818,40 @@ public class FolderMessageList extends ExpandableListActivity {
case R.id.account_settings:
onEditAccount();
return true;
+ case R.id.empty_trash:
+ onEmptyTrash(mAccount);
+ return true;
+
default:
return super.onOptionsItemSelected(item);
}
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
+ public boolean onCreateOptionsMenu(Menu menu)
+ {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.folder_message_list_option, menu);
return true;
}
@Override
- public boolean onContextItemSelected(MenuItem item) {
- ExpandableListContextMenuInfo info =
- (ExpandableListContextMenuInfo) item.getMenuInfo();
- int groupPosition =
- ExpandableListView.getPackedPositionGroup(info.packedPosition);
- int childPosition =
- ExpandableListView.getPackedPositionChild(info.packedPosition);
- FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(groupPosition);
- if (childPosition < mAdapter.getChildrenCount(groupPosition)) {
- MessageInfoHolder holder =
- (MessageInfoHolder) mAdapter.getChild(groupPosition, childPosition);
- switch (item.getItemId()) {
+ public boolean onContextItemSelected(MenuItem item)
+ {
+ ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item
+ .getMenuInfo();
+ int groupPosition = ExpandableListView
+ .getPackedPositionGroup(info.packedPosition);
+ int childPosition = ExpandableListView
+ .getPackedPositionChild(info.packedPosition);
+ FolderInfoHolder folder = (FolderInfoHolder) mAdapter
+ .getGroup(groupPosition);
+ if (childPosition >= 0 && childPosition < mAdapter.getChildrenCount(groupPosition))
+ {
+ MessageInfoHolder holder = (MessageInfoHolder) mAdapter.getChild(
+ groupPosition, childPosition);
+ switch (item.getItemId())
+ {
case R.id.open:
onOpenMessage(folder, holder);
break;
@@ -666,126 +870,250 @@ public class FolderMessageList extends ExpandableListActivity {
case R.id.mark_as_read:
onToggleRead(holder);
break;
+ case R.id.send_alternate:
+ onSendAlternate(mAccount, holder);
+ break;
+
}
}
+ else
+ {
+ switch (item.getItemId())
+ {
+ case R.id.refresh:
+ if (folder.outbox)
+ {
+ Log.i(Email.LOG_TAG, "sending pending messages from " + folder.name);
+ MessagingController.getInstance(getApplication())
+ .sendPendingMessages(mAccount, null);
+ }
+ else
+ {
+ Log.i(Email.LOG_TAG, "refresh folder " + folder.name);
+ new Thread(new FolderUpdateWorker(folder.name, true)).start();
+ }
+ break;
+ case R.id.folder_settings:
+ Log.i(Email.LOG_TAG, "edit folder settings for " + folder.name);
+ onEditFolder(mAccount, folder.name);
+ break;
+ case R.id.empty_trash:
+ Log.i(Email.LOG_TAG, "empty trash");
+ onEmptyTrash(mAccount);
+ break;
+ case R.id.mark_all_as_read:
+ Log.i(Email.LOG_TAG, "mark all unread messages as read " + folder.name);
+ onMarkAllAsRead(mAccount, folder);
+ break;
+
+ }
+ }
+
return super.onContextItemSelected(item);
}
+ public void onSendAlternate(Account account, MessageInfoHolder holder)
+ {
+ MessagingController.getInstance(getApplication()).sendAlternate(this, account, holder.message);
+ }
+
@Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo)
+ {
super.onCreateContextMenu(menu, v, menuInfo);
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
- if (ExpandableListView.getPackedPositionType(info.packedPosition) ==
- ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+ if (ExpandableListView.getPackedPositionType(info.packedPosition) == ExpandableListView.PACKED_POSITION_TYPE_CHILD)
+ {
long packedPosition = info.packedPosition;
- int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
- int childPosition = ExpandableListView.getPackedPositionChild(packedPosition);
- FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(groupPosition);
- if (folder.outbox) {
+ int groupPosition = ExpandableListView
+ .getPackedPositionGroup(packedPosition);
+ int childPosition = ExpandableListView
+ .getPackedPositionChild(packedPosition);
+ FolderInfoHolder folder = (FolderInfoHolder) mAdapter
+ .getGroup(groupPosition);
+ if (folder.outbox)
+ {
return;
}
- if (childPosition < folder.messages.size()) {
+ if (childPosition < folder.messages.size())
+ {
getMenuInflater().inflate(R.menu.folder_message_list_context, menu);
- MessageInfoHolder message =
- (MessageInfoHolder) mAdapter.getChild(groupPosition, childPosition);
- if (message.read) {
- menu.findItem(R.id.mark_as_read).setTitle(R.string.mark_as_unread_action);
+ MessageInfoHolder message = (MessageInfoHolder) mAdapter.getChild(
+ groupPosition, childPosition);
+ if (message.read)
+ {
+ menu.findItem(R.id.mark_as_read).setTitle(
+ R.string.mark_as_unread_action);
}
}
- }
- }
+ } else if (ExpandableListView.getPackedPositionType(info.packedPosition) == ExpandableListView.PACKED_POSITION_TYPE_GROUP)
+ {
+ getMenuInflater().inflate(R.menu.folder_context, menu);
- class FolderMessageListAdapter extends BaseExpandableListAdapter {
+ long packedPosition = info.packedPosition;
+ int groupPosition = ExpandableListView
+ .getPackedPositionGroup(packedPosition);
+ FolderInfoHolder folder = (FolderInfoHolder) mAdapter
+ .getGroup(groupPosition);
+
+ if (!folder.name.equals(mAccount.getTrashFolderName()))
+ {
+ menu.findItem(R.id.empty_trash).setVisible(false);
+ }
+ menu.setHeaderTitle(R.string.folder_context_menu_title);
+ }
+ }
+
+ private String truncateStatus(String mess)
+ {
+ if (mess != null && mess.length() > 27)
+ {
+ mess = mess.substring(0, 27);
+ }
+ return mess;
+ }
+
+ class FolderMessageListAdapter extends BaseExpandableListAdapter
+ {
private ArrayList mFolders = new ArrayList();
- private MessagingListener mListener = new MessagingListener() {
+ private MessagingListener mListener = new MessagingListener()
+ {
@Override
- public void listFoldersStarted(Account account) {
- if (!account.equals(mAccount)) {
+ public void listFoldersStarted(Account account)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(true);
}
@Override
- public void listFoldersFailed(Account account, String message) {
- if (!account.equals(mAccount)) {
+ public void listFoldersFailed(Account account, String message)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(false);
- if (Config.LOGV) {
+ if (Config.LOGV)
+ {
Log.v(Email.LOG_TAG, "listFoldersFailed " + message);
}
}
@Override
- public void listFoldersFinished(Account account) {
- if (!account.equals(mAccount)) {
+ public void listFoldersFinished(Account account)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(false);
- if (mInitialFolder != null) {
+ if (mInitialFolder != null)
+ {
int groupPosition = getFolderPosition(mInitialFolder);
mInitialFolder = null;
- if (groupPosition != -1) {
+ if (groupPosition != -1)
+ {
mHandler.expandGroup(groupPosition);
}
}
+ mHandler.dataChanged();
}
@Override
- public void listFolders(Account account, Folder[] folders) {
- if (!account.equals(mAccount)) {
+ public void listFolders(Account account, Folder[] folders)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
- for (Folder folder : folders) {
+ ArrayList newFolders = new ArrayList();
+
+ Account.FolderMode aMode = account.getFolderDisplayMode();
+ Preferences prefs = Preferences.getPreferences(getApplication().getApplicationContext());
+
+ for (Folder folder : folders)
+ {
+ try
+ {
+ folder.refresh(prefs);
+ Folder.FolderClass fMode = folder.getDisplayClass();
+
+ if ((aMode == Account.FolderMode.FIRST_CLASS && fMode != Folder.FolderClass.FIRST_CLASS)
+ || (aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
+ fMode != Folder.FolderClass.FIRST_CLASS &&
+ fMode != Folder.FolderClass.SECOND_CLASS)
+ || (aMode == Account.FolderMode.NOT_SECOND_CLASS && fMode == Folder.FolderClass.SECOND_CLASS))
+ {
+ continue;
+ }
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Couldn't get prefs to check for displayability of folder " + folder.getName(), me);
+ }
+
+
FolderInfoHolder holder = getFolder(folder.getName());
- if (holder == null) {
+
+ if (holder == null)
+ {
holder = new FolderInfoHolder();
- mFolders.add(holder);
}
+ newFolders.add(holder);
+ int unreadCount = 0;
+ try
+ {
+ folder.open(Folder.OpenMode.READ_WRITE);
+ unreadCount = folder.getUnreadMessageCount();
+ } catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Folder.getUnreadMessageCount() failed", me);
+ }
holder.name = folder.getName();
- if (holder.name.equalsIgnoreCase(Email.INBOX)) {
+ if (holder.name.equalsIgnoreCase(Email.INBOX))
+ {
holder.displayName = getString(R.string.special_mailbox_name_inbox);
- // XXX TOOD nuke when we do this for all folders
- try {
- holder.unreadMessageCount = folder.getUnreadMessageCount();
- }
- catch (MessagingException me) {
- Log.e(Email.LOG_TAG, "Folder.getUnreadMessageCount() failed", me);
- }
-
- }
- else {
+ } else
+ {
holder.displayName = folder.getName();
}
- if (holder.name.equals(mAccount.getOutboxFolderName())) {
+ if (holder.name.equals(mAccount.getOutboxFolderName()))
+ {
holder.outbox = true;
}
- if (holder.messages == null) {
+ if (holder.messages == null)
+ {
holder.messages = new ArrayList();
}
- /* TODO - once we're in a position to asynchronously list off
- * unread message counts quckly, start doing this again.
- * right now, they're not even displayed
-
- try {
- holder.unreadMessageCount = folder.getUnreadMessageCount();
- }
- catch (MessagingException me) {
- Log.e(Email.LOG_TAG, "Folder.getUnreadMessageCount() failed", me);
- }
+ holder.lastChecked = folder.getLastChecked();
+ String mess = truncateStatus(folder.getStatus());
+
+ holder.status = mess;
- */
- }
+ holder.unreadMessageCount = unreadCount;
+ try
+ {
+ folder.close(false);
+ } catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Folder.close() failed", me);
+ }
+ }
+ mFolders.clear();
+ mFolders.addAll(newFolders);
Collections.sort(mFolders);
mHandler.dataChanged();
-
/*
- * We will do this eventually. This restores the state of the list in the
- * case of a killed Activity but we have some message sync issues to take care of.
+ * We will do this eventually. This restores the state of the list in
+ * the case of a killed Activity but we have some message sync issues to
+ * take care of.
*/
// if (mRestoredState != null) {
// if (Config.LOGV) {
@@ -795,23 +1123,28 @@ public class FolderMessageList extends ExpandableListActivity {
// mListView.onRestoreInstanceState(mListViewState);
// mListViewState = null;
// }
-
/*
* Now we need to refresh any folders that are currently expanded. We do this
* in case the status or number of messages has changed.
*/
- for (int i = 0, count = getGroupCount(); i < count; i++) {
- if (mListView.isGroupExpanded(i)) {
- final FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(i);
- new Thread(new FolderUpdateWorker(folder.name, mRefreshRemote)).start();
+ for (int i = 0, count = getGroupCount(); i < count; i++)
+ {
+ if (mListView.isGroupExpanded(i))
+ {
+ final FolderInfoHolder folder = (FolderInfoHolder) mAdapter
+ .getGroup(i);
+ new Thread(new FolderUpdateWorker(folder.name, mRefreshRemote))
+ .start();
}
}
mRefreshRemote = false;
}
@Override
- public void listLocalMessagesStarted(Account account, String folder) {
- if (!account.equals(mAccount)) {
+ public void listLocalMessagesStarted(Account account, String folder)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(true);
@@ -819,8 +1152,11 @@ public class FolderMessageList extends ExpandableListActivity {
}
@Override
- public void listLocalMessagesFailed(Account account, String folder, String message) {
- if (!account.equals(mAccount)) {
+ public void listLocalMessagesFailed(Account account, String folder,
+ String message)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(false);
@@ -828,8 +1164,10 @@ public class FolderMessageList extends ExpandableListActivity {
}
@Override
- public void listLocalMessagesFinished(Account account, String folder) {
- if (!account.equals(mAccount)) {
+ public void listLocalMessagesFinished(Account account, String folder)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(false);
@@ -837,110 +1175,153 @@ public class FolderMessageList extends ExpandableListActivity {
}
@Override
- public void listLocalMessages(Account account, String folder, Message[] messages) {
- if (!account.equals(mAccount)) {
+ public void listLocalMessages(Account account, String folder,
+ Message[] messages)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
synchronizeMessages(folder, messages);
}
@Override
- public void synchronizeMailboxStarted(
- Account account,
- String folder) {
- if (!account.equals(mAccount)) {
+ public void synchronizeMailboxStarted(Account account, String folder)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(true);
mHandler.folderLoading(folder, true);
- mHandler.folderStatus(folder, null);
+// mHandler.folderStatus(folder, getString(R.string.status_loading));
+ mHandler.folderSyncing(folder);
}
@Override
- public void synchronizeMailboxFinished(
- Account account,
- String folder,
- int totalMessagesInMailbox,
- int numNewMessages) {
- if (!account.equals(mAccount)) {
+ public void synchronizeMailboxFinished(Account account, String folder,
+ int totalMessagesInMailbox, int numNewMessages)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(false);
mHandler.folderLoading(folder, false);
- mHandler.folderStatus(folder, null);
+ // mHandler.folderStatus(folder, null);
+ mHandler.folderSyncing(null);
+
onRefresh(false);
}
@Override
- public void synchronizeMailboxFailed(
- Account account,
- String folder,
- String message) {
- if (!account.equals(mAccount)) {
+ public void synchronizeMailboxFailed(Account account, String folder,
+ String message)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
mHandler.progress(false);
mHandler.folderLoading(folder, false);
- mHandler.folderStatus(folder, getString(R.string.status_network_error));
+
+ // String mess = truncateStatus(message);
+
+ // mHandler.folderStatus(folder, mess);
FolderInfoHolder holder = getFolder(folder);
- if (holder != null) {
- /*
- * Reset the last checked time to 0 so that the next expand will attempt to
- * refresh this folder.
- */
+ if (holder != null)
+ {
holder.lastChecked = 0;
}
+ mHandler.folderSyncing(null);
}
@Override
- public void synchronizeMailboxNewMessage(
- Account account,
- String folder,
- Message message) {
- if (!account.equals(mAccount)) {
+ public void synchronizeMailboxNewMessage(Account account, String folder,
+ Message message)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
addOrUpdateMessage(folder, message);
}
@Override
- public void synchronizeMailboxRemovedMessage(
- Account account,
- String folder,
- Message message) {
- if (!account.equals(mAccount)) {
+ public void synchronizeMailboxRemovedMessage(Account account,
+ String folder, Message message)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
removeMessage(folder, message.getUid());
}
@Override
- public void emptyTrashCompleted(Account account) {
- if (!account.equals(mAccount)) {
+ public void emptyTrashCompleted(Account account)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
onRefresh(false);
}
@Override
- public void sendPendingMessagesCompleted(Account account) {
- if (!account.equals(mAccount)) {
+ public void folderStatusChanged(Account account, String folderName)
+ {
+ if (!account.equals(mAccount))
+ {
return;
}
onRefresh(false);
}
@Override
- public void messageUidChanged(
- Account account,
- String folder,
- String oldUid,
- String newUid) {
- if (mAccount.equals(account)) {
+ public void sendPendingMessagesCompleted(Account account)
+ {
+ if (!account.equals(mAccount))
+ {
+ return;
+ }
+ mHandler.sendingOutbox(false);
+ onRefresh(false);
+ }
+
+ @Override
+ public void sendPendingMessagesStarted(Account account)
+ {
+ if (!account.equals(mAccount))
+ {
+ return;
+ }
+ mHandler.sendingOutbox(true);
+ }
+
+ @Override
+ public void sendPendingMessagesFailed(Account account)
+ {
+ if (!account.equals(mAccount))
+ {
+ return;
+ }
+ mHandler.sendingOutbox(false);
+ }
+
+ @Override
+ public void messageUidChanged(Account account, String folder,
+ String oldUid, String newUid)
+ {
+ if (mAccount.equals(account))
+ {
FolderInfoHolder holder = getFolder(folder);
- if (folder != null) {
- for (MessageInfoHolder message : holder.messages) {
- if (message.uid.equals(oldUid)) {
+ if (holder != null)
+ {
+ for (MessageInfoHolder message : holder.messages)
+ {
+ if (message.uid.equals(oldUid))
+ {
message.uid = newUid;
message.message.setUid(newUid);
}
@@ -952,87 +1333,118 @@ public class FolderMessageList extends ExpandableListActivity {
private Drawable mAttachmentIcon;
- FolderMessageListAdapter() {
- mAttachmentIcon = getResources().getDrawable(R.drawable.ic_mms_attachment_small);
+ FolderMessageListAdapter()
+ {
+ mAttachmentIcon = getResources().getDrawable(
+ R.drawable.ic_mms_attachment_small);
}
- public void removeMessage(String folder, String messageUid) {
+ public void removeMessage(String folder, String messageUid)
+ {
FolderInfoHolder f = getFolder(folder);
- if (f == null) {
+ if (f == null)
+ {
return;
}
MessageInfoHolder m = getMessage(f, messageUid);
- if (m == null) {
+ if (m == null)
+ {
return;
}
mHandler.removeMessage(f, m);
}
- public void synchronizeMessages(String folder, Message[] messages) {
+ public void synchronizeMessages(String folder, Message[] messages)
+ {
FolderInfoHolder f = getFolder(folder);
- if (f == null) {
+ if (f == null)
+ {
return;
}
mHandler.synchronizeMessages(f, messages);
}
- public void addOrUpdateMessage(String folder, Message message) {
+ public void addOrUpdateMessage(String folder, Message message)
+ {
addOrUpdateMessage(folder, message, true, true);
}
private void addOrUpdateMessage(FolderInfoHolder folder, Message message,
- boolean sort, boolean notify) {
+ boolean sort, boolean notify)
+ {
MessageInfoHolder m = getMessage(folder, message.getUid());
- if (m == null) {
+ if (m == null)
+ {
m = new MessageInfoHolder(message, folder);
folder.messages.add(m);
- }
- else {
+ } else
+ {
m.populate(message, folder);
}
- if (sort) {
+ if (sort)
+ {
Collections.sort(folder.messages);
}
- if (notify) {
+ if (notify)
+ {
mHandler.dataChanged();
}
}
private void addOrUpdateMessage(String folder, Message message,
- boolean sort, boolean notify) {
+ boolean sort, boolean notify)
+ {
FolderInfoHolder f = getFolder(folder);
- if (f == null) {
+ if (f == null)
+ {
return;
}
addOrUpdateMessage(f, message, sort, notify);
}
- public MessageInfoHolder getMessage(FolderInfoHolder folder, String messageUid) {
- for (MessageInfoHolder message : folder.messages) {
- if (message.uid.equals(messageUid)) {
+ public MessageInfoHolder getMessage(FolderInfoHolder folder,
+ String messageUid)
+ {
+ for (MessageInfoHolder message : folder.messages)
+ {
+ if (message.uid.equals(messageUid))
+ {
return message;
}
}
return null;
}
- public int getGroupCount() {
+ public int getGroupCount()
+ {
return mFolders.size();
}
- public long getGroupId(int groupPosition) {
+ public long getGroupId(int groupPosition)
+ {
return groupPosition;
}
- public Object getGroup(int groupPosition) {
+ public Object getGroup(int groupPosition)
+ {
+ try{
return mFolders.get(groupPosition);
+ }
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "getGroup(" + groupPosition + "), but mFolders.size() = " + mFolders.size(), e);
+ return null;
+ }
}
- public FolderInfoHolder getFolder(String folder) {
+ public FolderInfoHolder getFolder(String folder)
+ {
FolderInfoHolder folderHolder = null;
- for (int i = 0, count = getGroupCount(); i < count; i++) {
+ for (int i = 0, count = getGroupCount(); i < count; i++)
+ {
FolderInfoHolder holder = (FolderInfoHolder) getGroup(i);
- if (holder.name.equals(folder)) {
+ if (holder != null && holder.name != null && holder.name.equals(folder))
+ {
folderHolder = holder;
}
}
@@ -1040,141 +1452,207 @@ public class FolderMessageList extends ExpandableListActivity {
}
/**
- * Gets the group position of the given folder or returns -1 if the folder is not
- * found.
+ * Gets the group position of the given folder or returns -1 if the folder
+ * is not found.
+ *
* @param folder
* @return
*/
- public int getFolderPosition(String folder) {
- for (int i = 0, count = getGroupCount(); i < count; i++) {
+ public int getFolderPosition(String folder)
+ {
+ for (int i = 0, count = getGroupCount(); i < count; i++)
+ {
FolderInfoHolder holder = (FolderInfoHolder) getGroup(i);
- if (holder.name.equals(folder)) {
+ if (holder != null && holder.name.equals(folder))
+ {
return i;
}
}
return -1;
}
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
+ public View getGroupView(int groupPosition, boolean isExpanded,
+ View convertView, ViewGroup parent)
+ {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
View view;
- if (convertView != null) {
+ if (convertView != null)
+ {
view = convertView;
- } else {
- view = mInflater.inflate(R.layout.folder_message_list_group, parent, false);
+ } else
+ {
+ view = mInflater.inflate(R.layout.folder_message_list_group, parent,
+ false);
}
FolderViewHolder holder = (FolderViewHolder) view.getTag();
- if (holder == null) {
+ if (holder == null)
+ {
holder = new FolderViewHolder();
holder.folderName = (TextView) view.findViewById(R.id.folder_name);
- holder.newMessageCount = (TextView) view.findViewById(R.id.new_message_count);
+ holder.newMessageCount = (TextView) view
+ .findViewById(R.id.folder_unread_message_count);
holder.folderStatus = (TextView) view.findViewById(R.id.folder_status);
view.setTag(holder);
}
- holder.folderName.setText(folder.displayName);
+
+ if (folder == null)
+ {
+ return view;
+ }
+
+ holder.folderName.setText(folder.displayName);
- if (folder.status == null) {
- holder.folderStatus.setVisibility(View.GONE);
+ String statusText = "";
+
+ if (folder.loading)
+ {
+ statusText = getString(R.string.status_loading);
}
- else {
- holder.folderStatus.setText(folder.status);
+ else if (folder.status != null)
+ {
+ statusText = folder.status;
+ }
+ else if (folder.lastChecked != 0)
+ {
+ Date lastCheckedDate = new Date(folder.lastChecked);
+
+ statusText = (getDateFormat().format(lastCheckedDate) + " " + getTimeFormat()
+ .format(lastCheckedDate));
+ }
+ if (statusText != null)
+ {
+ holder.folderStatus.setText(statusText);
holder.folderStatus.setVisibility(View.VISIBLE);
}
+ else
+ {
+ holder.folderStatus.setText(null);
+ holder.folderStatus.setVisibility(View.GONE);
+ }
- if (folder.unreadMessageCount != 0) {
- holder.newMessageCount.setText(Integer.toString(folder.unreadMessageCount));
+ if (folder.unreadMessageCount != 0)
+ {
+ holder.newMessageCount.setText(Integer
+ .toString(folder.unreadMessageCount));
holder.newMessageCount.setVisibility(View.VISIBLE);
- }
- else {
+ } else
+ {
holder.newMessageCount.setVisibility(View.GONE);
}
return view;
}
- public int getChildrenCount(int groupPosition) {
+ public int getChildrenCount(int groupPosition)
+ {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
+ if (folder == null || folder.messages == null)
+ {
+ return 0;
+ }
return folder.messages.size() + 1;
}
- public long getChildId(int groupPosition, int childPosition) {
+ public long getChildId(int groupPosition, int childPosition)
+ {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
- if (childPosition < folder.messages.size()) {
+ if (childPosition < folder.messages.size())
+ {
MessageInfoHolder holder = folder.messages.get(childPosition);
return ((LocalStore.LocalMessage) holder.message).getId();
- } else {
+ } else
+ {
return -1;
}
}
- public Object getChild(int groupPosition, int childPosition) {
- FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
+ public Object getChild(int groupPosition, int childPosition)
+ {
+ FolderInfoHolder folder = null;
+ try
+ {
+ folder = (FolderInfoHolder) getGroup(groupPosition);
+ if (folder == null)
+ {
+ Log.e(Email.LOG_TAG, "Got null folder while retrieving groupPosition " + groupPosition);
+ }
return folder.messages.get(childPosition);
}
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "getChild(" + groupPosition + ", "+ childPosition + "), but folder.messages.size() = " + folder.messages.size(), e);
+ return null;
+ }
+ }
- public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
- View convertView, ViewGroup parent) {
+ public View getChildView(int groupPosition, int childPosition,
+ boolean isLastChild, View convertView, ViewGroup parent)
+ {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
- if (isLastChild) {
+ if (isLastChild)
+ {
View view;
if ((convertView != null)
- && (convertView.getId()
- == R.layout.folder_message_list_child_footer)) {
+ && (convertView.getId() == R.layout.folder_message_list_child_footer))
+ {
view = convertView;
- }
- else {
+ } else
+ {
view = mInflater.inflate(R.layout.folder_message_list_child_footer,
parent, false);
view.setId(R.layout.folder_message_list_child_footer);
}
FooterViewHolder holder = (FooterViewHolder) view.getTag();
- if (holder == null) {
+ if (holder == null)
+ {
holder = new FooterViewHolder();
holder.progress = (ProgressBar) view.findViewById(R.id.progress);
holder.main = (TextView) view.findViewById(R.id.main_text);
view.setTag(holder);
}
- if (folder.loading) {
+ if (folder.loading)
+ {
holder.main.setText(getString(R.string.status_loading_more));
holder.progress.setVisibility(View.VISIBLE);
- }
- else {
- if (folder.status == null) {
- // holder.main.setText(getString(R.string.message_list_load_more_messages_action));
- // holder.main.setText("Load up to " + Email.VISIBLE_LIMIT_INCREMENT + " more");
- holder.main.setText("Load up to " + mAccount.getDisplayCount() + " more");
- }
- else {
+ } else
+ {
+ if (folder.lastCheckFailed == false)
+ {
+ // TODO: Strings should be externalized
+ holder.main.setText("Load up to " + mAccount.getDisplayCount() + " more");
+ } else
+ {
holder.main.setText(getString(R.string.status_loading_more_failed));
}
holder.progress.setVisibility(View.GONE);
}
return view;
- }
- else {
- MessageInfoHolder message =
- (MessageInfoHolder) getChild(groupPosition, childPosition);
+ } else
+ {
+ MessageInfoHolder message = (MessageInfoHolder) getChild(groupPosition,
+ childPosition);
View view;
if ((convertView != null)
- && (convertView.getId() != R.layout.folder_message_list_child_footer)) {
+ && (convertView.getId() != R.layout.folder_message_list_child_footer))
+ {
view = convertView;
- } else {
- view = mInflater.inflate(R.layout.folder_message_list_child, parent, false);
+ } else
+ {
+ view = mInflater.inflate(R.layout.folder_message_list_child, parent,
+ false);
}
MessageViewHolder holder = (MessageViewHolder) view.getTag();
- if (holder == null) {
+ if (holder == null)
+ {
holder = new MessageViewHolder();
holder.subject = (TextView) view.findViewById(R.id.subject);
holder.from = (TextView) view.findViewById(R.id.from);
holder.date = (TextView) view.findViewById(R.id.date);
holder.chip = view.findViewById(R.id.chip);
/*
- * TODO
- * The line below and the commented lines a bit further down are work
- * in progress for outbox status. They should not be removed.
+ * TODO The line below and the commented lines a bit further down are
+ * work in progress for outbox status. They should not be removed.
*/
// holder.status = (TextView) view.findViewById(R.id.status);
-
/*
* This will need to move to below if we ever convert this whole thing
* to a combined inbox.
@@ -1183,14 +1661,29 @@ public class FolderMessageList extends ExpandableListActivity {
view.setTag(holder);
}
+ if (message != null)
+ {
holder.chip.getBackground().setAlpha(message.read ? 0 : 255);
holder.subject.setText(message.subject);
- holder.subject.setTypeface(null, message.read ? Typeface.NORMAL : Typeface.BOLD);
+ holder.subject.setTypeface(null, message.read ? Typeface.NORMAL
+ : Typeface.BOLD);
holder.from.setText(message.sender);
- holder.from.setTypeface(null, message.read ? Typeface.NORMAL : Typeface.BOLD);
+ holder.from.setTypeface(null, message.read ? Typeface.NORMAL
+ : Typeface.BOLD);
holder.date.setText(message.date);
holder.subject.setCompoundDrawablesWithIntrinsicBounds(null, null,
message.hasAttachments ? mAttachmentIcon : null, null);
+ }
+ else
+ {
+ holder.chip.getBackground().setAlpha(0);
+ holder.subject.setText("No subject");
+ holder.subject.setTypeface(null, Typeface.NORMAL);
+ holder.from.setText("No sender");
+ holder.from.setTypeface(null, Typeface.NORMAL);
+ holder.date.setText("No date");
+ holder.from.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ }
// if (folder.outbox) {
// holder.status.setText("Sending");
// }
@@ -1201,22 +1694,32 @@ public class FolderMessageList extends ExpandableListActivity {
}
}
- public boolean hasStableIds() {
+ public boolean hasStableIds()
+ {
return true;
}
- public boolean isChildSelectable(int groupPosition, int childPosition) {
+ public boolean isChildSelectable(int groupPosition, int childPosition)
+ {
return childPosition < getChildrenCount(groupPosition);
}
- public class FolderInfoHolder implements Comparable {
+ public class FolderInfoHolder implements Comparable
+ {
public String name;
+
public String displayName;
+
public ArrayList messages;
+
public long lastChecked;
+
public int unreadMessageCount;
+
public boolean loading;
+
public String status;
+
public boolean lastCheckFailed;
/**
@@ -1224,84 +1727,112 @@ public class FolderMessageList extends ExpandableListActivity {
*/
public boolean outbox;
- public int compareTo(FolderInfoHolder o) {
+ public int compareTo(FolderInfoHolder o)
+ {
String s1 = this.name;
String s2 = o.name;
- if (Email.INBOX.equalsIgnoreCase(s1)) {
+ if (Email.INBOX.equalsIgnoreCase(s1))
+ {
return -1;
- } else if (Email.INBOX.equalsIgnoreCase(s2)) {
+ } else if (Email.INBOX.equalsIgnoreCase(s2))
+ {
return 1;
} else
return s1.compareToIgnoreCase(s2);
}
}
- public class MessageInfoHolder implements Comparable {
+ public class MessageInfoHolder implements Comparable
+ {
public String subject;
+
public String date;
+
public Date compareDate;
+
public String sender;
+
public boolean hasAttachments;
+
public String uid;
+
public boolean read;
+
public Message message;
- public MessageInfoHolder(Message m, FolderInfoHolder folder) {
+ public MessageInfoHolder(Message m, FolderInfoHolder folder)
+ {
populate(m, folder);
}
- public void populate(Message m, FolderInfoHolder folder) {
- try {
+ public void populate(Message m, FolderInfoHolder folder)
+ {
+ try
+ {
LocalMessage message = (LocalMessage) m;
Date date = message.getSentDate();
this.compareDate = date;
- if (Utility.isDateToday(date)) {
- this.date = mTimeFormat.format(date);
- }
- else {
- this.date = mDateFormat.format(date);
+ if (Utility.isDateToday(date))
+ {
+ this.date = getTimeFormat().format(date);
+ } else
+ {
+ this.date = getDateFormat().format(date);
}
this.hasAttachments = message.getAttachmentCount() > 0;
this.read = message.isSet(Flag.SEEN);
- if (folder.outbox) {
- this.sender = Address.toFriendly(
- message.getRecipients(RecipientType.TO));
- }
- else {
+ if (folder.outbox)
+ {
+ this.sender = Address.toFriendly(message
+ .getRecipients(RecipientType.TO));
+ } else
+ {
this.sender = Address.toFriendly(message.getFrom());
}
this.subject = message.getSubject();
this.uid = message.getUid();
this.message = m;
- }
- catch (MessagingException me) {
- if (Config.LOGV) {
+ } catch (MessagingException me)
+ {
+ if (Config.LOGV)
+ {
Log.v(Email.LOG_TAG, "Unable to load message info", me);
}
}
}
- public int compareTo(MessageInfoHolder o) {
+ public int compareTo(MessageInfoHolder o)
+ {
return this.compareDate.compareTo(o.compareDate) * -1;
}
}
- class FolderViewHolder {
+ class FolderViewHolder
+ {
public TextView folderName;
+
public TextView folderStatus;
+
public TextView newMessageCount;
}
- class MessageViewHolder {
+ class MessageViewHolder
+ {
public TextView subject;
+
public TextView preview;
+
public TextView from;
+
public TextView date;
+
public View chip;
}
- class FooterViewHolder {
+ class FooterViewHolder
+ {
public ProgressBar progress;
+
public TextView main;
}
}
diff --git a/src/com/android/email/activity/MessageCompose.java b/src/com/android/email/activity/MessageCompose.java
index ffa1fbc98..b1bfce2aa 100644
--- a/src/com/android/email/activity/MessageCompose.java
+++ b/src/com/android/email/activity/MessageCompose.java
@@ -370,6 +370,17 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
return;
}
+ String text = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (text != null)
+ {
+ mMessageContentView.setText(text);
+ }
+ String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
+ if (subject != null)
+ {
+ mSubjectView.setText(subject);
+ }
+
String type = intent.getType();
Uri stream = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (stream != null && type != null) {
@@ -397,7 +408,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
mAccount,
mFolder,
mSourceMessageUid,
- mListener);
+ null);
}
if (ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action) ||
ACTION_EDIT_DRAFT.equals(action)) {
diff --git a/src/com/android/email/activity/MessageView.java b/src/com/android/email/activity/MessageView.java
index 59c94fff3..ff270f2a0 100644
--- a/src/com/android/email/activity/MessageView.java
+++ b/src/com/android/email/activity/MessageView.java
@@ -93,8 +93,31 @@ public class MessageView extends Activity
private String mNextMessageUid = null;
private String mPreviousMessageUid = null;
- private DateFormat mDateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
- private DateFormat mTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
+ private DateFormat dateFormat = null;
+ private DateFormat timeFormat = null;
+
+
+ private DateFormat getDateFormat()
+ {
+ if (dateFormat == null)
+ {
+ dateFormat = android.pim.DateFormat.getDateFormat(getApplication());
+ }
+ return dateFormat;
+ }
+ private DateFormat getTimeFormat()
+ {
+ if (timeFormat == null)
+ {
+ timeFormat = android.pim.DateFormat.getTimeFormat(getApplication());
+ }
+ return timeFormat;
+ }
+ private void clearFormats()
+ {
+ dateFormat = null;
+ timeFormat = null;
+ }
private Listener mListener = new Listener();
private MessageViewHandler mHandler = new MessageViewHandler();
@@ -109,7 +132,11 @@ public class MessageView extends Activity
case KeyEvent.KEYCODE_F: { onForward(); return true;}
case KeyEvent.KEYCODE_A: { onReplyAll(); return true; }
case KeyEvent.KEYCODE_R: { onReply(); return true; }
- case KeyEvent.KEYCODE_J: { onPrevious(); return true; }
+ case KeyEvent.KEYCODE_J:
+ case KeyEvent.KEYCODE_P:
+ { onPrevious(); return true; }
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_N:
case KeyEvent.KEYCODE_K: { onNext(); return true; }
case KeyEvent.KEYCODE_Z: { if (event.isShiftPressed()) {
mMessageContentView.zoomIn();
@@ -301,10 +328,10 @@ public class MessageView extends Activity
mAttachments.setVisibility(View.GONE);
mAttachmentIcon.setVisibility(View.GONE);
- findViewById(R.id.reply).setOnClickListener(this);
- findViewById(R.id.delete).setOnClickListener(this);
- findViewById(R.id.forward).setOnClickListener(this);
- findViewById(R.id.show_pictures).setOnClickListener(this);
+ setOnClickListener(R.id.reply);
+ setOnClickListener(R.id.delete);
+ setOnClickListener(R.id.forward);
+ setOnClickListener(R.id.show_pictures);
// UrlInterceptRegistry.registerHandler(this);
@@ -318,11 +345,12 @@ public class MessageView extends Activity
mFolder = intent.getStringExtra(EXTRA_FOLDER);
mMessageUid = intent.getStringExtra(EXTRA_MESSAGE);
mFolderUids = intent.getStringArrayListExtra(EXTRA_FOLDER_UIDS);
-
- findSurroundingMessagesUid();
-
+
View next = findViewById(R.id.next);
View previous = findViewById(R.id.previous);
+
+ findSurroundingMessagesUid();
+
/*
* Next and Previous Message are not shown in landscape mode, so
* we need to check before we use them.
@@ -331,6 +359,7 @@ public class MessageView extends Activity
next.setOnClickListener(this);
previous.setOnClickListener(this);
+
previous.setVisibility(mPreviousMessageUid != null ? View.VISIBLE : View.GONE);
next.setVisibility(mNextMessageUid != null ? View.VISIBLE : View.GONE);
@@ -351,10 +380,19 @@ public class MessageView extends Activity
mAccount,
mFolder,
mMessageUid,
- mListener);
+ null);
}
}.start();
}
+
+ private void setOnClickListener(int viewCode)
+ {
+ View thisView = findViewById(viewCode);
+ if (thisView != null)
+ {
+ thisView.setOnClickListener(this);
+ }
+ }
private void findSurroundingMessagesUid() {
for (int i = 0, count = mFolderUids.size(); i < count; i++) {
@@ -374,6 +412,7 @@ public class MessageView extends Activity
public void onResume() {
super.onResume();
+ clearFormats();
MessagingController.getInstance(getApplication()).addListener(mListener);
}
@@ -427,8 +466,22 @@ public class MessageView extends Activity
finish();
}
}
+
+ private void onSendAlternate() {
+ if (mMessage != null) {
+ MessagingController.getInstance(getApplication()).sendAlternate(this, mAccount, mMessage);
+
+ }
+ }
private void onNext() {
+ if (mNextMessageUid == null)
+ {
+ Toast.makeText(this,
+ getString(R.string.end_of_folder),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
Bundle extras = new Bundle(1);
extras.putBoolean(EXTRA_NEXT, true);
MessageView.actionView(this, mAccount, mFolder, mNextMessageUid, mFolderUids, extras);
@@ -436,6 +489,13 @@ public class MessageView extends Activity
}
private void onPrevious() {
+ if (mPreviousMessageUid == null)
+ {
+ Toast.makeText(this,
+ getString(R.string.end_of_folder),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
MessageView.actionView(this, mAccount, mFolder, mPreviousMessageUid, mFolderUids);
finish();
}
@@ -518,6 +578,9 @@ public class MessageView extends Activity
case R.id.reply:
onReply();
break;
+ case R.id.reply_all:
+ onReplyAll();
+ break;
case R.id.delete:
onDelete();
break;
@@ -556,6 +619,9 @@ public class MessageView extends Activity
case R.id.forward:
onForward();
break;
+ case R.id.send_alternate:
+ onSendAlternate();
+ break;
case R.id.mark_as_unread:
onMarkAsUnread();
break;
@@ -712,8 +778,8 @@ public class MessageView extends Activity
String subjectText = message.getSubject();
String fromText = Address.toFriendly(message.getFrom());
String dateText = Utility.isDateToday(message.getSentDate()) ?
- mTimeFormat.format(message.getSentDate()) :
- mDateTimeFormat.format(message.getSentDate());
+ getTimeFormat().format(message.getSentDate()) :
+ getDateFormat().format(message.getSentDate());
String toText = Address.toFriendly(message.getRecipients(RecipientType.TO));
boolean hasAttachments = ((LocalMessage) message).getAttachmentCount() > 0;
mHandler.setHeaders(subjectText,
@@ -773,7 +839,7 @@ public class MessageView extends Activity
/*
* TODO this should be smarter, change to regex for img, but consider how to
- * get backgroung images and a million other things that HTML allows.
+ * get background images and a million other things that HTML allows.
*/
if (text.contains(" securityTypesAdapter = new ArrayAdapter(this,
@@ -203,8 +208,6 @@ public class AccountSetupIncoming extends Activity implements OnClickListener {
mAccountPorts = imapPorts;
mAccountSchemes = imapSchemes;
- findViewById(R.id.account_delete_policy_label).setVisibility(View.GONE);
- mDeletePolicyView.setVisibility(View.GONE);
if (uri.getPath() != null && uri.getPath().length() > 0) {
mImapPathPrefixView.setText(uri.getPath().substring(1));
}
@@ -326,9 +329,10 @@ public class AccountSetupIncoming extends Activity implements OnClickListener {
throw new Error(use);
}
- mAccount.setDeletePolicy((Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value);
- AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
+ int deleteSpinnerVal = (Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value;
+ mAccount.setDeletePolicy(deleteSpinnerVal);
+ AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
}
public void onClick(View v) {
diff --git a/src/com/android/email/activity/setup/AccountSetupOptions.java b/src/com/android/email/activity/setup/AccountSetupOptions.java
index 70bf5512c..124186ff3 100644
--- a/src/com/android/email/activity/setup/AccountSetupOptions.java
+++ b/src/com/android/email/activity/setup/AccountSetupOptions.java
@@ -28,7 +28,6 @@ public class AccountSetupOptions extends Activity implements OnClickListener {
private CheckBox mDefaultView;
private CheckBox mNotifyView;
- private CheckBox mNotifyRingtoneView;
private Account mAccount;
@@ -48,13 +47,14 @@ public class AccountSetupOptions extends Activity implements OnClickListener {
mDisplayCountView = (Spinner)findViewById(R.id.account_display_count);
mDefaultView = (CheckBox)findViewById(R.id.account_default);
mNotifyView = (CheckBox)findViewById(R.id.account_notify);
- mNotifyRingtoneView = (CheckBox)findViewById(R.id.account_notify_ringtone);
findViewById(R.id.next).setOnClickListener(this);
SpinnerOption checkFrequencies[] = {
new SpinnerOption(-1,
getString(R.string.account_setup_options_mail_check_frequency_never)),
+ new SpinnerOption(1,
+ getString(R.string.account_setup_options_mail_check_frequency_1min)),
new SpinnerOption(5,
getString(R.string.account_setup_options_mail_check_frequency_5min)),
new SpinnerOption(10,
@@ -96,7 +96,6 @@ public class AccountSetupOptions extends Activity implements OnClickListener {
mDefaultView.setChecked(true);
}
mNotifyView.setChecked(mAccount.isNotifyNewMail());
- mNotifyRingtoneView.setChecked(mAccount.isNotifyRingtone());
SpinnerOption.setSpinnerOptionValue(mCheckFrequencyView, mAccount
.getAutomaticCheckIntervalMinutes());
SpinnerOption.setSpinnerOptionValue(mDisplayCountView, mAccount
@@ -106,7 +105,6 @@ public class AccountSetupOptions extends Activity implements OnClickListener {
private void onDone() {
mAccount.setDescription(mAccount.getEmail());
mAccount.setNotifyNewMail(mNotifyView.isChecked());
- mAccount.setNotifyRingtone(mNotifyRingtoneView.isChecked());
mAccount.setAutomaticCheckIntervalMinutes((Integer)((SpinnerOption)mCheckFrequencyView
.getSelectedItem()).value);
mAccount.setDisplayCount((Integer)((SpinnerOption)mDisplayCountView
diff --git a/src/com/android/email/activity/setup/FolderSettings.java b/src/com/android/email/activity/setup/FolderSettings.java
new file mode 100644
index 000000000..b06890650
--- /dev/null
+++ b/src/com/android/email/activity/setup/FolderSettings.java
@@ -0,0 +1,136 @@
+
+package com.android.email.activity.setup;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.preference.PreferenceActivity;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.RingtonePreference;
+
+import com.android.email.Account;
+import com.android.email.Email;
+import com.android.email.Preferences;
+import com.android.email.R;
+import com.android.email.mail.Folder;
+import com.android.email.mail.MessagingException;
+import com.android.email.mail.Store;
+import com.android.email.mail.Folder.FolderClass;
+import com.android.email.mail.store.LocalStore.LocalFolder;
+
+public class FolderSettings extends PreferenceActivity {
+
+ private static final String EXTRA_FOLDER_NAME = "com.android.email.folderName";
+ private static final String EXTRA_ACCOUNT = "com.android.email.account";
+
+ private static final String PREFERENCE_TOP_CATERGORY = "folder_settings";
+ private static final String PREFERENCE_DISPLAY_CLASS = "folder_settings_folder_display_mode";
+ private static final String PREFERENCE_SYNC_CLASS = "folder_settings_folder_sync_mode";
+
+ private LocalFolder mFolder;
+
+ private ListPreference mDisplayClass;
+ private ListPreference mSyncClass;
+
+ public static void actionSettings(Context context, Account account, String folderName) {
+ Intent i = new Intent(context, FolderSettings.class);
+ i.putExtra(EXTRA_FOLDER_NAME, folderName);
+ i.putExtra(EXTRA_ACCOUNT, account);
+ context.startActivity(i);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String folderName = (String)getIntent().getSerializableExtra(EXTRA_FOLDER_NAME);
+ Account mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT);
+
+ try
+ {
+ Store localStore = Store.getInstance(mAccount.getLocalStoreUri(),
+ getApplication());
+ mFolder = (LocalFolder) localStore.getFolder(folderName);
+ mFolder.refresh(Preferences.getPreferences(this));
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Unable to edit folder " + folderName + " preferences", me);
+ return;
+ }
+
+ addPreferencesFromResource(R.xml.folder_settings_preferences);
+
+ Preference category = findPreference(PREFERENCE_TOP_CATERGORY);
+ category.setTitle(getString(R.string.folder_settings_title));
+
+ mDisplayClass = (ListPreference) findPreference(PREFERENCE_DISPLAY_CLASS);
+ mDisplayClass.setValue(mFolder.getDisplayClass().name());
+ mDisplayClass.setSummary(mDisplayClass.getEntry());
+ mDisplayClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final String summary = newValue.toString();
+ int index = mDisplayClass.findIndexOfValue(summary);
+ mDisplayClass.setSummary(mDisplayClass.getEntries()[index]);
+ mDisplayClass.setValue(summary);
+ return false;
+ }
+ });
+
+ mSyncClass = (ListPreference) findPreference(PREFERENCE_SYNC_CLASS);
+ mSyncClass.setValue(mFolder.getRawSyncClass().name());
+ mSyncClass.setSummary(mSyncClass.getEntry());
+ mSyncClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final String summary = newValue.toString();
+ int index = mSyncClass.findIndexOfValue(summary);
+ mSyncClass.setSummary(mSyncClass.getEntries()[index]);
+ mSyncClass.setValue(summary);
+ return false;
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ try
+ {
+ mFolder.refresh(Preferences.getPreferences(this));
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Could not refresh folder preferences for folder " + mFolder.getName(), me);
+ }
+ }
+
+ private void saveSettings() {
+ mFolder.setDisplayClass(FolderClass.valueOf(mDisplayClass.getValue()));
+ mFolder.setSyncClass(FolderClass.valueOf(mSyncClass.getValue()));
+
+ try
+ {
+ mFolder.save(Preferences.getPreferences(this));
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "Could not refresh folder preferences for folder " + mFolder.getName(), me);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ saveSettings();
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+
+}
diff --git a/src/com/android/email/mail/Flag.java b/src/com/android/email/mail/Flag.java
index 71f299645..07dda8de1 100644
--- a/src/com/android/email/mail/Flag.java
+++ b/src/com/android/email/mail/Flag.java
@@ -18,6 +18,10 @@ public enum Flag {
* these flags and Strings to represent user defined flags. At that point the below
* flags should become user defined flags.
*/
+ /*
+ * For POP3 to indicate that the message does not have SEEN info
+ */
+ X_NO_SEEN_INFO,
/**
* Delete and remove from the LocalStore immediately.
*/
@@ -45,4 +49,9 @@ public enum Flag {
* This does not include attachments, which are never downloaded fully.
*/
X_DOWNLOADED_PARTIAL,
+
+ /**
+ * Indicates that the copy of a message to the Sent folder has started.
+ */
+ X_REMOTE_COPY_STARTED,
}
diff --git a/src/com/android/email/mail/Folder.java b/src/com/android/email/mail/Folder.java
index 853efc4ab..f77e5661c 100644
--- a/src/com/android/email/mail/Folder.java
+++ b/src/com/android/email/mail/Folder.java
@@ -1,11 +1,19 @@
package com.android.email.mail;
+import com.android.email.Preferences;
+
public abstract class Folder {
+ private String status = null;
+ private long lastChecked = 0;
public enum OpenMode {
READ_WRITE, READ_ONLY,
}
-
+
+ public enum FolderClass {
+ NONE, FIRST_CLASS, SECOND_CLASS;
+ }
+
public enum FolderType {
HOLDS_FOLDERS, HOLDS_MESSAGES,
}
@@ -84,6 +92,10 @@ public abstract class Folder {
public abstract void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException;
+
+ public abstract void setFlags(Flag[] flags, boolean value) throws MessagingException;
+
+ public abstract String getUidFromMessageId(Message message) throws MessagingException;
public abstract Message[] expunge() throws MessagingException;
@@ -104,4 +116,42 @@ public abstract class Folder {
public String toString() {
return getName();
}
+
+ public long getLastChecked()
+ {
+ return lastChecked;
+ }
+
+ public void setLastChecked(long lastChecked) throws MessagingException
+ {
+ this.lastChecked = lastChecked;
+ }
+
+ public FolderClass getDisplayClass()
+ {
+ return FolderClass.NONE;
+ }
+
+ public FolderClass getSyncClass()
+ {
+ return getDisplayClass();
+ }
+
+ public void refresh(Preferences preferences) throws MessagingException
+ {
+
+ }
+
+ public String getStatus()
+ {
+ return status;
+ }
+
+ public void setStatus(String status) throws MessagingException
+ {
+ this.status = status;
+ }
+
+
+
}
diff --git a/src/com/android/email/mail/Message.java b/src/com/android/email/mail/Message.java
index 5f13c33ff..e612b15d6 100644
--- a/src/com/android/email/mail/Message.java
+++ b/src/com/android/email/mail/Message.java
@@ -83,6 +83,8 @@ public abstract class Message implements Part, Body {
public boolean isMimeType(String mimeType) throws MessagingException {
return getContentType().startsWith(mimeType);
}
+
+ public void delete(String trashFolderName) throws MessagingException {} ;
/*
* TODO Refactor Flags at some point to be able to store user defined flags.
diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java
index 7312bbc8e..9e5cc4883 100644
--- a/src/com/android/email/mail/store/ImapStore.java
+++ b/src/com/android/email/mail/store/ImapStore.java
@@ -7,6 +7,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
@@ -17,6 +18,7 @@ import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
+import java.security.Security;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -44,6 +46,7 @@ import com.android.email.mail.MessagingException;
import com.android.email.mail.Part;
import com.android.email.mail.Store;
import com.android.email.mail.CertificateValidationException;
+import com.android.email.mail.Folder.FolderType;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeHeader;
import com.android.email.mail.internet.MimeMessage;
@@ -167,7 +170,7 @@ public class ImapStore extends Store {
synchronized (mFolderCache) {
folder = mFolderCache.get(name);
if (folder == null) {
- folder = new ImapFolder(name);
+ folder = new ImapFolder(this, name);
mFolderCache.put(name, folder);
}
}
@@ -263,7 +266,10 @@ public class ImapStore extends Store {
}
private void releaseConnection(ImapConnection connection) {
- mConnections.offer(connection);
+ synchronized(mConnections)
+ {
+ mConnections.offer(connection);
+ }
}
private String encodeFolderName(String name) {
@@ -307,14 +313,16 @@ public class ImapStore extends Store {
private ImapConnection mConnection;
private OpenMode mMode;
private boolean mExists;
+ private ImapStore store = null;
- public ImapFolder(String name) {
+ public ImapFolder(ImapStore nStore, String name) {
+ store = nStore;
this.mName = name;
}
public String getPrefixedName() {
String prefixedName = "";
- if(mPathPrefix.length() > 0 && !mName.equalsIgnoreCase(Email.INBOX)){
+ if(mPathPrefix != null && mPathPrefix.length() > 0 && !Email.INBOX.equalsIgnoreCase(mName)){
prefixedName += mPathPrefix + mPathDelimeter;
}
@@ -399,11 +407,14 @@ public class ImapStore extends Store {
return mMode;
}
- public void close(boolean expunge) {
+ public void close(boolean expunge) throws MessagingException {
if (!isOpen()) {
return;
}
- // TODO implement expunge
+ if (expunge)
+ {
+ expunge();
+ }
mMessageCount = -1;
synchronized (this) {
releaseConnection(mConnection);
@@ -1002,31 +1013,63 @@ public class ImapStore extends Store {
while (response.more());
} while(response.mTag == null);
- /*
- * Try to find the UID of the message we just appended using the
- * Message-ID header.
- */
- String[] messageIdHeader = message.getHeader("Message-ID");
- if (messageIdHeader == null || messageIdHeader.length == 0) {
- continue;
+ String newUid = getUidFromMessageId(message);
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "Got UID " + newUid + " for message");
+ }
+
+ if (newUid != null)
+ {
+ message.setUid(newUid);
}
- String messageId = messageIdHeader[0];
- List responses =
- mConnection.executeSimpleCommand(
- String.format("UID SEARCH (HEADER MESSAGE-ID %s)", messageId));
- for (ImapResponse response1 : responses) {
- if (response1.mTag == null && response1.get(0).equals("SEARCH")
- && response1.size() > 1) {
- message.setUid(response1.getString(1));
- }
- }
-
}
}
catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);
}
}
+
+ public String getUidFromMessageId(Message message) throws MessagingException
+ {
+ try
+ {
+ /*
+ * Try to find the UID of the message we just appended using the
+ * Message-ID header.
+ */
+ String[] messageIdHeader = message.getHeader("Message-ID");
+
+ if (messageIdHeader == null || messageIdHeader.length == 0) {
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "Did not get a message-id in order to search for UID");
+ }
+ return null;
+ }
+ String messageId = messageIdHeader[0];
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "Looking for UID for message with message-id " + messageId);
+ }
+
+ List responses =
+ mConnection.executeSimpleCommand(
+ String.format("UID SEARCH (HEADER MESSAGE-ID %s)", messageId));
+ for (ImapResponse response1 : responses) {
+ if (response1.mTag == null && response1.get(0).equals("SEARCH")
+ && response1.size() > 1) {
+ return response1.getString(1);
+ }
+ }
+ return null;
+ }
+ catch (IOException ioe)
+ {
+ throw new MessagingException("Could not find UID for message based on Message-ID", ioe);
+ }
+ }
+
public Message[] expunge() throws MessagingException {
checkOpen();
@@ -1038,6 +1081,31 @@ public class ImapStore extends Store {
return null;
}
+ @Override
+ public void setFlags(Flag[] flags, boolean value)
+ throws MessagingException {
+ checkOpen();
+
+ ArrayList flagNames = new ArrayList();
+ for (int i = 0, count = flags.length; i < count; i++) {
+ Flag flag = flags[i];
+ if (flag == Flag.SEEN) {
+ flagNames.add("\\Seen");
+ }
+ else if (flag == Flag.DELETED) {
+ flagNames.add("\\Deleted");
+ }
+ }
+ try {
+ mConnection.executeSimpleCommand(String.format("UID STORE 1:* %sFLAGS.SILENT (%s)",
+ value ? "+" : "-",
+ Utility.combine(flagNames.toArray(new String[flagNames.size()]), ' ')));
+ }
+ catch (IOException ioe) {
+ throw ioExceptionHandler(mConnection, ioe);
+ }
+ }
+
public void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException {
checkOpen();
@@ -1086,6 +1154,11 @@ public class ImapStore extends Store {
}
return super.equals(o);
}
+
+ protected ImapStore getStore()
+ {
+ return store;
+ }
}
/**
@@ -1104,9 +1177,21 @@ public class ImapStore extends Store {
}
mNextCommandTag = 1;
-
+ try
+ {
+ Security.setProperty("networkaddress.cache.ttl", "0");
+ }
+ catch (Exception e)
+ {
+ Log.w(Email.LOG_TAG, "Could not set DNS ttl to 0", e);
+ }
+
try {
- SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
+
+ SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
+
+ Log.i(Email.LOG_TAG, "Connecting to " + mHost + " @ IP addr " + socketAddress);
+
if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
SSLContext sslContext = SSLContext.getInstance("TLS");
@@ -1177,6 +1262,20 @@ public class ImapStore extends Store {
throw new MessagingException(
"Unable to open connection to IMAP server due to security error.", gse);
}
+ catch (ConnectException ce)
+ {
+ String ceMess = ce.getMessage();
+ String[] tokens = ceMess.split("-");
+ if (tokens != null && tokens.length > 1 && tokens[1] != null)
+ {
+ Log.e(Email.LOG_TAG, "Stripping host/port from ConnectionException", ce);
+ throw new ConnectException(tokens[1].trim());
+ }
+ else
+ {
+ throw ce;
+ }
+ }
}
public boolean isOpen() {
@@ -1245,11 +1344,19 @@ public class ImapStore extends Store {
public List executeSimpleCommand(String command, boolean sensitive)
throws IOException, ImapException, MessagingException {
+ if (Config.LOGV)
+ {
+ Log.v(Email.LOG_TAG, "Sending IMAP command " + command);
+ }
String tag = sendCommand(command, sensitive);
ArrayList responses = new ArrayList();
ImapResponse response;
do {
response = mParser.readResponse();
+ if (Config.LOGV)
+ {
+ Log.v(Email.LOG_TAG, "Got IMAP response " + response);
+ }
responses.add(response);
} while (response.mTag == null);
if (response.size() < 1 || !response.get(0).equals("OK")) {
@@ -1282,6 +1389,40 @@ public class ImapStore extends Store {
super.setFlag(flag, set);
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
}
+
+ @Override
+ public void delete(String trashFolderName) throws MessagingException
+ {
+ ImapFolder iFolder = (ImapFolder)getFolder();
+ Folder remoteTrashFolder = iFolder.getStore().getFolder(trashFolderName);
+ /*
+ * Attempt to copy the remote message to the remote trash folder.
+ */
+ if (!remoteTrashFolder.exists()) {
+ /*
+ * If the remote trash folder doesn't exist we try to create it.
+ */
+ Log.i(Email.LOG_TAG, "IMAPMessage.delete: attempting to create remote " + trashFolderName + " folder");
+ remoteTrashFolder.create(FolderType.HOLDS_MESSAGES);
+ }
+
+ if (remoteTrashFolder.exists()) {
+ if (Config.LOGD)
+ {
+ Log.d(Email.LOG_TAG, "IMAPMessage.delete: copying remote message to " + trashFolderName);
+ }
+ iFolder.copyMessages(new Message[] { this }, remoteTrashFolder);
+ setFlag(Flag.DELETED, true);
+ iFolder.expunge();
+ }
+ else
+ {
+ // Toast.makeText(context, R.string.message_delete_failed, Toast.LENGTH_SHORT).show();
+
+ Log.e(Email.LOG_TAG, "IMAPMessage.delete: remote Trash folder " + trashFolderName + " does not exist and could not be created");
+ }
+ }
+
}
class ImapBodyPart extends MimeBodyPart {
diff --git a/src/com/android/email/mail/store/LocalStore.java b/src/com/android/email/mail/store/LocalStore.java
index 2b37bc493..ee76bbc37 100644
--- a/src/com/android/email/mail/store/LocalStore.java
+++ b/src/com/android/email/mail/store/LocalStore.java
@@ -8,12 +8,14 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.UUID;
+import android.content.SharedPreferences;
import org.apache.commons.io.IOUtils;
@@ -26,6 +28,7 @@ import android.util.Config;
import android.util.Log;
import com.android.email.Email;
+import com.android.email.Preferences;
import com.android.email.Utility;
import com.android.email.codec.binary.Base64OutputStream;
import com.android.email.mail.Address;
@@ -38,6 +41,7 @@ import com.android.email.mail.MessageRetrievalListener;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Part;
import com.android.email.mail.Store;
+import com.android.email.mail.Folder.FolderClass;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeHeader;
@@ -52,14 +56,15 @@ import com.android.email.provider.AttachmentProvider;
* Implements a SQLite database backed local store for Messages.
*
*/
-public class LocalStore extends Store {
- private static final int DB_VERSION = 18;
+public class LocalStore extends Store implements Serializable {
+ private static final int DB_VERSION = 22;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN };
private String mPath;
private SQLiteDatabase mDb;
private File mAttachmentsDir;
private Application mApplication;
+ private String uUid = null;
/**
* @param uri local://localhost/path/to/database/uuid.db
@@ -77,6 +82,14 @@ public class LocalStore extends Store {
}
mPath = uri.getPath();
+
+ // We need to associate the localstore with the account. Since we don't have the account
+ // handy here, we'll take the filename from the DB and use the basename of the filename
+ // Folders probably should have references to their containing accounts
+ File dbFile = new File(mPath);
+ String[] tokens = dbFile.getName().split("\\.");
+ uUid = tokens[0];
+
File parentDir = new File(mPath).getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
@@ -95,58 +108,58 @@ public class LocalStore extends Store {
private void doDbUpgrade ( SQLiteDatabase mDb) {
-
- if (mDb.getVersion() < 18) {
- if (Config.LOGV) {
- Log.v(Email.LOG_TAG, String.format("Upgrading database from %d to %d", mDb
- .getVersion(), 18));
- }
- mDb.execSQL("DROP TABLE IF EXISTS folders");
- mDb.execSQL("CREATE TABLE folders (id INTEGER PRIMARY KEY, name TEXT, "
- + "last_updated INTEGER, unread_count INTEGER, visible_limit INTEGER)");
-
- mDb.execSQL("DROP TABLE IF EXISTS messages");
- mDb.execSQL("CREATE TABLE messages (id INTEGER PRIMARY KEY, folder_id INTEGER, uid TEXT, subject TEXT, "
- + "date INTEGER, flags TEXT, sender_list TEXT, to_list TEXT, cc_list TEXT, bcc_list TEXT, reply_to_list TEXT, "
- + "html_content TEXT, text_content TEXT, attachment_count INTEGER, internal_date INTEGER)");
-
- mDb.execSQL("DROP TABLE IF EXISTS attachments");
- mDb.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
- + "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
- + "mime_type TEXT)");
-
- mDb.execSQL("DROP TABLE IF EXISTS pending_commands");
- mDb.execSQL("CREATE TABLE pending_commands " +
- "(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)");
-
- mDb.execSQL("DROP TRIGGER IF EXISTS delete_folder");
- mDb.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;");
-
- mDb.execSQL("DROP TRIGGER IF EXISTS delete_message");
- mDb.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; END;");
- mDb.setVersion(18);
+ if (Config.LOGV) {
+ Log.v(Email.LOG_TAG, String.format("Upgrading database from %d to %d", mDb
+ .getVersion(), DB_VERSION));
}
+ mDb.execSQL("DROP TABLE IF EXISTS folders");
+ mDb.execSQL("CREATE TABLE folders (id INTEGER PRIMARY KEY, name TEXT, "
+ + "last_updated INTEGER, unread_count INTEGER, visible_limit INTEGER, status TEXT)");
+
+ mDb.execSQL("DROP TABLE IF EXISTS messages");
+ mDb.execSQL("CREATE TABLE messages (id INTEGER PRIMARY KEY, folder_id INTEGER, uid TEXT, subject TEXT, "
+ + "date INTEGER, flags TEXT, sender_list TEXT, to_list TEXT, cc_list TEXT, bcc_list TEXT, reply_to_list TEXT, "
+ + "html_content TEXT, text_content TEXT, attachment_count INTEGER, internal_date INTEGER, message_id TEXT)");
+
+ mDb.execSQL("DROP TABLE IF EXISTS attachments");
+ mDb.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
+ + "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
+ + "mime_type TEXT)");
+
+ mDb.execSQL("DROP TABLE IF EXISTS pending_commands");
+ mDb.execSQL("CREATE TABLE pending_commands " +
+ "(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)");
+
+ mDb.execSQL("DROP TRIGGER IF EXISTS delete_folder");
+ mDb.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;");
+
+ mDb.execSQL("DROP TRIGGER IF EXISTS delete_message");
+ mDb.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; END;");
+ mDb.setVersion(DB_VERSION);
if (mDb.getVersion() != DB_VERSION) {
throw new Error("Database upgrade failed!");
}
}
@Override
- public Folder getFolder(String name) throws MessagingException {
+ public LocalFolder getFolder(String name) throws MessagingException {
return new LocalFolder(name);
}
// TODO this takes about 260-300ms, seems slow.
@Override
- public Folder[] getPersonalNamespaces() throws MessagingException {
- ArrayList folders = new ArrayList();
+ public LocalFolder[] getPersonalNamespaces() throws MessagingException {
+ ArrayList folders = new ArrayList();
Cursor cursor = null;
try {
- cursor = mDb.rawQuery("SELECT name FROM folders", null);
+ cursor = mDb.rawQuery("SELECT name, id, unread_count, visible_limit, last_updated, status FROM folders", null);
while (cursor.moveToNext()) {
- folders.add(new LocalFolder(cursor.getString(0)));
+ LocalFolder folder = new LocalFolder(cursor.getString(0));
+ folder.open(cursor.getInt(1), cursor.getInt(2), cursor.getInt(3), cursor.getLong(4), cursor.getString(5));
+
+ folders.add(folder);
}
}
finally {
@@ -154,7 +167,7 @@ public class LocalStore extends Store {
cursor.close();
}
}
- return folders.toArray(new Folder[] {});
+ return folders.toArray(new LocalFolder[] {});
}
@Override
@@ -300,6 +313,10 @@ public class LocalStore extends Store {
public void removePendingCommand(PendingCommand command) {
mDb.delete("pending_commands", "id = ?", new String[] { Long.toString(command.mId) });
}
+
+ public void removePendingCommands() {
+ mDb.delete("pending_commands", null, null);
+ }
public static class PendingCommand {
private long mId;
@@ -310,21 +327,24 @@ public class LocalStore extends Store {
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(command);
- sb.append("\n");
+ sb.append(": ");
for (String argument : arguments) {
sb.append(" ");
sb.append(argument);
- sb.append("\n");
+ //sb.append("\n");
}
return sb.toString();
}
}
- public class LocalFolder extends Folder {
+ public class LocalFolder extends Folder implements Serializable {
private String mName;
private long mFolderId = -1;
private int mUnreadMessageCount = -1;
private int mVisibleLimit = -1;
+ private FolderClass displayClass = FolderClass.NONE;
+ private FolderClass syncClass = FolderClass.NONE;
+ private String prefId = null;
public LocalFolder(String name) {
this.mName = name;
@@ -341,26 +361,32 @@ public class LocalStore extends Store {
}
Cursor cursor = null;
try {
- cursor = mDb.rawQuery(
- "SELECT id, unread_count, visible_limit FROM folders "
- + "where folders.name = ?",
- new String[] { mName });
- if (cursor.moveToFirst()) {
- mFolderId = cursor.getInt(0);
- mUnreadMessageCount = cursor.getInt(1);
- mVisibleLimit = cursor.getInt(2);
- } else {
- // Calling exists on open is a little expensive. Instead,
- // just handle it when we don't find it.
- create(FolderType.HOLDS_MESSAGES);
- open(mode);
- }
- } finally {
+ cursor = mDb.rawQuery("SELECT id, unread_count, visible_limit, last_updated, status FROM folders "
+ + "where folders.name = ?",
+ new String[] {
+ mName
+ });
+ cursor.moveToFirst();
+ open(cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getLong(3), cursor.getString(4));
+
+ }
+ finally {
if (cursor != null) {
cursor.close();
}
}
}
+
+ private void open(int id, int unreadCount, int visibleLimit, long lastChecked, String status) throws MessagingException
+ {
+ mFolderId = id;
+ mUnreadMessageCount = unreadCount;
+ mVisibleLimit = visibleLimit;
+ super.setStatus(status);
+ // Only want to set the local variable stored in the super class. This class
+ // does a DB update on setLastChecked
+ super.setLastChecked(lastChecked);
+ }
@Override
public boolean isOpen() {
@@ -461,6 +487,13 @@ public class LocalStore extends Store {
mDb.execSQL("UPDATE folders SET unread_count = ? WHERE id = ?",
new Object[] { mUnreadMessageCount, mFolderId });
}
+
+ public void setLastChecked(long lastChecked) throws MessagingException {
+ open(OpenMode.READ_WRITE);
+ super.setLastChecked(lastChecked);
+ mDb.execSQL("UPDATE folders SET last_updated = ? WHERE id = ?",
+ new Object[] { lastChecked, mFolderId });
+ }
public int getVisibleLimit() throws MessagingException {
open(OpenMode.READ_WRITE);
@@ -474,8 +507,128 @@ public class LocalStore extends Store {
mDb.execSQL("UPDATE folders SET visible_limit = ? WHERE id = ?",
new Object[] { mVisibleLimit, mFolderId });
}
+
+ public void setStatus(String status) throws MessagingException
+ {
+ open(OpenMode.READ_WRITE);
+ super.setStatus(status);
+ mDb.execSQL("UPDATE folders SET status = ? WHERE id = ?",
+ new Object[] { status, mFolderId });
+ }
+ @Override
+ public FolderClass getDisplayClass()
+ {
+ return displayClass;
+ }
+
+ @Override
+ public FolderClass getSyncClass()
+ {
+ if (FolderClass.NONE == syncClass)
+ {
+ return displayClass;
+ }
+ else
+ {
+ return syncClass;
+ }
+ }
+
+ public FolderClass getRawSyncClass()
+ {
+
+ return syncClass;
+
+ }
+ public void setDisplayClass(FolderClass displayClass)
+ {
+ this.displayClass = displayClass;
+ }
+
+ public void setSyncClass(FolderClass syncClass)
+ {
+ this.syncClass = syncClass;
+ }
+
+ private String getPrefId() throws MessagingException
+ {
+ open(OpenMode.READ_WRITE);
+
+ if (prefId == null)
+ {
+ prefId = uUid + "." + mName;
+ }
+
+ return prefId;
+ }
+
+ public void delete(Preferences preferences) throws MessagingException {
+ String id = getPrefId();
+
+ SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
+
+ editor.remove(id + ".displayMode");
+ editor.remove(id + ".syncMode");
+ editor.commit();
+ }
+
+ public void save(Preferences preferences) throws MessagingException {
+ String id = getPrefId();
+
+ SharedPreferences.Editor editor = preferences.mSharedPreferences.edit();
+ // there can be a lot of folders. For the defaults, let's not save prefs, saving space
+ if (displayClass == FolderClass.NONE)
+ {
+ editor.remove(id + ".displayMode");
+ }
+ else
+ {
+ editor.putString(id + ".displayMode", displayClass.name());
+ }
+
+ if (syncClass == FolderClass.NONE)
+ {
+ editor.remove(id + ".syncMode");
+ }
+ else
+ {
+ editor.putString(id + ".syncMode", syncClass.name());
+ }
+
+ editor.commit();
+ }
+ public void refresh(Preferences preferences) throws MessagingException {
+
+ String id = getPrefId();
+
+ try
+ {
+ displayClass = FolderClass.valueOf(preferences.mSharedPreferences.getString(id + ".displayMode",
+ FolderClass.NONE.name()));
+ }
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "Unable to load displayMode for " + getName(), e);
+
+ displayClass = FolderClass.NONE;
+ }
+
+ try
+ {
+ syncClass = FolderClass.valueOf(preferences.mSharedPreferences.getString(id + ".syncMode",
+ FolderClass.NONE.name()));
+ }
+ catch (Exception e)
+ {
+ Log.e(Email.LOG_TAG, "Unable to load syncMode for " + getName(), e);
+
+ syncClass = FolderClass.NONE;
+ }
+
+ }
+
@Override
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
throws MessagingException {
@@ -596,6 +749,7 @@ public class LocalStore extends Store {
message.setReplyTo(Address.unpack(cursor.getString(9)));
message.mAttachmentCount = cursor.getInt(10);
message.setInternalDate(new Date(cursor.getLong(11)));
+ message.setHeader("Message-ID", cursor.getString(12));
}
@Override
@@ -614,7 +768,7 @@ public class LocalStore extends Store {
try {
cursor = mDb.rawQuery(
"SELECT subject, sender_list, date, uid, flags, id, to_list, cc_list, "
- + "bcc_list, reply_to_list, attachment_count, internal_date "
+ + "bcc_list, reply_to_list, attachment_count, internal_date, message_id "
+ "FROM messages " + "WHERE uid = ? " + "AND folder_id = ?",
new String[] {
message.getUid(), Long.toString(mFolderId)
@@ -640,7 +794,7 @@ public class LocalStore extends Store {
try {
cursor = mDb.rawQuery(
"SELECT subject, sender_list, date, uid, flags, id, to_list, cc_list, "
- + "bcc_list, reply_to_list, attachment_count, internal_date "
+ + "bcc_list, reply_to_list, attachment_count, internal_date, message_id "
+ "FROM messages " + "WHERE folder_id = ?", new String[] {
Long.toString(mFolderId)
});
@@ -761,6 +915,11 @@ public class LocalStore extends Store {
cv.put("attachment_count", attachments.size());
cv.put("internal_date", message.getInternalDate() == null
? System.currentTimeMillis() : message.getInternalDate().getTime());
+ String[] mHeaders = message.getHeader("Message-ID");
+ if (mHeaders != null && mHeaders.length > 0)
+ {
+ cv.put("message_id", mHeaders[0]);
+ }
long messageId = mDb.insert("messages", "uid", cv);
for (Part attachment : attachments) {
saveAttachment(messageId, attachment, copy);
@@ -968,7 +1127,21 @@ public class LocalStore extends Store {
message.setFlags(flags, value);
}
}
+
+ @Override
+ public void setFlags(Flag[] flags, boolean value)
+ throws MessagingException {
+ open(OpenMode.READ_WRITE);
+ for (Message message : getMessages(null)) {
+ message.setFlags(flags, value);
+ }
+ }
+ @Override
+ public String getUidFromMessageId(Message message) throws MessagingException
+ {
+ throw new MessagingException("Cannot call getUidFromMessageId on LocalFolder");
+ }
@Override
public Message[] expunge() throws MessagingException {
open(OpenMode.READ_WRITE);
@@ -979,6 +1152,13 @@ public class LocalStore extends Store {
*/
return expungedMessages.toArray(new Message[] {});
}
+
+ public void deleteMessagesOlderThan(long cutoff) throws MessagingException
+ {
+ open(OpenMode.READ_ONLY);
+ mDb.execSQL("DELETE FROM messages WHERE folder_id = ? and date < ?", new Object[] {
+ Long.toString(mFolderId), new Long(cutoff) } );
+ }
@Override
public void delete(boolean recurse) throws MessagingException {
@@ -1066,7 +1246,7 @@ public class LocalStore extends Store {
this.mUid = uid;
this.mFolder = folder;
}
-
+
public int getAttachmentCount() {
return mAttachmentCount;
}
diff --git a/src/com/android/email/mail/store/Pop3Store.java b/src/com/android/email/mail/store/Pop3Store.java
index 4d28cda2c..fe8499d33 100644
--- a/src/com/android/email/mail/store/Pop3Store.java
+++ b/src/com/android/email/mail/store/Pop3Store.java
@@ -282,7 +282,7 @@ public class Pop3Store extends Store {
@Override
public OpenMode getMode() throws MessagingException {
- return OpenMode.READ_ONLY;
+ return OpenMode.READ_WRITE;
}
@Override
@@ -487,13 +487,13 @@ public class Pop3Store extends Store {
@Override
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
- throw new UnsupportedOperationException("Pop3Folder.getMessage(MessageRetrievalListener)");
+ throw new UnsupportedOperationException("Pop3: No getMessages");
}
@Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException {
- throw new UnsupportedOperationException("Pop3Folder.getMessage(MessageRetrievalListener)");
+ throw new UnsupportedOperationException("Pop3: No getMessages by uids");
}
/**
@@ -560,7 +560,7 @@ public class Pop3Store extends Store {
*/
pop3Message.setBody(null);
}
- if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE)) {
+ if (listener != null && !(fp.contains(FetchProfile.Item.ENVELOPE) && fp.size() == 1)) {
listener.messageFinished(message, i, count);
}
} catch (IOException ioe) {
@@ -683,11 +683,24 @@ public class Pop3Store extends Store {
public void delete(boolean recurse) throws MessagingException {
}
-
+
+ @Override
+ public String getUidFromMessageId(Message message) throws MessagingException
+ {
+ return null;
+ }
+
public Message[] expunge() throws MessagingException {
return null;
}
+ @Override
+ public void setFlags(Flag[] flags, boolean value)
+ throws MessagingException {
+ Message[] messages = getMessages(null);
+ setFlags(messages, flags, value);
+ }
+
public void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException {
if (!value || !Utility.arrayContains(flags, Flag.DELETED)) {
@@ -696,6 +709,20 @@ public class Pop3Store extends Store {
*/
return;
}
+ ArrayList uids = new ArrayList();
+ try
+ {
+ for (Message message : messages)
+ {
+ uids.add(message.getUid());
+ }
+
+ indexUids(uids);
+ }
+ catch (IOException ioe)
+ {
+ throw new MessagingException("Could not get message number for uid " + uids, ioe);
+ }
try {
for (Message message : messages) {
executeSimpleCommand(String.format("DELE %s",
@@ -792,13 +819,20 @@ public class Pop3Store extends Store {
private String executeSimpleCommand(String command) throws IOException, MessagingException {
try {
open(OpenMode.READ_WRITE);
-
+ if (Config.LOGV)
+ {
+ Log.v(Email.LOG_TAG, "POP3: command '" + command + "'");
+ }
if (command != null) {
writeLine(command);
}
String response = readLine();
-
+ if (Config.LOGV)
+ {
+ Log.v(Email.LOG_TAG, "POP3: response '" + command + "'");
+ }
+
if (response.length() > 1 && response.charAt(0) == '-') {
throw new MessagingException(response);
}
@@ -831,6 +865,7 @@ public class Pop3Store extends Store {
mUid = uid;
mFolder = folder;
mSize = -1;
+ mFlags.add(Flag.X_NO_SEEN_INFO);
}
public void setSize(int size) {
@@ -846,6 +881,20 @@ public class Pop3Store extends Store {
super.setFlag(flag, set);
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
}
+
+ @Override
+ public void delete(String trashFolderName) throws MessagingException
+ {
+ // try
+ // {
+ // Poor POP3 users, we can't copy the message to the Trash folder, but they still want a delete
+ setFlag(Flag.DELETED, true);
+ // }
+// catch (MessagingException me)
+// {
+// Log.w(Email.LOG_TAG, "Could not delete non-existant message", me);
+// }
+ }
}
class Pop3Capabilities {
diff --git a/src/com/android/email/mail/transport/SmtpTransport.java b/src/com/android/email/mail/transport/SmtpTransport.java
index e5e704889..82af6861a 100644
--- a/src/com/android/email/mail/transport/SmtpTransport.java
+++ b/src/com/android/email/mail/transport/SmtpTransport.java
@@ -246,6 +246,10 @@ public class SmtpTransport extends Transport {
} catch (IOException ioe) {
throw new MessagingException("Unable to send message", ioe);
}
+ finally
+ {
+ close();
+ }
}
public void close() {
diff --git a/src/com/android/email/service/BootReceiver.java b/src/com/android/email/service/BootReceiver.java
index 3867eb574..155913f8e 100644
--- a/src/com/android/email/service/BootReceiver.java
+++ b/src/com/android/email/service/BootReceiver.java
@@ -1,6 +1,8 @@
package com.android.email.service;
+import com.android.email.Email;
+import android.util.Log;
import com.android.email.MessagingController;
import android.content.BroadcastReceiver;
@@ -9,8 +11,10 @@ import android.content.Intent;
public class BootReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
+ Log.v(Email.LOG_TAG, "BootReceiver.onReceive" + intent);
+
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- MailService.actionReschedule(context);
+ Email.setServicesEnabled(context);
}
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
MailService.actionCancel(context);
diff --git a/src/com/android/email/service/MailService.java b/src/com/android/email/service/MailService.java
index 6821d21f7..e5e2a64d9 100644
--- a/src/com/android/email/service/MailService.java
+++ b/src/com/android/email/service/MailService.java
@@ -1,6 +1,7 @@
package com.android.email.service;
+import java.util.Date;
import java.util.HashMap;
import android.app.AlarmManager;
@@ -27,6 +28,9 @@ import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.activity.Accounts;
import com.android.email.activity.FolderMessageList;
+import com.android.email.mail.Folder;
+import com.android.email.mail.MessagingException;
+import com.android.email.mail.Store;
/**
*/
@@ -34,7 +38,7 @@ public class MailService extends Service {
private static final String ACTION_CHECK_MAIL = "com.android.email.intent.action.MAIL_SERVICE_WAKEUP";
private static final String ACTION_RESCHEDULE = "com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE";
private static final String ACTION_CANCEL = "com.android.email.intent.action.MAIL_SERVICE_CANCEL";
-
+
private Listener mListener = new Listener();
private int mStartId;
@@ -52,17 +56,26 @@ public class MailService extends Service {
i.setAction(MailService.ACTION_CANCEL);
context.startService(i);
}
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.v(Email.LOG_TAG, "***** MailService *****: onCreate");
+ }
@Override
public void onStart(Intent intent, int startId) {
+ setForeground(true); // if it gets killed once, it'll never restart
+ Log.v(Email.LOG_TAG, "***** MailService *****: onStart(" + intent + ", " + startId + ")");
super.onStart(intent, startId);
this.mStartId = startId;
MessagingController.getInstance(getApplication()).addListener(mListener);
if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
- if (Config.LOGV) {
+ //if (Config.LOGV) {
+ MessagingController.getInstance(getApplication()).log("***** MailService *****: checking mail");
Log.v(Email.LOG_TAG, "***** MailService *****: checking mail");
- }
+ //}
mListener.wakeLockAcquire();
MessagingController.getInstance(getApplication()).checkMail(this, null, mListener);
}
@@ -70,6 +83,8 @@ public class MailService extends Service {
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "***** MailService *****: cancel");
}
+ MessagingController.getInstance(getApplication()).log("***** MailService *****: cancel");
+
cancel();
stopSelf(startId);
}
@@ -77,6 +92,7 @@ public class MailService extends Service {
if (Config.LOGV) {
Log.v(Email.LOG_TAG, "***** MailService *****: reschedule");
}
+ MessagingController.getInstance(getApplication()).log("***** MailService *****: reschedule");
reschedule();
stopSelf(startId);
}
@@ -84,6 +100,7 @@ public class MailService extends Service {
@Override
public void onDestroy() {
+ Log.v(Email.LOG_TAG, "***** MailService *****: onDestroy()");
super.onDestroy();
MessagingController.getInstance(getApplication()).removeListener(mListener);
}
@@ -103,22 +120,31 @@ public class MailService extends Service {
i.setClassName(getApplication().getPackageName(), "com.android.email.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
-
+
int shortestInterval = -1;
+
for (Account account : Preferences.getPreferences(this).getAccounts()) {
if (account.getAutomaticCheckIntervalMinutes() != -1
&& (account.getAutomaticCheckIntervalMinutes() < shortestInterval || shortestInterval == -1)) {
shortestInterval = account.getAutomaticCheckIntervalMinutes();
}
}
-
+
if (shortestInterval == -1) {
+ Log.v(Email.LOG_TAG, "No next check scheduled for package " + getApplication().getPackageName());
alarmMgr.cancel(pi);
}
- else {
- alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
- + (shortestInterval * (60 * 1000)), pi);
+ else
+ {
+ long delay = (shortestInterval * (60 * 1000));
+
+ long nextTime = System.currentTimeMillis() + delay;
+ String checkString = "Next check for package " + getApplication().getPackageName() + " scheduled for " + new Date(nextTime);
+ Log.v(Email.LOG_TAG, checkString);
+ MessagingController.getInstance(getApplication()).log(checkString);
+ alarmMgr.set(AlarmManager.RTC_WAKEUP, nextTime, pi);
}
+
}
public IBinder onBind(Intent intent) {
@@ -131,22 +157,24 @@ public class MailService extends Service {
// wakelock strategy is to be very conservative. If there is any reason to release, then release
// don't want to take the chance of running wild
- public synchronized void wakeLockAcquire() {
- if (wakeLock == null) {
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
- wakeLock.setReferenceCounted(false);
- wakeLock.acquire(Email.WAKE_LOCK_TIMEOUT);
+ public synchronized void wakeLockAcquire()
+ {
+ if (wakeLock == null)
+ {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Email");
+ wakeLock.setReferenceCounted(false);
+ wakeLock.acquire(Email.WAKE_LOCK_TIMEOUT);
}
}
-
- public synchronized void wakeLockRelease() {
- if (wakeLock != null) {
+ public synchronized void wakeLockRelease()
+ {
+ if (wakeLock != null)
+ {
wakeLock.release();
wakeLock = null;
}
}
-
@Override
public void checkMailStarted(Context context, Account account) {
accountsWithNewMail.clear();
@@ -154,8 +182,14 @@ public class MailService extends Service {
@Override
public void checkMailFailed(Context context, Account account, String reason) {
+ try
+ {
reschedule();
+ }
+ finally
+ {
wakeLockRelease();
+ }
stopSelf(mStartId);
}
@@ -169,54 +203,105 @@ public class MailService extends Service {
accountsWithNewMail.put(account, numNewMessages);
}
}
+
+ private void checkMailDone(Context context, Account doNotUseaccount)
+ {
+ if (accountsWithNewMail.isEmpty())
+ {
+ return;
+ }
+ StringBuffer notice = new StringBuffer();
+ int accountNumber = Email.FETCHING_EMAIL_NOTIFICATION_NO_ACCOUNT;
+
+ boolean vibrate = false;
+ String ringtone = null;
+ for (Account thisAccount : Preferences.getPreferences(context).getAccounts()) {
+ if (thisAccount.isNotifyNewMail())
+ {
+ int unreadMessageCount = 0;
+ try
+ {
+ unreadMessageCount = thisAccount.getUnreadMessageCount(context, getApplication());
+ if (unreadMessageCount > 0)
+ {
+ notice.append(getString(R.string.notification_new_one_account_fmt, unreadMessageCount,
+ thisAccount.getDescription()) + "\n");
+ if (accountNumber != Email.FETCHING_EMAIL_NOTIFICATION_MULTI_ACCOUNT_ID) // if already set to Multi, nothing to do
+ {
+ if (accountNumber == Email.FETCHING_EMAIL_NOTIFICATION_NO_ACCOUNT) // Haven't set to anything, yet, set to this account number
+ {
+ accountNumber = thisAccount.getAccountNumber();
+ }
+ else // Another account was already set, so there is more than one with new mail
+ {
+ accountNumber = Email.FETCHING_EMAIL_NOTIFICATION_MULTI_ACCOUNT_ID;
+ }
+ }
+ }
+ }
+ catch (MessagingException me)
+ {
+ Log.e(Email.LOG_TAG, "***** MailService *****: couldn't get unread count for account " +
+ thisAccount.getDescription(), me);
+ }
+ if (ringtone == null)
+ {
+ ringtone = thisAccount.getRingtone();
+ }
+ vibrate |= thisAccount.isVibrate();
+ }
+ }
+ if (notice.length() > 0)
+ {
+ NotificationManager notifMgr =
+ (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ Notification notif = new Notification(R.drawable.stat_notify_email_generic,
+ getString(R.string.notification_new_title), System.currentTimeMillis());
+
+ // If only one account has mail, maybe go back to the old way of targetting the account.
+ Intent i = new Intent(context, Accounts.class);
+ PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
+
+ notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
+ notice, pi);
+
+ // notif.defaults = Notification.DEFAULT_LIGHTS;
+ notif.sound = TextUtils.isEmpty(ringtone) ? null : Uri.parse(ringtone);
+ if (vibrate) {
+ notif.defaults |= Notification.DEFAULT_VIBRATE;
+ }
+
+ notif.flags |= Notification.FLAG_SHOW_LIGHTS;
+ notif.ledARGB = Email.NOTIFICATION_LED_COLOR;
+ notif.ledOnMS = Email.NOTIFICATION_LED_ON_TIME;
+ notif.ledOffMS = Email.NOTIFICATION_LED_OFF_TIME;
+
+ notifMgr.notify(accountNumber, notif);
+ }
+
+ }
@Override
public void checkMailFinished(Context context, Account account) {
- NotificationManager notifMgr = (NotificationManager)context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- if (accountsWithNewMail.size() > 0) {
- Notification notif = new Notification(R.drawable.stat_notify_email_generic,
- getString(R.string.notification_new_title), System.currentTimeMillis());
- boolean vibrate = false;
- String ringtone = null;
- if (accountsWithNewMail.size() > 1) {
- for (Account account1 : accountsWithNewMail.keySet()) {
- if (account1.isVibrate()) vibrate = true;
- if (account1.isNotifyRingtone()) ringtone = account1.getRingtone();
- }
- Intent i = new Intent(context, Accounts.class);
- PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
- notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
- getString(R.string.notification_new_multi_account_fmt,
- accountsWithNewMail.size()), pi);
- } else {
- Account account1 = accountsWithNewMail.keySet().iterator().next();
- int totalNewMails = accountsWithNewMail.get(account1);
- Intent i = FolderMessageList.actionHandleAccountIntent(context, account1, Email.INBOX);
- PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
- notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
- getString(R.string.notification_new_one_account_fmt, totalNewMails,
- account1.getDescription()), pi);
- vibrate = account1.isVibrate();
- if (account1.isNotifyRingtone()) ringtone = account1.getRingtone();
- }
-
- notif.sound = TextUtils.isEmpty(ringtone) ? null : Uri.parse(ringtone);
- if (vibrate) {
- notif.defaults |= Notification.DEFAULT_VIBRATE;
- }
- notif.flags |= Notification.FLAG_SHOW_LIGHTS;
- notif.ledARGB = Email.NOTIFICATION_LED_COLOR;
- notif.ledOnMS = Email.NOTIFICATION_LED_ON_TIME;
- notif.ledOffMS = Email.NOTIFICATION_LED_OFF_TIME;
-
- notifMgr.notify(Email.NEW_EMAIL_NOTIFICATION_ID, notif);
+ Log.v(Email.LOG_TAG, "***** MailService *****: checkMailFinished");
+ try
+ {
+ checkMailDone(context, account);
+ }
+ finally
+ {
+ try
+ {
+ reschedule();
+ }
+ finally
+ {
+ wakeLockRelease();
}
-
- reschedule();
- wakeLockRelease();
stopSelf(mStartId);
+ }
}
}
}