Merge "Order strequent contacts above type-to-filter search results" into ub-contactsdialer-b-dev
diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java
index d68788c..a2fb651 100644
--- a/src/com/android/contacts/common/list/ContactListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactListAdapter.java
@@ -23,11 +23,9 @@
 import android.provider.ContactsContract.Directory;
 import android.provider.ContactsContract.SearchSnippets;
 import android.text.TextUtils;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ListView;
 
-import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.R;
 import com.android.contacts.common.compat.ContactsCompat;
@@ -72,7 +70,9 @@
             Contacts.PHOTO_THUMBNAIL_URI,           // 5
             Contacts.LOOKUP_KEY,                    // 6
             Contacts.IS_USER_PROFILE,               // 7
-            SearchSnippets.SNIPPET,           // 8
+            Contacts.TIMES_CONTACTED,               // 8
+            Contacts.STARRED,                       // 9
+            SearchSnippets.SNIPPET,                 // 10
         };
 
         private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
@@ -84,7 +84,9 @@
             Contacts.PHOTO_THUMBNAIL_URI,           // 5
             Contacts.LOOKUP_KEY,                    // 6
             Contacts.IS_USER_PROFILE,               // 7
-            SearchSnippets.SNIPPET,           // 8
+            Contacts.TIMES_CONTACTED,               // 8
+            Contacts.STARRED,                       // 9
+            SearchSnippets.SNIPPET,                 // 10
         };
 
         public static final int CONTACT_ID               = 0;
@@ -95,7 +97,52 @@
         public static final int CONTACT_PHOTO_URI        = 5;
         public static final int CONTACT_LOOKUP_KEY       = 6;
         public static final int CONTACT_IS_USER_PROFILE  = 7;
-        public static final int CONTACT_SNIPPET          = 8;
+        public static final int CONTACT_TIMES_CONTACTED  = 8;
+        public static final int CONTACT_STARRED          = 9;
+        public static final int CONTACT_SNIPPET          = 10;
+    }
+
+    protected static class StrequentQuery {
+
+        private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
+                Contacts._ID,                           // 0
+                Contacts.DISPLAY_NAME_PRIMARY,          // 1
+                Contacts.CONTACT_PRESENCE,              // 2
+                Contacts.CONTACT_STATUS,                // 3
+                Contacts.PHOTO_ID,                      // 4
+                Contacts.PHOTO_THUMBNAIL_URI,           // 5
+                Contacts.LOOKUP_KEY,                    // 6
+                Contacts.IS_USER_PROFILE,               // 7
+                Contacts.TIMES_CONTACTED,               // 8
+                Contacts.STARRED,                       // 9
+                // SearchSnippets.SNIPPET not supported
+        };
+
+        private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
+                Contacts._ID,                           // 0
+                Contacts.DISPLAY_NAME_ALTERNATIVE,      // 1
+                Contacts.CONTACT_PRESENCE,              // 2
+                Contacts.CONTACT_STATUS,                // 3
+                Contacts.PHOTO_ID,                      // 4
+                Contacts.PHOTO_THUMBNAIL_URI,           // 5
+                Contacts.LOOKUP_KEY,                    // 6
+                Contacts.IS_USER_PROFILE,               // 7
+                Contacts.TIMES_CONTACTED,               // 8
+                Contacts.STARRED,                       // 9
+                // SearchSnippets.SNIPPET not supported
+        };
+
+        public static final int CONTACT_ID               = 0;
+        public static final int CONTACT_DISPLAY_NAME     = 1;
+        public static final int CONTACT_PRESENCE_STATUS  = 2;
+        public static final int CONTACT_CONTACT_STATUS   = 3;
+        public static final int CONTACT_PHOTO_ID         = 4;
+        public static final int CONTACT_PHOTO_URI        = 5;
+        public static final int CONTACT_LOOKUP_KEY       = 6;
+        public static final int CONTACT_IS_USER_PROFILE  = 7;
+        public static final int CONTACT_TIMES_CONTACTED  = 8;
+        public static final int CONTACT_STARRED          = 9;
+        // SearchSnippets.SNIPPET not supported
     }
 
     private CharSequence mUnknownNameText;
@@ -384,4 +431,11 @@
             }
         }
     }
+
+    protected final String[] getStrequentProjection() {
+        final int sortOrder = getContactNameDisplayOrder();
+        return sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY
+                ? StrequentQuery.FILTER_PROJECTION_PRIMARY
+                : StrequentQuery.FILTER_PROJECTION_ALTERNATIVE;
+    }
 }
diff --git a/src/com/android/contacts/common/list/ContactListItemView.java b/src/com/android/contacts/common/list/ContactListItemView.java
index 90b3d60..79fd12d 100644
--- a/src/com/android/contacts/common/list/ContactListItemView.java
+++ b/src/com/android/contacts/common/list/ContactListItemView.java
@@ -29,6 +29,7 @@
 import android.os.Bundle;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.SearchSnippets;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v7.widget.AppCompatCheckBox;
@@ -1481,7 +1482,8 @@
      * Shows search snippet.
      */
     public void showSnippet(Cursor cursor, int summarySnippetColumnIndex) {
-        if (cursor.getColumnCount() <= summarySnippetColumnIndex) {
+        if (cursor.getColumnCount() <= summarySnippetColumnIndex
+            || !SearchSnippets.SNIPPET.equals(cursor.getColumnName(summarySnippetColumnIndex))) {
             setSnippet(null);
             return;
         }
diff --git a/src/com/android/contacts/common/list/DefaultContactListAdapter.java b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
index e50e9ce..ec91753 100644
--- a/src/com/android/contacts/common/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
@@ -44,6 +44,10 @@
     public static final char SNIPPET_START_MATCH = '[';
     public static final char SNIPPET_END_MATCH = ']';
 
+    // Whether to show strequent contacts before the normal type-to-filter search results.
+    // TODO(wjang): set this using phenotype
+    private final boolean mShowStrequentsSearchResultsFirst = false;
+
     public DefaultContactListAdapter(Context context) {
         super(context);
     }
@@ -69,16 +73,26 @@
                 loader.setSelection("0");
             } else {
                 final Builder builder = ContactsCompat.getContentUri().buildUpon();
-                builder.appendPath(query); // Builder will encode the query
-                builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
-                        String.valueOf(directoryId));
-                if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
-                    builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
-                            String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
-                }
-                builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
+                appendSearchParameters(builder, query, directoryId);
                 loader.setUri(builder.build());
                 loader.setProjection(getProjection(true));
+                if (mShowStrequentsSearchResultsFirst) {
+                    // Filter out starred and frequently contacted contacts from the main loader
+                    // query results
+                    loader.setSelection(Contacts.TIMES_CONTACTED + "=0 AND "
+                            + Contacts.STARRED + "=0");
+
+                    // Strequent contacts will be merged back in before the main loader query
+                    // results and after the profile (ME).
+                    final ProfileAndContactsLoader profileAndContactsLoader =
+                            (ProfileAndContactsLoader) loader;
+                    profileAndContactsLoader.setLoadStrequent(true);
+                    final Builder strequentBuilder =
+                            Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon();
+                    appendSearchParameters(strequentBuilder, query, directoryId);
+                    profileAndContactsLoader.setStrequentUri(strequentBuilder.build());
+                    profileAndContactsLoader.setStrequentProjection(getStrequentProjection());
+                }
             }
         } else {
             configureUri(loader, directoryId, filter);
@@ -96,6 +110,17 @@
         loader.setSortOrder(sortOrder);
     }
 
+    private void appendSearchParameters(Builder builder, String query, long directoryId) {
+        builder.appendPath(query); // Builder will encode the query
+        builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                String.valueOf(directoryId));
+        if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
+            builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
+                    String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
+        }
+        builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
+    }
+
     protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
         Uri uri = Contacts.CONTENT_URI;
         if (filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
diff --git a/src/com/android/contacts/common/list/ProfileAndContactsLoader.java b/src/com/android/contacts/common/list/ProfileAndContactsLoader.java
index 698ef96..e68d4a1 100644
--- a/src/com/android/contacts/common/list/ProfileAndContactsLoader.java
+++ b/src/com/android/contacts/common/list/ProfileAndContactsLoader.java
@@ -20,7 +20,9 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.MergeCursor;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Profile;
 
 import com.google.common.collect.Lists;
@@ -34,7 +36,10 @@
 public class ProfileAndContactsLoader extends CursorLoader {
 
     private boolean mLoadProfile;
+    private boolean mLoadStrequent;
     private String[] mProjection;
+    private String[] mStrequentProjection;
+    private Uri mStrequentUri;
 
     public ProfileAndContactsLoader(Context context) {
         super(context);
@@ -44,11 +49,23 @@
         mLoadProfile = flag;
     }
 
+    public void setLoadStrequent(boolean flag) {
+        mLoadStrequent = flag;
+    }
+
     public void setProjection(String[] projection) {
         super.setProjection(projection);
         mProjection = projection;
     }
 
+    public void setStrequentProjection(String[] projection) {
+        mStrequentProjection = projection;
+    }
+
+    public void setStrequentUri(Uri uri) {
+        mStrequentUri = uri;
+    }
+
     @Override
     public Cursor loadInBackground() {
         // First load the profile, if enabled.
@@ -56,6 +73,9 @@
         if (mLoadProfile) {
             cursors.add(loadProfile());
         }
+        if (mLoadStrequent) {
+            cursors.add(loadStrequent());
+        }
         // ContactsCursor.loadInBackground() can return null; MergeCursor
         // correctly handles null cursors.
         Cursor cursor = null;
@@ -101,4 +121,12 @@
             cursor.close();
         }
     }
+
+    /**
+     * Loads starred and frequently contacted contacts
+     */
+    private Cursor loadStrequent() {
+        return getContext().getContentResolver().query(
+                mStrequentUri, mStrequentProjection, null, null, null);
+    }
 }