Merge "Bag o' QC UX improvements"
diff --git a/src/com/android/contacts/quickcontact/Action.java b/src/com/android/contacts/quickcontact/Action.java
index fa23286..7d904ab 100644
--- a/src/com/android/contacts/quickcontact/Action.java
+++ b/src/com/android/contacts/quickcontact/Action.java
@@ -45,7 +45,10 @@
     public Intent getAlternateIntent();
 
     /** Checks if the contact data for this action is primary. */
-    public Boolean isPrimary();
+    public boolean isPrimary();
+
+    /** Checks if the contact data for this action is super primary. */
+    public boolean isSuperPrimary();
 
     /**
      * Returns a lookup (@link Uri) for the contact data item or null if there is no data item
@@ -61,4 +64,14 @@
 
     /** Returns the presence of this item or -1 if it was never set */
     public int getPresence();
+
+    /**
+     * Returns the number of times this action has been used.
+     */
+    public Integer getTimesUsed();
+
+    /**
+     * Returns the last time this action was used.
+     */
+    public Long getLastTimeUsed();
 }
diff --git a/src/com/android/contacts/quickcontact/DataAction.java b/src/com/android/contacts/quickcontact/DataAction.java
index e29b3ef..a0df1e6 100644
--- a/src/com/android/contacts/quickcontact/DataAction.java
+++ b/src/com/android/contacts/quickcontact/DataAction.java
@@ -29,9 +29,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.contacts.R;
 import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.R;
 import com.android.contacts.common.MoreContactUtils;
 import com.android.contacts.common.model.account.AccountType.EditType;
 import com.android.contacts.common.model.dataitem.DataItem;
@@ -55,6 +55,8 @@
     private final Context mContext;
     private final DataKind mKind;
     private final String mMimeType;
+    private final Integer mTimesUsed;
+    private final Long mLastTimeUsed;
 
     private CharSequence mBody;
     private CharSequence mSubtitle;
@@ -67,6 +69,7 @@
     private Uri mDataUri;
     private long mDataId;
     private boolean mIsPrimary;
+    private boolean mIsSuperPrimary;
 
     /**
      * Create an action from common {@link Data} elements.
@@ -75,6 +78,8 @@
         mContext = context;
         mKind = kind;
         mMimeType = item.getMimeType();
+        mTimesUsed = item.getTimesUsed();
+        mLastTimeUsed = item.getLastTimeUsed();
 
         // Determine type for subtitle
         mSubtitle = "";
@@ -96,7 +101,8 @@
             }
         }
 
-        mIsPrimary = item.isSuperPrimary();
+        mIsPrimary = item.isPrimary();
+        mIsSuperPrimary = item.isSuperPrimary();
         mBody = item.buildDataStringForDisplay(context, kind);
 
         mDataId = item.getId();
@@ -265,11 +271,16 @@
     }
 
     @Override
-    public Boolean isPrimary() {
+    public boolean isPrimary() {
         return mIsPrimary;
     }
 
     @Override
+    public boolean isSuperPrimary() {
+        return mIsSuperPrimary;
+    }
+
+    @Override
     public Drawable getAlternateIcon() {
         if (mAlternateIconRes == 0) return null;
 
@@ -322,4 +333,14 @@
         }
         return true;
     }
+
+    @Override
+    public Integer getTimesUsed() {
+        return mTimesUsed;
+    }
+
+    @Override
+    public Long getLastTimeUsed() {
+        return mLastTimeUsed;
+    }
 }
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 1453ff4..c222aff 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -528,7 +528,6 @@
 
         Trace.endSection();
 
-        final List<String> sortedActionMimeTypes = Lists.newArrayList();
         // Maintain a list of phone numbers to pass into SmsInteractionsLoader
         final Set<String> phoneNumbers = new HashSet<>();
         // Maintain a list of email addresses to pass into CalendarInteractionsLoader
@@ -539,8 +538,7 @@
         mEntriesAndActionsTask = new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                computeEntriesAndActions(data, phoneNumbers, emailAddresses,
-                        sortedActionMimeTypes, entries);
+                computeEntriesAndActions(data, phoneNumbers, emailAddresses, entries);
                 return null;
             }
 
@@ -551,8 +549,7 @@
                 // is still running before binding to UI. A new intent could invalidate
                 // the results, for example.
                 if (data == mContactData && !isCancelled()) {
-                    bindEntriesAndActions(entries, phoneNumbers, emailAddresses,
-                            sortedActionMimeTypes);
+                    bindEntriesAndActions(entries, phoneNumbers, emailAddresses);
                     showActivity();
                 }
             }
@@ -562,8 +559,7 @@
 
     private void bindEntriesAndActions(List<Entry> entries,
             Set<String> phoneNumbers,
-            Set<String> emailAddresses,
-            List<String> sortedActionMimeTypes) {
+            Set<String> emailAddresses) {
         Trace.beginSection("start sms loader");
         final Bundle phonesExtraBundle = new Bundle();
         phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES,
@@ -598,7 +594,7 @@
                     /* isExpanded = */ false);
         }
 
-        final boolean hasData = !sortedActionMimeTypes.isEmpty();
+        final boolean hasData = !entries.isEmpty();
         mCommunicationCard.setVisibility(hasData ? View.VISIBLE : View.GONE);
 
         Trace.endSection();
@@ -618,7 +614,7 @@
     }
 
     private void computeEntriesAndActions(Contact data, Set<String> phoneNumbers,
-            Set<String> emailAddresses, List<String> sortedActionMimeTypes, List<Entry> entries) {
+            Set<String> emailAddresses, List<Entry> entries) {
         Trace.beginSection("inflate entries and actions");
 
         final ResolveCache cache = ResolveCache.getInstance(this);
@@ -685,35 +681,85 @@
         Trace.endSection();
         Trace.beginSection("sort mimetypes");
 
-        // All the mime-types to add.
-        final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
-        // First, add LEADING_MIMETYPES, which are most common.
-        for (String mimeType : LEADING_MIMETYPES) {
-            if (containedTypes.contains(mimeType)) {
-                sortedActionMimeTypes.add(mimeType);
-                containedTypes.remove(mimeType);
-                entries.addAll(actionsToEntries(mActions.get(mimeType)));
-            }
+        /*
+         * Sorting is a multi part step. The end result is to a have a sorted list of the most
+         * used actions, one per mimetype. Then, within each mimetype, the list of actions for that
+         * type is also sorted, based off of {super primary, primary, times used} in that order.
+         */
+        final List<Action> topActions = new ArrayList<>();
+        for (List<Action> mimeTypeActions : mActions.values()) {
+            Collections.sort(mimeTypeActions, new Comparator<Action>() {
+                @Override
+                public int compare(Action lhs, Action rhs) {
+                    /*
+                     * Actions are compared to the same mimetype based off of three qualities:
+                     * 1. Super primary
+                     * 2. Primary
+                     * 3. Times used
+                     */
+                    if (lhs.isSuperPrimary()) {
+                        return -1;
+                    } else if (rhs.isSuperPrimary()) {
+                        return 1;
+                    } else if (lhs.isPrimary() && !rhs.isPrimary()) {
+                        return -1;
+                    } else if (!lhs.isPrimary() && rhs.isPrimary()) {
+                        return 1;
+                    } else {
+                        int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
+                        int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
+
+                        return rhsTimesUsed - lhsTimesUsed;
+                    }
+                }
+            });
+            topActions.add(mimeTypeActions.get(0));
         }
 
-        // Add all the remaining ones that are not TRAILING
-        for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) {
-            if (!TRAILING_MIMETYPES.contains(mimeType)) {
-                sortedActionMimeTypes.add(mimeType);
-                containedTypes.remove(mimeType);
-                entries.addAll(actionsToEntries(mActions.get(mimeType)));
-            }
-        }
+        // topActions now contains the top action for each mimetype. This list now needs to be
+        // sorted, based off of {times used, last used, statically defined} in that order.
+        Collections.sort(topActions, new Comparator<Action>() {
+            @Override
+            public int compare(Action lhs, Action rhs) {
+                int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
+                int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
+                int timesUsedDifference = rhsTimesUsed - lhsTimesUsed;
+                if (timesUsedDifference != 0) {
+                    return timesUsedDifference;
+                }
 
-        // Then, add TRAILING_MIMETYPES, which are least common.
-        for (String mimeType : TRAILING_MIMETYPES) {
-            if (containedTypes.contains(mimeType)) {
-                containedTypes.remove(mimeType);
-                sortedActionMimeTypes.add(mimeType);
-                entries.addAll(actionsToEntries(mActions.get(mimeType)));
-            }
-        }
+                long lhsLastTimeUsed = lhs.getLastTimeUsed() == null ? 0 : lhs.getLastTimeUsed();
+                long rhsLastTimeUsed = rhs.getLastTimeUsed() == null ? 0 : rhs.getLastTimeUsed();
+                long lastTimeUsedDifference = rhsLastTimeUsed - lhsLastTimeUsed;
+                if (lastTimeUsedDifference > 0) {
+                    return 1;
+                } else if (lastTimeUsedDifference < 0) {
+                    return -1;
+                }
 
+                // Times used and last time used are the same. Resort to statically defined.
+                String lhsMimeType = lhs.getMimeType();
+                String rhsMimeType = rhs.getMimeType();
+                for (String mimeType : LEADING_MIMETYPES) {
+                    if (lhsMimeType.equals(mimeType)) {
+                        return -1;
+                    } else if (rhsMimeType.equals(mimeType)) {
+                        return 1;
+                    }
+                }
+                // Trailing types come last, so flip the returns
+                for (String mimeType : TRAILING_MIMETYPES) {
+                    if (lhsMimeType.equals(mimeType)) {
+                        return 1;
+                    } else if (rhsMimeType.equals(mimeType)) {
+                        return -1;
+                    }
+                }
+                return 0;
+            }
+        });
+
+        entries.addAll(actionsToEntries(topActions));
         Trace.endSection();
     }