am 7a9b65fc: merge from froyo-plus-aosp

Merge commit '7a9b65fc1306714db3eaac604b0b95960b3e783a'

* commit '7a9b65fc1306714db3eaac604b0b95960b3e783a':
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a21a23d..e982ec6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -216,26 +216,18 @@
             </intent-filter>
         </activity>
         <!--EXCHANGE-REMOVE-SECTION-START-->
-       <receiver android:name="com.android.exchange.EmailSyncAlarmReceiver"/>
-       <receiver android:name="com.android.exchange.MailboxAlarmReceiver"/>
-       <receiver android:name="com.android.exchange.BootReceiver" android:enabled="true">
-             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-       </receiver>
+        <receiver android:name="com.android.exchange.EmailSyncAlarmReceiver"/>
+        <receiver android:name="com.android.exchange.MailboxAlarmReceiver"/>
         <!--EXCHANGE-REMOVE-SECTION-END-->
 
-       <receiver android:name=".service.BootReceiver" android:enabled="true">
+        <receiver android:name=".service.EmailBroadcastReceiver" android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-            <intent-filter>
                 <action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
-            </intent-filter>
-            <intent-filter>
                 <action android:name="android.intent.action.DEVICE_STORAGE_OK" />
             </intent-filter>
         </receiver>
+        <service android:name=".service.EmailBroadcastProcessorService" />
 
         <!-- Support for DeviceAdmin / DevicePolicyManager.  See SecurityPolicy class for impl. -->
         <receiver
@@ -250,15 +242,6 @@
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
             </intent-filter>
         </receiver>
-        
-        <receiver
-            android:name=".OneTimeInitializer"
-            android:enabled="true"
-            >
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-        </receiver>
 
         <service
             android:name=".service.MailService"
diff --git a/proguard.flags b/proguard.flags
index bbffc8a..f3b7e57 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -23,9 +23,9 @@
 
 -keep class * extends org.apache.james.mime4j.util.TempStorage
 
-
 # Keep names that are used only by unit tests
 
+# Any methods whose name is '*ForTest' are preserved.
 -keep class ** {
   *** *ForTest(...);
 }
@@ -172,3 +172,7 @@
 -keep class org.apache.james.mime4j.message.Message {
   *;
 }
+
+-keepclasseswithmembers class org.apache.commons.io.IOUtils {
+  *** toByteArray(...);
+}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 95b4e9e..e209501 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -278,9 +278,8 @@
     <string name="message_view_fetching_attachment_toast">Fetching attachment.</string>
     <!-- Appears progress dialog for fetching attachment -->
     <string name="message_view_fetching_attachment_progress">Fetching attachment <xliff:g id="filename">%s</xliff:g></string>
-    <!-- Calendar invitation, link to calendar.
-         Preserve the chevron (unicode &#xbb;) untranslated -->
-    <string name="message_view_invite_view">View in Calendar &#xbb;</string>
+    <!-- Calendar invitation, label of the button to open in calendar -->
+    <string name="message_view_invite_view">View in Calendar</string>
     <!-- String shown with a calendar invitation. -->
     <string name="message_view_invite_title">Calendar Invite</string>
     <!-- String shown with a calendar invitation. -->
@@ -445,6 +444,8 @@
     <string name="account_setup_exchange_ssl_label">Use secure connection (SSL)</string>
     <!-- On "Exchange" setup screen, the trust ssl certificates checkbox label -->
     <string name="account_setup_exchange_trust_certificates_label">Accept all SSL certificates</string>
+    <!-- On "Exchange" setup screen, the exchange device-id label -->
+    <string name="account_setup_exchange_device_id_label">Mobile Device ID</string>
 
     <!-- In Account setup options screen, Activity title -->
     <string name="account_setup_options_title">Account options</string>
diff --git a/src/com/android/exchange/BootReceiver.java b/src/com/android/exchange/BootReceiver.java
deleted file mode 100644
index 1ebfa7b..0000000
--- a/src/com/android/exchange/BootReceiver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.exchange;
-
-import com.android.email.ExchangeUtils;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class BootReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.d("Exchange", "BootReceiver onReceive");
-        ExchangeUtils.startExchangeService(context);
-    }
-}
diff --git a/src/com/android/exchange/Eas.java b/src/com/android/exchange/Eas.java
index 8e561e7..2fa162b 100644
--- a/src/com/android/exchange/Eas.java
+++ b/src/com/android/exchange/Eas.java
@@ -45,6 +45,8 @@
     public static final double SUPPORTED_PROTOCOL_EX2003_DOUBLE = 2.5;
     public static final String SUPPORTED_PROTOCOL_EX2007 = "12.0";
     public static final double SUPPORTED_PROTOCOL_EX2007_DOUBLE = 12.0;
+    public static final String SUPPORTED_PROTOCOL_EX2007_SP1 = "12.1";
+    public static final double SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE = 12.1;
     public static final String DEFAULT_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_EX2003;
 
     // From EAS spec
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 3eb48c6..62d7804 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -352,7 +352,8 @@
         // Find the most recent version we support
         for (String version: supportedVersionsArray) {
             if (version.equals(Eas.SUPPORTED_PROTOCOL_EX2003) ||
-                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2007)) {
+                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2007) ||
+                    version.equals(Eas.SUPPORTED_PROTOCOL_EX2007_SP1)) {
                 ourVersion = version;
             }
         }
@@ -400,10 +401,17 @@
                 // Make sure we've got the right protocol version set up
                 setupProtocolVersion(svc, versions);
 
-                // Run second test here for provisioning failures...
-                Serializer s = new Serializer();
+                // Run second test here for provisioning failures using FolderSync
                 userLog("Try folder sync");
-                s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text("0")
+                // Send "0" as the sync key for new accounts; otherwise we'll use the current key
+                String syncKey = "0";
+                Account existingAccount =
+                    Utility.findExistingAccount(context, -1L, hostAddress, userName);
+                if (existingAccount != null && existingAccount.mSyncKey != null) {
+                    syncKey = existingAccount.mSyncKey;
+                }
+                Serializer s = new Serializer();
+                s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
                     .end().end().done();
                 resp = svc.sendHttpClientPost("FolderSync", s.toByteArray());
                 code = resp.getStatusLine().getStatusCode();
@@ -1979,9 +1987,12 @@
             userLog("sync, sending ", className, " syncKey: ", syncKey);
             s.start(Tags.SYNC_SYNC)
                 .start(Tags.SYNC_COLLECTIONS)
-                .start(Tags.SYNC_COLLECTION)
-                .data(Tags.SYNC_CLASS, className)
-                .data(Tags.SYNC_SYNC_KEY, syncKey)
+                .start(Tags.SYNC_COLLECTION);
+            // The "Class" element is removed in EAS 12.1 and later versions
+            if (mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
+                s.data(Tags.SYNC_CLASS, className);
+            }
+            s.data(Tags.SYNC_SYNC_KEY, syncKey)
                 .data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId);
 
             // Start with the default timeout
diff --git a/src/com/android/exchange/MailboxAlarmReceiver.java b/src/com/android/exchange/MailboxAlarmReceiver.java
index f8551e4..565b158 100644
--- a/src/com/android/exchange/MailboxAlarmReceiver.java
+++ b/src/com/android/exchange/MailboxAlarmReceiver.java
@@ -29,9 +29,14 @@
 public class MailboxAlarmReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
-        long mid = intent.getLongExtra("mailbox", -1);
-        SyncManager.log("Alarm received for: " + SyncManager.alarmOwner(mid));
-        SyncManager.alert(context, mid);
+        long mailboxId = intent.getLongExtra("mailbox", SyncManager.SYNC_MANAGER_ID);
+        // SYNC_MANAGER_SERVICE_ID tells us that the service is asking to be started
+        if (mailboxId == SyncManager.SYNC_MANAGER_SERVICE_ID) {
+            context.startService(new Intent(context, SyncManager.class));
+        } else {
+            SyncManager.log("Alarm received for: " + SyncManager.alarmOwner(mailboxId));
+            SyncManager.alert(context, mailboxId);
+        }
     }
 }
 
diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java
index 7a56cbb..651cf63 100644
--- a/src/com/android/exchange/SyncManager.java
+++ b/src/com/android/exchange/SyncManager.java
@@ -114,7 +114,8 @@
     private static final String TAG = "EAS SyncManager";
 
     // The SyncManager's mailbox "id"
-    private static final int SYNC_MANAGER_ID = -1;
+    protected static final int SYNC_MANAGER_ID = -1;
+    protected static final int SYNC_MANAGER_SERVICE_ID = 0;
 
     private static final int SECONDS = 1000;
     private static final int MINUTES = 60*SECONDS;
@@ -182,8 +183,6 @@
     // All threads can use this lock to wait for connectivity
     public static final Object sConnectivityLock = new Object();
     public static boolean sConnectivityHold = false;
-    // Keep our cached list of active Accounts here
-    public static final AccountList sAccountList = new AccountList();
 
     // Keeps track of running services (by mailbox id)
     private HashMap<Long, AbstractSyncService> mServiceMap =
@@ -196,6 +195,8 @@
     private HashMap<Long, PendingIntent> mPendingIntents = new HashMap<Long, PendingIntent>();
     // The actual WakeLock obtained by SyncManager
     private WakeLock mWakeLock = null;
+    // Keep our cached list of active Accounts here
+    public final AccountList mAccountList = new AccountList();
 
     // Observers that we use to look for changed mail-related data
     private Handler mHandler = new Handler();
@@ -204,6 +205,7 @@
     private SyncedMessageObserver mSyncedMessageObserver;
     private MessageObserver mMessageObserver;
     private EasSyncStatusObserver mSyncStatusObserver;
+    private Object mStatusChangeListener;
     private EasAccountsUpdatedListener mAccountsUpdatedListener;
 
     private HashMap<Long, CalendarObserver> mCalendarObservers =
@@ -221,7 +223,7 @@
     // Count of ClientConnectionManager shutdowns
     private static volatile int sClientConnectionManagerShutdownCount = 0;
 
-    private boolean mStop = false;
+    private static volatile boolean sStop = false;
 
     // The reason for SyncManager's next wakeup call
     private String mNextWaitReason;
@@ -434,18 +436,18 @@
             super(handler);
             // At startup, we want to see what EAS accounts exist and cache them
             Context context = getContext();
-            synchronized (sAccountList) {
+            synchronized (mAccountList) {
                 Cursor c = getContentResolver().query(Account.CONTENT_URI,
                         Account.CONTENT_PROJECTION, null, null, null);
                 // Build the account list from the cursor
                 try {
-                    collectEasAccounts(c, sAccountList);
+                    collectEasAccounts(c, mAccountList);
                 } finally {
                     c.close();
                 }
 
                 // Create an account mailbox for any account without one
-                for (Account account : sAccountList) {
+                for (Account account : mAccountList) {
                     int cnt = Mailbox.count(context, Mailbox.CONTENT_URI, "accountKey="
                             + account.mId, null);
                     if (cnt == 0) {
@@ -464,8 +466,8 @@
             if (mSyncableEasMailboxSelector == null) {
                 StringBuilder sb = new StringBuilder(WHERE_NOT_INTERVAL_NEVER_AND_ACCOUNT_KEY_IN);
                 boolean first = true;
-                synchronized (sAccountList) {
-                    for (Account account : sAccountList) {
+                synchronized (mAccountList) {
+                    for (Account account : mAccountList) {
                         if (!first) {
                             sb.append(',');
                         } else {
@@ -489,8 +491,8 @@
             if (mEasAccountSelector == null) {
                 StringBuilder sb = new StringBuilder(ACCOUNT_KEY_IN);
                 boolean first = true;
-                synchronized (sAccountList) {
-                    for (Account account : sAccountList) {
+                synchronized (mAccountList) {
+                    for (Account account : mAccountList) {
                         if (!first) {
                             sb.append(',');
                         } else {
@@ -520,8 +522,8 @@
                     Account.CONTENT_PROJECTION, null, null, null);
             try {
                 collectEasAccounts(c, currentAccounts);
-                synchronized (sAccountList) {
-                    for (Account account : sAccountList) {
+                synchronized (mAccountList) {
+                    for (Account account : mAccountList) {
                         // Ignore accounts not fully created
                         if ((account.mFlags & Account.FLAGS_INCOMPLETE) != 0) {
                             log("Account observer noticed incomplete account; ignoring");
@@ -568,7 +570,7 @@
                     }
                     // Look for new accounts
                     for (Account account : currentAccounts) {
-                        if (!sAccountList.contains(account.mId)) {
+                        if (!mAccountList.contains(account.mId)) {
                             // Don't forget to cache the HostAuth
                             HostAuth ha = HostAuth.restoreHostAuthWithId(getContext(),
                                     account.mHostAuthKeyRecv);
@@ -577,14 +579,14 @@
                             // This is an addition; create our magic hidden mailbox...
                             log("Account observer found new account: " + account.mDisplayName);
                             addAccountMailbox(account.mId);
-                            sAccountList.add(account);
+                            mAccountList.add(account);
                             mSyncableEasMailboxSelector = null;
                             mEasAccountSelector = null;
                         }
                     }
                     // Finally, make sure our account list is up to date
-                    sAccountList.clear();
-                    sAccountList.addAll(currentAccounts);
+                    mAccountList.clear();
+                    mAccountList.addAll(currentAccounts);
                 }
             } finally {
                 c.close();
@@ -599,7 +601,7 @@
             new Thread(new Runnable() {
                public void run() {
                    onAccountChanged();
-                }}).start();
+                }}, "Account Observer").start();
         }
 
         private void collectEasAccounts(Cursor c, ArrayList<Account> accounts) {
@@ -773,7 +775,7 @@
                         } finally {
                             c.close();
                         }
-                    }}).start();
+                    }}, "Calendar Observer").start();
             }
         }
     }
@@ -831,9 +833,14 @@
     }
 
     static public Account getAccountById(long accountId) {
-        synchronized (sAccountList) {
-            return sAccountList.getById(accountId);
+        SyncManager syncManager = INSTANCE;
+        if (syncManager != null) {
+            AccountList accountList = syncManager.mAccountList;
+            synchronized (accountList) {
+                return accountList.getById(accountId);
+            }
         }
+        return null;
     }
 
     static public String getEasAccountSelector() {
@@ -963,13 +970,13 @@
             public void run() {
                 android.accounts.Account[] accountMgrList = AccountManager.get(syncManager)
                         .getAccountsByType(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
-                synchronized (sAccountList) {
+                synchronized (mAccountList) {
                     // Make sure we have an up-to-date sAccountList.  If not (for example, if the
                     // service has been destroyed), we would be reconciling against an empty account
                     // list, which would cause the deletion of all of our accounts
                     if (mAccountObserver != null) {
                         mAccountObserver.onAccountChanged();
-                        reconcileAccountsWithAccountManager(syncManager, sAccountList,
+                        reconcileAccountsWithAccountManager(syncManager, mAccountList,
                                 accountMgrList, false, mResolver);
                     }
                 }
@@ -1055,113 +1062,6 @@
         return mBinder;
     }
 
-    /**
-     * Note that there are two ways the EAS SyncManager service can be created:
-     *
-     * 1) as a background service instantiated via startService (which happens on boot, when the
-     * first EAS account is created, etc), in which case the service thread is spun up, mailboxes
-     * sync, etc. and
-     * 2) to execute an RPC call from the UI, in which case the background service will already be
-     * running most of the time (unless we're creating a first EAS account)
-     *
-     * If the running background service detects that there are no EAS accounts (on boot, if none
-     * were created, or afterward if the last remaining EAS account is deleted), it will call
-     * stopSelf() to terminate operation.
-     *
-     * The goal is to ensure that the background service is running at all times when there is at
-     * least one EAS account in existence
-     *
-     * Because there are edge cases in which our process can crash (typically, this has been seen
-     * in UI crashes, ANR's, etc.), it's possible for the UI to start up again without the
-     * background service having been started.  We explicitly try to start the service in Welcome
-     * (to handle the case of the app having been reloaded).  We also start the service on any
-     * startSync call (if it isn't already running)
-     */
-    @Override
-    public void onCreate() {
-        alwaysLog("!!! EAS SyncManager, onCreate");
-        if (INSTANCE == null) {
-            INSTANCE = this;
-            mResolver = getContentResolver();
-            mAccountObserver = new AccountObserver(mHandler);
-            mResolver.registerContentObserver(Account.CONTENT_URI, true, mAccountObserver);
-            mMailboxObserver = new MailboxObserver(mHandler);
-            mSyncedMessageObserver = new SyncedMessageObserver(mHandler);
-            mMessageObserver = new MessageObserver(mHandler);
-            mSyncStatusObserver = new EasSyncStatusObserver();
-        } else {
-            alwaysLog("!!! EAS SyncManager onCreated, but INSTANCE not null??");
-        }
-        if (sDeviceId == null) {
-            try {
-                getDeviceId(this);
-            } catch (IOException e) {
-                // We can't run in this situation
-                throw new RuntimeException();
-            }
-        }
-        // Run the reconciler and clean up any mismatched accounts - if we weren't running when
-        // accounts were deleted, it won't have been called.
-        runAccountReconciler();
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        alwaysLog("!!! EAS SyncManager, onStartCommand");
-
-        // Restore accounts, if it has not happened already
-        AccountBackupRestore.restoreAccountsIfNeeded(this);
-
-        maybeStartSyncManagerThread();
-        if (sServiceThread == null) {
-            alwaysLog("!!! EAS SyncManager, stopping self");
-            stopSelf();
-        }
-        return Service.START_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        alwaysLog("!!! EAS SyncManager, onDestroy");
-        if (INSTANCE != null) {
-            INSTANCE = null;
-            mResolver.unregisterContentObserver(mAccountObserver);
-            unregisterCalendarObservers();
-            mResolver = null;
-            mAccountObserver = null;
-            mMailboxObserver = null;
-            mSyncedMessageObserver = null;
-            mMessageObserver = null;
-            mSyncStatusObserver = null;
-            mAccountsUpdatedListener = null;
-        }
-    }
-
-    void maybeStartSyncManagerThread() {
-        // Start our thread...
-        // See if there are any EAS accounts; otherwise, just go away
-        if (EmailContent.count(this, HostAuth.CONTENT_URI, WHERE_PROTOCOL_EAS, null) > 0) {
-            if (sServiceThread == null || !sServiceThread.isAlive()) {
-                log(sServiceThread == null ? "Starting thread..." : "Restarting thread...");
-                sServiceThread = new Thread(this, "SyncManager");
-                sServiceThread.start();
-            }
-        }
-    }
-
-    static void checkSyncManagerServiceRunning() {
-        // Get the service thread running if it isn't
-        // This is a stopgap for cases in which SyncManager died (due to a crash somewhere in
-        // com.android.email) and hasn't been restarted
-        // See the comment for onCreate for details
-        SyncManager syncManager = INSTANCE;
-        if (syncManager == null) return;
-        if (sServiceThread == null) {
-            alwaysLog("!!! checkSyncManagerServiceRunning; starting service...");
-            syncManager.startService(new Intent(syncManager, SyncManager.class));
-        }
-    }
-
     static public ConnPerRoute sConnPerRoute = new ConnPerRoute() {
         public int getMaxForRoute(HttpRoute route) {
             return 8;
@@ -1453,6 +1353,10 @@
             if (service != null) {
                 // Handle alerts in a background thread, as we are typically called from a
                 // broadcast receiver, and are therefore running in the UI thread
+                String threadName = "SyncManager Alert: ";
+                if (service.mMailbox != null) {
+                    threadName += service.mMailbox.mDisplayName;
+                }
                 new Thread(new Runnable() {
                    public void run() {
                        Mailbox m = Mailbox.restoreMailboxWithId(syncManager, id);
@@ -1484,7 +1388,7 @@
                                SyncManager.shutdownConnectionManager();
                            }
                        }
-                    }}).start();
+                    }}, threadName).start();
             }
         }
     }
@@ -1541,8 +1445,8 @@
      * Make our sync settings match those of AccountManager
      */
     private void checkPIMSyncSettings() {
-        synchronized (sAccountList) {
-            for (Account account : sAccountList) {
+        synchronized (mAccountList) {
+            for (Account account : mAccountList) {
                 updatePIMSyncSettings(account, Mailbox.TYPE_CONTACTS, ContactsContract.AUTHORITY);
                 updatePIMSyncSettings(account, Mailbox.TYPE_CALENDAR, Calendar.AUTHORITY);
             }
@@ -1668,8 +1572,8 @@
                 // Otherwise, stop all syncs
                 } else {
                     log("Background data off: stop all syncs");
-                    synchronized (sAccountList) {
-                        for (Account account : sAccountList)
+                    synchronized (mAccountList) {
+                        for (Account account : mAccountList)
                             SyncManager.stopAccountSyncs(account.mId);
                     }
                 }
@@ -1723,7 +1627,7 @@
 
     private void requestSync(Mailbox m, int reason, Request req) {
         // Don't sync if there's no connectivity
-        if (sConnectivityHold || (m == null)) return;
+        if (sConnectivityHold || (m == null) || sStop) return;
         synchronized (sSyncLock) {
             Account acct = Account.restoreAccountWithId(this, m.mAccountKey);
             if (acct != null) {
@@ -1770,7 +1674,7 @@
         boolean waiting = false;
         ConnectivityManager cm =
             (ConnectivityManager)this.getSystemService(Context.CONNECTIVITY_SERVICE);
-        while (!mStop) {
+        while (!sStop) {
             NetworkInfo info = cm.getActiveNetworkInfo();
             if (info != null) {
                 // We're done if there's an active network
@@ -1807,9 +1711,112 @@
         }
     }
 
-    public void run() {
-        mStop = false;
+    /**
+     * Note that there are two ways the EAS SyncManager service can be created:
+     *
+     * 1) as a background service instantiated via startService (which happens on boot, when the
+     * first EAS account is created, etc), in which case the service thread is spun up, mailboxes
+     * sync, etc. and
+     * 2) to execute an RPC call from the UI, in which case the background service will already be
+     * running most of the time (unless we're creating a first EAS account)
+     *
+     * If the running background service detects that there are no EAS accounts (on boot, if none
+     * were created, or afterward if the last remaining EAS account is deleted), it will call
+     * stopSelf() to terminate operation.
+     *
+     * The goal is to ensure that the background service is running at all times when there is at
+     * least one EAS account in existence
+     *
+     * Because there are edge cases in which our process can crash (typically, this has been seen
+     * in UI crashes, ANR's, etc.), it's possible for the UI to start up again without the
+     * background service having been started.  We explicitly try to start the service in Welcome
+     * (to handle the case of the app having been reloaded).  We also start the service on any
+     * startSync call (if it isn't already running)
+     */
+    @Override
+    public void onCreate() {
+        synchronized (sSyncLock) {
+            alwaysLog("!!! EAS SyncManager, onCreate");
+            // If we're in the process of shutting down, try again in 5 seconds
+            if (sStop) {
+                return;
+            }
+            if (sDeviceId == null) {
+                try {
+                    getDeviceId(this);
+                } catch (IOException e) {
+                    // We can't run in this situation
+                    throw new RuntimeException();
+                }
+            }
+            // Run the reconciler and clean up any mismatched accounts - if we weren't running when
+            // accounts were deleted, it won't have been called.
+            runAccountReconciler();
+        }
+    }
 
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        synchronized (sSyncLock) {
+            alwaysLog("!!! EAS SyncManager, onStartCommand");
+            // Restore accounts, if it has not happened already
+            AccountBackupRestore.restoreAccountsIfNeeded(this);
+            maybeStartSyncManagerThread();
+            if (sServiceThread == null) {
+                alwaysLog("!!! EAS SyncManager, stopping self");
+                stopSelf();
+            } else if (sStop) {
+                // If we were in the middle of trying to stop, attempt a restart in 5 seconds
+                setAlarm(SYNC_MANAGER_SERVICE_ID, 5*SECONDS);
+            }
+            return Service.START_STICKY;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        synchronized(sSyncLock) {
+            alwaysLog("!!! EAS SyncManager, onDestroy");
+            // Stop the sync manager thread and return
+            synchronized (sSyncLock) {
+                sStop = true;
+                if (sServiceThread != null) {
+                    sServiceThread.interrupt();
+                }
+            }
+        }
+    }
+
+    void maybeStartSyncManagerThread() {
+        // Start our thread...
+        // See if there are any EAS accounts; otherwise, just go away
+        if (EmailContent.count(this, HostAuth.CONTENT_URI, WHERE_PROTOCOL_EAS, null) > 0) {
+            if (sServiceThread == null || !sServiceThread.isAlive()) {
+                log(sServiceThread == null ? "Starting thread..." : "Restarting thread...");
+                sServiceThread = new Thread(this, "SyncManager");
+                INSTANCE = this;
+                sServiceThread.start();
+            }
+        }
+    }
+
+    /**
+     * Start up the SyncManager service if it's not already running
+     * This is a stopgap for cases in which SyncManager died (due to a crash somewhere in
+     * com.android.email) and hasn't been restarted. See the comment for onCreate for details
+     */
+    static void checkSyncManagerServiceRunning() {
+        SyncManager syncManager = INSTANCE;
+        if (syncManager == null) return;
+        if (sServiceThread == null) {
+            alwaysLog("!!! checkSyncManagerServiceRunning; starting service...");
+            syncManager.startService(new Intent(syncManager, SyncManager.class));
+        }
+    }
+
+    public void run() {
+        sStop = false;
+        alwaysLog("!!! SyncManager thread running");
         // If we're really debugging, turn on all logging
         if (Eas.DEBUG) {
             Eas.USER_LOG = true;
@@ -1822,41 +1829,56 @@
             Debug.waitForDebugger();
         }
 
-        // Set up our observers; we need them to know when to start/stop various syncs based
-        // on the insert/delete/update of mailboxes and accounts
-        // We also observe synced messages to trigger upsyncs at the appropriate time
-        mResolver.registerContentObserver(Mailbox.CONTENT_URI, false, mMailboxObserver);
-        mResolver.registerContentObserver(Message.SYNCED_CONTENT_URI, true, mSyncedMessageObserver);
-        mResolver.registerContentObserver(Message.CONTENT_URI, true, mMessageObserver);
-        ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS,
-                mSyncStatusObserver);
-        mAccountsUpdatedListener = new EasAccountsUpdatedListener();
-        // TODO Find and fix root cause of duplication
-        try {
-            AccountManager.get(getApplication())
-                .addOnAccountsUpdatedListener(mAccountsUpdatedListener, mHandler, true);
-        } catch (IllegalStateException e1) {
-            // This exception is more of a warning; we shouldn't be in the state in which we
-            // already have a listener.
+        // Synchronize here to prevent a shutdown from happening while we initialize our observers
+        // and receivers
+        synchronized (sSyncLock) {
+            if (INSTANCE != null) {
+                mResolver = getContentResolver();
+
+                // Set up our observers; we need them to know when to start/stop various syncs based
+                // on the insert/delete/update of mailboxes and accounts
+                // We also observe synced messages to trigger upsyncs at the appropriate time
+                mAccountObserver = new AccountObserver(mHandler);
+                mResolver.registerContentObserver(Account.CONTENT_URI, true, mAccountObserver);
+                mMailboxObserver = new MailboxObserver(mHandler);
+                mResolver.registerContentObserver(Mailbox.CONTENT_URI, false, mMailboxObserver);
+                mSyncedMessageObserver = new SyncedMessageObserver(mHandler);
+                mResolver.registerContentObserver(Message.SYNCED_CONTENT_URI, true,
+                        mSyncedMessageObserver);
+                mMessageObserver = new MessageObserver(mHandler);
+                mResolver.registerContentObserver(Message.CONTENT_URI, true, mMessageObserver);
+                mSyncStatusObserver = new EasSyncStatusObserver();
+                mStatusChangeListener =
+                    ContentResolver.addStatusChangeListener(
+                            ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncStatusObserver);
+
+                // Set up our observer for AccountManager
+                mAccountsUpdatedListener = new EasAccountsUpdatedListener();
+                AccountManager.get(getApplication()).addOnAccountsUpdatedListener(
+                        mAccountsUpdatedListener, mHandler, true);
+
+                // Set up receivers for connectivity and background data setting
+                mConnectivityReceiver = new ConnectivityReceiver();
+                registerReceiver(mConnectivityReceiver, new IntentFilter(
+                        ConnectivityManager.CONNECTIVITY_ACTION));
+
+                mBackgroundDataSettingReceiver = new ConnectivityReceiver();
+                registerReceiver(mBackgroundDataSettingReceiver, new IntentFilter(
+                        ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED));
+                // Save away the current background data setting; we'll keep track of it with the
+                // receiver we just registered
+                ConnectivityManager cm = (ConnectivityManager)getSystemService(
+                        Context.CONNECTIVITY_SERVICE);
+                mBackgroundData = cm.getBackgroundDataSetting();
+
+                // See if any settings have changed while we weren't running...
+                checkPIMSyncSettings();
+            }
         }
 
-        // Set up receivers for ConnectivityManager
-        mConnectivityReceiver = new ConnectivityReceiver();
-        registerReceiver(mConnectivityReceiver,
-                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
-        mBackgroundDataSettingReceiver = new ConnectivityReceiver();
-        registerReceiver(mBackgroundDataSettingReceiver,
-                 new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED));
-        // Save away background data setting; we'll keep track of it with the receiver
-        ConnectivityManager cm =
-            (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
-        mBackgroundData = cm.getBackgroundDataSetting();
-
-        // See if any settings have changed while we weren't running...
-        checkPIMSyncSettings();
-
         try {
-            while (!mStop) {
+            // Loop indefinitely until we're shut down
+            while (!sStop) {
                 runAwake(SYNC_MANAGER_ID);
                 waitForConnectivity();
                 mNextWaitReason = "Heartbeat";
@@ -1877,6 +1899,7 @@
                     }
                 } catch (InterruptedException e) {
                     // Needs to be caught, but causes no problem
+                    log("SyncManager interrupted");
                 } finally {
                     synchronized (this) {
                         if (mKicked) {
@@ -1891,51 +1914,78 @@
             Log.e(TAG, "RuntimeException in SyncManager", e);
             throw e;
         } finally {
-            log("Finishing SyncManager");
-            // Lots of cleanup here
-            // Stop our running syncs
-            stopServiceThreads();
-
-            // Stop receivers and content observers
-            if (mConnectivityReceiver != null) {
-                unregisterReceiver(mConnectivityReceiver);
-            }
-            if (mBackgroundDataSettingReceiver != null) {
-                unregisterReceiver(mBackgroundDataSettingReceiver);
-            }
-
-            if (INSTANCE != null) {
-                ContentResolver resolver = getContentResolver();
-                resolver.unregisterContentObserver(mAccountObserver);
-                resolver.unregisterContentObserver(mMailboxObserver);
-                resolver.unregisterContentObserver(mSyncedMessageObserver);
-                resolver.unregisterContentObserver(mMessageObserver);
-                unregisterCalendarObservers();
-            }
-            // Don't leak the Intent associated with this listener
-            if (mAccountsUpdatedListener != null) {
-                AccountManager.get(this).removeOnAccountsUpdatedListener(mAccountsUpdatedListener);
-                mAccountsUpdatedListener = null;
-            }
-
-            // Clear pending alarms and associated Intents
-            clearAlarms();
-
-            // Release our wake lock, if we have one
-            synchronized (mWakeLocks) {
-                if (mWakeLock != null) {
-                    mWakeLock.release();
-                    mWakeLock = null;
-                }
-            }
-
-            log("Goodbye");
+            shutdown();
         }
+    }
 
-        if (!mStop) {
-            // If this wasn't intentional, try to restart the service
-            throw new RuntimeException("EAS SyncManager crash; please restart me...");
-       }
+    private void shutdown() {
+        synchronized (sSyncLock) {
+            // If INSTANCE is null, we've already been shut down
+            if (INSTANCE != null) {
+                log("SyncManager shutting down...");
+
+                // Stop our running syncs
+                stopServiceThreads();
+
+                // Stop receivers
+                if (mConnectivityReceiver != null) {
+                    unregisterReceiver(mConnectivityReceiver);
+                }
+                if (mBackgroundDataSettingReceiver != null) {
+                    unregisterReceiver(mBackgroundDataSettingReceiver);
+                }
+
+                // Unregister observers
+                ContentResolver resolver = getContentResolver();
+                if (mSyncedMessageObserver != null) {
+                    resolver.unregisterContentObserver(mSyncedMessageObserver);
+                    mSyncedMessageObserver = null;
+                }
+                if (mMessageObserver != null) {
+                    resolver.unregisterContentObserver(mMessageObserver);
+                    mMessageObserver = null;
+                }
+                if (mAccountObserver != null) {
+                    resolver.unregisterContentObserver(mAccountObserver);
+                    mAccountObserver = null;
+                }
+                if (mMailboxObserver != null) {
+                    resolver.unregisterContentObserver(mMailboxObserver);
+                    mMailboxObserver = null;
+                }
+                unregisterCalendarObservers();
+
+                // Remove account listener (registered with AccountManager)
+                if (mAccountsUpdatedListener != null) {
+                    AccountManager.get(this).removeOnAccountsUpdatedListener(
+                            mAccountsUpdatedListener);
+                    mAccountsUpdatedListener = null;
+                }
+
+                // Remove the sync status change listener (and null out the observer)
+                if (mStatusChangeListener != null) {
+                    ContentResolver.removeStatusChangeListener(mStatusChangeListener);
+                    mStatusChangeListener = null;
+                    mSyncStatusObserver = null;
+                }
+
+                // Clear pending alarms and associated Intents
+                clearAlarms();
+
+                // Release our wake lock, if we have one
+                synchronized (mWakeLocks) {
+                    if (mWakeLock != null) {
+                        mWakeLock.release();
+                        mWakeLock = null;
+                    }
+                }
+
+                INSTANCE = null;
+                sServiceThread = null;
+                sStop = false;
+                log("Goodbye");
+            }
+        }
     }
 
     private void releaseMailbox(long mailboxId) {
@@ -2112,7 +2162,7 @@
                         long requestTime = service.mRequestTime;
                         if (requestTime > 0) {
                             long timeToRequest = requestTime - now;
-                            if (service instanceof AbstractSyncService && timeToRequest <= 0) {
+                            if (timeToRequest <= 0) {
                                 service.mRequestTime = 0;
                                 service.alarm();
                             } else if (requestTime > 0 && timeToRequest < nextWait) {
diff --git a/src/com/android/exchange/adapter/ContactsSyncAdapter.java b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
index 8b4adba..c05a820 100644
--- a/src/com/android/exchange/adapter/ContactsSyncAdapter.java
+++ b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
@@ -1071,8 +1071,8 @@
 
             // If we've found an existing data row, we'll delete it.  Any rows left at the
             // end should be deleted...
-            if (result != null) {
-                list.remove(result);
+            for (NamedContentValues values : result) {
+                list.remove(values);
             }
 
             // Return the row found (or null)
diff --git a/src/com/android/exchange/adapter/ProvisionParser.java b/src/com/android/exchange/adapter/ProvisionParser.java
index af1ecdd..5304639 100644
--- a/src/com/android/exchange/adapter/ProvisionParser.java
+++ b/src/com/android/exchange/adapter/ProvisionParser.java
@@ -56,7 +56,7 @@
         return mRemoteWipe;
     }
 
-    public void parseProvisionDocWbxml() throws IOException {
+    private void parseProvisionDocWbxml() throws IOException {
         int minPasswordLength = 0;
         int passwordMode = PolicySet.PASSWORD_MODE_NONE;
         int maxPasswordFails = 0;
@@ -64,6 +64,7 @@
         boolean canSupport = true;
 
         while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) {
+            boolean supported = true;
             switch (tag) {
                 case Tags.PROVISION_DEVICE_PASSWORD_ENABLED:
                     if (getValueInt() == 1) {
@@ -92,10 +93,28 @@
                     // Hint: I haven't seen any that's more specific than "simple"
                     getValue();
                     break;
-                // The following policy, if false, can't be supported at the moment
+                // The following policies, if false, can't be supported at the moment
                 case Tags.PROVISION_ATTACHMENTS_ENABLED:
+                case Tags.PROVISION_ALLOW_STORAGE_CARD:
+                case Tags.PROVISION_ALLOW_CAMERA:
+                case Tags.PROVISION_ALLOW_UNSIGNED_APPLICATIONS:
+                case Tags.PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES:
+                case Tags.PROVISION_ALLOW_WIFI:
+                case Tags.PROVISION_ALLOW_TEXT_MESSAGING:
+                case Tags.PROVISION_ALLOW_POP_IMAP_EMAIL:
+                case Tags.PROVISION_ALLOW_IRDA:
+                case Tags.PROVISION_ALLOW_HTML_EMAIL:
+                case Tags.PROVISION_ALLOW_BROWSER:
+                case Tags.PROVISION_ALLOW_CONSUMER_EMAIL:
+                case Tags.PROVISION_ALLOW_INTERNET_SHARING:
                     if (getValueInt() == 0) {
-                        canSupport = false;
+                        supported = false;
+                    }
+                    break;
+                // Bluetooth: 0 = no bluetooth; 1 = only hands-free; 2 = allowed
+                case Tags.PROVISION_ALLOW_BLUETOOTH:
+                    if (getValueInt() != 2) {
+                        supported = false;
                     }
                     break;
                 // The following policies, if true, can't be supported at the moment
@@ -103,14 +122,68 @@
                 case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED:
                 case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION:
                 case Tags.PROVISION_DEVICE_PASSWORD_HISTORY:
-                case Tags.PROVISION_MAX_ATTACHMENT_SIZE:
+                case Tags.PROVISION_REQUIRE_DEVICE_ENCRYPTION:
+                case Tags.PROVISION_REQUIRE_SIGNED_SMIME_MESSAGES:
+                case Tags.PROVISION_REQUIRE_ENCRYPTED_SMIME_MESSAGES:
+                case Tags.PROVISION_REQUIRE_SIGNED_SMIME_ALGORITHM:
+                case Tags.PROVISION_REQUIRE_ENCRYPTION_SMIME_ALGORITHM:
+                case Tags.PROVISION_REQUIRE_MANUAL_SYNC_WHEN_ROAMING:
                     if (getValueInt() == 1) {
-                        canSupport = false;
+                        supported = false;
+                    }
+                    break;
+                // The following, if greater than zero, can't be supported at the moment
+                case Tags.PROVISION_MAX_ATTACHMENT_SIZE:
+                    if (getValueInt() > 0) {
+                        supported = false;
+                    }
+                    break;
+                // Complex character setting is only used if we're in "strong" (alphanumeric) mode
+                case Tags.PROVISION_MIN_DEVICE_PASSWORD_COMPLEX_CHARS:
+                    if ((passwordMode == PolicySet.PASSWORD_MODE_STRONG) && (getValueInt() > 0)) {
+                        supported = false;
+                    }
+                    break;
+                // The following policies are moot; they allow functionality that we don't support
+                case Tags.PROVISION_ALLOW_DESKTOP_SYNC:
+                case Tags.PROVISION_ALLOW_SMIME_ENCRYPTION_NEGOTIATION:
+                case Tags.PROVISION_ALLOW_SMIME_SOFT_CERTS:
+                case Tags.PROVISION_ALLOW_REMOTE_DESKTOP:
+                    skipTag();
+                    break;
+                // We don't handle approved/unapproved application lists
+                case Tags.PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST:
+                case Tags.PROVISION_APPROVED_APPLICATION_LIST:
+                    // Parse and throw away the content
+                    if (specifiesApplications(tag)) {
+                        supported = false;
+                    }
+                    break;
+                // NOTE: We can support these entirely within the email application if we choose
+                case Tags.PROVISION_MAX_CALENDAR_AGE_FILTER:
+                case Tags.PROVISION_MAX_EMAIL_AGE_FILTER:
+                    // 0 indicates no specified filter
+                    if (getValueInt() != 0) {
+                        supported = false;
+                    }
+                    break;
+                // NOTE: We can support these entirely within the email application if we choose
+                case Tags.PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE:
+                case Tags.PROVISION_MAX_EMAIL_HTML_BODY_TRUNCATION_SIZE:
+                    String value = getValue();
+                    // -1 indicates no required truncation
+                    if (!value.equals("-1")) {
+                        supported = false;
                     }
                     break;
                 default:
                     skipTag();
             }
+
+            if (!supported) {
+                log("** Policy not supported");
+                canSupport = false;
+            }
         }
 
         if (canSupport) {
@@ -119,6 +192,27 @@
         }
     }
 
+    /**
+     * Return whether or not either of the application list tags specifies any applications
+     * @param endTag the tag whose children we're walking through
+     * @return whether any applications were specified (by name or by hash)
+     * @throws IOException
+     */
+    private boolean specifiesApplications(int endTag) throws IOException {
+        boolean specifiesApplications = false;
+        while (nextTag(endTag) != END) {
+            switch (tag) {
+                case Tags.PROVISION_APPLICATION_NAME:
+                case Tags.PROVISION_HASH:
+                    specifiesApplications = true;
+                    break;
+                default:
+                    skipTag();
+            }
+        }
+        return specifiesApplications;
+    }
+
     class ShadowPolicySet {
         int mMinPasswordLength = 0;
         int mPasswordMode = PolicySet.PASSWORD_MODE_NONE;
@@ -126,7 +220,7 @@
         int mMaxScreenLockTime = 0;
     }
 
-    public void parseProvisionDocXml(String doc) throws IOException {
+    /*package*/ void parseProvisionDocXml(String doc) throws IOException {
         ShadowPolicySet sps = new ShadowPolicySet();
 
         try {
@@ -154,7 +248,7 @@
     /**
      * Return true if password is required; otherwise false.
      */
-    boolean parseSecurityPolicy(XmlPullParser parser, ShadowPolicySet sps)
+    private boolean parseSecurityPolicy(XmlPullParser parser, ShadowPolicySet sps)
             throws XmlPullParserException, IOException {
         boolean passwordRequired = true;
         while (true) {
@@ -177,7 +271,7 @@
         return passwordRequired;
     }
 
-    void parseCharacteristic(XmlPullParser parser, ShadowPolicySet sps)
+    private void parseCharacteristic(XmlPullParser parser, ShadowPolicySet sps)
             throws XmlPullParserException, IOException {
         boolean enforceInactivityTimer = true;
         while (true) {
@@ -219,7 +313,7 @@
         }
     }
 
-    void parseRegistry(XmlPullParser parser, ShadowPolicySet sps)
+    private void parseRegistry(XmlPullParser parser, ShadowPolicySet sps)
             throws XmlPullParserException, IOException {
       while (true) {
           int type = parser.nextTag();
@@ -234,7 +328,7 @@
       }
     }
 
-    void parseWapProvisioningDoc(XmlPullParser parser, ShadowPolicySet sps)
+    private void parseWapProvisioningDoc(XmlPullParser parser, ShadowPolicySet sps)
             throws XmlPullParserException, IOException {
         while (true) {
             int type = parser.nextTag();
@@ -258,7 +352,7 @@
         }
     }
 
-    public void parseProvisionData() throws IOException {
+    private void parseProvisionData() throws IOException {
         while (nextTag(Tags.PROVISION_DATA) != END) {
             if (tag == Tags.PROVISION_EAS_PROVISION_DOC) {
                 parseProvisionDocWbxml();
@@ -268,7 +362,7 @@
         }
     }
 
-    public void parsePolicy() throws IOException {
+    private void parsePolicy() throws IOException {
         String policyType = null;
         while (nextTag(Tags.PROVISION_POLICY) != END) {
             switch (tag) {
@@ -297,7 +391,7 @@
         }
     }
 
-    public void parsePolicies() throws IOException {
+    private void parsePolicies() throws IOException {
         while (nextTag(Tags.PROVISION_POLICIES) != END) {
             if (tag == Tags.PROVISION_POLICY) {
                 parsePolicy();
diff --git a/src/com/android/exchange/adapter/Tags.java b/src/com/android/exchange/adapter/Tags.java
index ef70981..2745e33 100644
--- a/src/com/android/exchange/adapter/Tags.java
+++ b/src/com/android/exchange/adapter/Tags.java
@@ -596,7 +596,7 @@
             "MinDevicePasswordComplexCharacters", "AllowWiFi", "AllowTextMessaging",
             "AllowPOPIMAPEmail", "AllowBluetooth", "AllowIrDA", "RequireManualSyncWhenRoaming",
             "AllowDesktopSync",
-            "MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilder",
+            "MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilter",
             "MaxEmailBodyTruncationSize", "MaxEmailHTMLBodyTruncationSize",
             "RequireSignedSMIMEMessages", "RequireEncryptedSMIMEMessages",
             "RequireSignedSMIMEAlgorithm", "RequireEncryptionSMIMEAlgorithm",