X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=src%2Fcom%2Fandroid%2Fcalendar%2FEditEvent.java;h=b4ce580c0149aa4f7e1d7496d70fccfa1d8a30cf;hb=ed912e5a595274c54ec2d52b9a7868c2063048bb;hp=a772bb7401c27a91166c9bc24a82f88f86b8da70;hpb=62fc24304588776305d9f3550149f72995231774;p=android-x86%2Fpackages-apps-Calendar.git diff --git a/src/com/android/calendar/EditEvent.java b/src/com/android/calendar/EditEvent.java index a772bb7..b4ce580 100644 --- a/src/com/android/calendar/EditEvent.java +++ b/src/com/android/calendar/EditEvent.java @@ -18,6 +18,11 @@ package com.android.calendar; import static android.provider.Calendar.EVENT_BEGIN_TIME; import static android.provider.Calendar.EVENT_END_TIME; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.app.Activity; import android.app.AlertDialog; import android.app.DatePickerDialog; @@ -26,28 +31,41 @@ import android.app.TimePickerDialog; import android.app.DatePickerDialog.OnDateSetListener; import android.app.TimePickerDialog.OnTimeSetListener; import android.content.AsyncQueryHandler; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.OperationApplicationException; import android.content.SharedPreferences; +import android.content.ContentProviderOperation.Builder; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.RemoteException; import android.pim.EventRecurrence; import android.preference.PreferenceManager; +import android.provider.Calendar.Attendees; import android.provider.Calendar.Calendars; import android.provider.Calendar.Events; import android.provider.Calendar.Reminders; +import android.text.Editable; +import android.text.InputFilter; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.text.format.Time; +import android.text.util.Rfc822Token; +import android.text.util.Rfc822Tokenizer; +import android.text.util.Rfc822Validator; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -62,12 +80,16 @@ import android.widget.CompoundButton; import android.widget.DatePicker; import android.widget.ImageButton; import android.widget.LinearLayout; +import android.widget.MultiAutoCompleteTextView; import android.widget.ResourceCursorAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.TimePicker; import android.widget.Toast; +import com.google.android.googlelogin.GoogleLoginServiceConstants; + +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -75,6 +97,9 @@ import java.util.TimeZone; public class EditEvent extends Activity implements View.OnClickListener, DialogInterface.OnCancelListener, DialogInterface.OnClickListener { + private static final String TAG = "EditEvent"; + private static final boolean DEBUG = false; + /** * This is the symbolic name for the key used to pass in the boolean * for creating all-day events that is part of the extra data of the intent. @@ -125,12 +150,12 @@ public class EditEvent extends Activity implements View.OnClickListener, private static final int EVENT_INDEX_VISIBILITY = 13; private static final String[] CALENDARS_PROJECTION = new String[] { - Calendars._ID, // 0 - Calendars.DISPLAY_NAME, // 1 - Calendars.TIMEZONE, // 2 + Calendars._ID, // 0 + Calendars.DISPLAY_NAME, // 1 + Calendars.OWNER_ACCOUNT, // 2 }; private static final int CALENDARS_INDEX_DISPLAY_NAME = 1; - private static final int CALENDARS_INDEX_TIMEZONE = 2; + private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2; private static final String CALENDARS_WHERE = Calendars.ACCESS_LEVEL + ">=" + Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SYNC_EVENTS + "=1"; @@ -143,6 +168,15 @@ public class EditEvent extends Activity implements View.OnClickListener, Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" + Reminders.METHOD_DEFAULT + ")"; + private static final String[] ATTENDEES_PROJECTION = new String[] { + Attendees.ATTENDEE_NAME, // 0 + Attendees.ATTENDEE_EMAIL, // 1 + }; + private static final int ATTENDEES_INDEX_NAME = 0; + private static final int ATTENDEES_INDEX_EMAIL = 1; + private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=? AND " + + Attendees.ATTENDEE_RELATIONSHIP + "<>" + Attendees.RELATIONSHIP_ORGANIZER; + private static final int DOES_NOT_REPEAT = 0; private static final int REPEATS_DAILY = 1; private static final int REPEATS_EVERY_WEEKDAY = 2; @@ -156,7 +190,7 @@ public class EditEvent extends Activity implements View.OnClickListener, private static final int MODIFY_SELECTED = 1; private static final int MODIFY_ALL = 2; private static final int MODIFY_ALL_FOLLOWING = 3; - + private static final int DAY_IN_SECONDS = 24 * 60 * 60; private int mFirstDayOfWeek; // cached in onCreate @@ -184,6 +218,9 @@ public class EditEvent extends Activity implements View.OnClickListener, private LinearLayout mExtraOptions; private ArrayList mOriginalMinutes = new ArrayList(); private ArrayList mReminderItems = new ArrayList(0); + MultiAutoCompleteTextView mAttendeesList; + private EmailAddressAdapter mAddressAdapter; + private String mOriginalAttendees = ""; private EventRecurrence mEventRecurrence = new EventRecurrence(); private String mRrule; @@ -214,7 +251,8 @@ public class EditEvent extends Activity implements View.OnClickListener, private DeleteEventHelper mDeleteEventHelper; private QueryHandler mQueryHandler; - + private AccountManager mAccountManager; + /* This class is used to update the time buttons. */ private class TimeListener implements OnTimeSetListener { private View mView; @@ -373,7 +411,7 @@ public class EditEvent extends Activity implements View.OnClickListener, } return; } - + if (v == mDeleteButton) { long begin = mStartTime.toMillis(false /* use isDst */); long end = mEndTime.toMillis(false /* use isDst */); @@ -392,12 +430,12 @@ public class EditEvent extends Activity implements View.OnClickListener, mDeleteEventHelper.delete(begin, end, mEventCursor, which); return; } - + if (v == mDiscardButton) { finish(); return; } - + // This must be a click on one of the "remove reminder" buttons LinearLayout reminderItem = (LinearLayout) v.getParent(); LinearLayout parent = (LinearLayout) reminderItem.getParent(); @@ -426,7 +464,7 @@ public class EditEvent extends Activity implements View.OnClickListener, finish(); } } - + private class QueryHandler extends AsyncQueryHandler { public QueryHandler(ContentResolver cr) { super(cr); @@ -442,7 +480,7 @@ public class EditEvent extends Activity implements View.OnClickListener, } else { mCalendarsCursor = cursor; startManagingCursor(cursor); - + // Stop the spinner getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_VISIBILITY_OFF); @@ -454,7 +492,7 @@ public class EditEvent extends Activity implements View.OnClickListener, if (mSaveAfterQueryComplete) { mLoadingCalendarsDialog.cancel(); } - + // Create an error message for the user that, when clicked, // will exit this activity without saving the event. AlertDialog.Builder builder = new AlertDialog.Builder(EditEvent.this); @@ -467,15 +505,76 @@ public class EditEvent extends Activity implements View.OnClickListener, return; } + int primaryCalendarPosition = findPrimaryCalendarPosition(); + // populate the calendars spinner CalendarsAdapter adapter = new CalendarsAdapter(EditEvent.this, mCalendarsCursor); mCalendarsSpinner.setAdapter(adapter); + mCalendarsSpinner.setSelection(primaryCalendarPosition); mCalendarsQueryComplete = true; if (mSaveAfterQueryComplete) { mLoadingCalendarsDialog.cancel(); save(); finish(); } + + // Find user domain and set it to the validator + if(cursor.moveToPosition(primaryCalendarPosition)) { + String ownEmail = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); + if (ownEmail != null) { + int separator = ownEmail.lastIndexOf('@'); + if (separator != -1 && ++separator < ownEmail.length()) { + mAttendeesList.setValidator(new Rfc822Validator(ownEmail + .substring(separator))); + } + } + } + } + } + + // Find the calendar position in the cursor that matches the signed-in + // account + private int findPrimaryCalendarPosition() { + int primaryCalendarPosition = -1; + try { + Account[] accounts = mAccountManager.getAccountsByTypeAndFeatures( + GoogleLoginServiceConstants.ACCOUNT_TYPE, new String[] { + GoogleLoginServiceConstants.FEATURE_LEGACY_HOSTED_OR_GOOGLE + }, null, null).getResult(); + if (accounts.length > 0) { + for (int i = 0; i < accounts.length && primaryCalendarPosition == -1; ++i) { + String name = accounts[i].name; + if (name == null) { + continue; + } + + int position = 0; + mCalendarsCursor.moveToPosition(-1); + while (mCalendarsCursor.moveToNext()) { + if (name.equals(mCalendarsCursor + .getString(CALENDARS_INDEX_OWNER_ACCOUNT))) { + primaryCalendarPosition = position; + break; + } + position++; + } + } + } + } catch (OperationCanceledException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (AuthenticatorException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + if (primaryCalendarPosition != -1) { + return primaryCalendarPosition; + } else { + return 0; + } } } } @@ -485,6 +584,7 @@ public class EditEvent extends Activity implements View.OnClickListener, super.onCreate(icicle); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.edit_event); + mAccountManager = AccountManager.get(this); mFirstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek(); @@ -514,7 +614,7 @@ public class EditEvent extends Activity implements View.OnClickListener, String rrule = mEventCursor.getString(EVENT_INDEX_RRULE); String timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE); long calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID); - + // Remember the initial values mInitialValues = new ContentValues(); mInitialValues.put(EVENT_BEGIN_TIME, begin); @@ -527,7 +627,7 @@ public class EditEvent extends Activity implements View.OnClickListener, // We are creating a new event, so set the default from the // intent (if specified). allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false); - + // Start the spinner getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_VISIBILITY_ON); @@ -584,6 +684,9 @@ public class EditEvent extends Activity implements View.OnClickListener, mRemindersContainer = (LinearLayout) findViewById(R.id.reminder_items_container); mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container); + mAddressAdapter = new EmailAddressAdapter(this); + mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees, R.string.hint_attendees); + mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { @@ -648,14 +751,15 @@ public class EditEvent extends Activity implements View.OnClickListener, prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0"); mDefaultReminderMinutes = Integer.parseInt(durationString); + long eventId = (mEventCursor == null) ? -1 : mEventCursor.getLong(EVENT_INDEX_ID); + ContentResolver cr = getContentResolver(); + // Reminders cursor boolean hasAlarm = (mEventCursor != null) && (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0); if (hasAlarm) { Uri uri = Reminders.CONTENT_URI; - long eventId = mEventCursor.getLong(EVENT_INDEX_ID); String where = String.format(REMINDERS_WHERE, eventId); - ContentResolver cr = getContentResolver(); Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null); try { // First pass: collect all the custom reminder minutes (e.g., @@ -664,7 +768,7 @@ public class EditEvent extends Activity implements View.OnClickListener, int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES); EditEvent.addMinutesToList(this, mReminderValues, mReminderLabels, minutes); } - + // Second pass: create the reminder spinners reminderCursor.moveToPosition(-1); while (reminderCursor.moveToNext()) { @@ -684,45 +788,140 @@ public class EditEvent extends Activity implements View.OnClickListener, public void onClick(View v) { addReminder(); } - }; + }; ImageButton reminderRemoveButton = (ImageButton) findViewById(R.id.reminder_add); reminderRemoveButton.setOnClickListener(addReminderOnClickListener); mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */); + // Attendees cursor + if (eventId != -1) { + Uri uri = Attendees.CONTENT_URI; + String[] whereArgs = {Long.toString(eventId)}; + Cursor attendeeCursor = cr.query(uri, ATTENDEES_PROJECTION, ATTENDEES_WHERE, whereArgs, + null); + try { + StringBuilder b = new StringBuilder(); + while (attendeeCursor.moveToNext()) { + String name = attendeeCursor.getString(ATTENDEES_INDEX_NAME); + String email = attendeeCursor.getString(ATTENDEES_INDEX_EMAIL); + if (email != null) { + if (name != null && name.length() > 0 && !name.equals(email)) { + b.append('"').append(name).append("\" "); + } + b.append('<').append(email).append(">, "); + } + } + if (b.length() > 0) { + mOriginalAttendees = b.toString(); + mAttendeesList.setText(mOriginalAttendees); + } + } finally { + attendeeCursor.close(); + } + } if (mEventCursor == null) { // Allow the intent to specify the fields in the event. // This will allow other apps to create events easily. initFromIntent(intent); } } - + + private Rfc822Token[] getAddressesFromList(MultiAutoCompleteTextView list) { + list.clearComposingText(); + return Rfc822Tokenizer.tokenize(list.getText()); + } + + // From com.google.android.gm.ComposeActivity + private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res, int hintId) { + MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) findViewById(res); + list.setAdapter(mAddressAdapter); + list.setTokenizer(new Rfc822Tokenizer()); + + // NOTE: assumes no other filters are set + list.setFilters(sRecipientFilters); + + return list; + } + + /** + * From com.google.android.gm.ComposeActivity + * Implements special address cleanup rules: + * The first space key entry following an "@" symbol that is followed by any combination + * of letters and symbols, including one+ dots and zero commas, should insert an extra + * comma (followed by the space). + */ + private static InputFilter[] sRecipientFilters = new InputFilter[] { new InputFilter() { + + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, + int dstart, int dend) { + + // quick check - did they enter a single space? + if (end-start != 1 || source.charAt(start) != ' ') { + return null; + } + + // determine if the characters before the new space fit the pattern + // follow backwards and see if we find a comma, dot, or @ + int scanBack = dstart; + boolean dotFound = false; + while (scanBack > 0) { + char c = dest.charAt(--scanBack); + switch (c) { + case '.': + dotFound = true; // one or more dots are req'd + break; + case ',': + return null; + case '@': + if (!dotFound) { + return null; + } + // we have found a comma-insert case. now just do it + // in the least expensive way we can. + if (source instanceof Spanned) { + SpannableStringBuilder sb = new SpannableStringBuilder(","); + sb.append(source); + return sb; + } else { + return ", "; + } + default: + // just keep going + } + } + + // no termination cases were found, so don't edit the input + return null; + } + }}; + private void initFromIntent(Intent intent) { String title = intent.getStringExtra(Events.TITLE); if (title != null) { mTitleTextView.setText(title); } - + String location = intent.getStringExtra(Events.EVENT_LOCATION); if (location != null) { mLocationTextView.setText(location); } - + String description = intent.getStringExtra(Events.DESCRIPTION); if (description != null) { mDescriptionTextView.setText(description); } - + int availability = intent.getIntExtra(Events.TRANSPARENCY, -1); if (availability != -1) { mAvailabilitySpinner.setSelection(availability); } - + int visibility = intent.getIntExtra(Events.VISIBILITY, -1); if (visibility != -1) { mVisibilitySpinner.setSelection(visibility); } - + String rrule = intent.getStringExtra(Events.RRULE); if (rrule != null) { mRrule = rrule; @@ -741,7 +940,7 @@ public class EditEvent extends Activity implements View.OnClickListener, return; } } - + if (mEventCursor != null) { Cursor cursor = mEventCursor; cursor.moveToFirst(); @@ -795,7 +994,7 @@ public class EditEvent extends Activity implements View.OnClickListener, } else if (which == 2) { mModification = MODIFY_ALL_FOLLOWING; } - + // If we are modifying all the events in a // series then disable and ignore the date. if (mModification == MODIFY_ALL) { @@ -819,22 +1018,25 @@ public class EditEvent extends Activity implements View.OnClickListener, // since we can't change the calendar. View calendarGroup = findViewById(R.id.calendar_group); calendarGroup.setVisibility(View.GONE); - } else if (Time.isEpoch(mStartTime) && Time.isEpoch(mEndTime)) { - mStartTime.setToNow(); + } else { + // New event + if (Time.isEpoch(mStartTime) && Time.isEpoch(mEndTime)) { + mStartTime.setToNow(); + + // Round the time to the nearest half hour. + mStartTime.second = 0; + int minute = mStartTime.minute; + if (minute > 0 && minute <= 30) { + mStartTime.minute = 30; + } else { + mStartTime.minute = 0; + mStartTime.hour += 1; + } - // Round the time to the nearest half hour. - mStartTime.second = 0; - int minute = mStartTime.minute; - if (minute > 0 && minute <= 30) { - mStartTime.minute = 30; - } else { - mStartTime.minute = 0; - mStartTime.hour += 1; + long startMillis = mStartTime.normalize(true /* ignore isDst */); + mEndTime.set(startMillis + DateUtils.HOUR_IN_MILLIS); } - long startMillis = mStartTime.normalize(true /* ignore isDst */); - mEndTime.set(startMillis + DateUtils.HOUR_IN_MILLIS); - } else { // New event - set the default reminder if (mDefaultReminderMinutes != 0) { addReminder(this, this, mReminderItems, mReminderValues, @@ -1069,7 +1271,7 @@ public class EditEvent extends Activity implements View.OnClickListener, LinearLayout parent = (LinearLayout) activity.findViewById(R.id.reminder_items_container); LinearLayout reminderItem = (LinearLayout) inflater.inflate(R.layout.edit_reminder_item, null); parent.addView(reminderItem); - + Spinner spinner = (Spinner) reminderItem.findViewById(R.id.reminder_value); Resources res = activity.getResources(); spinner.setPrompt(res.getString(R.string.reminders_label)); @@ -1077,7 +1279,7 @@ public class EditEvent extends Activity implements View.OnClickListener, ArrayAdapter adapter = new ArrayAdapter(activity, resource, labels); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); - + ImageButton reminderRemoveButton; reminderRemoveButton = (ImageButton) reminderItem.findViewById(R.id.reminder_remove); reminderRemoveButton.setOnClickListener(listener); @@ -1088,17 +1290,17 @@ public class EditEvent extends Activity implements View.OnClickListener, return true; } - + static void addMinutesToList(Context context, ArrayList values, ArrayList labels, int minutes) { int index = values.indexOf(minutes); if (index != -1) { return; } - + // The requested "minutes" does not exist in the list, so insert it // into the list. - + String label = constructReminderLabel(context, minutes, false); int len = values.size(); for (int i = 0; i < len; i++) { @@ -1108,14 +1310,14 @@ public class EditEvent extends Activity implements View.OnClickListener, return; } } - + values.add(minutes); labels.add(len, label); } - + /** * Finds the index of the given "minutes" in the "values" list. - * + * * @param values the list of minutes corresponding to the spinner choices * @param minutes the minutes to search for in the values list * @return the index of "minutes" in the "values" list @@ -1129,7 +1331,7 @@ public class EditEvent extends Activity implements View.OnClickListener, } return index; } - + // Constructs a label given an arbitrary number of minutes. For example, // if the given minutes is 63, then this returns the string "63 minutes". // As another example, if the given minutes is 120, then this returns @@ -1137,7 +1339,7 @@ public class EditEvent extends Activity implements View.OnClickListener, static String constructReminderLabel(Context context, int minutes, boolean abbrev) { Resources resources = context.getResources(); int value, resId; - + if (minutes % 60 != 0) { value = minutes; if (abbrev) { @@ -1185,7 +1387,7 @@ public class EditEvent extends Activity implements View.OnClickListener, // Saves the event. Returns true if it is okay to exit this activity. private boolean save() { boolean forceSaveReminders = false; - + // If we are creating a new event, then make sure we wait until the // query to fetch the list of calendars has finished. if (mEventCursor == null) { @@ -1215,30 +1417,40 @@ public class EditEvent extends Activity implements View.OnClickListener, Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show(); } - ContentResolver cr = getContentResolver(); + ArrayList ops = new ArrayList(); + int eventIdIndex = -1; + ContentValues values = getContentValuesFromUi(); Uri uri = mUri; + // Update the "hasAlarm" field for the event + ArrayList reminderMinutes = reminderItemsToMinutes(mReminderItems, + mReminderValues); + int len = reminderMinutes.size(); + values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0); + // For recurring events, we must make sure that we use duration rather // than dtend. if (uri == null) { // Create new event with new contents addRecurrenceRule(values); - uri = cr.insert(Events.CONTENT_URI, values); + eventIdIndex = ops.size(); + Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values); + ops.add(b.build()); forceSaveReminders = true; } else if (mRrule == null) { // Modify contents of a non-repeating event addRecurrenceRule(values); checkTimeDependentFields(values); - cr.update(uri, values, null, null); - + ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); + } else if (mInitialValues.getAsString(Events.RRULE) == null) { // This event was changed from a non-repeating event to a // repeating event. addRecurrenceRule(values); values.remove(Events.DTEND); - cr.update(uri, values, null, null); + ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); } else if (mModification == MODIFY_SELECTED) { // Modify contents of the current instance of repeating event @@ -1250,7 +1462,9 @@ public class EditEvent extends Activity implements View.OnClickListener, boolean allDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0; values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0); - uri = cr.insert(Events.CONTENT_URI, values); + eventIdIndex = ops.size(); + Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values); + ops.add(b.build()); forceSaveReminders = true; } else if (mModification == MODIFY_ALL_FOLLOWING) { @@ -1263,56 +1477,157 @@ public class EditEvent extends Activity implements View.OnClickListener, // then delete the whole series. Otherwise, update the series // to end at the new start time. if (isFirstEventInSeries()) { - cr.delete(uri, null, null); + ops.add(ContentProviderOperation.newDelete(uri).build()); } else { // Update the current repeating event to end at the new // start time. - updatePastEvents(cr, uri); + updatePastEvents(ops, uri); } - uri = cr.insert(Events.CONTENT_URI, values); + eventIdIndex = ops.size(); + ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values) + .build()); } else { if (isFirstEventInSeries()) { checkTimeDependentFields(values); values.remove(Events.DTEND); - cr.update(uri, values, null, null); + Builder b = ContentProviderOperation.newUpdate(uri).withValues(values); + ops.add(b.build()); } else { // Update the current repeating event to end at the new // start time. - updatePastEvents(cr, uri); + updatePastEvents(ops, uri); // Create a new event with the user-modified fields values.remove(Events.DTEND); - uri = cr.insert(Events.CONTENT_URI, values); + eventIdIndex = ops.size(); + ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues( + values).build()); } } forceSaveReminders = true; } else if (mModification == MODIFY_ALL) { - + // Modify all instances of repeating event addRecurrenceRule(values); - + if (mRrule == null) { // We've changed a recurring event to a non-recurring event. // Delete the whole series and replace it with a new // non-recurring event. - cr.delete(uri, null, null); - uri = cr.insert(Events.CONTENT_URI, values); + ops.add(ContentProviderOperation.newDelete(uri).build()); + + eventIdIndex = ops.size(); + ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values) + .build()); forceSaveReminders = true; } else { checkTimeDependentFields(values); values.remove(Events.DTEND); - cr.update(uri, values, null, null); + ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); } } - if (uri != null) { + if (eventIdIndex != -1) { + saveRemindersWithBackRef(ops, eventIdIndex, reminderMinutes, mOriginalMinutes, + forceSaveReminders); + } else if (uri != null) { long eventId = ContentUris.parseId(uri); - ArrayList reminderMinutes = reminderItemsToMinutes(mReminderItems, - mReminderValues); - saveReminders(cr, eventId, reminderMinutes, mOriginalMinutes, + saveReminders(ops, eventId, reminderMinutes, mOriginalMinutes, forceSaveReminders); } + + Builder b; + + // New event/instance - Set Organizer's response as yes + if (eventIdIndex != -1) { + values.clear(); + int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition(); + if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) { + String ownerEmail = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); + if (ownerEmail != null) { + String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); + if (displayName != null) { + values.put(Attendees.ATTENDEE_NAME, displayName); + } + values.put(Attendees.ATTENDEE_EMAIL, ownerEmail); + values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER); + values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE); + values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED); + + b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) + .withValues(values); + b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex); + ops.add(b.build()); + } + } + } + + if (eventIdIndex != -1 || uri != null) { + Editable attendeesText = mAttendeesList.getText(); + // Hit the content provider only if the user has changed it + if (!mOriginalAttendees.equals(attendeesText.toString())) { + // TODO we could do a diff and modify the rows only as needed + // Delete all the existing attendees for this event + b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI); + + long eventId = -1; + if (eventIdIndex == -1) { + eventId = ContentUris.parseId(uri); + String[] args = new String[] { + Long.toString(eventId) + }; + b.withSelection(ATTENDEES_WHERE, args); + } else { + // Delete all the existing reminders for this event + b.withSelection(ATTENDEES_WHERE, new String[1]); + b.withSelectionBackReference(0, eventIdIndex); + } + ops.add(b.build()); + + if (attendeesText.length() > 0) { + Rfc822Token[] attendees = getAddressesFromList(mAttendeesList); + // Insert the attendees + for (Rfc822Token attendee : attendees) { + values.clear(); + values.put(Attendees.ATTENDEE_NAME, attendee.getName()); + values.put(Attendees.ATTENDEE_EMAIL, attendee.getAddress()); + values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); + values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE); + values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE); + + if (eventIdIndex != -1) { + b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) + .withValues(values); + b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex); + } else { + values.put(Attendees.EVENT_ID, eventId); + b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI) + .withValues(values); + } + ops.add(b.build()); + } + } + } + } + + try { + // TODO Move this to background thread + ContentProviderResult[] results = + getContentResolver().applyBatch(android.provider.Calendar.AUTHORITY, ops); + if (DEBUG) { + for (int i = 0; i < results.length; i++) { + Log.v(TAG, "results = " + results[i].toString()); + } + } + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (OperationApplicationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return true; } @@ -1322,7 +1637,7 @@ public class EditEvent extends Activity implements View.OnClickListener, return start == mStartTime.toMillis(true); } - private void updatePastEvents(ContentResolver cr, Uri uri) { + private void updatePastEvents(ArrayList ops, Uri uri) { long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART); String oldDuration = mEventCursor.getString(EVENT_INDEX_DURATION); boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0; @@ -1338,17 +1653,17 @@ public class EditEvent extends Activity implements View.OnClickListener, // must include just the date field, and not the time field. The // repeating events repeat up to and including the "until" time. untilTime.timezone = Time.TIMEZONE_UTC; - + // Subtract one second from the old begin time to get the new // "until" time. - untilTime.set(begin - 1000); // subtract one second (1000 millis) + untilTime.set(begin - 1000); // subtract one second (1000 millis) if (allDay) { untilTime.hour = 0; untilTime.minute = 0; untilTime.second = 0; untilTime.allDay = true; untilTime.normalize(false); - + // For all-day events, the duration must be in days, not seconds. // Otherwise, Google Calendar will (mistakenly) change this event // into a non-all-day event. @@ -1364,7 +1679,8 @@ public class EditEvent extends Activity implements View.OnClickListener, oldValues.put(Events.DTSTART, oldStartMillis); oldValues.put(Events.DURATION, oldDuration); oldValues.put(Events.RRULE, mEventRecurrence.toString()); - cr.update(uri, oldValues, null, null); + Builder b = ContentProviderOperation.newUpdate(uri).withValues(oldValues); + ops.add(b.build()); } private void checkTimeDependentFields(ContentValues values) { @@ -1373,13 +1689,13 @@ public class EditEvent extends Activity implements View.OnClickListener, boolean oldAllDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0; String oldRrule = mInitialValues.getAsString(Events.RRULE); String oldTimezone = mInitialValues.getAsString(Events.EVENT_TIMEZONE); - + long newBegin = values.getAsLong(Events.DTSTART); long newEnd = values.getAsLong(Events.DTEND); boolean newAllDay = values.getAsInteger(Events.ALL_DAY) != 0; String newRrule = values.getAsString(Events.RRULE); String newTimezone = values.getAsString(Events.EVENT_TIMEZONE); - + // If none of the time-dependent fields changed, then remove them. if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay && TextUtils.equals(oldRrule, newRrule) @@ -1414,7 +1730,7 @@ public class EditEvent extends Activity implements View.OnClickListener, values.put(Events.DTSTART, oldStartMillis); } } - + static ArrayList reminderItemsToMinutes(ArrayList reminderItems, ArrayList reminderValues) { int len = reminderItems.size(); @@ -1431,8 +1747,8 @@ public class EditEvent extends Activity implements View.OnClickListener, /** * Saves the reminders, if they changed. Returns true if the database * was updated. - * - * @param cr the ContentResolver + * + * @param ops the array of ContentProviderOperations * @param eventId the id of the event whose reminders are being updated * @param reminderMinutes the array of reminders set by the user * @param originalMinutes the original array of reminders @@ -1440,7 +1756,7 @@ public class EditEvent extends Activity implements View.OnClickListener, * change * @return true if the database was updated */ - static boolean saveReminders(ContentResolver cr, long eventId, + static boolean saveReminders(ArrayList ops, long eventId, ArrayList reminderMinutes, ArrayList originalMinutes, boolean forceSave) { // If the reminders have not changed, then don't update the database @@ -1451,14 +1767,12 @@ public class EditEvent extends Activity implements View.OnClickListener, // Delete all the existing reminders for this event String where = Reminders.EVENT_ID + "=?"; String[] args = new String[] { Long.toString(eventId) }; - cr.delete(Reminders.CONTENT_URI, where, args); + Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI); + b.withSelection(where, args); + ops.add(b.build()); - // Update the "hasAlarm" field for the event ContentValues values = new ContentValues(); int len = reminderMinutes.size(); - values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0); - Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); - cr.update(uri, values, null /* where */, null /* selection args */); // Insert the new reminders, if any for (int i = 0; i < len; i++) { @@ -1468,7 +1782,39 @@ public class EditEvent extends Activity implements View.OnClickListener, values.put(Reminders.MINUTES, minutes); values.put(Reminders.METHOD, Reminders.METHOD_ALERT); values.put(Reminders.EVENT_ID, eventId); - cr.insert(Reminders.CONTENT_URI, values); + b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values); + ops.add(b.build()); + } + return true; + } + + static boolean saveRemindersWithBackRef(ArrayList ops, + int eventIdIndex, ArrayList reminderMinutes, + ArrayList originalMinutes, boolean forceSave) { + // If the reminders have not changed, then don't update the database + if (reminderMinutes.equals(originalMinutes) && !forceSave) { + return false; + } + + // Delete all the existing reminders for this event + Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI); + b.withSelection(Reminders.EVENT_ID + "=?", new String[1]); + b.withSelectionBackReference(0, eventIdIndex); + ops.add(b.build()); + + ContentValues values = new ContentValues(); + int len = reminderMinutes.size(); + + // Insert the new reminders, if any + for (int i = 0; i < len; i++) { + int minutes = reminderMinutes.get(i); + + values.clear(); + values.put(Reminders.MINUTES, minutes); + values.put(Reminders.METHOD, Reminders.METHOD_ALERT); + b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values); + b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex); + ops.add(b.build()); } return true; } @@ -1479,7 +1825,7 @@ public class EditEvent extends Activity implements View.OnClickListener, if (mRrule == null) { return; } - + values.put(Events.RRULE, mRrule); long end = mEndTime.toMillis(true /* ignore dst */); long start = mStartTime.toMillis(true /* ignore dst */); @@ -1599,7 +1945,7 @@ public class EditEvent extends Activity implements View.OnClickListener, mEndTime.monthDay++; mEndTime.timezone = timezone; endMillis = mEndTime.normalize(true); - + if (mEventCursor == null) { // This is a new event calendarId = mCalendarsSpinner.getSelectedItemId(); @@ -1612,7 +1958,7 @@ public class EditEvent extends Activity implements View.OnClickListener, if (mEventCursor != null) { // This is an existing event timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE); - + // The timezone might be null if we are changing an existing // all-day event to a non-all-day event. We need to assign // a timezone to the non-all-day event. @@ -1623,7 +1969,7 @@ public class EditEvent extends Activity implements View.OnClickListener, } else { // This is a new event calendarId = mCalendarsSpinner.getSelectedItemId(); - + // The timezone for a new event is the currently displayed // timezone, NOT the timezone of the containing calendar. timezone = TimeZone.getDefault().getID();