Merge "Fix account deletion when removing security policies." into ub-mail-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9d26f0d..d5b3c4e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -682,6 +682,11 @@
                 <action android:name="com.android.mail.action.CLEAR_NEW_MAIL_NOTIFICATIONS" />
                 <data android:scheme="content" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="com.android.mail.action.update_notification"
+                        android:priority="-10"/>
+                <data android:mimeType="@string/application_mime_type" />
+            </intent-filter>
         </service>
 
         <service android:name="com.android.mail.NotificationActionIntentService"
diff --git a/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java b/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java
index 17dff5c..ce02d10 100644
--- a/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java
+++ b/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java
@@ -94,11 +94,16 @@
             return new String[2];
         }
         String[] messageBody = new String[] { body.mTextContent, body.mHtmlContent };
-        if (useSmartReply && body.mQuotedTextStartPos > 0) {
+        final int pos = body.mQuotedTextStartPos;
+        if (useSmartReply && pos > 0) {
             if (messageBody[0] != null) {
-                messageBody[0] = messageBody[0].substring(0, body.mQuotedTextStartPos);
+                if (pos < messageBody[0].length()) {
+                    messageBody[0] = messageBody[0].substring(0, pos);
+                }
             } else if (messageBody[1] != null) {
-                messageBody[1] = messageBody[1].substring(0, body.mQuotedTextStartPos);
+                if (pos < messageBody[1].length()) {
+                    messageBody[1] = messageBody[1].substring(0, pos);
+                }
             }
         }
         return messageBody;
diff --git a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
index f7b8d72..0abaa79 100755
--- a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
+++ b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
@@ -988,6 +988,8 @@
                 if (doSave) {
                     return super.save(context);
                 } else {
+                    // FLAG: Should we be doing this? In the base class, if someone calls "save" on
+                    // an EmailContent that is already saved, it throws an exception.
                     // Call update, rather than super.update in case we ever override it
                     if (update(context, toContentValues()) == 1) {
                         return getUri();
diff --git a/emailcommon/src/com/android/emailcommon/utility/EmailClientConnectionManager.java b/emailcommon/src/com/android/emailcommon/utility/EmailClientConnectionManager.java
index a275a12..ca0c21a 100644
--- a/emailcommon/src/com/android/emailcommon/utility/EmailClientConnectionManager.java
+++ b/emailcommon/src/com/android/emailcommon/utility/EmailClientConnectionManager.java
@@ -18,6 +18,7 @@
 package com.android.emailcommon.utility;
 
 import android.content.Context;
+import android.text.TextUtils;
 
 import com.android.emailcommon.Logging;
 import com.android.emailcommon.provider.HostAuth;
@@ -90,6 +91,9 @@
      */
     public synchronized void registerClientCert(Context context, HostAuth hostAuth)
             throws CertificateException {
+        if (TextUtils.isEmpty(hostAuth.mClientCertAlias)) {
+            return;
+        }
         SchemeRegistry registry = getSchemeRegistry();
         String schemeName = makeSchemeForClientCert(hostAuth.mClientCertAlias,
                 hostAuth.shouldTrustAllServerCerts());
diff --git a/src/com/android/email/EmailIntentService.java b/src/com/android/email/EmailIntentService.java
index 005df2f..7bf1b2d 100644
--- a/src/com/android/email/EmailIntentService.java
+++ b/src/com/android/email/EmailIntentService.java
@@ -18,6 +18,7 @@
 import android.content.Intent;
 
 import com.android.mail.MailIntentService;
+import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
 
@@ -35,6 +36,10 @@
     protected void onHandleIntent(final Intent intent) {
         super.onHandleIntent(intent);
 
+        if (UIProvider.ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) {
+            NotificationController.handleUpdateNotificationIntent(this, intent);
+        }
+
         LogUtils.v(LOG_TAG, "Handling intent %s", intent);
     }
 }
diff --git a/src/com/android/email/LegacyConversions.java b/src/com/android/email/LegacyConversions.java
index 4a903d8..bd8b4ca 100644
--- a/src/com/android/email/LegacyConversions.java
+++ b/src/com/android/email/LegacyConversions.java
@@ -92,6 +92,9 @@
         }
         if (sentDate != null) {
             localMessage.mTimeStamp = sentDate.getTime();
+        } else if (internalDate != null) {
+            LogUtils.w(Logging.LOG_TAG, "No sentDate, falling back to internalDate");
+            localMessage.mTimeStamp = internalDate.getTime();
         }
         if (subject != null) {
             localMessage.mSubject = subject;
diff --git a/src/com/android/email/NotificationController.java b/src/com/android/email/NotificationController.java
index 101f519..f362640 100644
--- a/src/com/android/email/NotificationController.java
+++ b/src/com/android/email/NotificationController.java
@@ -592,14 +592,73 @@
 
     private static void refreshNotificationsForAccountInternal(final Context context,
             final long accountId) {
+        final Uri accountUri = EmailProvider.uiUri("uiaccount", accountId);
+
         final ContentResolver contentResolver = context.getContentResolver();
 
-        final Cursor accountCursor = contentResolver.query(
-                EmailProvider.uiUri("uiaccount", accountId), UIProvider.ACCOUNTS_PROJECTION,
-                null, null, null);
+        final Cursor mailboxCursor = contentResolver.query(
+                ContentUris.withAppendedId(EmailContent.MAILBOX_NOTIFICATION_URI, accountId),
+                null, null, null, null);
+        try {
+            while (mailboxCursor.moveToNext()) {
+                final long mailboxId =
+                        mailboxCursor.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
+                if (mailboxId == 0) continue;
+
+                final int unseenCount = mailboxCursor.getInt(
+                        EmailContent.NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN);
+
+                final int unreadCount;
+                // If nothing is unseen, clear the notification
+                if (unseenCount == 0) {
+                    unreadCount = 0;
+                } else {
+                    unreadCount = mailboxCursor.getInt(
+                            EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
+                }
+
+                final Uri folderUri = EmailProvider.uiUri("uifolder", mailboxId);
+
+
+                LogUtils.d(LOG_TAG, "Changes to account " + accountId + ", folder: "
+                        + mailboxId + ", unreadCount: " + unreadCount + ", unseenCount: "
+                        + unseenCount);
+
+                final Intent intent = new Intent(UIProvider.ACTION_UPDATE_NOTIFICATION);
+                intent.setPackage(context.getPackageName());
+                intent.setType(EmailProvider.EMAIL_APP_MIME_TYPE);
+
+                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT, accountUri);
+                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER, folderUri);
+                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT,
+                        unreadCount);
+                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNSEEN_COUNT,
+                        unseenCount);
+
+                context.sendOrderedBroadcast(intent, null);
+            }
+        } finally {
+            mailboxCursor.close();
+        }
+    }
+
+    public static void handleUpdateNotificationIntent(Context context, Intent intent) {
+        final Uri accountUri =
+                intent.getParcelableExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT);
+        final Uri folderUri =
+                intent.getParcelableExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER);
+        final int unreadCount = intent.getIntExtra(
+                UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT, 0);
+        final int unseenCount = intent.getIntExtra(
+                UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNSEEN_COUNT, 0);
+
+        final ContentResolver contentResolver = context.getContentResolver();
+
+        final Cursor accountCursor = contentResolver.query(accountUri,
+                UIProvider.ACCOUNTS_PROJECTION,  null, null, null);
 
         if (accountCursor == null) {
-            LogUtils.e(LOG_TAG, "Null account cursor for account id %d", accountId);
+            LogUtils.e(LOG_TAG, "Null account cursor for account " + accountUri);
             return;
         }
 
@@ -613,58 +672,37 @@
         }
 
         if (account == null) {
-            LogUtils.d(LOG_TAG, "Tried to create a notification for a missing account %d",
-                    accountId);
+            LogUtils.d(LOG_TAG, "Tried to create a notification for a missing account "
+                    + accountUri);
             return;
         }
 
-        final Cursor mailboxCursor = contentResolver.query(
-                ContentUris.withAppendedId(EmailContent.MAILBOX_NOTIFICATION_URI, accountId),
-                null, null, null, null);
+        final Cursor folderCursor = contentResolver.query(folderUri, UIProvider.FOLDERS_PROJECTION,
+                null, null, null);
+
+        if (folderCursor == null) {
+            LogUtils.e(LOG_TAG, "Null folder cursor for account " + accountUri + ", mailbox "
+                    + folderUri);
+            return;
+        }
+
+        Folder folder = null;
         try {
-            while (mailboxCursor.moveToNext()) {
-                final long mailboxId =
-                        mailboxCursor.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
-                if (mailboxId == 0) continue;
-
-                final int unreadCount = mailboxCursor.getInt(
-                        EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
-                final int unseenCount = mailboxCursor.getInt(
-                        EmailContent.NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN);
-
-                final Cursor folderCursor = contentResolver.query(
-                        EmailProvider.uiUri("uifolder", mailboxId),
-                        UIProvider.FOLDERS_PROJECTION, null, null, null);
-
-                if (folderCursor == null) {
-                    LogUtils.e(LOG_TAG, "Null folder cursor for account %d, mailbox %d",
-                            accountId, mailboxId);
-                    continue;
-                }
-
-                Folder folder = null;
-                try {
-                    if (folderCursor.moveToFirst()) {
-                        folder = new Folder(folderCursor);
-                    } else {
-                        LogUtils.e(LOG_TAG, "Empty folder cursor for account %d, mailbox %d",
-                                accountId, mailboxId);
-                        continue;
-                    }
-                } finally {
-                    folderCursor.close();
-                }
-
-                LogUtils.d(LOG_TAG, "Changes to account " + account.name + ", folder: "
-                        + folder.name + ", unreadCount: " + unreadCount + ", unseenCount: "
-                        + unseenCount);
-
-                NotificationUtils.setNewEmailIndicator(context, unreadCount, unseenCount,
-                        account, folder, true);
+            if (folderCursor.moveToFirst()) {
+                folder = new Folder(folderCursor);
+            } else {
+                LogUtils.e(LOG_TAG, "Empty folder cursor for account " + accountUri + ", mailbox "
+                        + folderUri);
+                return;
             }
         } finally {
-            mailboxCursor.close();
+            folderCursor.close();
         }
+
+        // TODO: we don't always want getAttention to be true, but we don't necessarily have a
+        // good heuristic for when it should or shouldn't be.
+        NotificationUtils.setNewEmailIndicator(context, unreadCount, unseenCount, account, folder,
+                true /* getAttention */);
     }
 
     private static void refreshAllNotifications(final Context context) {
diff --git a/src/com/android/email/activity/setup/AccountSetupActivity.java b/src/com/android/email/activity/setup/AccountSetupActivity.java
index 5813270..fb29581 100644
--- a/src/com/android/email/activity/setup/AccountSetupActivity.java
+++ b/src/com/android/email/activity/setup/AccountSetupActivity.java
@@ -46,7 +46,8 @@
 
         super.onCreate(savedInstanceState);
         if (DEBUG_SETUP_FLOWS) {
-            LogUtils.d(Logging.LOG_TAG, "%s onCreate %s", getClass().getName(), mSetupData.debugString());
+            LogUtils.d(Logging.LOG_TAG, "%s onCreate %s", getClass().getName(),
+                    mSetupData.debugString());
         }
     }
 
diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java
index e0523f1..d1be0b2 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -2896,7 +2896,9 @@
             caps |= UIProvider.FolderCapabilities.IS_VIRTUAL;
         }
 
-        if (!info.offerMoveTo) {
+        // If we don't know the protocol or the protocol doesn't support it, don't allow moving
+        // messages
+        if (info == null || !info.offerMoveTo) {
             caps &= ~UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES &
                     ~UIProvider.FolderCapabilities.ALLOWS_REMOVE_CONVERSATION &
                     ~UIProvider.FolderCapabilities.ALLOWS_MOVE_TO_INBOX;
diff --git a/src/com/android/email/service/EmailBroadcastProcessorService.java b/src/com/android/email/service/EmailBroadcastProcessorService.java
index 4e54214..a8853c3 100644
--- a/src/com/android/email/service/EmailBroadcastProcessorService.java
+++ b/src/com/android/email/service/EmailBroadcastProcessorService.java
@@ -31,8 +31,10 @@
 import android.os.Bundle;
 import android.provider.CalendarContract;
 import android.provider.ContactsContract;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 
+import com.android.email.EmailIntentService;
 import com.android.email.Preferences;
 import com.android.email.R;
 import com.android.email.SecurityPolicy;
@@ -44,12 +46,16 @@
 import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.EmailContent.AccountColumns;
 import com.android.emailcommon.provider.HostAuth;
+import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Maps;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * The service that really handles broadcast intents on a worker thread.
@@ -132,6 +138,9 @@
                 AccountSettings.actionSettingsWithDebug(this);
             } else if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(broadcastAction)) {
                 onSystemAccountChanged();
+            } else if (UIProvider.ACTION_UPDATE_NOTIFICATION.equals((broadcastAction))) {
+                broadcastIntent.setClass(this, EmailIntentService.class);
+                startService(broadcastIntent);
             }
         } else if (ACTION_DEVICE_POLICY_ADMIN.equals(action)) {
             int message = intent.getIntExtra(EXTRA_DEVICE_POLICY_ADMIN, -1);
@@ -231,13 +240,23 @@
         return Collections.emptyMap();
     }
 
+    @VisibleForTesting
+    protected static void removeNoopUpgrades(final Map<String, String> protocolMap) {
+        final Set<String> keySet = new HashSet<String>(protocolMap.keySet());
+        for (final String key : keySet) {
+            if (TextUtils.equals(key, protocolMap.get(key))) {
+                protocolMap.remove(key);
+            }
+        }
+    }
+
     private void onAppUpgrade() {
         if (isComponentDisabled(EmailUpgradeBroadcastReceiver.class)) {
             return;
         }
-        // TODO: Only do this for Email2Google.
-        // When upgrading to Email2Google, we need to essentially rename the account manager
-        // type for all existing accounts, so we add new ones and delete the old.
+        // When upgrading to a version that changes the protocol strings, we need to essentially
+        // rename the account manager type for all existing accounts, so we add new ones and delete
+        // the old.
         // We specify the translations in this map. We map from old protocol name to new protocol
         // name, and from protocol name + "_type" to new account manager type name. (Email1 did
         // not use distinct account manager types for POP and IMAP, but Email2 does, hence this
@@ -245,14 +264,20 @@
         final Map<String, String> protocolMap = Maps.newHashMapWithExpectedSize(4);
         protocolMap.put("imap", getString(R.string.protocol_legacy_imap));
         protocolMap.put("pop3", getString(R.string.protocol_pop3));
-        protocolMap.put("imap_type", getString(R.string.account_manager_type_legacy_imap));
-        protocolMap.put("pop3_type", getString(R.string.account_manager_type_pop3));
-        updateAccountManagerAccountsOfType("com.android.email", protocolMap);
+        removeNoopUpgrades(protocolMap);
+        if (!protocolMap.isEmpty()) {
+            protocolMap.put("imap_type", getString(R.string.account_manager_type_legacy_imap));
+            protocolMap.put("pop3_type", getString(R.string.account_manager_type_pop3));
+            updateAccountManagerAccountsOfType("com.android.email", protocolMap);
+        }
 
         protocolMap.clear();
         protocolMap.put("eas", getString(R.string.protocol_eas));
-        protocolMap.put("eas_type", getString(R.string.account_manager_type_exchange));
-        updateAccountManagerAccountsOfType("com.android.exchange", protocolMap);
+        removeNoopUpgrades(protocolMap);
+        if (!protocolMap.isEmpty()) {
+            protocolMap.put("eas_type", getString(R.string.account_manager_type_exchange));
+            updateAccountManagerAccountsOfType("com.android.exchange", protocolMap);
+        }
 
         // Disable the old authenticators.
         disableComponent(LegacyEmailAuthenticatorService.class);
diff --git a/tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java b/tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java
index 29c61fd..d171335 100644
--- a/tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java
+++ b/tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java
@@ -135,4 +135,17 @@
         assertEquals(0x00000008, accountFlags6);
     }
 
+    public void testNoopRemover() {
+        final Map<String, String> protocolMap = Maps.newHashMap();
+        protocolMap.put("imap", "imap");
+        protocolMap.put("pop3", "gPop3");
+
+        EmailBroadcastProcessorService.removeNoopUpgrades(protocolMap);
+
+        final Map<String, String> protocolMapExpected = Maps.newHashMap();
+        protocolMapExpected.put("pop3", "gPop3");
+
+        assertEquals(protocolMap, protocolMapExpected);
+    }
+
 }