EasService fixes & improvements.

- Make sure to call EmailContent.init.
- Add function to determine if an account needs to ping.
  This is currently unused but will be necessary later.
- Track the last ping/not ping status in PingSyncSynchronizer.
  This is used to know whether to restart a ping, rather than
  expecting the EasService to pass this in.

Change-Id: Ibb0aa45cf5174ea5dd3ba6210104a25e9060dcf8
(cherry picked from commit 215cae569676dde52c864718515397e803eafa49)
diff --git a/src/com/android/exchange/service/EasService.java b/src/com/android/exchange/service/EasService.java
index 54b7f45..bb1f4da 100644
--- a/src/com/android/exchange/service/EasService.java
+++ b/src/com/android/exchange/service/EasService.java
@@ -17,12 +17,19 @@
 package com.android.exchange.service;
 
 import android.app.Service;
+import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.SyncResult;
+import android.database.Cursor;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.provider.CalendarContract;
+import android.provider.ContactsContract;
 
+import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.HostAuth;
+import com.android.emailcommon.provider.Mailbox;
 import com.android.emailcommon.service.IEmailService;
 import com.android.emailcommon.service.IEmailServiceCallback;
 import com.android.emailcommon.service.SearchParams;
@@ -31,6 +38,9 @@
 import com.android.exchange.eas.EasOperation;
 import com.android.mail.utils.LogUtils;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Service to handle all communication with the EAS server. Note that this is completely decoupled
  * from the sync adapters; sync adapters should make blocking calls on this service to actually
@@ -40,6 +50,13 @@
 
     private static final String TAG = Eas.LOG_TAG;
 
+    /**
+     * The content authorities that can be synced for EAS accounts. Initialization must wait until
+     * after we have a chance to call {@link EmailContent#init} (and, for future content types,
+     * possibly other initializations) because that's how we can know what the email authority is.
+     */
+    private static String[] AUTHORITIES_TO_SYNC;
+
     private final PingSyncSynchronizer mSynchronizer;
 
     private final IEmailService.Stub mBinder = new IEmailService.Stub() {
@@ -101,6 +118,13 @@
 
     @Override
     public void onCreate() {
+        super.onCreate();
+        EmailContent.init(this);
+        AUTHORITIES_TO_SYNC = new String[] {
+                EmailContent.AUTHORITY,
+                CalendarContract.AUTHORITY,
+                ContactsContract.AUTHORITY
+        };
         // TODO: Restart all pings that are needed.
     }
 
@@ -127,8 +151,82 @@
         try {
             return operation.performOperation(syncResult);
         } finally {
-            // TODO: Fix pushEnabled param
-            mSynchronizer.syncEnd(accountId, false);
+            mSynchronizer.syncEnd(accountId);
         }
     }
+
+    /**
+     * Determine whether this account is configured with folders that are ready for push
+     * notifications.
+     * @param account The {@link Account} that we're interested in.
+     * @return Whether this account needs to ping.
+     */
+    public boolean pingNeededForAccount(final Account account) {
+        // Check account existence.
+        if (account == null || account.mId == Account.NO_ACCOUNT) {
+            LogUtils.d(TAG, "Do not ping: Account not found or not valid");
+            return false;
+        }
+
+        // Check if account is configured for a push sync interval.
+        if (account.mSyncInterval != Account.CHECK_INTERVAL_PUSH) {
+            LogUtils.d(TAG, "Do not ping: Account %d not configured for push", account.mId);
+            return false;
+        }
+
+        // Check security hold status of the account.
+        if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
+            LogUtils.d(TAG, "Do not ping: Account %d is on security hold", account.mId);
+            return false;
+        }
+
+        // Check if the account has performed at least one sync so far (accounts must perform
+        // the initial sync before push is possible).
+        if (EmailContent.isInitialSyncKey(account.mSyncKey)) {
+            LogUtils.d(TAG, "Do not ping: Account %d has not done initial sync", account.mId);
+            return false;
+        }
+
+        // Check that there's at least one mailbox that is both configured for push notifications,
+        // and whose content type is enabled for sync in the account manager.
+        final android.accounts.Account amAccount = new android.accounts.Account(
+                        account.mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
+
+        final Set<String> authsToSync = getAuthoritiesToSync(amAccount, AUTHORITIES_TO_SYNC);
+        // If we have at least one sync-enabled content type, check for syncing mailboxes.
+        if (!authsToSync.isEmpty()) {
+            final Cursor c = Mailbox.getMailboxesForPush(getContentResolver(), account.mId);
+            if (c != null) {
+                try {
+                    while (c.moveToNext()) {
+                        final int mailboxType = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
+                        if (authsToSync.contains(Mailbox.getAuthority(mailboxType))) {
+                            return true;
+                        }
+                    }
+                } finally {
+                    c.close();
+                }
+            }
+        }
+        LogUtils.d(TAG, "Do not ping: Account %d has no folders configured for push", account.mId);
+        return false;
+    }
+
+    /**
+     * Determine which content types are set to sync for an account.
+     * @param account The account whose sync settings we're looking for.
+     * @param authorities All possible authorities we could care about.
+     * @return The authorities for the content types we want to sync for account.
+     */
+    private static Set<String> getAuthoritiesToSync(final android.accounts.Account account,
+            final String[] authorities) {
+        final HashSet<String> authsToSync = new HashSet();
+        for (final String authority : authorities) {
+            if (ContentResolver.getSyncAutomatically(account, authority)) {
+                authsToSync.add(authority);
+            }
+        }
+        return authsToSync;
+    }
 }
diff --git a/src/com/android/exchange/service/PingSyncSynchronizer.java b/src/com/android/exchange/service/PingSyncSynchronizer.java
index 9fa0197..54c34a9 100644
--- a/src/com/android/exchange/service/PingSyncSynchronizer.java
+++ b/src/com/android/exchange/service/PingSyncSynchronizer.java
@@ -79,6 +79,12 @@
         private PingTask mPingTask;
 
         /**
+         * Tracks whether this account wants to get push notifications, based on calls to
+         * {@link #pushModify} and {@link #pushStop} (i.e. it tracks the last requested push state).
+         */
+        private boolean mPushEnabled;
+
+        /**
          * The number of syncs that are blocked waiting for the current operation to complete.
          * Unlike Pings, sync operations do not start their own tasks and are assumed to run in
          * whatever thread calls into this class.
@@ -94,6 +100,7 @@
          */
         public AccountSyncState(final Lock lock) {
             mPingTask = null;
+            mPushEnabled = false;
             mSyncCount = 0;
             mCondition = lock.newCondition();
         }
@@ -125,17 +132,16 @@
         /**
          * Update bookkeeping when a sync completes. This includes signaling pending ops to
          * go ahead, or starting the ping if appropriate and there are no waiting ops.
-         * @param pushEnabled Whether this account is configured for push.
          * @return Whether this account is now idle.
          */
-        public boolean syncEnd(final boolean pushEnabled) {
+        public boolean syncEnd() {
             --mSyncCount;
             if (mSyncCount > 0) {
                 LogUtils.d(TAG, "Signalling a pending sync to proceed.");
                 mCondition.signal();
                 return false;
             } else {
-                if (pushEnabled) {
+                if (mPushEnabled) {
                     // TODO: Start the ping task
                     return false;
                 }
@@ -145,16 +151,15 @@
 
         /**
          * Update bookkeeping when the ping task terminates, including signaling any waiting ops.
-         * @param pushEnabled Whether this account is configured for push.
          * @return Whether this account is now idle.
          */
-        public boolean pingEnd(final boolean pushEnabled) {
+        public boolean pingEnd() {
             mPingTask = null;
             if (mSyncCount > 0) {
                 mCondition.signal();
                 return false;
             } else {
-                if (pushEnabled) {
+                if (mPushEnabled) {
                     // TODO: request a push-only sync.
                     return false;
                 }
@@ -166,6 +171,7 @@
          * Modifies or starts a ping for this account if no syncs are running.
          */
         public void pushModify() {
+            mPushEnabled = true;
             if (mSyncCount == 0) {
                 if (mPingTask == null) {
                     // No ping, no running syncs -- start a new ping.
@@ -183,6 +189,7 @@
          * Stop the currently running ping.
          */
         public void pushStop() {
+            mPushEnabled = false;
             if (mPingTask != null) {
                 mPingTask.stop();
             }
@@ -263,7 +270,7 @@
         }
     }
 
-    public void syncEnd(final long accountId, final boolean pushEnabled) {
+    public void syncEnd(final long accountId) {
         mLock.lock();
         try {
             LogUtils.d(TAG, "PSS syncEnd for account %d", accountId);
@@ -272,7 +279,7 @@
                 LogUtils.w(TAG, "PSS syncEnd for account %d but no state found", accountId);
                 return;
             }
-            if (accountState.syncEnd(pushEnabled)) {
+            if (accountState.syncEnd()) {
                 removeAccount(accountId);
             }
         } finally {
@@ -280,7 +287,7 @@
         }
     }
 
-    public void pingEnd(final long accountId, final boolean pushEnabled) {
+    public void pingEnd(final long accountId) {
         mLock.lock();
         try {
             LogUtils.d(TAG, "PSS pingEnd for account %d", accountId);
@@ -289,7 +296,7 @@
                 LogUtils.w(TAG, "PSS pingEnd for account %d but no state found", accountId);
                 return;
             }
-            if (accountState.pingEnd(pushEnabled)) {
+            if (accountState.pingEnd()) {
                 removeAccount(accountId);
             }
         } finally {
diff --git a/src/com/android/exchange/service/PingTask.java b/src/com/android/exchange/service/PingTask.java
index a016556..98b71f4 100644
--- a/src/com/android/exchange/service/PingTask.java
+++ b/src/com/android/exchange/service/PingTask.java
@@ -91,8 +91,7 @@
             mSyncHandlerMap.pingComplete(mOperation.getAmAccount(), mOperation.getAccountId(),
                     pingStatus);
         } else {
-            // TODO: Fix the pushEnabled param.
-            mPingSyncSynchronizer.pingEnd(mOperation.getAccountId(), false);
+            mPingSyncSynchronizer.pingEnd(mOperation.getAccountId());
         }
         return null;
     }
@@ -106,8 +105,7 @@
             mSyncHandlerMap.pingComplete(mOperation.getAmAccount(), mOperation.getAccountId(),
                     EasOperation.RESULT_REQUEST_FAILURE);
         } else {
-            // TODO: Fix the pushEnabled param.
-            mPingSyncSynchronizer.pingEnd(mOperation.getAccountId(), false);
+            mPingSyncSynchronizer.pingEnd(mOperation.getAccountId());
         }
     }
 }