diff options
author | Tony Mak <tonymak@google.com> | 2015-06-08 10:50:34 +0100 |
---|---|---|
committer | Tony Mak <tonymak@google.com> | 2015-06-08 10:50:34 +0100 |
commit | 503a798e5f76ecce75607277292bd9a326ba79ec (patch) | |
tree | e4cb5d294d8156cacf4bb09a4475379098d23955 | |
parent | ea28dfc327c87b24855f7abd9a48ba9a1b3f43f5 (diff) | |
download | android_packages_providers_CalendarProvider-503a798e5f76ecce75607277292bd9a326ba79ec.tar.gz android_packages_providers_CalendarProvider-503a798e5f76ecce75607277292bd9a326ba79ec.tar.bz2 android_packages_providers_CalendarProvider-503a798e5f76ecce75607277292bd9a326ba79ec.zip |
Fix crash when inserting reminder/attendee/extended property to a non-existent event
The problem is when inserting reminder/attendee/extended property, the code assumes the event it belongs to exists.
However, it may not be true in some edge cases.
For example, user is on the event edit page, while the event is being deleted in server at this moment.
Add checking to confirm the event does exists before having the insert logic.
As all logics in insert is run within a single transaction, we can be assured that the event does exist throughout the transaction.
Bug: b2/13287671
Change-Id: I655720c67ab3176d52759c43032682874aa3eb8b
3 files changed, 88 insertions, 26 deletions
diff --git a/src/com/android/providers/calendar/CalendarDatabaseHelper.java b/src/com/android/providers/calendar/CalendarDatabaseHelper.java index 407e21a..bb979c6 100644 --- a/src/com/android/providers/calendar/CalendarDatabaseHelper.java +++ b/src/com/android/providers/calendar/CalendarDatabaseHelper.java @@ -3368,21 +3368,12 @@ import java.util.TimeZone; */ protected void duplicateEvent(final long id) { final SQLiteDatabase db = getWritableDatabase(); - try { - final long canPartiallyUpdate = DatabaseUtils.longForQuery(db, "SELECT " - + Calendars.CAN_PARTIALLY_UPDATE + " FROM " + Views.EVENTS - + " WHERE " + Events._ID + " = ?", new String[] { + final long canPartiallyUpdate = DatabaseUtils.longForQuery(db, "SELECT " + + Calendars.CAN_PARTIALLY_UPDATE + " FROM " + Views.EVENTS + + " WHERE " + Events._ID + " = ?", new String[]{ String.valueOf(id) - }); - if (canPartiallyUpdate == 0) { - return; - } - } catch (SQLiteDoneException e) { - // b/11392862 - // If no results are returned, this will be thrown. This can happen if the Events View - // has no rows for the provided id. This might happen for example if someone inserts a - // reminder that refers to a non existent event id. - // Return without doing anything because there is no event to duplicate. + }); + if (canPartiallyUpdate == 0) { return; } diff --git a/src/com/android/providers/calendar/CalendarProvider2.java b/src/com/android/providers/calendar/CalendarProvider2.java index 73a2275..ea2dd38 100644 --- a/src/com/android/providers/calendar/CalendarProvider2.java +++ b/src/com/android/providers/calendar/CalendarProvider2.java @@ -2174,8 +2174,8 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun } if (updatedValues.containsKey(Events.ORIGINAL_SYNC_ID) && !updatedValues.containsKey(Events.ORIGINAL_ID)) { - long originalId = getOriginalId(updatedValues.getAsString( - Events.ORIGINAL_SYNC_ID), + long originalId = getOriginalId(updatedValues + .getAsString(Events.ORIGINAL_SYNC_ID), updatedValues.getAsString(Events.CALENDAR_ID)); if (originalId != -1) { updatedValues.put(Events.ORIGINAL_ID, originalId); @@ -2284,11 +2284,16 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun } id = mDbHelper.colorsInsert(values); break; - case ATTENDEES: + case ATTENDEES: { if (!values.containsKey(Attendees.EVENT_ID)) { throw new IllegalArgumentException("Attendees values must " + "contain an event_id"); } + Long eventIdObj = values.getAsLong(Reminders.EVENT_ID); + if (!doesEventExist(eventIdObj)) { + Log.i(TAG, "Trying to insert a attendee to a non-existent event"); + return null; + } if (!callerIsSyncAdapter) { final Long eventId = values.getAsLong(Attendees.EVENT_ID); mDbHelper.duplicateEvent(eventId); @@ -2299,13 +2304,18 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun // Copy the attendee status value to the Events table. updateEventAttendeeStatus(mDb, values); break; - case REMINDERS: - { + } + case REMINDERS: { Long eventIdObj = values.getAsLong(Reminders.EVENT_ID); if (eventIdObj == null) { throw new IllegalArgumentException("Reminders values must " + "contain a numeric event_id"); } + if (!doesEventExist(eventIdObj)) { + Log.i(TAG, "Trying to insert a reminder to a non-existent event"); + return null; + } + if (!callerIsSyncAdapter) { mDbHelper.duplicateEvent(eventIdObj); setEventDirty(eventIdObj); @@ -2322,19 +2332,31 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun mCalendarAlarm.scheduleNextAlarm(false /* do not remove alarms */); break; } - case CALENDAR_ALERTS: - if (!values.containsKey(CalendarAlerts.EVENT_ID)) { + case CALENDAR_ALERTS: { + Long eventIdObj = values.getAsLong(Reminders.EVENT_ID); + if (eventIdObj == null) { throw new IllegalArgumentException("CalendarAlerts values must " - + "contain an event_id"); + + "contain a numeric event_id"); + } + if (!doesEventExist(eventIdObj)) { + Log.i(TAG, "Trying to insert an alert to a non-existent event"); + return null; } id = mDbHelper.calendarAlertsInsert(values); // Note: dirty bit is not set for Alerts because it is not synced. // It is generated from Reminders, which is synced. break; - case EXTENDED_PROPERTIES: - if (!values.containsKey(CalendarContract.ExtendedProperties.EVENT_ID)) { + } + case EXTENDED_PROPERTIES: { + Long eventIdObj = values.getAsLong(Reminders.EVENT_ID); + if (eventIdObj == null) { throw new IllegalArgumentException("ExtendedProperties values must " - + "contain an event_id"); + + "contain a numeric event_id"); + } + if (!doesEventExist(eventIdObj)) { + Log.i(TAG, "Trying to insert extended properties to a non-existent event id = " + + eventIdObj); + return null; } if (!callerIsSyncAdapter) { final Long eventId = values @@ -2344,6 +2366,7 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun } id = mDbHelper.extendedPropertiesInsert(values); break; + } case EMMA: // Special target used during code-coverage evaluation. handleEmmaRequest(values); @@ -2364,10 +2387,14 @@ public class CalendarProvider2 extends SQLiteContentProvider implements OnAccoun if (id < 0) { return null; } - return ContentUris.withAppendedId(uri, id); } + private boolean doesEventExist(long eventId) { + return DatabaseUtils.queryNumEntries(mDb, Tables.EVENTS, Events._ID + "=?", + new String[]{String.valueOf(eventId)}) > 0; + } + /** * Handles special commands related to EMMA code-coverage testing. * diff --git a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java index 98c4b6a..a4c5e35 100644 --- a/tests/src/com/android/providers/calendar/CalendarProvider2Test.java +++ b/tests/src/com/android/providers/calendar/CalendarProvider2Test.java @@ -2014,6 +2014,50 @@ public class CalendarProvider2Test extends AndroidTestCase { cursor.close(); } + public void testInsertAlertToNonExistentEvent() { + Uri alertUri = CalendarContract.CalendarAlerts.insert(mResolver, 1 /* eventId */, + 2 /* begin */, 3 /* end */, 4 /* alarmTime */, 5 /* minutes */); + assertEquals(null, alertUri); + } + + public void testInsertReminderToNonExistentEvent() { + ContentValues reminder = new ContentValues(); + reminder.put(CalendarContract.Reminders.MINUTES, 30); + reminder.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_EMAIL); + reminder.put(CalendarContract.Attendees.EVENT_ID, 1); + Uri reminderUri = mResolver.insert( + updatedUri(CalendarContract.Reminders.CONTENT_URI, true, DEFAULT_ACCOUNT, + DEFAULT_ACCOUNT_TYPE), reminder); + assertEquals(null, reminderUri); + } + + public void testInsertAttendeeToNonExistentEvent() { + ContentValues attendee = new ContentValues(); + attendee.put(CalendarContract.Attendees.ATTENDEE_NAME, "Joe"); + attendee.put(CalendarContract.Attendees.ATTENDEE_EMAIL, DEFAULT_ACCOUNT); + attendee.put(CalendarContract.Attendees.ATTENDEE_TYPE, + CalendarContract.Attendees.TYPE_REQUIRED); + attendee.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, + CalendarContract.Attendees.RELATIONSHIP_ORGANIZER); + attendee.put(CalendarContract.Attendees.EVENT_ID, 1); + attendee.put(CalendarContract.Attendees.ATTENDEE_IDENTITY, "ID1"); + attendee.put(CalendarContract.Attendees.ATTENDEE_ID_NAMESPACE, "IDNS1"); + Uri attendeesUri = mResolver.insert(CalendarContract.Attendees.CONTENT_URI, attendee); + assertEquals(null, attendeesUri); + } + + public void testInsertExtendedPropertyToNonExistentEvent() { + ContentValues extended = new ContentValues(); + extended.put(CalendarContract.ExtendedProperties.NAME, "foo"); + extended.put(CalendarContract.ExtendedProperties.VALUE, "bar"); + extended.put(CalendarContract.ExtendedProperties.EVENT_ID, 1); + + Uri extendedUri = mResolver.insert( + updatedUri(CalendarContract.ExtendedProperties.CONTENT_URI, true, + DEFAULT_ACCOUNT, DEFAULT_ACCOUNT_TYPE), extended); + assertEquals(null, extendedUri); + } + void checkEvents(int count, SQLiteDatabase db) { Cursor cursor = db.query("Events", null, null, null, null, null, null); try { |