Merge "Provider side changes for exposing data usage stats" into jb-mr2-dev
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 9e52f54..cf9068a 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -210,6 +210,8 @@
      */
     private static final int DEFAULT_PREAUTHORIZED_URI_EXPIRATION = 5 * 60 * 1000;
 
+    private static final int USAGE_TYPE_ALL = -1;
+
     /**
      * Random URI parameter that will be appended to preauthorized URIs for uniqueness.
      */
@@ -671,6 +673,11 @@
             .add(Data.STATUS_ICON, StatusUpdatesColumns.CONCRETE_STATUS_ICON)
             .build();
 
+    private static final ProjectionMap sDataUsageColumns = ProjectionMap.builder()
+            .add(Data.TIMES_USED, Tables.DATA_USAGE_STAT + "." + Data.TIMES_USED)
+            .add(Data.LAST_TIME_USED, Tables.DATA_USAGE_STAT + "." + Data.LAST_TIME_USED)
+            .build();
+
     /** Contains just BaseColumns._COUNT */
     private static final ProjectionMap sCountProjectionMap = ProjectionMap.builder()
             .add(BaseColumns._COUNT, "COUNT(*)")
@@ -824,6 +831,7 @@
             .addAll(sRawContactColumns)
             .addAll(sContactsColumns)
             .addAll(sContactPresenceColumns)
+            .addAll(sDataUsageColumns)
             .build();
 
     /** Contains columns from the data view used for SIP address lookup. */
@@ -841,6 +849,7 @@
             .addAll(sDataPresenceColumns)
             .addAll(sContactsColumns)
             .addAll(sContactPresenceColumns)
+            .addAll(sDataUsageColumns)
             .build();
 
     /** Contains columns from the data view used for SIP address lookup. */
@@ -5913,7 +5922,9 @@
 
             case DATA:
             case PROFILE_DATA: {
-                setTablesAndProjectionMapForData(qb, uri, projection, false);
+                final String usageType = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE);
+                final int typeInt = getDataUsageFeedbackType(usageType, USAGE_TYPE_ALL);
+                setTablesAndProjectionMapForData(qb, uri, projection, false, typeInt);
                 if (uri.getBooleanQueryParameter(Data.VISIBLE_CONTACTS_ONLY, false)) {
                     qb.appendWhere(" AND " + Data.CONTACT_ID + " in " +
                             Tables.DEFAULT_DIRECTORY);
@@ -7009,9 +7020,8 @@
         appendDataPresenceJoin(sb, projection, DataColumns.CONCRETE_ID);
         appendDataStatusUpdateJoin(sb, projection, DataColumns.CONCRETE_ID);
 
-        if (usageType != null) {
-            appendDataUsageStatJoin(sb, usageType, DataColumns.CONCRETE_ID);
-        }
+        appendDataUsageStatJoin(sb, usageType == null ? USAGE_TYPE_ALL : usageType,
+                DataColumns.CONCRETE_ID);
 
         qb.setTables(sb.toString());
 
@@ -7108,9 +7118,29 @@
     }
 
     private void appendDataUsageStatJoin(StringBuilder sb, int usageType, String dataIdColumn) {
-        sb.append(" LEFT OUTER JOIN " + Tables.DATA_USAGE_STAT +
-                " ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "=" + dataIdColumn +
-                " AND " + DataUsageStatColumns.CONCRETE_USAGE_TYPE + "=" + usageType + ")");
+        if (usageType != USAGE_TYPE_ALL) {
+            sb.append(" LEFT OUTER JOIN " + Tables.DATA_USAGE_STAT +
+                    " ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "=");
+            sb.append(dataIdColumn);
+            sb.append(" AND " + DataUsageStatColumns.CONCRETE_USAGE_TYPE + "=");
+            sb.append(usageType);
+            sb.append(")");
+        } else {
+            sb.append(
+                    " LEFT OUTER JOIN " +
+                        "(SELECT " +
+                            DataUsageStatColumns.CONCRETE_DATA_ID + ", " +
+                            "SUM(" + DataUsageStatColumns.CONCRETE_TIMES_USED +
+                                ") as " + DataUsageStatColumns.TIMES_USED + ", " +
+                            "MAX(" + DataUsageStatColumns.CONCRETE_LAST_TIME_USED +
+                                ") as " + DataUsageStatColumns.LAST_TIME_USED +
+                        " FROM " + Tables.DATA_USAGE_STAT + " GROUP BY " +
+                            DataUsageStatColumns.DATA_ID + ") as " + Tables.DATA_USAGE_STAT
+                    );
+            sb.append(" ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "=");
+            sb.append(dataIdColumn);
+            sb.append(")");
+        }
     }
 
     private void appendContactPresenceJoin(StringBuilder sb, String[] projection,
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 384d547..8e763f1 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -17,6 +17,7 @@
 package com.android.providers.contacts;
 
 import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
+import static com.android.providers.contacts.TestUtils.cv;
 
 import android.accounts.Account;
 import android.content.ContentProvider;
@@ -1112,6 +1113,22 @@
         assertTrue(message.toString(), result);
     }
 
+    protected void assertCursorContains(Cursor cursor, ContentValues expectedValues) {
+        final StringBuilder message = new StringBuilder();
+        boolean found = false;
+        cursor.moveToPosition(-1);
+        while (cursor.moveToNext()) {
+            message.setLength(0);
+            final int pos = cursor.getPosition();
+            found = equalsWithExpectedValues(cursor, expectedValues, message);
+            if (found) {
+                break;
+            }
+        }
+        assertTrue("Expected values can not be found " + expectedValues + "," + message.toString(),
+                found);
+    }
+
     protected void assertCursorValues(Cursor cursor, ContentValues... expectedValues) {
         StringBuilder message = new StringBuilder();
 
@@ -1179,6 +1196,25 @@
         return true;
     }
 
+    private static final String[] DATA_USAGE_PROJECTION =
+            new String[] {Data.DATA1, Data.TIMES_USED, Data.LAST_TIME_USED};
+
+    protected void assertDataUsageCursorContains(Uri uri, String data1, int timesUsed,
+            int lastTimeUsed) {
+        final Cursor cursor = mResolver.query(uri, DATA_USAGE_PROJECTION, null, null,
+                null);
+        try {
+            assertCursorContains(cursor,
+                    cv(
+                            Data.DATA1, data1,
+                            Data.TIMES_USED, timesUsed,
+                            Data.LAST_TIME_USED, lastTimeUsed)
+            );
+        } finally {
+            cursor.close();
+        }
+    }
+
     private String[] buildProjection(ContentValues values) {
         String[] projection = new String[values.size()];
         Iterator<Entry<String, Object>> iter = values.valueSet().iterator();
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 84404b2..3f8b001 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -32,7 +32,7 @@
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.AggregationExceptions;
 import android.provider.ContactsContract.CommonDataKinds.Callable;
-import android.provider.ContactsContract.CommonDataKinds.Contactables;;
+import android.provider.ContactsContract.CommonDataKinds.Contactables;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -343,6 +343,8 @@
                 Data.STATUS_RES_PACKAGE,
                 Data.STATUS_LABEL,
                 Data.STATUS_ICON,
+                Data.TIMES_USED,
+                Data.LAST_TIME_USED,
                 RawContacts.ACCOUNT_NAME,
                 RawContacts.ACCOUNT_TYPE,
                 RawContacts.DATA_SET,
@@ -424,6 +426,8 @@
                 Data.STATUS_RES_PACKAGE,
                 Data.STATUS_LABEL,
                 Data.STATUS_ICON,
+                Data.TIMES_USED,
+                Data.LAST_TIME_USED,
                 RawContacts.RAW_CONTACT_IS_USER_PROFILE,
                 Contacts._ID,
                 Contacts.DISPLAY_NAME_PRIMARY,
@@ -2514,6 +2518,48 @@
 
         // Now we have only 1 frequent.
         assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values1});
+
+    }
+
+    public void testQueryDataUsageStat() {
+        ContentValues values1 = new ContentValues();
+        final String email1 = "a@acme.com";
+        final long cid1 = createContact(values1, "Noah", "Tever", "18004664411",
+                email1, StatusUpdates.OFFLINE, 0, 0, 0, 0);
+
+        sMockClock.install();
+        sMockClock.setCurrentTimeMillis(100);
+
+        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
+
+        assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 1, 100);
+
+        sMockClock.setCurrentTimeMillis(111);
+        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
+
+        assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 2, 111);
+
+        sMockClock.setCurrentTimeMillis(123);
+        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+
+        assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 3, 123);
+
+        final Uri dataUriWithUsageTypeLongText = Data.CONTENT_URI.buildUpon().appendQueryParameter(
+                DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_LONG_TEXT).build();
+
+        assertDataUsageCursorContains(dataUriWithUsageTypeLongText, "a@acme.com", 2, 111);
+
+        sMockClock.setCurrentTimeMillis(200);
+        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
+        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
+        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
+
+        assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 6, 200);
+
+        final Uri dataUriWithUsageTypeCall = Data.CONTENT_URI.buildUpon().appendQueryParameter(
+                DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_CALL).build();
+
+        assertDataUsageCursorContains(dataUriWithUsageTypeCall, "a@acme.com", 3, 200);
     }
 
     public void testQueryContactGroup() {