Moving package installation and account update listeners in the bg

Bug: 3228687
Change-Id: Id082dac2c2b21f09ccfc30afb5f734e8c36cb2d8
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..4e94248 100644
--- a/src/com/android/contacts/model/AccountTypes.java
+++ b/src/com/android/contacts/model/AccountTypes.java
@@ -32,14 +32,19 @@
 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;
 
@@ -47,7 +52,8 @@
  * 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,22 +62,40 @@
 
     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_PROCESS_BROADCAST_INTENT = 0;
+    private static final int MESSAGE_SYNC_STATUS_CHANGED = 1;
+
+    private HandlerThread mListenerThread;
+    private Handler mListenerHandler;
+
+    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;
     }
 
     /**
@@ -87,6 +111,22 @@
 
         queryAccounts();
 
+        mListenerThread = new HandlerThread("AccountChangeListener");
+        mListenerThread.start();
+        mListenerHandler = new Handler(mListenerThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MESSAGE_PROCESS_BROADCAST_INTENT:
+                        processBroadcastIntent((Intent) msg.obj);
+                        break;
+                    case MESSAGE_SYNC_STATUS_CHANGED:
+                        queryAccounts();
+                        break;
+                }
+            }
+        };
+
         // Request updates when packages or accounts change
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -103,7 +143,9 @@
         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);
     }
 
     /** @hide exposed for unit tests */
@@ -118,9 +160,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)
@@ -170,7 +221,7 @@
         }
     }
 
-    /** {@inheritDoc} */
+    /* This notification will arrive on the background thread */
     public void onAccountsUpdated(Account[] accounts) {
         // Refresh to catch any changed accounts
         queryAccounts();
@@ -182,6 +233,8 @@
     protected synchronized void queryAccounts() {
         mSources.clear();
         mKnownPackages.clear();
+        mAccounts.clear();
+        mWritableAccounts.clear();
 
         final AccountManager am = mAccountManager;
         final IContentService cs = ContentResolver.getContentService();
@@ -223,6 +276,34 @@
         } 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 = getInflatedSource(
+                        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);
     }
 
     /**
@@ -240,40 +321,10 @@
     }
 
     /**
-     * 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;
+    public synchronized ArrayList<Account> getAccounts(boolean writableOnly) {
+        return writableOnly ? mWritableAccounts : mAccounts;
     }
 
     /**