Merge "Import revised translations."
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index df12cf8..1e5c14e 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -17,7 +17,14 @@
 package com.android.contacts;
 
 import android.app.Application;
+import android.content.Context;
 import android.os.StrictMode;
+import android.preference.PreferenceManager;
+
+import java.util.Locale;
+
+import com.android.contacts.model.AccountTypes;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
 
 public final class ContactsApplication extends Application {
 
@@ -25,6 +32,12 @@
     public void onCreate() {
         super.onCreate();
 
+        // Priming caches to placate the StrictMode police
+        Context context = getApplicationContext();
+        PreferenceManager.getDefaultSharedPreferences(context);
+        PhoneNumberUtil.getInstance().getAsYouTypeFormatter(Locale.getDefault().getCountry());
+        AccountTypes.getInstance(context);
+
         StrictMode.setThreadPolicy(
                 new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
     }
diff --git a/src/com/android/contacts/list/ContactListFilter.java b/src/com/android/contacts/list/ContactListFilter.java
index dcaf608..2a2caf7 100644
--- a/src/com/android/contacts/list/ContactListFilter.java
+++ b/src/com/android/contacts/list/ContactListFilter.java
@@ -97,12 +97,12 @@
 
     @Override
     public int compareTo(ContactListFilter another) {
-        int res = accountType.compareTo(another.accountType);
+        int res = accountName.compareTo(another.accountName);
         if (res != 0) {
             return res;
         }
 
-        res = accountName.compareTo(another.accountName);
+        res = accountType.compareTo(another.accountType);
         if (res != 0) {
             return res;
         }
diff --git a/src/com/android/contacts/model/AccountTypes.java b/src/com/android/contacts/model/AccountTypes.java
index 9b282fb..92ffba2 100644
--- a/src/com/android/contacts/model/AccountTypes.java
+++ b/src/com/android/contacts/model/AccountTypes.java
@@ -32,22 +32,29 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SyncAdapterType;
+import android.content.SyncStatusObserver;
 import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.lang.ref.SoftReference;
 import java.util.ArrayList;
+import java.util.Collections;
+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
  * system, typically filled through {@link PackageManager} queries.
  */
-public class AccountTypes extends BroadcastReceiver implements OnAccountsUpdateListener {
+public class AccountTypes extends BroadcastReceiver
+        implements OnAccountsUpdateListener, SyncStatusObserver {
     private static final String TAG = "AccountTypes";
 
     private Context mContext;
@@ -56,28 +63,52 @@
 
     private AccountType mFallbackSource = null;
 
+    private ArrayList<Account> mAccounts = Lists.newArrayList();
+    private ArrayList<Account> mWritableAccounts = Lists.newArrayList();
     private HashMap<String, AccountType> mSources = Maps.newHashMap();
     private HashSet<String> mKnownPackages = Sets.newHashSet();
 
-    private static SoftReference<AccountTypes> sInstance = null;
+    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>() {
+
+        @Override
+        public int compare(Account account1, Account account2) {
+            int diff = account1.name.compareTo(account2.name);
+            if (diff != 0) {
+                return diff;
+            }
+            return account1.type.compareTo(account2.type);
+        }
+    };
 
     /**
      * Requests the singleton instance of {@link AccountTypes} with data bound from
      * the available authenticators. This method can safely be called from the UI thread.
      */
     public static synchronized AccountTypes getInstance(Context context) {
-        AccountTypes sources = sInstance == null ? null : sInstance.get();
-        if (sources == null) {
-            sources = new AccountTypes(context);
-            sInstance = new SoftReference<AccountTypes>(sources);
+        if (sInstance == null) {
+            sInstance = new AccountTypes(context);
         }
-        return sources;
+        return sInstance;
     }
 
     /**
      * Internal constructor that only performs initial parsing.
      */
     private AccountTypes(Context context) {
+        mInitializationLatch = new CountDownLatch(1);
+
         mContext = context;
         mApplicationContext = context.getApplicationContext();
         mAccountManager = AccountManager.get(mApplicationContext);
@@ -85,7 +116,24 @@
         // 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:
+                        loadAccountsInBackground();
+                        break;
+                }
+            }
+        };
 
         // Request updates when packages or accounts change
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -103,7 +151,11 @@
         filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
         mApplicationContext.registerReceiver(this, filter);
 
-        mAccountManager.addOnAccountsUpdatedListener(this, null, false);
+        mAccountManager.addOnAccountsUpdatedListener(this, mListenerHandler, false);
+
+        ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
+
+        mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
     }
 
     /** @hide exposed for unit tests */
@@ -118,9 +170,18 @@
         mKnownPackages.add(source.resPackageName);
     }
 
-    /** {@inheritDoc} */
     @Override
     public void onReceive(Context context, Intent intent) {
+        Message msg = mListenerHandler.obtainMessage(MESSAGE_PROCESS_BROADCAST_INTENT, intent);
+        mListenerHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void onStatusChanged(int which) {
+        mListenerHandler.sendEmptyMessage(MESSAGE_SYNC_STATUS_CHANGED);
+    }
+
+    public void processBroadcastIntent(Intent intent) {
         final String action = intent.getAction();
 
         if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
@@ -145,7 +206,7 @@
                         invalidateCache(packageName);
                     } else {
                         // Unknown source, so reload from scratch
-                        queryAccounts();
+                        loadAccountsInBackground();
                     }
                 }
             }
@@ -170,18 +231,40 @@
         }
     }
 
-    /** {@inheritDoc} */
+    /* 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();
+        mWritableAccounts.clear();
 
         final AccountManager am = mAccountManager;
         final IContentService cs = ContentResolver.getContentService();
@@ -223,6 +306,38 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Problem loading accounts: " + e.toString());
         }
+
+        Account[] accounts = mAccountManager.getAccounts();
+        for (Account account : accounts) {
+            boolean syncable = false;
+            try {
+                int isSyncable = cs.getIsSyncable(account, ContactsContract.AUTHORITY);
+                if (isSyncable > 0) {
+                    syncable = true;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Cannot obtain sync flag for account: " + account, e);
+            }
+
+            if (syncable) {
+                // Ensure we have details loaded for each account
+                final AccountType accountType = getAccountType(
+                        account.type, AccountType.LEVEL_SUMMARY);
+                if (accountType != null) {
+                    mAccounts.add(account);
+                    if (!accountType.readOnly) {
+                        mWritableAccounts.add(account);
+                    }
+                }
+            }
+        }
+
+        Collections.sort(mAccounts, ACCOUNT_COMPARATOR);
+        Collections.sort(mWritableAccounts, ACCOUNT_COMPARATOR);
+        if (mInitializationLatch != null) {
+            mInitializationLatch.countDown();
+            mInitializationLatch = null;
+        }
     }
 
     /**
@@ -240,40 +355,11 @@
     }
 
     /**
-     * Return list of all known, writable {@link AccountType}. AccountTypes
-     * returned may require inflation before they can be used.
+     * Return list of all known, writable {@link Account}'s.
      */
     public ArrayList<Account> getAccounts(boolean writableOnly) {
-        final AccountManager am = mAccountManager;
-        final Account[] accounts = am.getAccounts();
-        final ArrayList<Account> matching = Lists.newArrayList();
-        final IContentService cs = ContentResolver.getContentService();
-
-        for (Account account : accounts) {
-            boolean syncable = false;
-            try {
-                int isSyncable = cs.getIsSyncable(account, ContactsContract.AUTHORITY);
-                if (isSyncable > 0) {
-                    syncable = true;
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "Cannot obtain sync flag for account: " + account, e);
-            }
-            // Log.v(TAG, String.format("found account (name: %s, type: %s, syncable: %b",
-            // account.name, account.type, syncable));
-            if (syncable) {
-                // Ensure we have details loaded for each account
-                final AccountType accountType = getInflatedSource(account.type,
-                        AccountType.LEVEL_SUMMARY);
-                final boolean hasContacts = accountType != null;
-                final boolean matchesWritable =
-                    (!writableOnly || (writableOnly && !accountType.readOnly));
-                if (hasContacts && matchesWritable) {
-                    matching.add(account);
-                }
-            }
-        }
-        return matching;
+        ensureAccountsLoaded();
+        return writableOnly ? mWritableAccounts : mAccounts;
     }
 
     /**
@@ -285,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
@@ -311,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;