Merge "add emma filter to make file"
diff --git a/src/com/android/providers/calendar/CalendarProvider2.java b/src/com/android/providers/calendar/CalendarProvider2.java
index e656a26..69a20c4 100644
--- a/src/com/android/providers/calendar/CalendarProvider2.java
+++ b/src/com/android/providers/calendar/CalendarProvider2.java
@@ -255,6 +255,20 @@
         CalendarDatabaseHelper.Tables.EVENTS + "."
         + Calendar.Events._ID + ")";
 
+    private static final String INSTANCE_SEARCH_QUERY_TABLES = "(" +
+        CalendarDatabaseHelper.Tables.INSTANCES + " INNER JOIN " +
+        CalendarDatabaseHelper.Views.EVENTS + " AS " +
+        CalendarDatabaseHelper.Tables.EVENTS +
+        " ON (" + CalendarDatabaseHelper.Tables.INSTANCES + "."
+        + Calendar.Instances.EVENT_ID + "=" +
+        CalendarDatabaseHelper.Tables.EVENTS + "."
+        + Calendar.Events._ID + ")" + ") LEFT OUTER JOIN " +
+        CalendarDatabaseHelper.Tables.ATTENDEES +
+        " ON (" + CalendarDatabaseHelper.Tables.ATTENDEES + "."
+        + Calendar.Attendees.EVENT_ID + "=" +
+        CalendarDatabaseHelper.Tables.EVENTS + "."
+        + Calendar.Events._ID + ")";
+
     private static final String BETWEEN_DAY_WHERE =
         Calendar.Instances.START_DAY + "<=? AND " +
         Calendar.Instances.END_DAY + ">=?";
@@ -295,10 +309,26 @@
     private static final Pattern SEARCH_ESCAPE_PATTERN =
         Pattern.compile("([%_" + SEARCH_ESCAPE_CHAR + "])");
 
+    /**
+     * Alias used for aggregate concatenation of attendee e-mails when grouping
+     * attendees by instance.
+     */
+    private static final String ATTENDEES_EMAIL_CONCAT =
+        "group_concat(" + Calendar.Attendees.ATTENDEE_EMAIL + ")";
+
+    /**
+     * Alias used for aggregate concatenation of attendee names when grouping
+     * attendees by instance.
+     */
+    private static final String ATTENDEES_NAME_CONCAT =
+        "group_concat(" + Calendar.Attendees.ATTENDEE_NAME + ")";
+
     private static final String[] SEARCH_COLUMNS = new String[] {
         Calendar.Events.TITLE,
         Calendar.Events.DESCRIPTION,
-        Calendar.Events.EVENT_LOCATION
+        Calendar.Events.EVENT_LOCATION,
+        ATTENDEES_EMAIL_CONCAT,
+        ATTENDEES_NAME_CONCAT
     };
 
     private AlarmManager mAlarmManager;
@@ -955,39 +985,43 @@
                     sb.append("OR ");
                 }
             }
-            sb.append(") AND ");
+            sb.append(")");
+            if (j < tokens.length - 1) {
+                sb.append(" AND ");
+            }
         }
         return sb.toString();
     }
 
     @VisibleForTesting
     String[] constructSearchArgs(String[] tokens, long rangeBegin, long rangeEnd) {
+        int numCols = SEARCH_COLUMNS.length;
+        int numArgs = tokens.length * numCols + 2;
         // the additional two elements here are for begin/end time
-        String[] selectionArgs =
-            new String[tokens.length * SEARCH_COLUMNS.length + 2];
+        String[] selectionArgs = new String[numArgs];
+        selectionArgs[0] =  String.valueOf(rangeEnd);
+        selectionArgs[1] =  String.valueOf(rangeBegin);
         for (int j = 0; j < tokens.length; j++) {
-            for (int i = 0; i < SEARCH_COLUMNS.length; i++) {
-                selectionArgs[j * SEARCH_COLUMNS.length + i] =
-                    "%" + tokens[j] + "%";
+            int start = 2 + numCols * j;
+            for (int i = start; i < start + numCols; i++) {
+                selectionArgs[i] = "%" + tokens[j] + "%";
             }
         }
-        selectionArgs[selectionArgs.length - 2] =  String.valueOf(rangeEnd);
-        selectionArgs[selectionArgs.length - 1] =  String.valueOf(rangeBegin);
         return selectionArgs;
     }
 
     private Cursor handleInstanceSearchQuery(SQLiteQueryBuilder qb,
             long rangeBegin, long rangeEnd, String query, String[] projection,
             String selection, String sort, boolean searchByDay) {
-        qb.setTables(INSTANCE_QUERY_TABLES);
+        qb.setTables(INSTANCE_SEARCH_QUERY_TABLES);
         qb.setProjectionMap(sInstancesProjectionMap);
 
         String[] tokens = tokenizeSearchQuery(query);
         String[] selectionArgs = constructSearchArgs(tokens, rangeBegin, rangeEnd);
+        // we pass this in as a HAVING instead of a WHERE so the filtering
+        // happens after the grouping
         String searchWhere = constructSearchWhere(tokens);
 
-        qb.appendWhere(searchWhere);
-
         if (searchByDay) {
             // Convert the first and last Julian day range to a range that uses
             // UTC milliseconds.
@@ -997,17 +1031,20 @@
             // Julian day and we want to include all the events on the last day.
             long endMs = time.setJulianDay((int) rangeEnd + 1);
             // will lock the database.
+            // we expand the instances here because we might be searching over
+            // a range where instance expansion has not occurred yet
             acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */);
             qb.appendWhere(BETWEEN_DAY_WHERE);
         } else {
             // will lock the database.
+            // we expand the instances here because we might be searching over
+            // a range where instance expansion has not occurred yet
             acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */);
             qb.appendWhere(BETWEEN_WHERE);
         }
 
-        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
-                null /* having */, sort);
-
+        return qb.query(mDb, projection, selection, selectionArgs,
+                Instances._ID /* groupBy */, searchWhere /* having */, sort);
     }
 
     private Cursor handleEventDayQuery(SQLiteQueryBuilder qb, int begin, int end,
diff --git a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
index d2ebde4..ef748d6 100644
--- a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
+++ b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java
@@ -1351,8 +1351,10 @@
     public void testConstructSearchWhere() {
         String[] tokens = new String[] {"red"};
         String expected = "(title LIKE ? ESCAPE \"#\" OR "
-                + "description LIKE ? ESCAPE \"#\" OR "
-                + "eventLocation LIKE ? ESCAPE \"#\" ) AND ";
+            + "description LIKE ? ESCAPE \"#\" OR "
+            + "eventLocation LIKE ? ESCAPE \"#\" OR "
+            + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR "
+            + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" )";
         assertEquals(expected, mProvider.constructSearchWhere(tokens));
 
         tokens = new String[] {};
@@ -1362,19 +1364,27 @@
         tokens = new String[] {"red", "green"};
         expected = "(title LIKE ? ESCAPE \"#\" OR "
                 + "description LIKE ? ESCAPE \"#\" OR "
-                + "eventLocation LIKE ? ESCAPE \"#\" ) AND "
+                + "eventLocation LIKE ? ESCAPE \"#\" OR "
+                + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR "
+                + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" ) AND "
                 + "(title LIKE ? ESCAPE \"#\" OR "
                 + "description LIKE ? ESCAPE \"#\" OR "
-                + "eventLocation LIKE ? ESCAPE \"#\" ) AND ";
+                + "eventLocation LIKE ? ESCAPE \"#\" OR "
+                + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR "
+                + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" )";
         assertEquals(expected, mProvider.constructSearchWhere(tokens));
 
         tokens = new String[] {"red blue", "green"};
         expected = "(title LIKE ? ESCAPE \"#\" OR "
-                + "description LIKE ? ESCAPE \"#\" OR "
-                + "eventLocation LIKE ? ESCAPE \"#\" ) AND "
-                + "(title LIKE ? ESCAPE \"#\" OR "
-                + "description LIKE ? ESCAPE \"#\" OR "
-                + "eventLocation LIKE ? ESCAPE \"#\" ) AND ";
+            + "description LIKE ? ESCAPE \"#\" OR "
+            + "eventLocation LIKE ? ESCAPE \"#\" OR "
+            + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR "
+            + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" ) AND "
+            + "(title LIKE ? ESCAPE \"#\" OR "
+            + "description LIKE ? ESCAPE \"#\" OR "
+            + "eventLocation LIKE ? ESCAPE \"#\" OR "
+            + "group_concat(attendeeEmail) LIKE ? ESCAPE \"#\" OR "
+            + "group_concat(attendeeName) LIKE ? ESCAPE \"#\" )";
         assertEquals(expected, mProvider.constructSearchWhere(tokens));
     }
 
@@ -1384,13 +1394,15 @@
         long rangeEnd = 10;
 
         String[] tokens = new String[] {"red"};
-        String[] expected = new String[] {"%red%", "%red%", "%red%", "10", "0" };
+        String[] expected = new String[] {"10", "0", "%red%", "%red%",
+                "%red%", "%red%", "%red%" };
         assertArrayEquals(expected, mProvider.constructSearchArgs(tokens,
                 rangeBegin, rangeEnd));
 
         tokens = new String[] {"red", "blue"};
-        expected = new String[] {"%red%", "%red%", "%red%",
-                "%blue%", "%blue%","%blue%", "10", "0" };
+        expected = new String[] { "10", "0", "%red%", "%red%", "%red%",
+                "%red%", "%red%", "%blue%", "%blue%",
+                "%blue%", "%blue%","%blue%"};
         assertArrayEquals(expected, mProvider.constructSearchArgs(tokens,
                 rangeBegin, rangeEnd));