diff --git a/res/layout/attachment_forward_failed_notification.xml b/res/layout/attachment_forward_failed_notification.xml
deleted file mode 100644
index 04394a0fb..000000000
--- a/res/layout/attachment_forward_failed_notification.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f8cfcd1e7..265e25463 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -378,11 +378,15 @@ save attachment.
messages moved to %2$s
- "An attachment couldn't be forwarded"
+ Could not forward one or more attachments
- "The attachment "%s
- " couldn't be sent with your outgoing mail because it couldn't be downloaded."
-
+ Could not forward
+ %s
+
+ %s
+ sign-in failed
+
+ Touch to change account settings
diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java
index a4156f6d8..44ec5db0a 100644
--- a/src/com/android/email/MessagingController.java
+++ b/src/com/android/email/MessagingController.java
@@ -16,6 +16,7 @@
package com.android.email;
+import com.android.email.mail.AuthenticationFailedException;
import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag;
import com.android.email.mail.Folder;
@@ -376,6 +377,7 @@ public class MessagingController implements Runnable {
private void synchronizeMailboxSynchronous(final EmailContent.Account account,
final EmailContent.Mailbox folder) {
mListeners.synchronizeMailboxStarted(account.mId, folder.mId);
+ NotificationController nc = NotificationController.getInstance(mContext);
try {
processPendingActionsSynchronous(account);
@@ -393,10 +395,16 @@ public class MessagingController implements Runnable {
mListeners.synchronizeMailboxFinished(account.mId, folder.mId,
results.mTotalMessages,
results.mNewMessages);
+ // Clear authentication notification for this account
+ nc.cancelLoginFailedNotification(account.mId);
} catch (MessagingException e) {
if (Email.LOGD) {
Log.v(Email.LOG_TAG, "synchronizeMailbox", e);
}
+ if (e instanceof AuthenticationFailedException) {
+ // Generate authentication notification
+ nc.showLoginFailedNotification(account.mId);
+ }
mListeners.synchronizeMailboxFailed(account.mId, folder.mId, e);
}
}
@@ -1974,6 +1982,7 @@ public class MessagingController implements Runnable {
*/
public void sendPendingMessagesSynchronous(final EmailContent.Account account,
long sentFolderId) {
+ NotificationController nc = NotificationController.getInstance(mContext);
// 1. Loop through all messages in the account's outbox
long outboxId = Mailbox.findMailboxOfType(mContext, account.mId, Mailbox.TYPE_OUTBOX);
if (outboxId == Mailbox.NO_MAILBOX) {
@@ -2018,6 +2027,9 @@ public class MessagingController implements Runnable {
sender.sendMessage(messageId);
} catch (MessagingException me) {
// report error for this message, but keep trying others
+ if (me instanceof AuthenticationFailedException) {
+ nc.showLoginFailedNotification(account.mId);
+ }
mListeners.sendPendingMessagesFailed(account.mId, messageId, me);
continue;
}
@@ -2045,8 +2057,11 @@ public class MessagingController implements Runnable {
}
// 6. report completion/success
mListeners.sendPendingMessagesCompleted(account.mId);
-
+ nc.cancelLoginFailedNotification(account.mId);
} catch (MessagingException me) {
+ if (me instanceof AuthenticationFailedException) {
+ nc.showLoginFailedNotification(account.mId);
+ }
mListeners.sendPendingMessagesFailed(account.mId, -1, me);
} finally {
c.close();
diff --git a/src/com/android/email/NotificationController.java b/src/com/android/email/NotificationController.java
index dd7e35e89..80a24990f 100644
--- a/src/com/android/email/NotificationController.java
+++ b/src/com/android/email/NotificationController.java
@@ -18,15 +18,18 @@ package com.android.email;
import com.android.email.activity.ContactStatusLoader;
import com.android.email.activity.Welcome;
+import com.android.email.activity.setup.AccountSettingsXL;
import com.android.email.mail.Address;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
+import com.android.email.provider.EmailContent.Attachment;
import com.android.email.provider.EmailContent.Message;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
@@ -41,8 +44,10 @@ import android.text.TextUtils;
public class NotificationController {
public static final int NOTIFICATION_ID_SECURITY_NEEDED = 1;
public static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2;
- public static final int NOTIFICATION_ID_WARNING = 3;
- private static final int NOTIFICATION_ID_NEW_MESSAGES_BASE = 10;
+ public static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
+
+ private static final int NOTIFICATION_ID_BASE_NEW_MESSAGES = 0x10000000;
+ private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
private static NotificationController sInstance;
private final Context mContext;
@@ -70,7 +75,7 @@ public class NotificationController {
* accountID won't be too huge. Any other smarter/cleaner way?
*/
private int getNewMessageNotificationId(long accountId) {
- return (int) (NOTIFICATION_ID_NEW_MESSAGES_BASE + accountId);
+ return (int) (NOTIFICATION_ID_BASE_NEW_MESSAGES + accountId);
}
/**
@@ -224,4 +229,51 @@ public class NotificationController {
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.defaults |= Notification.DEFAULT_LIGHTS;
}
+
+ /**
+ * Generic warning notification
+ */
+ public void showWarningNotification(int id, String tickerText, String notificationText,
+ Intent intent) {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ Notification n = new Notification(android.R.drawable.stat_notify_error, tickerText,
+ System.currentTimeMillis());
+ n.setLatestEventInfo(mContext, tickerText, notificationText, pendingIntent);
+ n.flags = Notification.FLAG_AUTO_CANCEL;
+ mNotificationManager.notify(id, n);
+ }
+
+ /**
+ * Alert the user that an attachment couldn't be forwarded. This is a very unusual case, and
+ * perhaps we shouldn't even send a notification. For now, it's helpful for debugging.
+ */
+ public void showDownloadForwardFailedNotification(Attachment att) {
+ showWarningNotification(NOTIFICATION_ID_ATTACHMENT_WARNING,
+ mContext.getString(R.string.forward_download_failed_ticker),
+ mContext.getString(R.string.forward_download_failed_notification,
+ att.mFileName),
+ Welcome.createOpenCombinedOutboxIntent(mContext));
+ }
+
+ /**
+ * Alert the user that login failed for the specified account
+ */
+ private int getLoginFailedNotificationId(long accountId) {
+ return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int)accountId;
+ }
+
+ // NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
+ public void showLoginFailedNotification(long accountId) {
+ final Account account = Account.restoreAccountWithId(mContext, accountId);
+ if (account == null) return;
+ showWarningNotification(getLoginFailedNotificationId(accountId),
+ mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
+ mContext.getString(R.string.login_failed_notification),
+ AccountSettingsXL.createAccountSettingsIntent(mContext, accountId));
+ }
+
+ public void cancelLoginFailedNotification(long accountId) {
+ mNotificationManager.cancel(getLoginFailedNotificationId(accountId));
+ }
}
diff --git a/src/com/android/email/activity/Welcome.java b/src/com/android/email/activity/Welcome.java
index 991566112..211879496 100644
--- a/src/com/android/email/activity/Welcome.java
+++ b/src/com/android/email/activity/Welcome.java
@@ -110,7 +110,7 @@ public class Welcome extends Activity {
}
/**
- * Create an Intent to open "Combined Inbox".
+ * Create an Intent to open "Combined Outbox".
*/
public static Intent createOpenCombinedInboxIntent(Context context) {
Intent i = new Intent(context, Welcome.class);
@@ -118,6 +118,15 @@ public class Welcome extends Activity {
return i;
}
+ /**
+ * Create an Intent to open "Combined Inbox".
+ */
+ public static Intent createOpenCombinedOutboxIntent(Context context) {
+ Intent i = new Intent(context, Welcome.class);
+ i.putExtra(EXTRA_MAILBOX_ID, Mailbox.QUERY_ALL_OUTBOX);
+ return i;
+ }
+
/**
* Open account's inbox.
*/
diff --git a/src/com/android/email/activity/setup/AccountSettingsXL.java b/src/com/android/email/activity/setup/AccountSettingsXL.java
index fdf4e9ea3..45b43ece0 100644
--- a/src/com/android/email/activity/setup/AccountSettingsXL.java
+++ b/src/com/android/email/activity/setup/AccountSettingsXL.java
@@ -121,6 +121,16 @@ public class AccountSettingsXL extends PreferenceActivity implements OnClickList
fromActivity.startActivity(i);
}
+ /**
+ * Create and return an intent to display (and edit) settings for a specific account, or -1
+ * for any/all accounts
+ */
+ public static Intent createAccountSettingsIntent(Context context, long accountId) {
+ Intent i = new Intent(context, AccountSettingsXL.class);
+ i.putExtra(EXTRA_ACCOUNT_ID, accountId);
+ return i;
+ }
+
/**
* Launch generic settings and pre-enable the debug preferences
*/
diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java
index b7b9428af..71cd58211 100644
--- a/src/com/android/email/mail/store/ImapStore.java
+++ b/src/com/android/email/mail/store/ImapStore.java
@@ -418,9 +418,17 @@ public class ImapStore extends Store {
} catch (IOException ioe) {
connection.close();
throw new MessagingException("Unable to get folder list.", ioe);
- } finally {
+ } catch (AuthenticationFailedException afe) {
+ // We do NOT want this connection pooled, or we will continue to send NOOP and SELECT
+ // commands to the server
connection.destroyResponses();
- poolConnection(connection);
+ connection = null;
+ throw afe;
+ } finally {
+ if (connection != null) {
+ connection.destroyResponses();
+ poolConnection(connection);
+ }
}
}
@@ -601,6 +609,11 @@ public class ImapStore extends Store {
} finally {
destroyResponses();
}
+ } catch (AuthenticationFailedException e) {
+ // Don't cache this connection, so we're forced to try connecting/login again
+ mConnection = null;
+ close(false);
+ throw e;
} catch (MessagingException e) {
mExists = false;
close(false);
@@ -1633,6 +1646,8 @@ public class ImapStore extends Store {
}
static class ImapException extends MessagingException {
+ private static final long serialVersionUID = 1L;
+
String mAlertText;
public ImapException(String message, String alertText, Throwable throwable) {
diff --git a/src/com/android/email/service/AttachmentDownloadService.java b/src/com/android/email/service/AttachmentDownloadService.java
index 9657af679..96d4190c8 100644
--- a/src/com/android/email/service/AttachmentDownloadService.java
+++ b/src/com/android/email/service/AttachmentDownloadService.java
@@ -18,11 +18,9 @@ package com.android.email.service;
import com.android.email.Email;
import com.android.email.NotificationController;
-import com.android.email.R;
import com.android.email.Utility;
import com.android.email.Controller.ControllerService;
import com.android.email.ExchangeUtils.NullEmailService;
-import com.android.email.activity.Welcome;
import com.android.email.provider.AttachmentProvider;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
@@ -30,9 +28,6 @@ import com.android.email.provider.EmailContent.Attachment;
import com.android.email.provider.EmailContent.Message;
import com.android.exchange.ExchangeService;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
@@ -42,7 +37,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Log;
-import android.widget.RemoteViews;
import java.io.File;
import java.io.FileDescriptor;
@@ -349,7 +343,8 @@ public class AttachmentDownloadService extends Service implements Runnable {
// message never get sent
EmailContent.delete(mContext, Attachment.CONTENT_URI, attachment.mId);
// TODO: Talk to UX about whether this is even worth doing
- showDownloadForwardFailedNotification(attachment);
+ NotificationController nc = NotificationController.getInstance(mContext);
+ nc.showDownloadForwardFailedNotification(attachment);
deleted = true;
}
// If we're an attachment on forwarded mail, and if we're not still blocked,
@@ -457,31 +452,6 @@ public class AttachmentDownloadService extends Service implements Runnable {
}
}
- /**
- * Alert the user that an attachment couldn't be forwarded. This is a very unusual case, and
- * perhaps we shouldn't even send a notification. For now, it's helpful for debugging.
- * Note the STOPSHIP below...
- */
- void showDownloadForwardFailedNotification(Attachment att) {
- // STOPSHIP: Tentative UI; if we use a notification, replace this text with a resource
- RemoteViews contentView = new RemoteViews(getPackageName(),
- R.layout.attachment_forward_failed_notification);
- contentView.setImageViewResource(R.id.image, R.drawable.ic_email_attachment);
- contentView.setTextViewText(R.id.text,
- getString(R.string.forward_download_failed_notification, att.mFileName));
- Notification n = new Notification(R.drawable.stat_notify_email_generic,
- getString(R.string.forward_download_failed_ticker), System.currentTimeMillis());
- n.contentView = contentView;
- Intent i = new Intent(mContext, Welcome.class);
- PendingIntent pending = PendingIntent.getActivity(mContext, 0, i,
- PendingIntent.FLAG_UPDATE_CURRENT);
- n.contentIntent = pending;
- n.flags = Notification.FLAG_AUTO_CANCEL;
- NotificationManager nm =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(NotificationController.NOTIFICATION_ID_WARNING, n);
- }
-
/**
* Return the class of the service used by the account type of the provided account id. We
* cache the results to avoid repeated database access
diff --git a/src/com/android/email/service/MailService.java b/src/com/android/email/service/MailService.java
index eccf7ac80..b0aa0ab7f 100644
--- a/src/com/android/email/service/MailService.java
+++ b/src/com/android/email/service/MailService.java
@@ -454,7 +454,7 @@ public class MailService extends Service {
* Create a watchdog alarm and set it. This is used in case a mail check fails (e.g. we are
* killed by the system due to memory pressure.) Normally, a mail check will complete and
* the watchdog will be replaced by the call to reschedule().
- * @param accountId the account we were trying to check
+ * @param accountId the account we were trying to check
* @param alarmMgr system alarm manager
*/
private void setWatchdog(long accountId, AlarmManager alarmMgr) {
@@ -701,7 +701,9 @@ public class MailService extends Service {
@Override
public void updateMailboxCallback(MessagingException result, long accountId,
long mailboxId, int progress, int numNewMessages) {
- if (result != null || progress == 100) {
+ // First, look for authentication failures and notify
+ //checkAuthenticationStatus(result, accountId);
+ if (result != null || progress == 100) {
// We only track the inbox here in the service - ignore other mailboxes
long inboxId = Mailbox.findMailboxOfType(MailService.this,
accountId, Mailbox.TYPE_INBOX);
diff --git a/src/com/android/exchange/ExchangeService.java b/src/com/android/exchange/ExchangeService.java
index 7b87b75fa..6f103b0aa 100644
--- a/src/com/android/exchange/ExchangeService.java
+++ b/src/com/android/exchange/ExchangeService.java
@@ -19,6 +19,7 @@ package com.android.exchange;
import com.android.email.AccountBackupRestore;
import com.android.email.Email;
+import com.android.email.NotificationController;
import com.android.email.Utility;
import com.android.email.mail.transport.SSLUtils;
import com.android.email.provider.EmailContent;
@@ -975,14 +976,17 @@ public class ExchangeService extends Service implements Runnable {
* is null, mailboxes from all accounts with the specified hold will be released
* @param reason the reason for the SyncError (AbstractSyncService.EXIT_XXX)
* @param account an Account whose mailboxes should be released (or all if null)
+ * @return whether or not any mailboxes were released
*/
- /*package*/ void releaseSyncHolds(Context context, int reason, Account account) {
- releaseSyncHoldsImpl(context, reason, account);
+ /*package*/ boolean releaseSyncHolds(Context context, int reason, Account account) {
+ boolean holdWasReleased = releaseSyncHoldsImpl(context, reason, account);
kick("security release");
+ return holdWasReleased;
}
- private void releaseSyncHoldsImpl(Context context, int reason, Account account) {
+ private boolean releaseSyncHoldsImpl(Context context, int reason, Account account) {
synchronized(sSyncLock) {
+ boolean holdWasReleased = false;
ArrayList releaseList = new ArrayList();
for (long mailboxId: mSyncErrorMap.keySet()) {
if (account != null) {
@@ -1000,7 +1004,9 @@ public class ExchangeService extends Service implements Runnable {
}
for (long mailboxId: releaseList) {
mSyncErrorMap.remove(mailboxId);
+ holdWasReleased = true;
}
+ return holdWasReleased;
}
}
@@ -2376,6 +2382,20 @@ public class ExchangeService extends Service implements Runnable {
SyncError syncError = errorMap.get(mailboxId);
exchangeService.releaseMailbox(mailboxId);
int exitStatus = svc.mExitStatus;
+ Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId);
+ if (m == null) return;
+
+ if (exitStatus != AbstractSyncService.EXIT_LOGIN_FAILURE) {
+ long accountId = m.mAccountKey;
+ Account account = Account.restoreAccountWithId(exchangeService, accountId);
+ if (account == null) return;
+ if (exchangeService.releaseSyncHolds(exchangeService,
+ AbstractSyncService.EXIT_LOGIN_FAILURE, account)) {
+ NotificationController.getInstance(exchangeService)
+ .cancelLoginFailedNotification(accountId);
+ }
+ }
+
switch (exitStatus) {
case AbstractSyncService.EXIT_DONE:
if (svc.hasPendingRequests()) {
@@ -2389,8 +2409,6 @@ public class ExchangeService extends Service implements Runnable {
break;
// I/O errors get retried at increasing intervals
case AbstractSyncService.EXIT_IO_ERROR:
- Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId);
- if (m == null) return;
if (syncError != null) {
syncError.escalate();
log(m.mDisplayName + " held for " + syncError.holdDelay + "ms");
@@ -2400,8 +2418,11 @@ public class ExchangeService extends Service implements Runnable {
}
break;
// These errors are not retried automatically
- case AbstractSyncService.EXIT_SECURITY_FAILURE:
case AbstractSyncService.EXIT_LOGIN_FAILURE:
+ NotificationController.getInstance(exchangeService)
+ .showLoginFailedNotification(m.mAccountKey);
+ // Fall through
+ case AbstractSyncService.EXIT_SECURITY_FAILURE:
case AbstractSyncService.EXIT_EXCEPTION:
errorMap.put(mailboxId, exchangeService.new SyncError(exitStatus, true));
break;
diff --git a/tests/src/com/android/exchange/ExchangeServiceAccountTests.java b/tests/src/com/android/exchange/ExchangeServiceAccountTests.java
index 425e9c482..9b9272695 100644
--- a/tests/src/com/android/exchange/ExchangeServiceAccountTests.java
+++ b/tests/src/com/android/exchange/ExchangeServiceAccountTests.java
@@ -73,7 +73,8 @@ public class ExchangeServiceAccountTests extends AccountTestCase {
// We should have 4
assertEquals(4, errorMap.keySet().size());
// Release the holds on acct2 (there are two of them)
- exchangeService.releaseSyncHolds(context, AbstractSyncService.EXIT_SECURITY_FAILURE, acct2);
+ assertTrue(exchangeService.releaseSyncHolds(context,
+ AbstractSyncService.EXIT_SECURITY_FAILURE, acct2));
// There should be two left
assertEquals(2, errorMap.keySet().size());
// And these are the two...
@@ -86,19 +87,22 @@ public class ExchangeServiceAccountTests extends AccountTestCase {
// We should have 4 again
assertEquals(4, errorMap.keySet().size());
// Release all of the security holds
- exchangeService.releaseSyncHolds(context, AbstractSyncService.EXIT_SECURITY_FAILURE, null);
+ assertTrue(exchangeService.releaseSyncHolds(context,
+ AbstractSyncService.EXIT_SECURITY_FAILURE, null));
// There should be one left
assertEquals(1, errorMap.keySet().size());
// And this is the one
assertNotNull(errorMap.get(box2.mId));
// Release the i/o holds on account 2 (there aren't any)
- exchangeService.releaseSyncHolds(context, AbstractSyncService.EXIT_IO_ERROR, acct2);
+ assertFalse(exchangeService.releaseSyncHolds(context,
+ AbstractSyncService.EXIT_IO_ERROR, acct2));
// There should still be one left
assertEquals(1, errorMap.keySet().size());
// Release the i/o holds on account 1 (there's one)
- exchangeService.releaseSyncHolds(context, AbstractSyncService.EXIT_IO_ERROR, acct1);
+ assertTrue(exchangeService.releaseSyncHolds(context,
+ AbstractSyncService.EXIT_IO_ERROR, acct1));
// There should still be one left
assertEquals(0, errorMap.keySet().size());
}