Merge "Add calendar database upgrade code."
diff --git a/src/com/android/providers/calendar/CalendarProvider2.java b/src/com/android/providers/calendar/CalendarProvider2.java
index fc9624b..8c89ae1 100644
--- a/src/com/android/providers/calendar/CalendarProvider2.java
+++ b/src/com/android/providers/calendar/CalendarProvider2.java
@@ -73,7 +73,7 @@
 
     private static final String TAG = "CalendarProvider2";
 
-    private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE) || true;
+    private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
 
     private static final boolean PROFILE = false;
     private static final boolean MULTIPLE_ATTENDEES_PER_EVENT = true;
@@ -404,8 +404,8 @@
             case EVENTS_ID:
                 qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
                 qb.setProjectionMap(sEventsProjectionMap);
-                qb.appendWhere("_id=");
-                qb.appendWhere(uri.getPathSegments().get(1));
+                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
+                qb.appendWhere("_id=?");
                 break;
 
             case EVENT_ENTITIES:
@@ -416,8 +416,8 @@
             case EVENT_ENTITIES_ID:
                 qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
                 qb.setProjectionMap(sEventEntitiesProjectionMap);
-                qb.appendWhere("_id=" + uri.getPathSegments().get(1));
-                qb.appendWhere(uri.getPathSegments().get(1));
+                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
+                qb.appendWhere("_id=?");
                 break;
 
             case CALENDARS:
@@ -426,8 +426,8 @@
                 break;
             case CALENDARS_ID:
                 qb.setTables("Calendars");
-                qb.appendWhere("_id=");
-                qb.appendWhere(uri.getPathSegments().get(1));
+                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
+                qb.appendWhere("_id=?");
                 break;
             case INSTANCES:
             case INSTANCES_BY_DAY:
@@ -471,9 +471,8 @@
             case ATTENDEES_ID:
                 qb.setTables("Attendees, Events");
                 qb.setProjectionMap(sAttendeesProjectionMap);
-                qb.appendWhere("Attendees._id=");
-                qb.appendWhere(uri.getPathSegments().get(1));
-                qb.appendWhere(" AND Events._id=Attendees.event_id");
+                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
+                qb.appendWhere("Attendees._id=?  AND Events._id=Attendees.event_id");
                 break;
             case REMINDERS:
                 qb.setTables("Reminders");
@@ -481,9 +480,8 @@
             case REMINDERS_ID:
                 qb.setTables("Reminders, Events");
                 qb.setProjectionMap(sRemindersProjectionMap);
-                qb.appendWhere("Reminders._id=");
-                qb.appendWhere(uri.getLastPathSegment());
-                qb.appendWhere(" AND Events._id=Reminders.event_id");
+                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
+                qb.appendWhere("Reminders._id=? AND Events._id=Reminders.event_id");
                 break;
             case CALENDAR_ALERTS:
                 qb.setTables("CalendarAlerts, Events");
@@ -499,17 +497,16 @@
             case CALENDAR_ALERTS_ID:
                 qb.setTables("CalendarAlerts, Events");
                 qb.setProjectionMap(sCalendarAlertsProjectionMap);
-                qb.appendWhere("CalendarAlerts._id=");
-                qb.appendWhere(uri.getLastPathSegment());
-                qb.appendWhere(" AND Events._id=CalendarAlerts.event_id");
+                selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
+                qb.appendWhere("CalendarAlerts._id=?  AND Events._id=CalendarAlerts.event_id");
                 break;
             case EXTENDED_PROPERTIES:
                 qb.setTables("ExtendedProperties");
                 break;
             case EXTENDED_PROPERTIES_ID:
                 qb.setTables("ExtendedProperties");
-                qb.appendWhere("ExtendedProperties._id=");
-                qb.appendWhere(uri.getPathSegments().get(1));
+                selectionArgs = insertSelectionArg(selectionArgs, uri.getPathSegments().get(1));
+                qb.appendWhere("ExtendedProperties._id=?");
                 break;
             default:
                 throw new IllegalArgumentException("Unknown URL " + uri);
@@ -561,18 +558,15 @@
             long endMs = time.setJulianDay((int) rangeEnd + 1);
             // will lock the database.
             acquireInstanceRange(beginMs, endMs, true /* use minimum expansion window */);
-            qb.appendWhere("startDay <= ");
-            qb.appendWhere(String.valueOf(rangeEnd));
-            qb.appendWhere(" AND endDay >= ");
+            qb.appendWhere("startDay<=? AND endDay>=?");
         } else {
             // will lock the database.
             acquireInstanceRange(rangeBegin, rangeEnd, true /* use minimum expansion window */);
-            qb.appendWhere("begin <= ");
-            qb.appendWhere(String.valueOf(rangeEnd));
-            qb.appendWhere(" AND end >= ");
+            qb.appendWhere("begin<=? AND end>=?");
         }
-        qb.appendWhere(String.valueOf(rangeBegin));
-        return qb.query(mDb, projection, selection, null /* selectionArgs */, null /* groupBy */,
+        String selectionArgs[] = new String[] {String.valueOf(rangeEnd),
+                String.valueOf(rangeBegin)};
+        return qb.query(mDb, projection, selection, selectionArgs, null /* groupBy */,
                 null /* having */, sort);
     }
 
@@ -590,11 +584,10 @@
         long endMs = time.setJulianDay((int) end + 1);
 
         acquireInstanceRange(beginMs, endMs, true);
-        qb.appendWhere("startDay <= ");
-        qb.appendWhere(String.valueOf(end));
-        qb.appendWhere(" AND endDay >= ");
-        qb.appendWhere(String.valueOf(begin));
-        return qb.query(mDb, projection, selection, null /* selectionArgs */,
+        qb.appendWhere("startDay<=? AND endDay>=?");
+        String selectionArgs[] = new String[] {String.valueOf(end), String.valueOf(begin)};
+
+        return qb.query(mDb, projection, selection, selectionArgs,
                 Instances.START_DAY /* groupBy */, null /* having */, null);
     }
 
@@ -760,33 +753,24 @@
         String beginString = String.valueOf(begin);
         String endString = String.valueOf(end);
 
-        qb.appendWhere("(dtstart <= ");
-        qb.appendWhere(endString);
-        qb.appendWhere(" AND ");
-        qb.appendWhere("(lastDate IS NULL OR lastDate >= ");
-        qb.appendWhere(beginString);
-        qb.appendWhere(")) OR (");
         // grab recurrence exceptions that fall outside our expansion window but modify
         // recurrences that do fall within our window.  we won't insert these into the output
         // set of instances, but instead will just add them to our cancellations list, so we
         // can cancel the correct recurrence expansion instances.
-        qb.appendWhere("originalInstanceTime IS NOT NULL ");
-        qb.appendWhere("AND originalInstanceTime <= ");
-        qb.appendWhere(endString);
-        qb.appendWhere(" AND ");
         // we don't have originalInstanceDuration or end time.  for now, assume the original
         // instance lasts no longer than 1 week.
         // TODO: compute the originalInstanceEndTime or get this from the server.
-        qb.appendWhere("originalInstanceTime >= ");
-        qb.appendWhere(String.valueOf(begin - MAX_ASSUMED_DURATION));
-        qb.appendWhere(")");
-
+        qb.appendWhere("(dtstart <= ? AND (lastDate IS NULL OR lastDate >= ?)) OR " +
+                "(originalInstanceTime IS NOT NULL AND originalInstanceTime <= ? AND " +
+                "originalInstanceTime >= ?)");
+        String selectionArgs[] = new String[] {endString, beginString, endString,
+                String.valueOf(begin - MAX_ASSUMED_DURATION)};
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "Retrieving events to expand: " + qb.toString());
         }
 
         return qb.query(mDb, EXPAND_COLUMNS, null /* selection */,
-                null /* selectionArgs */, null /* groupBy */,
+                selectionArgs, null /* groupBy */,
                 null /* having */, null /* sortOrder */);
     }
 
@@ -1319,7 +1303,7 @@
     }
 
     private void setEventDirty(int eventId) {
-        mDb.execSQL("UPDATE Events SET _sync_dirty=1 where _id=" + eventId);
+        mDb.execSQL("UPDATE Events SET _sync_dirty=1 where _id=?", new Integer[] {eventId});
     }
 
     /**
@@ -1460,7 +1444,7 @@
 
         ContentValues values = new ContentValues();
         values.put(Events.SELF_ATTENDEE_STATUS, status);
-        db.update("Events", values, "_id="+eventId, null);
+        db.update("Events", values, "_id=?", new String[] {String.valueOf(eventId)});
     }
 
     /**
@@ -1500,7 +1484,7 @@
             // For recurrence or exception, more deletion may happen below if we
             // do an instance expansion.  This deletion will suffice if the exception
             // is moved outside the window, for instance.
-            db.delete("Instances", "event_id=" + rowId, null /* selectionArgs */);
+            db.delete("Instances", "event_id=?", new String[] {String.valueOf(rowId)});
         }
 
         if (isRecurrenceEvent(values))  {
@@ -1576,19 +1560,21 @@
 
         qb.setTables(CalendarDatabaseHelper.Views.EVENTS);
         qb.setProjectionMap(sEventsProjectionMap);
+        String selectionArgs[];
         if (recurrenceSyncId == null) {
-            String where = "_id = " + rowId;
+            String where = "_id =?";
             qb.appendWhere(where);
+            selectionArgs = new String[] {String.valueOf(rowId)};
         } else {
-            String where = "_sync_id = \"" + recurrenceSyncId + "\""
-                    + " OR originalEvent = \"" + recurrenceSyncId + "\"";
+            String where = "_sync_id = ? OR originalEvent = ?";
             qb.appendWhere(where);
+            selectionArgs = new String[] {recurrenceSyncId, recurrenceSyncId};
         }
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "Retrieving events to expand: " + qb.toString());
         }
 
-        return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, null /* selectionArgs */,
+        return qb.query(mDb, EXPAND_COLUMNS, null /* selection */, selectionArgs,
                 null /* groupBy */, null /* having */, null /* sortOrder */);
     }
 
@@ -1613,7 +1599,7 @@
         } else {
             // Get the recurrence's sync id from the database
             recurrenceSyncId = DatabaseUtils.stringForQuery(db, "SELECT _sync_id FROM Events"
-                    + " WHERE _id = " + rowId, null /* selection args */);
+                    + " WHERE _id=?", new String[] {String.valueOf(rowId)});
         }
         // recurrenceSyncId is the _sync_id of the underlying recurrence
         // If the recurrence hasn't gone to the server, it will be null.
@@ -1855,7 +1841,7 @@
                 }
                 if (callerIsSyncAdapter) {
                     long id = ContentUris.parseId(uri);
-                    return mDb.delete("Attendees", "_id=" + id, null /* selectionArgs */);
+                    return mDb.delete("Attendees", "_id=?", new String[] {String.valueOf(id)});
                 } else {
                     return deleteFromTable("Attendees", uri, null /* selection */,
                                            null /* selectionArgs */);
@@ -1876,7 +1862,7 @@
                 }
                 if (callerIsSyncAdapter) {
                     long id = ContentUris.parseId(uri);
-                    return mDb.delete("Reminders", "_id=" + id, null /* selectionArgs */);
+                    return mDb.delete("Reminders", "_id=?", new String[] {String.valueOf(id)});
                 } else {
                     return deleteFromTable("Reminders", uri, null /* selection */,
                                            null /* selectionArgs */);
@@ -1897,7 +1883,8 @@
                 }
                 if (callerIsSyncAdapter) {
                     long id = ContentUris.parseId(uri);
-                    return mDb.delete("ExtendedProperties", "_id=" + id, null /* selectionArgs */);
+                    return mDb.delete("ExtendedProperties", "_id=?",
+                            new String[] {String.valueOf(id)});
                 } else {
                     return deleteFromTable("ExtendedProperties", uri, null /* selection */,
                                            null /* selectionArgs */);
@@ -1919,7 +1906,7 @@
                 // Note: dirty bit is not set for Alerts because it is not synced.
                 // It is generated from Reminders, which is synced.
                 long id = ContentUris.parseId(uri);
-                return mDb.delete("CalendarAlerts", "_id=" + id, null /* selectionArgs */);
+                return mDb.delete("CalendarAlerts", "_id=?", new String[] {String.valueOf(id)});
             }
             case DELETED_EVENTS:
                 throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
@@ -1950,8 +1937,8 @@
 
         // Query this event to get the fields needed for deleting.
         Cursor cursor = mDb.query("Events", EVENTS_PROJECTION,
-                "_id=" + id,
-                null /* selectionArgs */, null /* groupBy */,
+                "_id=?", new String[] {String.valueOf(id)},
+                null /* groupBy */,
                 null /* having */, null /* sortOrder */);
         try {
             if (cursor.moveToNext()) {
@@ -1978,12 +1965,12 @@
                 }
 
                 if (callerIsSyncAdapter) {
-                    mDb.delete("Events", "_id = " + id, null);
+                    mDb.delete("Events", "_id=?", new String[] {String.valueOf(id)});
                 } else {
                     ContentValues values = new ContentValues();
                     values.put(Events.DELETED, 1);
                     values.put(Events._SYNC_DIRTY, 1);
-                    mDb.update("Events", values, "_id = " + id, null);
+                    mDb.update("Events", values, "_id=?", new String[] {String.valueOf(id)});
                 }
             }
         } finally {
@@ -1992,12 +1979,13 @@
         }
         triggerAppWidgetUpdate(-1);
 
-        mDb.delete("Instances", "event_id=" + id, null /* selectionArgs */);
-        mDb.delete("EventsRawTimes", "event_id=" + id, null /* selectionArgs */);
-        mDb.delete("Attendees", "event_id=" + id, null /* selectionArgs */);
-        mDb.delete("Reminders", "event_id=" + id, null /* selectionArgs */);
-        mDb.delete("CalendarAlerts", "event_id=" + id, null /* selectionArgs */);
-        mDb.delete("ExtendedProperties", "event_id=" + id, null /* selectionArgs */);
+        String selectionArgs[] = new String[] {String.valueOf(id)};
+        mDb.delete("Instances", "event_id=?", selectionArgs);
+        mDb.delete("EventsRawTimes", "event_id=?", selectionArgs);
+        mDb.delete("Attendees", "event_id=?", selectionArgs);
+        mDb.delete("Reminders", "event_id=?", selectionArgs);
+        mDb.delete("CalendarAlerts", "event_id=?", selectionArgs);
+        mDb.delete("ExtendedProperties", "event_id=?", selectionArgs);
         return result;
     }
 
@@ -2019,8 +2007,8 @@
             while(c.moveToNext()) {
                 long id = c.getLong(ID_INDEX);
                 long event_id = c.getLong(EVENT_ID_INDEX);
-                mDb.delete(table, "_id = " + id, null /* selectionArgs */);
-                mDb.update("Events", values, "_id = " + event_id, null /* selectionArgs */);
+                mDb.delete(table, "_id=?", new String[] {String.valueOf(id)});
+                mDb.update("Events", values, "_id=?", new String[] {String.valueOf(event_id)});
                 count++;
             }
         } finally {
@@ -2049,9 +2037,8 @@
             while(c.moveToNext()) {
                 long id = c.getLong(ID_INDEX);
                 long event_id = c.getLong(EVENT_ID_INDEX);
-                mDb.update(table, values, "_id = " + id, null /* selectionArgs */);
-                mDb.update("Events", dirtyValues, "_id = " + event_id, null /* selectionArgs */);
-                mDb.update(table, values, "_id = " + id, null /* selectionArgs */);
+                mDb.update(table, values, "_id=?", new String[] {String.valueOf(id)});
+                mDb.update("Events", dirtyValues, "_id=?", new String[] {String.valueOf(event_id)});
                 count++;
             }
         } finally {
@@ -2130,7 +2117,8 @@
                     modifyCalendarSubscription(id, syncEvents == 1);
                 }
 
-                int result = mDb.update("Calendars", values, "_id="+ id, null /* selectionArgs */);
+                int result = mDb.update("Calendars", values, "_id=?",
+                        new String[] {String.valueOf(id)});
 
                 return result;
             }
@@ -2179,8 +2167,8 @@
                     return 0;
                 }
 
-                int result = mDb.update("Events", updatedValues, "_id=" + id,
-                        null /* selectionArgs */);
+                int result = mDb.update("Events", updatedValues, "_id=?",
+                        new String[] {String.valueOf(id)});
                 if (result > 0) {
                     updateEventRawTimesLocked(id, updatedValues);
                     updateInstancesLocked(updatedValues, id, false /* not a new event */, mDb);
@@ -2206,7 +2194,8 @@
 
                 if (callerIsSyncAdapter) {
                     long id = ContentUris.parseId(uri);
-                    return mDb.update("Attendees", values, "_id=" + id, null);
+                    return mDb.update("Attendees", values, "_id=?",
+                        new String[] {String.valueOf(id)});
                 } else {
                     return updateInTable("Attendees", values, uri, null /* selection */,
                             null /* selectionArgs */);
@@ -2219,7 +2208,8 @@
                 // Note: dirty bit is not set for Alerts because it is not synced.
                 // It is generated from Reminders, which is synced.
                 long id = ContentUris.parseId(uri);
-                return mDb.update("CalendarAlerts", values, "_id=" + id, null /* selectionArgs */);
+                return mDb.update("CalendarAlerts", values, "_id=?",
+                        new String[] {String.valueOf(id)});
             }
             case CALENDAR_ALERTS: {
                 // Note: dirty bit is not set for Alerts because it is not synced.
@@ -2232,7 +2222,8 @@
                 }
                 if (callerIsSyncAdapter) {
                     long id = ContentUris.parseId(uri);
-                    count = mDb.update("Reminders", values, "_id=" + id, null /* selectionArgs */);
+                    count = mDb.update("Reminders", values, "_id=?",
+                        new String[] {String.valueOf(id)});
                 } else {
                     count = updateInTable("Reminders", values, uri, null /* selection */,
                             null /* selectionArgs */);
@@ -2252,8 +2243,8 @@
                 }
                 if (callerIsSyncAdapter) {
                     long id = ContentUris.parseId(uri);
-                    return mDb.update("ExtendedProperties", values, "_id=" + id,
-                            null /* selectionArgs */);
+                    return mDb.update("ExtendedProperties", values, "_id=?",
+                            new String[] {String.valueOf(id)});
                 } else {
                     return updateInTable("ExtendedProperties", values, uri, null /* selection */,
                             null /* selectionArgs */);
@@ -2353,7 +2344,7 @@
             // work.  We need to keep the calendar entry in the Calendars table
             // in order to know not to sync the events for that calendar from
             // the server.
-            String[] args = new String[] {Long.toString(id)};
+            String[] args = new String[] {String.valueOf(id)};
             mDb.delete("Events", CALENDAR_ID_SELECTION, args);
 
             // TODO: cancel any pending/ongoing syncs for this calendar.
@@ -3019,4 +3010,24 @@
 
         return Uri.decode(value);
     }
+
+    /**
+     * Inserts an argument at the beginning of the selection arg list.
+     *
+     * The {@link android.database.sqlite.SQLiteQueryBuilder}'s where clause is
+     * prepended to the user's where clause (combined with 'AND') to generate
+     * the final where close, so arguments associated with the QueryBuilder are
+     * prepended before any user selection args to keep them in the right order.
+     */
+    private String[] insertSelectionArg(String[] selectionArgs, String arg) {
+        if (selectionArgs == null) {
+            return new String[] {arg};
+        } else {
+            int newLength = selectionArgs.length + 1;
+            String[] newSelectionArgs = new String[newLength];
+            newSelectionArgs[0] = arg;
+            System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
+            return newSelectionArgs;
+        }
+    }
 }