Add support for visibility of ungrouped contacts.

Built a tricky UPDATE query to determine the visibility of
any Contact by using any GroupMembership entries, otherwise
fall back to Settings.UNGROUPED_VISIBLE.
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 4ceca6f..409b305 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -548,18 +548,15 @@
         // RawContacts and groups projection map
         columns = new HashMap<String, String>();
         columns.putAll(sGroupsProjectionMap);
-
         columns.put(Groups.SUMMARY_COUNT, "(SELECT COUNT(DISTINCT " + ContactsColumns.CONCRETE_ID
                 + ") FROM " + Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS + " WHERE "
                 + Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP + " AND " + Clauses.BELONGS_TO_GROUP
                 + ") AS " + Groups.SUMMARY_COUNT);
-
         columns.put(Groups.SUMMARY_WITH_PHONES, "(SELECT COUNT(DISTINCT "
                 + ContactsColumns.CONCRETE_ID + ") FROM "
                 + Tables.DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS + " WHERE "
                 + Clauses.MIMETYPE_IS_GROUP_MEMBERSHIP + " AND " + Clauses.BELONGS_TO_GROUP
                 + " AND " + Contacts.HAS_PHONE_NUMBER + ") AS " + Groups.SUMMARY_WITH_PHONES);
-
         sGroupsSummaryProjectionMap = columns;
 
         // Aggregate exception projection map
@@ -574,12 +571,20 @@
 
         // Settings projection map
         columns = new HashMap<String, String>();
-        columns.put(Settings._ID, Settings._ID);
         columns.put(Settings.ACCOUNT_NAME, Settings.ACCOUNT_NAME);
         columns.put(Settings.ACCOUNT_TYPE, Settings.ACCOUNT_TYPE);
         columns.put(Settings.UNGROUPED_VISIBLE, Settings.UNGROUPED_VISIBLE);
         columns.put(Settings.SHOULD_SYNC_MODE, Settings.SHOULD_SYNC_MODE);
         columns.put(Settings.SHOULD_SYNC, Settings.SHOULD_SYNC);
+        columns.put(Settings.UNGROUPED_COUNT, "(SELECT COUNT(DISTINCT " + RawContacts.CONTACT_ID
+                + ") FROM " + Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS + " WHERE "
+                + Clauses.UNGROUPED + " GROUP BY " + Clauses.GROUP_BY_ACCOUNT + ") AS "
+                + Settings.UNGROUPED_COUNT);
+        columns.put(Settings.UNGROUPED_WITH_PHONES, "(SELECT COUNT(DISTINCT "
+                + RawContacts.CONTACT_ID + ") FROM "
+                + Tables.SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS + " WHERE "
+                + Clauses.UNGROUPED + " AND " + Contacts.HAS_PHONE_NUMBER + " GROUP BY "
+                + Clauses.GROUP_BY_ACCOUNT + ") AS " + Settings.UNGROUPED_WITH_PHONES);
         sSettingsProjectionMap = columns;
 
         columns = new HashMap<String, String>();
@@ -1255,7 +1260,7 @@
             }
 
             case SETTINGS: {
-                id = mDb.insert(Tables.SETTINGS, null, values);
+                id = insertSettings(values);
                 break;
             }
 
@@ -1537,6 +1542,14 @@
         return mDb.insert(Tables.GROUPS, Groups.TITLE, overriddenValues);
     }
 
+    private long insertSettings(ContentValues values) {
+        final long id = mDb.insert(Tables.SETTINGS, null, values);
+        if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
+            mOpenHelper.updateAllVisible();
+        }
+        return id;
+    }
+
     /**
      * Inserts a presence update.
      */
@@ -1717,7 +1730,7 @@
             }
 
             case SETTINGS: {
-                return mDb.delete(Tables.SETTINGS, selection, selectionArgs);
+                return deleteSettings(selection, selectionArgs);
             }
 
             case PRESENCE: {
@@ -1759,6 +1772,14 @@
         }
     }
 
+    private int deleteSettings(String selection, String[] selectionArgs) {
+        final int count = mDb.delete(Tables.SETTINGS, selection, selectionArgs);
+        if (count > 0) {
+            mOpenHelper.updateAllVisible();
+        }
+        return count;
+    }
+
     public int deleteRawContact(long rawContactId, boolean permanently) {
         // TODO delete aggregation exceptions
         mOpenHelper.removeContactIfSingleton(rawContactId);
@@ -1856,7 +1877,7 @@
             }
 
             case SETTINGS: {
-                count = mDb.update(Tables.SETTINGS, values, selection, selectionArgs);
+                count = updateSettings(values, selection, selectionArgs);
                 break;
             }
 
@@ -1889,6 +1910,14 @@
         return count;
     }
 
+    private int updateSettings(ContentValues values, String selection, String[] selectionArgs) {
+        final int count = mDb.update(Tables.SETTINGS, values, selection, selectionArgs);
+        if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
+            mOpenHelper.updateAllVisible();
+        }
+        return count;
+    }
+
     private int updateRawContact(long rawContactId, ContentValues values, String selection,
             String[] selectionArgs) {
 
@@ -2426,6 +2455,18 @@
             case SETTINGS: {
                 qb.setTables(Tables.SETTINGS);
                 qb.setProjectionMap(sSettingsProjectionMap);
+
+                // When requesting specific columns, this query requires
+                // late-binding of the GroupMembership MIME-type.
+                final String groupMembershipMimetypeId = Long.toString(mOpenHelper
+                        .getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE));
+                if (isContained(projection, Settings.UNGROUPED_COUNT)) {
+                    selectionArgs = insertSelectionArg(selectionArgs, groupMembershipMimetypeId);
+                }
+                if (isContained(projection, Settings.UNGROUPED_WITH_PHONES)) {
+                    selectionArgs = insertSelectionArg(selectionArgs, groupMembershipMimetypeId);
+                }
+
                 break;
             }
 
diff --git a/src/com/android/providers/contacts/OpenHelper.java b/src/com/android/providers/contacts/OpenHelper.java
index 006a978..d555aa3 100644
--- a/src/com/android/providers/contacts/OpenHelper.java
+++ b/src/com/android/providers/contacts/OpenHelper.java
@@ -41,9 +41,7 @@
 import android.provider.ContactsContract.Presence;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.Settings;
-import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.SocialContract.Activities;
 import android.telephony.PhoneNumberUtils;
@@ -60,7 +58,8 @@
 /* package */ class OpenHelper extends SQLiteOpenHelper {
     private static final String TAG = "OpenHelper";
 
-    private static final int DATABASE_VERSION = 72;
+    private static final int DATABASE_VERSION = 73;
+
     private static final String DATABASE_NAME = "contacts2.db";
     private static final String DATABASE_PRESENCE = "presence_db";
 
@@ -114,6 +113,25 @@
         public static final String RAW_CONTACTS_JOIN_CONTACTS = "raw_contacts "
                 + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
 
+        // NOTE: This requires late binding of GroupMembership MIME-type
+        public static final String RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS = "raw_contacts "
+                + "LEFT OUTER JOIN settings ON ("
+                    + "raw_contacts.account_name = settings.account_name AND "
+                    + "raw_contacts.account_type = settings.account_type) "
+                + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
+                    + "data.raw_contact_id = raw_contacts._id) "
+                + "LEFT OUTER JOIN groups ON (groups._id = data." + GroupMembership.GROUP_ROW_ID
+                + ")";
+
+        // NOTE: This requires late binding of GroupMembership MIME-type
+        public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "settings "
+                + "LEFT OUTER JOIN raw_contacts ON ("
+                    + "raw_contacts.account_name = settings.account_name AND "
+                    + "raw_contacts.account_type = settings.account_type) "
+                + "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
+                    + "data.raw_contact_id = raw_contacts._id) "
+                + "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
+
         public static final String DATA_JOIN_MIMETYPES_RAW_CONTACTS_CONTACTS = "data "
                 + "LEFT OUTER JOIN mimetypes ON (data.mimetype_id = mimetypes._id) "
                 + "LEFT OUTER JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) "
@@ -195,22 +213,31 @@
     }
 
     public interface Clauses {
-        public static final String MIMETYPE_IS_GROUP_MEMBERSHIP = MimetypesColumns.CONCRETE_MIMETYPE
-                + "='" + GroupMembership.CONTENT_ITEM_TYPE + "'";
+        final String MIMETYPE_IS_GROUP_MEMBERSHIP = MimetypesColumns.CONCRETE_MIMETYPE + "='"
+                + GroupMembership.CONTENT_ITEM_TYPE + "'";
 
-        public static final String BELONGS_TO_GROUP = DataColumns.CONCRETE_GROUP_ID + "="
+        final String BELONGS_TO_GROUP = DataColumns.CONCRETE_GROUP_ID + "="
                 + GroupsColumns.CONCRETE_ID;
 
-        // TODO: add in check against package_visible
-        public static final String IN_VISIBLE_GROUP = "SELECT MIN(COUNT(" + DataColumns.CONCRETE_ID
-                + "),1) FROM " + Tables.DATA_JOIN_RAW_CONTACTS_GROUPS + " WHERE "
-                + DataColumns.MIMETYPE_ID + "=? AND " + RawContacts.CONTACT_ID + "="
-                + ContactsColumns.CONCRETE_ID + " AND " + Groups.GROUP_VISIBLE + "=1";
+        final String UNGROUPED = DataColumns.CONCRETE_GROUP_ID + " IS NULL";
 
-        public static final String GROUP_HAS_ACCOUNT_AND_SOURCE_ID =
-                Groups.SOURCE_ID + "=? AND "
-                        + Groups.ACCOUNT_NAME + "=? AND "
-                        + Groups.ACCOUNT_TYPE + "=?";
+        final String GROUP_BY_ACCOUNT = SettingsColumns.CONCRETE_ACCOUNT_NAME + ","
+                + SettingsColumns.CONCRETE_ACCOUNT_TYPE;
+
+        final String RAW_CONTACT_IS_LOCAL = RawContactsColumns.CONCRETE_ACCOUNT_NAME
+                + " IS NULL AND " + RawContactsColumns.CONCRETE_ACCOUNT_TYPE + " IS NULL";
+
+        final String ZERO_GROUP_MEMBERSHIPS = "COUNT(" + GroupsColumns.CONCRETE_ID + ")=0";
+
+        final String CONTACT_IS_VISIBLE = "SELECT (CASE WHEN " + RAW_CONTACT_IS_LOCAL
+                + " THEN 1 WHEN " + ZERO_GROUP_MEMBERSHIPS + " THEN " + Settings.UNGROUPED_VISIBLE
+                + " ELSE MAX(" + Groups.GROUP_VISIBLE + ") END) FROM "
+                + Tables.RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS + " WHERE "
+                + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + " GROUP BY "
+                + RawContacts.CONTACT_ID;
+
+        final String GROUP_HAS_ACCOUNT_AND_SOURCE_ID = Groups.SOURCE_ID + "=? AND "
+                + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?";
     }
 
     public interface ContactsColumns {
@@ -376,6 +403,13 @@
         public static final String CLUSTER = "cluster";
     }
 
+    public interface SettingsColumns {
+        public static final String CONCRETE_ACCOUNT_NAME = Tables.SETTINGS + "."
+                + Settings.ACCOUNT_NAME;
+        public static final String CONCRETE_ACCOUNT_TYPE = Tables.SETTINGS + "."
+                + Settings.ACCOUNT_TYPE;
+    }
+
     public interface PresenceColumns {
         String RAW_CONTACT_ID = "presence_raw_contact_id";
     }
@@ -414,7 +448,7 @@
     private HashMap<String, String[]> mNicknameClusterCache;
 
     /** Compiled statements for updating {@link Contacts#IN_VISIBLE_GROUP}. */
-    private SQLiteStatement mVisibleAllUpdate;
+    private SQLiteStatement mVisibleUpdate;
     private SQLiteStatement mVisibleSpecificUpdate;
 
     private Delegate mDelegate;
@@ -477,10 +511,11 @@
                 + NameLookupColumns.RAW_CONTACT_ID + "," + NameLookupColumns.NAME_TYPE + ","
                 + NameLookupColumns.NORMALIZED_NAME + ") VALUES (?,?,?)");
 
+        // Compile statements for updating visibility
         final String visibleUpdate = "UPDATE " + Tables.CONTACTS + " SET "
-                + Contacts.IN_VISIBLE_GROUP + "= (" + Clauses.IN_VISIBLE_GROUP + ")";
+                + Contacts.IN_VISIBLE_GROUP + "=(" + Clauses.CONTACT_IS_VISIBLE + ")";
 
-        mVisibleAllUpdate = db.compileStatement(visibleUpdate);
+        mVisibleUpdate = db.compileStatement(visibleUpdate);
         mVisibleSpecificUpdate = db.compileStatement(visibleUpdate + " WHERE "
                 + ContactsColumns.CONCRETE_ID + "=?");
 
@@ -527,7 +562,7 @@
                 Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
                 Contacts.LAST_TIME_CONTACTED + " INTEGER," +
                 Contacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
-                Contacts.IN_VISIBLE_GROUP + " INTEGER NOT NULL DEFAULT 1," +
+                Contacts.IN_VISIBLE_GROUP + " INTEGER DEFAULT 1," +
                 Contacts.HAS_PHONE_NUMBER + " INTEGER NOT NULL DEFAULT 0," +
                 ContactsColumns.SINGLE_IS_RESTRICTED + " INTEGER NOT NULL DEFAULT 0" +
         ");");
@@ -774,15 +809,13 @@
                 AggregationExceptionColumns.RAW_CONTACT_ID1 +
         ");");
 
-        // Settings uses SYNC_MODE_UNSUPPORTED as default unless specified.
         db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.SETTINGS + " (" +
                 Settings.ACCOUNT_NAME + " STRING NOT NULL," +
                 Settings.ACCOUNT_TYPE + " STRING NOT NULL," +
                 Settings.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
-                Settings.SHOULD_SYNC_MODE + " INTEGER NOT NULL DEFAULT 0, " +
                 Settings.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1, " +
                 "PRIMARY KEY (" + Settings.ACCOUNT_NAME + ", " +
-                Settings.ACCOUNT_TYPE + ") ON CONFLICT REPLACE" +
+                    Settings.ACCOUNT_TYPE + ") ON CONFLICT REPLACE" +
         ");");
 
         // The table for recent calls is here so we can do table joins
@@ -1194,8 +1227,8 @@
      */
     public void updateAllVisible() {
         final long groupMembershipMimetypeId = getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
-        mVisibleAllUpdate.bindLong(1, groupMembershipMimetypeId);
-        mVisibleAllUpdate.execute();
+        mVisibleUpdate.bindLong(1, groupMembershipMimetypeId);
+        mVisibleUpdate.execute();
     }
 
     /**