Performing all account meta-data loading on background thread.

Bug: 3228687
Change-Id: Id66989ed9a4e671019b931c172de254b8b055a4f
diff --git a/src/com/android/contacts/model/AccountTypes.java b/src/com/android/contacts/model/AccountTypes.java
index 4e94248..92ffba2 100644
--- a/src/com/android/contacts/model/AccountTypes.java
+++ b/src/com/android/contacts/model/AccountTypes.java
@@ -47,6 +47,7 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Singleton holder for all parsed {@link AccountType} available on the
@@ -67,12 +68,16 @@
     private HashMap<String, AccountType> mSources = Maps.newHashMap();
     private HashSet<String> mKnownPackages = Sets.newHashSet();
 
-    private static final int MESSAGE_PROCESS_BROADCAST_INTENT = 0;
-    private static final int MESSAGE_SYNC_STATUS_CHANGED = 1;
+    private static final int MESSAGE_LOAD_DATA = 0;
+    private static final int MESSAGE_PROCESS_BROADCAST_INTENT = 1;
+    private static final int MESSAGE_SYNC_STATUS_CHANGED = 2;
 
     private HandlerThread mListenerThread;
     private Handler mListenerHandler;
 
+    /* A latch that ensures that asynchronous initialization completes before data is used */
+    private CountDownLatch mInitializationLatch;
+
     private static AccountTypes sInstance = null;
 
     private static final Comparator<Account> ACCOUNT_COMPARATOR = new Comparator<Account>() {
@@ -102,6 +107,8 @@
      * Internal constructor that only performs initial parsing.
      */
     private AccountTypes(Context context) {
+        mInitializationLatch = new CountDownLatch(1);
+
         mContext = context;
         mApplicationContext = context.getApplicationContext();
         mAccountManager = AccountManager.get(mApplicationContext);
@@ -109,19 +116,20 @@
         // Create fallback contacts source for on-phone contacts
         mFallbackSource = new FallbackAccountType();
 
-        queryAccounts();
-
         mListenerThread = new HandlerThread("AccountChangeListener");
         mListenerThread.start();
         mListenerHandler = new Handler(mListenerThread.getLooper()) {
             @Override
             public void handleMessage(Message msg) {
                 switch (msg.what) {
+                    case MESSAGE_LOAD_DATA:
+                        loadAccountsInBackground();
+                        break;
                     case MESSAGE_PROCESS_BROADCAST_INTENT:
                         processBroadcastIntent((Intent) msg.obj);
                         break;
                     case MESSAGE_SYNC_STATUS_CHANGED:
-                        queryAccounts();
+                        loadAccountsInBackground();
                         break;
                 }
             }
@@ -146,6 +154,8 @@
         mAccountManager.addOnAccountsUpdatedListener(this, mListenerHandler, false);
 
         ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
+
+        mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
     }
 
     /** @hide exposed for unit tests */
@@ -196,7 +206,7 @@
                         invalidateCache(packageName);
                     } else {
                         // Unknown source, so reload from scratch
-                        queryAccounts();
+                        loadAccountsInBackground();
                     }
                 }
             }
@@ -224,13 +234,33 @@
     /* This notification will arrive on the background thread */
     public void onAccountsUpdated(Account[] accounts) {
         // Refresh to catch any changed accounts
-        queryAccounts();
+        loadAccountsInBackground();
     }
 
     /**
-     * Loads all {@link AuthenticatorDescription} known by the {@link AccountManager} on the system.
+     * Returns instantly if accounts and account types have already been loaded.
+     * Otherwise waits for the background thread to complete the loading.
      */
-    protected synchronized void queryAccounts() {
+    void ensureAccountsLoaded() {
+        CountDownLatch latch = mInitializationLatch;
+        if (latch == null) {
+            return;
+        }
+        while (true) {
+            try {
+                latch.await();
+                return;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Loads account list and corresponding account types. Always called on a
+     * background thread.
+     */
+    protected void loadAccountsInBackground() {
         mSources.clear();
         mKnownPackages.clear();
         mAccounts.clear();
@@ -291,7 +321,7 @@
 
             if (syncable) {
                 // Ensure we have details loaded for each account
-                final AccountType accountType = getInflatedSource(
+                final AccountType accountType = getAccountType(
                         account.type, AccountType.LEVEL_SUMMARY);
                 if (accountType != null) {
                     mAccounts.add(account);
@@ -304,6 +334,10 @@
 
         Collections.sort(mAccounts, ACCOUNT_COMPARATOR);
         Collections.sort(mWritableAccounts, ACCOUNT_COMPARATOR);
+        if (mInitializationLatch != null) {
+            mInitializationLatch.countDown();
+            mInitializationLatch = null;
+        }
     }
 
     /**
@@ -323,7 +357,8 @@
     /**
      * Return list of all known, writable {@link Account}'s.
      */
-    public synchronized ArrayList<Account> getAccounts(boolean writableOnly) {
+    public ArrayList<Account> getAccounts(boolean writableOnly) {
+        ensureAccountsLoaded();
         return writableOnly ? mWritableAccounts : mAccounts;
     }
 
@@ -336,6 +371,7 @@
      */
     public DataKind getKindOrFallback(String accountType, String mimeType, Context context,
             int inflateLevel) {
+        ensureAccountsLoaded();
         DataKind kind = null;
 
         // Try finding source and kind matching request
@@ -362,6 +398,11 @@
      * Return {@link AccountType} for the given account type.
      */
     public AccountType getInflatedSource(String accountType, int inflateLevel) {
+        ensureAccountsLoaded();
+        return getAccountType(accountType, inflateLevel);
+    }
+
+    AccountType getAccountType(String accountType, int inflateLevel) {
         // Try finding specific source, otherwise use fallback
         AccountType source = mSources.get(accountType);
         if (source == null) source = mFallbackSource;