Contacts to display: hide accounts without raw contacts

- Hide extension accounts with no raw contacts.

- Now that building the list hits the database we need to use AsyncTaskLoader.

- Did a little refactoring (e.g. moving mFilter to the adpter) so the code will
  work better with a loader.

Bug 5160471

Change-Id: I780985a73325d4d453962e8ea5dca0d68c3c869e
diff --git a/src/com/android/contacts/list/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index fb0cf9e..02abb53 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -22,13 +22,24 @@
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.AccountWithDataSet;
+import com.google.android.collect.Lists;
 
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.AsyncTaskLoader;
 import android.content.Context;
 import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
@@ -53,9 +64,13 @@
 
     public static final String KEY_EXTRA_CONTACT_LIST_FILTER = "contactListFilter";
 
+    private static final int FILTER_LOADER_ID = 0;
+
     private ListView mListView;
 
-    private List<ContactListFilter> mFilters = new ArrayList<ContactListFilter>();
+    private static final String[] ID_PROJECTION = new String[] {BaseColumns._ID};
+    private static final Uri RAW_CONTACTS_URI_LIMIT_1 = RawContacts.CONTENT_URI.buildUpon()
+            .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, "1").build();
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -70,38 +85,118 @@
             actionBar.setDisplayHomeAsUpEnabled(true);
         }
 
-        loadAccountFilters();
+        getLoaderManager().initLoader(FILTER_LOADER_ID, null, new MyLoaderCallbacks());
     }
 
-    private void loadAccountFilters() {
-        ArrayList<ContactListFilter> accountFilters = new ArrayList<ContactListFilter>();
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
+    private static class FilterLoader extends AsyncTaskLoader<List<ContactListFilter>> {
+        private Context mContext;
+
+        public FilterLoader(Context context) {
+            super(context);
+            mContext = context;
+        }
+
+        @Override
+        public List<ContactListFilter> loadInBackground() {
+            return loadAccountFilters(mContext);
+        }
+
+        @Override
+        protected void onStartLoading() {
+            forceLoad();
+        }
+
+        @Override
+        protected void onStopLoading() {
+            cancelLoad();
+        }
+
+        @Override
+        protected void onReset() {
+            onStopLoading();
+        }
+    }
+
+    private static List<ContactListFilter> loadAccountFilters(Context context) {
+        final ArrayList<ContactListFilter> result = Lists.newArrayList();
+        final ArrayList<ContactListFilter> accountFilters = Lists.newArrayList();
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
         List<AccountWithDataSet> accounts = accountTypes.getAccounts(false);
         for (AccountWithDataSet account : accounts) {
             AccountType accountType = accountTypes.getAccountType(account.type, account.dataSet);
-            Drawable icon = accountType != null ? accountType.getDisplayIcon(this) : null;
+            if (accountType.isExtension() && !hasAccountData(context, account)) {
+                // Hide extensions with no raw_contacts.
+                continue;
+            }
+            Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null;
             accountFilters.add(ContactListFilter.createAccountFilter(account.type, account.name,
                     account.dataSet, icon, account.name));
         }
-        final int count = accountFilters.size();
 
+        // Always show "All", even when there's no accounts.  (We may have local contacts)
+        result.add(ContactListFilter.createFilterWithType(
+                ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
+
+        final int count = accountFilters.size();
         if (count >= 1) {
-            mFilters.add(ContactListFilter.createFilterWithType(
-                    ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
             // If we only have one account, don't show it as "account", instead show it as "all"
             if (count > 1) {
-                mFilters.addAll(accountFilters);
+                result.addAll(accountFilters);
             }
-            mFilters.add(ContactListFilter.createFilterWithType(
+            result.add(ContactListFilter.createFilterWithType(
                     ContactListFilter.FILTER_TYPE_CUSTOM));
         }
+        return result;
+    }
 
-        mListView.setAdapter(new FilterListAdapter(this));
+    private static boolean hasAccountData(Context context, AccountWithDataSet account) {
+        final String BASE_SELECTION =
+                RawContacts.ACCOUNT_TYPE + " = ?" + " AND " + RawContacts.ACCOUNT_NAME + " = ?";
+        final String selection;
+        final String[] args;
+        if (TextUtils.isEmpty(account.dataSet)) {
+            selection = BASE_SELECTION + " AND " + RawContacts.DATA_SET + " IS NULL";
+            args = new String[] {account.type, account.name};
+        } else {
+            selection = BASE_SELECTION + " AND " + RawContacts.DATA_SET + " = ?";
+            args = new String[] {account.type, account.name, account.dataSet};
+        }
+
+        final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1,
+                ID_PROJECTION, selection, args, null);
+        if (c == null) return false;
+        try {
+            return c.moveToFirst();
+        } finally {
+            c.close();
+        }
+    }
+
+    private class MyLoaderCallbacks implements LoaderCallbacks<List<ContactListFilter>> {
+        @Override
+        public Loader<List<ContactListFilter>> onCreateLoader(int id, Bundle args) {
+            return new FilterLoader(AccountFilterActivity.this);
+        }
+
+        @Override
+        public void onLoadFinished(
+                Loader<List<ContactListFilter>> loader, List<ContactListFilter> data) {
+            if (data == null) { // Just in case...
+                Log.e(TAG, "Failed to load filters");
+                return;
+            }
+            mListView.setAdapter(new FilterListAdapter(AccountFilterActivity.this, data));
+        }
+
+        @Override
+        public void onLoaderReset(Loader<List<ContactListFilter>> loader) {
+        }
     }
 
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        ContactListFilter filter = mFilters.get(position);
+        final ContactListFilter filter = (ContactListFilter) view.getTag();
+        if (filter == null) return; // Just in case
         if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
             final Intent intent = new Intent(this,
                     CustomContactListFilterActivity.class);
@@ -143,12 +238,14 @@
         }
     }
 
-    private class FilterListAdapter extends BaseAdapter {
-        private LayoutInflater mLayoutInflater;
+    private static class FilterListAdapter extends BaseAdapter {
+        private final List<ContactListFilter> mFilters;
+        private final LayoutInflater mLayoutInflater;
 
-        public FilterListAdapter(Context context) {
+        public FilterListAdapter(Context context, List<ContactListFilter> filters) {
             mLayoutInflater = (LayoutInflater) context.getSystemService
                     (Context.LAYOUT_INFLATER_SERVICE);
+            mFilters = filters;
         }
 
         @Override
@@ -167,7 +264,7 @@
         }
 
         public View getView(int position, View convertView, ViewGroup parent) {
-            ContactListFilterView view;
+            final ContactListFilterView view;
             if (convertView != null) {
                 view = (ContactListFilterView) convertView;
             } else {
@@ -175,9 +272,10 @@
                         R.layout.contact_list_filter_item, parent, false);
             }
             view.setSingleAccount(mFilters.size() == 1);
-            ContactListFilter filter = mFilters.get(position);
+            final ContactListFilter filter = mFilters.get(position);
             view.setContactListFilter(filter);
             view.bindView(true);
+            view.setTag(filter);
             return view;
         }
     }
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 8ee1aa1..879a89f 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -87,6 +87,10 @@
         return false;
     }
 
+    public boolean isExtension() {
+        return false;
+    }
+
     /**
      * Returns an optional custom edit activity.  The activity class should reside
      * in the sync adapter package as determined by {@link #resPackageName}.
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 2d821e6..d28d5bb 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -309,7 +309,7 @@
                     // TODO: use syncadapter package instead, since it provides resources
                     Log.d(TAG, "Registering external account type=" + type
                             + ", packageName=" + auth.packageName);
-                    accountType = new ExternalAccountType(mContext, auth.packageName);
+                    accountType = new ExternalAccountType(mContext, auth.packageName, false);
                     if (!((ExternalAccountType) accountType).isInitialized()) {
                         // Skip external account types that couldn't be initialized.
                         continue;
@@ -333,7 +333,7 @@
                 Log.d(TAG, "Registering " + extensionPackages.size() + " extension packages");
                 for (String extensionPackage : extensionPackages) {
                     ExternalAccountType accountType =
-                            new ExternalAccountType(mContext, extensionPackage);
+                            new ExternalAccountType(mContext, extensionPackage, true);
                     if (!accountType.isInitialized()) {
                         // Skip external account types that couldn't be initialized.
                         continue;
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index b6649c9..7fefc44 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -69,6 +69,8 @@
     private static final String ATTR_ACCOUNT_LABEL = "accountTypeLabel";
     private static final String ATTR_ACCOUNT_ICON = "accountTypeIcon";
 
+    private final boolean mIsExtension;
+
     private String mEditContactActivityClassName;
     private String mCreateContactActivityClassName;
     private String mInviteContactActivity;
@@ -84,7 +86,8 @@
     private boolean mInitSuccessful;
     private boolean mHasContactsMetadata;
 
-    public ExternalAccountType(Context context, String resPackageName) {
+    public ExternalAccountType(Context context, String resPackageName, boolean isExtension) {
+        this.mIsExtension = isExtension;
         this.resPackageName = resPackageName;
         this.summaryResPackageName = resPackageName;
 
@@ -127,6 +130,11 @@
         return true;
     }
 
+    @Override
+    public boolean isExtension() {
+        return mIsExtension;
+    }
+
     /**
      * Whether this account type was able to be fully initialized.  This may be false if
      * (for example) the package name associated with the account type could not be found.