Merge "Turn on ActionBar on the 1-pane message list"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f8cfcd1..c9f6ebb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -181,8 +181,8 @@
          used when only one account is set up.
          "num_new_message" will be replaced with the number of new messages. [CHAR LIMIT=none] -->
     <plurals name="notification_num_new_messages_single_account">
-        <item quantity="one"><xliff:g id="num_new_message">%1$d</xliff:g></item> new
-        <item quantity="other"><xliff:g id="num_new_message">%1$d</xliff:g></item> new
+        <item quantity="one"><xliff:g id="num_new_message">%1$d</xliff:g> new</item>
+        <item quantity="other"><xliff:g id="num_new_message">%1$d</xliff:g> new</item>
     </plurals>
 
     <!-- "new message" notification body for the number of new messages,
@@ -378,11 +378,15 @@
         messages moved to <xliff:g id="mailbox_name" example="Inbox" >%2$s</xliff:g></item>
     </plurals>
     <!-- Notification ticker when a forwarded attachment couldn't be sent [CHAR LIMIT=none] -->
-    <string name="forward_download_failed_ticker">"An attachment couldn't be forwarded"</string>
+    <string name="forward_download_failed_ticker">Could not forward one or more attachments</string>
     <!-- Notification text when a forwarded attachment couldn't be sent -->
-    <string name="forward_download_failed_notification">"The attachment "<xliff:g id="filename">%s
-        </xliff:g>" couldn't be sent with your outgoing mail because it couldn't be downloaded."
-        </string>
+    <string name="forward_download_failed_notification">Could not forward <xliff:g id="filename">
+        %s</xliff:g></string>
+    <!-- Notification ticker when email account authentication fails [CHAR LIMIT=20] -->
+    <string name="login_failed_ticker"><xliff:g id="account_name">%s
+        </xliff:g> sign-in failed</string>
+    <!-- Notification text when email account authentication fails [CHAR LIMIT=75]-->
+    <string name="login_failed_notification">Touch to change account settings</string>
 
     <!-- Size unit for bytes for attachments [CHAR LIMIT=10] -->
     <plurals name="message_view_attachment_bytes">
@@ -646,6 +650,11 @@
          being supported -->
     <string name="account_setup_failed_security_policies_unsupported">
          This server requires security features your phone does not support.</string>
+    <!-- Warning given to users when they request disabling device administration (i.e. that their
+         administered accounts will be deleted) [CHAR LIMIT=none] -->
+    <string name="disable_admin_warning">WARNING: Deactivating the Email application\'s authority
+         to administer your device will delete all Email accounts that require it, along with their
+         email, contacts, calendar events, and other data.</string>
 
     <!-- Notification ticker  when device security required -->
     <string name="security_notification_ticker_fmt">
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index a541ab0..cd24535 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -2363,10 +2363,14 @@
                 status = EmailServiceStatus.SUCCESS;
             }
 
-            try {
-                ExchangeService.callback().syncMailboxStatus(mMailboxId, status, 0);
-            } catch (RemoteException e1) {
-                // Don't care if this fails
+            // Send a callback if this run was initiated by a service call
+            if (mSyncReason == ExchangeService.SYNC_SERVICE_START_SYNC ||
+                    mSyncReason == ExchangeService.SYNC_SERVICE_PART_REQUEST) {
+                try {
+                    ExchangeService.callback().syncMailboxStatus(mMailboxId, status, 0);
+                } catch (RemoteException e1) {
+                    // Don't care if this fails
+                }
             }
 
             // Make sure ExchangeService knows about this
diff --git a/src/com/android/exchange/ExchangeService.java b/src/com/android/exchange/ExchangeService.java
index 7b87b75..ff0cd35 100644
--- a/src/com/android/exchange/ExchangeService.java
+++ b/src/com/android/exchange/ExchangeService.java
@@ -19,6 +19,7 @@
 
 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;
@@ -260,15 +261,23 @@
                 (INSTANCE == null) ? null: INSTANCE.mCallbackList;
             if (callbackList != null) {
                 // Call everyone on our callback list
-                // Exceptions can be safely ignored
                 int count = callbackList.beginBroadcast();
-                for (int i = 0; i < count; i++) {
-                    try {
-                        wrapper.call(callbackList.getBroadcastItem(i));
-                    } catch (RemoteException e) {
+                try {
+                    for (int i = 0; i < count; i++) {
+                        try {
+                            wrapper.call(callbackList.getBroadcastItem(i));
+                        } catch (RemoteException e) {
+                            // Safe to ignore
+                        } catch (RuntimeException e) {
+                            // We don't want an exception in one call to prevent other calls, so
+                            // we'll just log this and continue
+                            Log.e(TAG, "Caught RuntimeException in broadcast", e);
+                        }
                     }
+                } finally {
+                    // No matter what, we need to finish the broadcast
+                    callbackList.finishBroadcast();
                 }
-                callbackList.finishBroadcast();
             }
         }
 
@@ -975,14 +984,17 @@
      * 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<Long> releaseList = new ArrayList<Long>();
             for (long mailboxId: mSyncErrorMap.keySet()) {
                 if (account != null) {
@@ -1000,7 +1012,9 @@
             }
             for (long mailboxId: releaseList) {
                 mSyncErrorMap.remove(mailboxId);
+                holdWasReleased = true;
             }
+            return holdWasReleased;
         }
     }
 
@@ -2376,6 +2390,20 @@
             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 +2417,6 @@
                     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 +2426,11 @@
                     }
                     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/src/com/android/exchange/provider/ExchangeDirectoryProvider.java b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
index 11b7230..b267da9 100644
--- a/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
+++ b/src/com/android/exchange/provider/ExchangeDirectoryProvider.java
@@ -228,7 +228,7 @@
                             } else if (column.equals(Directory.EXPORT_SUPPORT)) {
                                 row[i] = Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY;
                             } else if (column.equals(Directory.SHORTCUT_SUPPORT)) {
-                                row[i] = Directory.SHORTCUT_SUPPORT_FULL;
+                                row[i] = Directory.SHORTCUT_SUPPORT_NONE;
                             }
                         }
                         cursor.addRow(row);
diff --git a/tests/src/com/android/exchange/ExchangeServiceAccountTests.java b/tests/src/com/android/exchange/ExchangeServiceAccountTests.java
index 425e9c4..9b92726 100644
--- a/tests/src/com/android/exchange/ExchangeServiceAccountTests.java
+++ b/tests/src/com/android/exchange/ExchangeServiceAccountTests.java
@@ -73,7 +73,8 @@
         // 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 @@
         // 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());
     }