Directly query Gmail for account list

Also, handle updates to the account list
Change-Id: Iae0f16ba3e4fbf0ba800638548617d6c8099c281
diff --git a/gmail_src/com/android/mail/providers/proto/boot/GmailAccountService.java b/gmail_src/com/android/mail/providers/proto/boot/GmailAccountService.java
index a5041fa..b26ece8 100644
--- a/gmail_src/com/android/mail/providers/proto/boot/GmailAccountService.java
+++ b/gmail_src/com/android/mail/providers/proto/boot/GmailAccountService.java
@@ -15,22 +15,25 @@
  */
 package com.android.mail.providers.protos.boot;
 
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import com.android.mail.providers.Account;
 import com.android.mail.providers.AccountCacheProvider;
+import com.android.mail.providers.AccountCacheProvider.CachedAccount;
 import com.android.mail.providers.protos.mock.MockUiProvider;
+import com.android.mail.providers.UIProvider;
 import com.android.mail.providers.UIProvider.AccountCapabilities;
 import com.android.mail.providers.UIProvider.AccountColumns;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
+import com.android.mail.utils.LogUtils;
 
 import android.app.IntentService;
 import android.content.Intent;
+import android.database.Cursor;
 import android.net.Uri;
 
-import java.io.IOException;
 import java.util.Map;
 
 
@@ -45,46 +48,15 @@
 
     private static final Uri BASE_SETTINGS_URI = Uri.parse("setting://gmail/");
 
+    private static final Uri ACCOUNTS_URI =
+            Uri.parse(GMAIL_UI_PROVIDER_BASE_URI_STRING + "/accounts");
+
     public static final String DEFAULT_HELP_URL =
             "http://www.google.com/support/mobile/?hl=%locale%";
 
+    private final static String LOG_TAG = new LogUtils().getLogTag();
 
-    private static Uri getAccountUri(String account) {
-        return Uri.parse(GMAIL_UI_PROVIDER_BASE_URI_STRING + "/account/" + account);
-    }
-
-    private static Uri getAccountFoldersUri(String account) {
-        return Uri.parse(GMAIL_UI_PROVIDER_BASE_URI_STRING + "/" + account + "/labels");
-    }
-
-    private static Uri getAccountSendMailUri(String account) {
-        return Uri.parse(GMAIL_UI_PROVIDER_BASE_URI_STRING + "/" + account + "/sendNewMessage");
-    }
-
-    private static Uri getAccountUndoUri(String account) {
-        return Uri.parse(GMAIL_UI_PROVIDER_BASE_URI_STRING + "/" + account + "/undo");
-    }
-
-    private static Uri getAccountSettingUri(String account) {
-        return BASE_SETTINGS_URI.buildUpon().appendQueryParameter("account", account).build();
-    }
-
-    private static Uri getHelpUri() {
-        // TODO(pwestbro): allow this url to be changed via Gservices
-        return Uri.parse(DEFAULT_HELP_URL);
-    }
-
-    private static Uri getComposeUri(String account) {
-        return Uri.parse("gmailfrom://gmail-ls/account/" + account);
-    }
-
-    private static Uri getAccountSettingsQueryUri(String account) {
-        return Uri.parse(GMAIL_UI_PROVIDER_BASE_URI_STRING + "/" + account + "/settings");
-    }
-
-    private static Uri getAccountSaveDraftUri(String account) {
-        return Uri.parse(GMAIL_UI_PROVIDER_BASE_URI_STRING + "/" + account + "/saveNewMessage");
-    }
+    private static GmailAccountListObserver sGmailAccountListObserver = null;
 
     public GmailAccountService() {
         super("GmailAccountService");
@@ -102,76 +74,56 @@
     private void getAndRegisterGmailAccounts() {
         // Get the list of Google accounts that have the mail feature, and register thiese with
         // the AccountCacheProvider
-        AccountManagerFuture<Account[]> future;
-        future = AccountManager.get(this).getAccountsByTypeAndFeatures(
-                "com.google",
-                // Ideally we would call GoogleLoginServiceConstants.featureForService("mail") here,
-                // but we can't depend on Google code from this project.  Just use the resulting
-                // value
-                new String[] { "service_mail" },
-                null, null);
-        try {
-            // Block this IntentService on the result because this thread may not be around later
-            // to handle anything if it's killed in the interim.  This is a blockable non-UI thread.
-            Account[] accounts = future.getResult();
 
-            registerGmailAccounts(accounts);
-        } catch (OperationCanceledException oce) {
-            // should not happen.
-        } catch (IOException ioe) {
-            // should not happen
-        } catch (AuthenticatorException ae) {
-            // should not happen
+        final ContentResolver resolver = getContentResolver();
+        // Get the accounts from Gmail
+        // NOTE: Once Gmail & Unified Email are merged, this service should be removed
+        final Uri uri = Uri.parse("content://com.android.gmail.ui/accounts");
+        final Cursor c = resolver.query(uri, UIProvider.ACCOUNTS_PROJECTION, null, null,
+                null);
+        if (c == null) {
+            LogUtils.d(LOG_TAG, "null account cursor returned");
+            return;
+        }
+        try {
+            // This is a work around to make sure that changes in the underlying provider changes
+            // that UnifiedEmail is updates with those change
+            // TODO(pwestbro): add AccountCacheProvider#addAccounts(Uri uri, Cursor c) that will
+            // both create the account objects and registers for notifications from the cursor
+            if (sGmailAccountListObserver == null) {
+
+                // The current thread may not be around after this IntentService stops, make
+                // sure to handle notifications on the main thread
+                final Handler notificationHandler = new Handler(Looper.getMainLooper());
+
+                sGmailAccountListObserver =
+                        new GmailAccountListObserver(this, notificationHandler);
+
+                resolver.registerContentObserver(ACCOUNTS_URI, true,
+                        sGmailAccountListObserver);
+            }
+            while (c.moveToNext()) {
+                Account a = new Account(c);
+                AccountCacheProvider.addAccount(new CachedAccount(a));
+            }
+        } finally {
+            c.close();
         }
     }
 
-    private void registerGmailAccounts(Account[] accounts) {
-        for (int i = 0; i < accounts.length; i++) {
-            final Account account = accounts[i];
+    static class GmailAccountListObserver extends ContentObserver {
+        final Context mContext;
+        GmailAccountListObserver(Context context, Handler handler) {
+            super(handler);
+            mContext = context;
+        }
 
-            final int gmailAccountId = account.hashCode();
-
-            // NOTE: This doesn't completely populate the provider.  A query for the account uri
-            // will not return a cursor.
-            final Map<String, Object> mockAccountMap =
-                    MockUiProvider.createAccountDetailsMap(i % MockUiProvider.NUM_MOCK_ACCOUNTS,
-                            false /* don't cache */);
-            // TODO: where should this really be stored?
-            long capabilities = Long.valueOf(
-                    AccountCapabilities.SYNCABLE_FOLDERS |
-                    AccountCapabilities.REPORT_SPAM |
-                    AccountCapabilities.ARCHIVE |
-                    AccountCapabilities.MUTE |
-                    AccountCapabilities.SERVER_SEARCH |
-                    AccountCapabilities.FOLDER_SERVER_SEARCH |
-                    AccountCapabilities.SANITIZED_HTML |
-                    AccountCapabilities.DRAFT_SYNCHRONIZATION |
-                    AccountCapabilities.MULTIPLE_FROM_ADDRESSES |
-                    AccountCapabilities.LOCAL_SEARCH |
-                    AccountCapabilities.THREADED_CONVERSATIONS |
-                    AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV |
-                    AccountCapabilities.UNDO |
-                    AccountCapabilities.HELP_CONTENT |
-                    AccountCapabilities.MARK_IMPORTANT);
-            final AccountCacheProvider.CachedAccount cachedAccount =
-                    new AccountCacheProvider.CachedAccount(gmailAccountId,
-                            account.name,
-                            getAccountUri(account.name),
-                            capabilities,
-                            getAccountFoldersUri(account.name),
-                            (Uri) mockAccountMap.get(AccountColumns.SEARCH_URI),
-                            (Uri) mockAccountMap.get(AccountColumns.ACCOUNT_FROM_ADDRESSES_URI),
-                            getAccountSaveDraftUri(account.name),
-                            getAccountSendMailUri(account.name),
-                            (Uri) mockAccountMap.get(AccountColumns.EXPUNGE_MESSAGE_URI),
-                            getAccountUndoUri(account.name),
-                            getAccountSettingUri(account.name),
-                            getAccountSettingsQueryUri(account.name),
-                            getHelpUri(),
-                            0,
-                            getComposeUri(account.name));
-
-            AccountCacheProvider.addAccount(cachedAccount);
+        @Override
+        public void onChange(boolean selfChange) {
+            LogUtils.d(LOG_TAG, "GmailAccountListObserver#onChange called.");
+            // Send an intent to cause the account list to be requeried
+            final Intent intent = new Intent(AccountReceiver.ACTION_PROVIDER_CREATED);
+            mContext.sendBroadcast(intent);
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 3cad6cf..01ef42b 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.Loader;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
@@ -55,6 +56,11 @@
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+
 /**
  * This is an abstract implementation of the Activity Controller. This class
  * knows how to respond to menu items, state changes, layout changes, etc. It
@@ -116,6 +122,7 @@
             mActivity.invalidateOptionsMenu();
         }
     };
+    private final Set<Uri> mCurrentAccountUris = Sets.newHashSet();
     protected static final String LOG_TAG = new LogUtils().getLogTag();
     private static final int ACCOUNT_CURSOR_LOADER = 0;
     private static final int FOLDER_CURSOR_LOADER = 1;
@@ -636,22 +643,35 @@
         return null;
     }
 
-    /**
-     * Return whether the given account exists in the cursor.
-     *
-     * @param accountCursor
-     * @param account
-     * @return true if the account exists in the account cursor, false
-     *         otherwise.
-     */
-    private boolean missingFromCursor(Cursor accountCursor, Account account) {
-        if (account == null || !accountCursor.moveToFirst())
+    private boolean accountsUpdated(Cursor accountCursor) {
+        // Check to see if the current account hasn't been set, or the account cursor is empty
+        if (mAccount == null || !accountCursor.moveToFirst()) {
             return true;
+        }
+
+        // Check to see if the number of accounts are different, from the number we saw on the last
+        // updated
+        if (mCurrentAccountUris.size() != accountCursor.getCount()) {
+            return true;
+        }
+
+        // Check to see if the account list is different or if the current account is not found in
+        // the cursor.
+        boolean foundCurrentAccount = false;
         do {
-            if (account.equals(new Account(accountCursor)))
-                return false;
+            final Uri accountUri =
+                    Uri.parse(accountCursor.getString(UIProvider.ACCOUNT_URI_COLUMN));
+            if (!foundCurrentAccount && mAccount.uri.equals(accountUri)) {
+                foundCurrentAccount = true;
+            }
+
+            if (!mCurrentAccountUris.contains(accountUri)) {
+                return true;
+            }
         } while (accountCursor.moveToNext());
-        return true;
+
+        // As long as we found the current account, the list hasn't been updated
+        return !foundCurrentAccount;
     }
 
     /**
@@ -660,19 +680,37 @@
      *
      * @param loader
      * @param accounts cursor into the AccountCache
-     * @param currentAccountMissing whether the current account is missing from the set of accounts
      * @return true if the update was successful, false otherwise
      */
-    private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts,
-            boolean currentAccountMissing) {
+    private boolean updateAccounts(Loader<Cursor> loader, Cursor accounts) {
         if (accounts == null || !accounts.moveToFirst()) {
             return false;
         }
-        Account newAccount = (mAccount == null || currentAccountMissing) ?
-                new Account(accounts) : mAccount;
-        onAccountChanged(newAccount);
-        fetchAccountFolderInfo();
+
         final Account[] allAccounts = Account.getAllAccounts(accounts);
+
+        // Save the uris for the accounts
+        mCurrentAccountUris.clear();
+        for (Account account : allAccounts) {
+            mCurrentAccountUris.add(account.uri);
+        }
+
+        final Account newAccount;
+        if (mAccount == null || !mCurrentAccountUris.contains(mAccount.uri)) {
+            accounts.moveToFirst();
+            newAccount = new Account(accounts);
+        } else {
+            newAccount = mAccount;
+        }
+        // only bother updating the account/folder if the new account is different than the
+        // existing one
+        final boolean refetchFolderInfo = !newAccount.equals(mAccount);
+        onAccountChanged(newAccount);
+
+        if(refetchFolderInfo) {
+            fetchAccountFolderInfo();
+        }
+
         mActionBarView.setAccounts(allAccounts);
         return (allAccounts.length > 0);
     }
@@ -686,9 +724,10 @@
         // if the current account has vanished.
         final int id = loader.getId();
         if (id == ACCOUNT_CURSOR_LOADER) {
-            final boolean currentAccountMissing = missingFromCursor(data, mAccount);
-            if (!isLoaderInitialized || currentAccountMissing) {
-                isLoaderInitialized = updateAccounts(loader, data, currentAccountMissing);
+
+            final boolean accountListUpdated = accountsUpdated(data);
+            if (!isLoaderInitialized || accountListUpdated) {
+                isLoaderInitialized = updateAccounts(loader, data);
             }
         } else if (id == FOLDER_CURSOR_LOADER) {
             // Check status of the cursor.