summaryrefslogtreecommitdiffstats
path: root/src/com/android/calendar/EditEvent.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calendar/EditEvent.java')
-rw-r--r--src/com/android/calendar/EditEvent.java1456
1 files changed, 1456 insertions, 0 deletions
diff --git a/src/com/android/calendar/EditEvent.java b/src/com/android/calendar/EditEvent.java
new file mode 100644
index 00000000..8c11974b
--- /dev/null
+++ b/src/com/android/calendar/EditEvent.java
@@ -0,0 +1,1456 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calendar;
+
+import static android.provider.Calendar.EVENT_BEGIN_TIME;
+import static android.provider.Calendar.EVENT_END_TIME;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.pim.EventRecurrence;
+import android.pim.Time;
+import android.preference.PreferenceManager;
+import android.provider.Calendar.Calendars;
+import android.provider.Calendar.Events;
+import android.provider.Calendar.Reminders;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.DatePicker;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ResourceCursorAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TimePicker;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+
+public class EditEvent extends Activity implements View.OnClickListener {
+ /**
+ * 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.
+ * This is used only for creating new events and is set to true if
+ * the default for the new event should be an all-day event.
+ */
+ public static final String EVENT_ALL_DAY = "allDay";
+
+ private static final int MAX_REMINDERS = 5;
+
+ private static final int MENU_GROUP_REMINDER = 1;
+ private static final int MENU_GROUP_SHOW_OPTIONS = 2;
+ private static final int MENU_GROUP_HIDE_OPTIONS = 3;
+
+ private static final int MENU_ADD_REMINDER = 1;
+ private static final int MENU_SHOW_EXTRA_OPTIONS = 2;
+ private static final int MENU_HIDE_EXTRA_OPTIONS = 3;
+
+ private static final String[] EVENT_PROJECTION = new String[] {
+ Events._ID, // 0
+ Events.TITLE, // 1
+ Events.DESCRIPTION, // 2
+ Events.EVENT_LOCATION, // 3
+ Events.ALL_DAY, // 4
+ Events.HAS_ALARM, // 5
+ Events.CALENDAR_ID, // 6
+ Events.DTSTART, // 7
+ Events.DURATION, // 8
+ Events.EVENT_TIMEZONE, // 9
+ Events.RRULE, // 10
+ Events._SYNC_ID, // 11
+ Events.TRANSPARENCY, // 12
+ Events.VISIBILITY, // 13
+ };
+ private static final int EVENT_INDEX_ID = 0;
+ private static final int EVENT_INDEX_TITLE = 1;
+ private static final int EVENT_INDEX_DESCRIPTION = 2;
+ private static final int EVENT_INDEX_EVENT_LOCATION = 3;
+ private static final int EVENT_INDEX_ALL_DAY = 4;
+ private static final int EVENT_INDEX_HAS_ALARM = 5;
+ private static final int EVENT_INDEX_CALENDAR_ID = 6;
+ private static final int EVENT_INDEX_DTSTART = 7;
+ private static final int EVENT_INDEX_DURATION = 8;
+ private static final int EVENT_INDEX_TIMEZONE = 9;
+ private static final int EVENT_INDEX_RRULE = 10;
+ private static final int EVENT_INDEX_SYNC_ID = 11;
+ private static final int EVENT_INDEX_TRANSPARENCY = 12;
+ 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
+ };
+ private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
+ private static final int CALENDARS_INDEX_TIMEZONE = 2;
+ private static final String CALENDARS_WHERE = Calendars.ACCESS_LEVEL + ">=" +
+ Calendars.CONTRIBUTOR_ACCESS;
+
+ private static final String[] REMINDERS_PROJECTION = new String[] {
+ Reminders._ID, // 0
+ Reminders.MINUTES, // 1
+ };
+ private static final int REMINDERS_INDEX_MINUTES = 1;
+ private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=%d AND (" +
+ Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
+ Reminders.METHOD_DEFAULT + ")";
+
+ private static final int DOES_NOT_REPEAT = 0;
+ private static final int REPEATS_DAILY = 1;
+ private static final int REPEATS_EVERY_WEEKDAY = 2;
+ private static final int REPEATS_WEEKLY_ON_DAY = 3;
+ private static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4;
+ private static final int REPEATS_MONTHLY_ON_DAY = 5;
+ private static final int REPEATS_YEARLY = 6;
+ private static final int REPEATS_CUSTOM = 7;
+
+ private static final int MODIFY_UNINITIALIZED = 0;
+ private static final int MODIFY_SELECTED = 1;
+ private static final int MODIFY_ALL = 2;
+ private static final int MODIFY_ALL_FOLLOWING = 3;
+
+ private int mFirstDayOfWeek; // cached in onCreate
+ private Uri mUri;
+ private Cursor mEventCursor;
+ private Cursor mCalendarsCursor;
+
+ private Button mStartDateButton;
+ private Button mEndDateButton;
+ private Button mStartTimeButton;
+ private Button mEndTimeButton;
+ private Button mSaveButton;
+ private Button mDeleteButton;
+ private Button mDiscardButton;
+ private CheckBox mAllDayCheckBox;
+ private Spinner mCalendarsSpinner;
+ private Spinner mRepeatsSpinner;
+ private Spinner mAvailabilitySpinner;
+ private Spinner mVisibilitySpinner;
+ private TextView mTitleTextView;
+ private TextView mLocationTextView;
+ private TextView mDescriptionTextView;
+ private View mRemindersSeparator;
+ private LinearLayout mRemindersContainer;
+ private LinearLayout mExtraOptions;
+ private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
+ private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
+
+ private EventRecurrence mEventRecurrence = new EventRecurrence();
+ private String mRrule;
+ private ContentValues mInitialValues;
+
+ /**
+ * If the repeating event is created on the phone and it hasn't been
+ * synced yet to the web server, then there is a bug where you can't
+ * delete or change an instance of the repeating event. This case
+ * can be detected with mSyncId. If mSyncId == null, then the repeating
+ * event has not been synced to the phone, in which case we won't allow
+ * the user to change one instance.
+ */
+ private String mSyncId;
+
+ private ArrayList<Integer> mRecurrenceIndexes = new ArrayList<Integer> (0);
+ private ArrayList<Integer> mReminderValues;
+ private ArrayList<String> mReminderLabels;
+
+ private Time mStartTime;
+ private Time mEndTime;
+ private int mModification = MODIFY_UNINITIALIZED;
+ private int mDefaultReminderMinutes;
+
+ private DeleteEventHelper mDeleteEventHelper;
+
+ /* This class is used to update the time buttons. */
+ private class TimeListener implements OnTimeSetListener {
+ private View mView;
+
+ public TimeListener(View view) {
+ mView = view;
+ }
+
+ public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
+ // Cache the member variables locally to avoid inner class overhead.
+ Time startTime = mStartTime;
+ Time endTime = mEndTime;
+
+ // Cache the start and end millis so that we limit the number
+ // of calls to normalize() and toMillis(), which are fairly
+ // expensive.
+ long startMillis;
+ long endMillis;
+ if (mView == mStartTimeButton) {
+ // The start time was changed.
+ int hourDuration = endTime.hour - startTime.hour;
+ int minuteDuration = endTime.minute - startTime.minute;
+
+ startTime.hour = hourOfDay;
+ startTime.minute = minute;
+ startMillis = startTime.normalize(true);
+
+ // Also update the end time to keep the duration constant.
+ endTime.hour = hourOfDay + hourDuration;
+ endTime.minute = minute + minuteDuration;
+ endMillis = endTime.normalize(true);
+ } else {
+ // The end time was changed.
+ startMillis = startTime.toMillis(true);
+ endTime.hour = hourOfDay;
+ endTime.minute = minute;
+ endMillis = endTime.normalize(true);
+
+ // Do not allow an event to have an end time before the start time.
+ if (endTime.before(startTime)) {
+ endTime.set(startTime);
+ endMillis = startMillis;
+ }
+ }
+
+ setDate(mEndDateButton, endMillis);
+ setTime(mStartTimeButton, startMillis);
+ setTime(mEndTimeButton, endMillis);
+ }
+ }
+
+ private class TimeClickListener implements View.OnClickListener {
+ private Time mTime;
+
+ public TimeClickListener(Time time) {
+ mTime = time;
+ }
+
+ public void onClick(View v) {
+ new TimePickerDialog(EditEvent.this, new TimeListener(v),
+ mTime.hour, mTime.minute,
+ DateFormat.is24HourFormat(EditEvent.this)).show();
+ }
+ }
+
+ private class DateListener implements OnDateSetListener {
+ View mView;
+
+ public DateListener(View view) {
+ mView = view;
+ }
+
+ public void onDateSet(DatePicker view, int year, int month, int monthDay) {
+ // Cache the member variables locally to avoid inner class overhead.
+ Time startTime = mStartTime;
+ Time endTime = mEndTime;
+
+ // Cache the start and end millis so that we limit the number
+ // of calls to normalize() and toMillis(), which are fairly
+ // expensive.
+ long startMillis;
+ long endMillis;
+ if (mView == mStartDateButton) {
+ // The start date was changed.
+ int yearDuration = endTime.year - startTime.year;
+ int monthDuration = endTime.month - startTime.month;
+ int monthDayDuration = endTime.monthDay - startTime.monthDay;
+
+ startTime.year = year;
+ startTime.month = month;
+ startTime.monthDay = monthDay;
+ startMillis = startTime.normalize(true);
+
+ // Also update the end date to keep the duration constant.
+ endTime.year = year + yearDuration;
+ endTime.month = month + monthDuration;
+ endTime.monthDay = monthDay + monthDayDuration;
+ endMillis = endTime.normalize(true);
+
+ // If the start date has changed then update the repeats.
+ populateRepeats();
+ } else {
+ // The end date was changed.
+ startMillis = startTime.toMillis(true);
+ endTime.year = year;
+ endTime.month = month;
+ endTime.monthDay = monthDay;
+ endMillis = endTime.normalize(true);
+
+ // Do not allow an event to have an end time before the start time.
+ if (endTime.before(startTime)) {
+ endTime.set(startTime);
+ endMillis = startMillis;
+ }
+ }
+
+ setDate(mStartDateButton, startMillis);
+ setDate(mEndDateButton, endMillis);
+ setTime(mEndTimeButton, endMillis); // In case end time had to be reset
+ }
+ }
+
+ private class DateClickListener implements View.OnClickListener {
+ private Time mTime;
+
+ public DateClickListener(Time time) {
+ mTime = time;
+ }
+
+ public void onClick(View v) {
+ new DatePickerDialog(EditEvent.this, new DateListener(v), mTime.year,
+ mTime.month, mTime.monthDay).show();
+ }
+ }
+
+ private class CalendarsAdapter extends ResourceCursorAdapter {
+ public CalendarsAdapter(Context context, Cursor c) {
+ super(context, R.layout.calendars_item, c);
+ setDropDownViewResource(R.layout.calendars_dropdown_item);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView name = (TextView) view.findViewById(R.id.calendar_name);
+ name.setText(cursor.getString(CALENDARS_INDEX_DISPLAY_NAME));
+ }
+ }
+
+ // This is called if the user clicks on one of the buttons: "Save",
+ // "Discard", or "Delete". This is also called if the user clicks
+ // on the "remove reminder" button.
+ public void onClick(View v) {
+ if (v == mSaveButton) {
+ save();
+ finish();
+ return;
+ }
+
+ if (v == mDeleteButton) {
+ long begin = mStartTime.toMillis(false /* use isDst */);
+ long end = mEndTime.toMillis(false /* use isDst */);
+ int which = -1;
+ switch (mModification) {
+ case MODIFY_SELECTED:
+ which = DeleteEventHelper.DELETE_SELECTED;
+ break;
+ case MODIFY_ALL_FOLLOWING:
+ which = DeleteEventHelper.DELETE_ALL_FOLLOWING;
+ break;
+ case MODIFY_ALL:
+ which = DeleteEventHelper.DELETE_ALL;
+ break;
+ }
+ 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();
+ parent.removeView(reminderItem);
+ mReminderItems.remove(reminderItem);
+ updateRemindersVisibility();
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.edit_event);
+
+ mFirstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
+
+ mStartTime = new Time();
+ mEndTime = new Time();
+
+ Intent intent = getIntent();
+ mUri = intent.getData();
+
+ if (mUri != null) {
+ mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null);
+ }
+
+ long begin = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
+ long end = intent.getLongExtra(EVENT_END_TIME, 0);
+
+ boolean allDay = false;
+ if (mEventCursor != null) {
+ // The event already exists so fetch the all-day status
+ mEventCursor.moveToFirst();
+ allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
+ String rrule = mEventCursor.getString(EVENT_INDEX_RRULE);
+ String timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
+
+ // Remember the initial values
+ mInitialValues = new ContentValues();
+ mInitialValues.put(EVENT_BEGIN_TIME, begin);
+ mInitialValues.put(EVENT_END_TIME, end);
+ mInitialValues.put(Events.ALL_DAY, allDay);
+ mInitialValues.put(Events.RRULE, rrule);
+ mInitialValues.put(Events.EVENT_TIMEZONE, timezone);
+ } else {
+ // We are creating a new event, so set the default from the
+ // intent (if specified).
+ allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false);
+ }
+
+ // If the event is all-day, read the times in UTC timezone
+ if (begin != 0) {
+ if (allDay) {
+ String tz = mStartTime.timezone;
+ mStartTime.timezone = Time.TIMEZONE_UTC;
+ mStartTime.set(begin);
+ mStartTime.timezone = tz;
+
+ // Calling normalize to calculate isDst
+ mStartTime.normalize(true);
+ } else {
+ mStartTime.set(begin);
+ }
+ }
+
+ if (end != 0) {
+ if (allDay) {
+ String tz = mStartTime.timezone;
+ mEndTime.timezone = Time.TIMEZONE_UTC;
+ mEndTime.set(end);
+ mEndTime.timezone = tz;
+
+ // Calling normalize to calculate isDst
+ mEndTime.normalize(true);
+ } else {
+ mEndTime.set(end);
+ }
+ }
+
+ mCalendarsCursor = managedQuery(Calendars.CONTENT_URI, CALENDARS_PROJECTION,
+ CALENDARS_WHERE, null);
+
+ // cache all the widgets
+ mTitleTextView = (TextView) findViewById(R.id.title);
+ mLocationTextView = (TextView) findViewById(R.id.location);
+ mDescriptionTextView = (TextView) findViewById(R.id.description);
+ mStartDateButton = (Button) findViewById(R.id.start_date);
+ mEndDateButton = (Button) findViewById(R.id.end_date);
+ mStartTimeButton = (Button) findViewById(R.id.start_time);
+ mEndTimeButton = (Button) findViewById(R.id.end_time);
+ mAllDayCheckBox = (CheckBox) findViewById(R.id.is_all_day);
+ mCalendarsSpinner = (Spinner) findViewById(R.id.calendars);
+ mRepeatsSpinner = (Spinner) findViewById(R.id.repeats);
+ mAvailabilitySpinner = (Spinner) findViewById(R.id.availability);
+ mVisibilitySpinner = (Spinner) findViewById(R.id.visibility);
+ mRemindersSeparator = findViewById(R.id.reminders_separator);
+ mRemindersContainer = (LinearLayout) findViewById(R.id.reminders_container);
+ mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container);
+
+ mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ if (mEndTime.hour == 0 && mEndTime.minute == 0) {
+ mEndTime.monthDay--;
+ long endMillis = mEndTime.normalize(true);
+
+ // Do not allow an event to have an end time before the start time.
+ if (mEndTime.before(mStartTime)) {
+ mEndTime.set(mStartTime);
+ endMillis = mEndTime.normalize(true);
+ }
+ setDate(mEndDateButton, endMillis);
+ setTime(mEndTimeButton, endMillis);
+ }
+
+ mStartTimeButton.setVisibility(View.GONE);
+ mEndTimeButton.setVisibility(View.GONE);
+ } else {
+ if (mEndTime.hour == 0 && mEndTime.minute == 0) {
+ mEndTime.monthDay++;
+ long endMillis = mEndTime.normalize(true);
+ setDate(mEndDateButton, endMillis);
+ setTime(mEndTimeButton, endMillis);
+ }
+
+ mStartTimeButton.setVisibility(View.VISIBLE);
+ mEndTimeButton.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
+ if (allDay) {
+ mAllDayCheckBox.setChecked(true);
+ } else {
+ mAllDayCheckBox.setChecked(false);
+ }
+
+ mSaveButton = (Button) findViewById(R.id.save);
+ mSaveButton.setOnClickListener(this);
+
+ mDeleteButton = (Button) findViewById(R.id.delete);
+ mDeleteButton.setOnClickListener(this);
+
+ mDiscardButton = (Button) findViewById(R.id.discard);
+ mDiscardButton.setOnClickListener(this);
+
+ // Initialize the reminder values array.
+ Resources r = getResources();
+ String[] strings = r.getStringArray(R.array.reminder_minutes_values);
+ int size = strings.length;
+ ArrayList<Integer> list = new ArrayList<Integer>(size);
+ for (int i = 0 ; i < size ; i++) {
+ list.add(Integer.parseInt(strings[i]));
+ }
+ mReminderValues = list;
+ String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
+ mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ String durationString =
+ prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
+ mDefaultReminderMinutes = Integer.parseInt(durationString);
+
+ // 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.,
+ // a reminder of 8 minutes) into a global list.
+ while (reminderCursor.moveToNext()) {
+ 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()) {
+ int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
+ mOriginalMinutes.add(minutes);
+ EditEvent.addReminder(this, this, mReminderItems, mReminderValues,
+ mReminderLabels, minutes);
+ }
+ } finally {
+ reminderCursor.close();
+ }
+ }
+ updateRemindersVisibility();
+
+ mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // populate the calendars spinner
+ mCalendarsSpinner = (Spinner) findViewById(R.id.calendars);
+ CalendarsAdapter adapter = new CalendarsAdapter(this, mCalendarsCursor);
+ mCalendarsSpinner.setAdapter(adapter);
+
+ if (mEventCursor != null) {
+ Cursor cursor = mEventCursor;
+ cursor.moveToFirst();
+
+ mRrule = cursor.getString(EVENT_INDEX_RRULE);
+
+ String title = cursor.getString(EVENT_INDEX_TITLE);
+ String description = cursor.getString(EVENT_INDEX_DESCRIPTION);
+ String location = cursor.getString(EVENT_INDEX_EVENT_LOCATION);
+ long calendarId = cursor.getLong(EVENT_INDEX_CALENDAR_ID);
+ int availability = cursor.getInt(EVENT_INDEX_TRANSPARENCY);
+ int visibility = cursor.getInt(EVENT_INDEX_VISIBILITY);
+ if (visibility > 0) {
+ // For now we the array contains the values 0, 2, and 3. We subtract one to match.
+ visibility--;
+ }
+
+ if (!TextUtils.isEmpty(mRrule) && mModification == MODIFY_UNINITIALIZED) {
+ // If this event has not been synced, then don't allow deleting
+ // or changing a single instance.
+ mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID);
+ mEventRecurrence.parse(mRrule);
+
+ // If we haven't synced this repeating event yet, then don't
+ // allow the user to change just one instance.
+ int itemIndex = 0;
+ CharSequence[] items;
+ if (mSyncId == null) {
+ items = new CharSequence[2];
+ } else {
+ items = new CharSequence[3];
+ items[itemIndex++] = getText(R.string.modify_event);
+ }
+ items[itemIndex++] = getText(R.string.modify_all);
+ items[itemIndex++] = getText(R.string.modify_all_following);
+
+ // Display the modification dialog.
+ new AlertDialog.Builder(this)
+ .setOnCancelListener(new OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ })
+ .setTitle(R.string.edit_event_label)
+ .setItems(items, new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == 0) {
+ mModification =
+ (mSyncId == null) ? MODIFY_ALL : MODIFY_SELECTED;
+ } else if (which == 1) {
+ mModification =
+ (mSyncId == null) ? MODIFY_ALL_FOLLOWING : MODIFY_ALL;
+ } 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) {
+ mStartDateButton.setEnabled(false);
+ mEndDateButton.setEnabled(false);
+ } else if (mModification == MODIFY_SELECTED) {
+ mRepeatsSpinner.setEnabled(false);
+ } else {
+ // We could allow changing the Rrule for
+ // all following instances but we'll
+ // keep it simple for now.
+ mRepeatsSpinner.setEnabled(false);
+ }
+ }
+ })
+ .show();
+ }
+
+ mTitleTextView.setText(title);
+ mLocationTextView.setText(location);
+ mDescriptionTextView.setText(description);
+ mAvailabilitySpinner.setSelection(availability);
+ mVisibilitySpinner.setSelection(visibility);
+
+ // If there is a calendarId set, move the spinner to the proper
+ // position and hide the spinner, since this is an existing event.
+ if (calendarId != -1) {
+ int count = adapter.getCount();
+ for (int pos = 0 ; pos < count ; pos++) {
+ long rowID = adapter.getItemId(pos);
+ if (rowID == calendarId) {
+ mCalendarsSpinner.setSelection(pos);
+ }
+ }
+ }
+ View calendarSeparator = findViewById(R.id.calendar_separator);
+ View calendarLabel = findViewById(R.id.calendar_label);
+ calendarSeparator.setVisibility(View.GONE);
+ calendarLabel.setVisibility(View.GONE);
+ mCalendarsSpinner.setVisibility(View.GONE);
+ } else 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;
+ }
+
+ 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,
+ mReminderLabels, mDefaultReminderMinutes);
+ }
+
+ // Hide delete button
+ mDeleteButton.setVisibility(View.GONE);
+ }
+
+ updateRemindersVisibility();
+ populateWhen();
+ populateRepeats();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItem item;
+ item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
+ R.string.add_new_reminder);
+ item.setIcon(R.drawable.ic_menu_reminder);
+ item.setAlphabeticShortcut('r');
+
+ item = menu.add(MENU_GROUP_SHOW_OPTIONS, MENU_SHOW_EXTRA_OPTIONS, 0,
+ R.string.edit_event_show_extra_options);
+ item.setIcon(R.drawable.ic_menu_show_list);
+ item = menu.add(MENU_GROUP_HIDE_OPTIONS, MENU_HIDE_EXTRA_OPTIONS, 0,
+ R.string.edit_event_hide_extra_options);
+ item.setIcon(R.drawable.ic_menu_show_list);
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (mReminderItems.size() < MAX_REMINDERS) {
+ menu.setGroupVisible(MENU_GROUP_REMINDER, true);
+ menu.setGroupEnabled(MENU_GROUP_REMINDER, true);
+ } else {
+ menu.setGroupVisible(MENU_GROUP_REMINDER, false);
+ menu.setGroupEnabled(MENU_GROUP_REMINDER, false);
+ }
+
+ if (mExtraOptions.getVisibility() == View.VISIBLE) {
+ menu.setGroupVisible(MENU_GROUP_SHOW_OPTIONS, false);
+ menu.setGroupVisible(MENU_GROUP_HIDE_OPTIONS, true);
+ } else {
+ menu.setGroupVisible(MENU_GROUP_SHOW_OPTIONS, true);
+ menu.setGroupVisible(MENU_GROUP_HIDE_OPTIONS, false);
+ }
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ADD_REMINDER:
+ // TODO: when adding a new reminder, make it different from the
+ // last one in the list (if any).
+ if (mDefaultReminderMinutes == 0) {
+ addReminder(this, this, mReminderItems, mReminderValues,
+ mReminderLabels, 10 /* minutes */);
+ } else {
+ addReminder(this, this, mReminderItems, mReminderValues,
+ mReminderLabels, mDefaultReminderMinutes);
+ }
+ updateRemindersVisibility();
+ return true;
+ case MENU_SHOW_EXTRA_OPTIONS:
+ mExtraOptions.setVisibility(View.VISIBLE);
+ return true;
+ case MENU_HIDE_EXTRA_OPTIONS:
+ mExtraOptions.setVisibility(View.GONE);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ // If we are creating a new event, do not create it if the
+ // title, location and description are all empty, in order to
+ // prevent accidental "no subject" event creations.
+ if (mUri != null || !isEmpty()) {
+ save();
+ }
+ break;
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private void populateWhen() {
+ long startMillis = mStartTime.toMillis(false /* use isDst */);
+ long endMillis = mEndTime.toMillis(false /* use isDst */);
+ setDate(mStartDateButton, startMillis);
+ setDate(mEndDateButton, endMillis);
+
+ setTime(mStartTimeButton, startMillis);
+ setTime(mEndTimeButton, endMillis);
+
+ mStartDateButton.setOnClickListener(new DateClickListener(mStartTime));
+ mEndDateButton.setOnClickListener(new DateClickListener(mEndTime));
+
+ mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime));
+ mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime));
+ }
+
+ private void populateRepeats() {
+ Time time = mStartTime;
+ Resources r = getResources();
+ int resource = android.R.layout.simple_spinner_item;
+
+ String[] days = r.getStringArray(R.array.day_labels);
+ String[] ordinals = r.getStringArray(R.array.ordinal_labels);
+
+ // Only display "Custom" in the spinner if the device does not support the
+ // recurrence functionality of the event. Only display every weekday if
+ // the event starts on a weekday.
+ boolean isCustomRecurrence = isCustomRecurrence();
+ boolean isWeekdayEvent = isWeekdayEvent();
+
+ ArrayList<String> repeatArray = new ArrayList<String>(0);
+ ArrayList<Integer> recurrenceIndexes = new ArrayList<Integer>(0);
+
+ repeatArray.add(r.getString(R.string.does_not_repeat));
+ recurrenceIndexes.add(DOES_NOT_REPEAT);
+
+ repeatArray.add(r.getString(R.string.daily));
+ recurrenceIndexes.add(REPEATS_DAILY);
+
+ if (isWeekdayEvent) {
+ repeatArray.add(r.getString(R.string.every_weekday));
+ recurrenceIndexes.add(REPEATS_EVERY_WEEKDAY);
+ }
+
+ String format = r.getString(R.string.weekly);
+ repeatArray.add(String.format(format, time.format("%A")));
+ recurrenceIndexes.add(REPEATS_WEEKLY_ON_DAY);
+
+ // Calculate whether this is the 1st, 2nd, 3rd, 4th, or last appearance of the given day.
+ int dayNumber = (time.monthDay - 1) / 7;
+ format = r.getString(R.string.monthly_on_day_count);
+ repeatArray.add(String.format(format, ordinals[dayNumber], days[time.weekDay]));
+ recurrenceIndexes.add(REPEATS_MONTHLY_ON_DAY_COUNT);
+
+ format = r.getString(R.string.monthly_on_day);
+ repeatArray.add(String.format(format, time.monthDay));
+ recurrenceIndexes.add(REPEATS_MONTHLY_ON_DAY);
+
+ long when = time.toMillis(false);
+ format = r.getString(R.string.yearly);
+ int flags = 0;
+ if (DateFormat.is24HourFormat(this)) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+ repeatArray.add(String.format(format, DateUtils.formatDateRange(when, when, flags)));
+ recurrenceIndexes.add(REPEATS_YEARLY);
+
+ if (isCustomRecurrence) {
+ repeatArray.add(r.getString(R.string.custom));
+ recurrenceIndexes.add(REPEATS_CUSTOM);
+ }
+ mRecurrenceIndexes = recurrenceIndexes;
+
+ int position = recurrenceIndexes.indexOf(DOES_NOT_REPEAT);
+ if (mRrule != null) {
+ if (isCustomRecurrence) {
+ position = recurrenceIndexes.indexOf(REPEATS_CUSTOM);
+ } else {
+ switch (mEventRecurrence.freq) {
+ case EventRecurrence.DAILY:
+ position = recurrenceIndexes.indexOf(REPEATS_DAILY);
+ break;
+ case EventRecurrence.WEEKLY:
+ if (mEventRecurrence.repeatsOnEveryWeekDay()) {
+ position = recurrenceIndexes.indexOf(REPEATS_EVERY_WEEKDAY);
+ } else {
+ position = recurrenceIndexes.indexOf(REPEATS_WEEKLY_ON_DAY);
+ }
+ break;
+ case EventRecurrence.MONTHLY:
+ if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
+ position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY_COUNT);
+ } else {
+ position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY);
+ }
+ break;
+ case EventRecurrence.YEARLY:
+ position = recurrenceIndexes.indexOf(REPEATS_YEARLY);
+ break;
+ }
+ }
+ }
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, resource, repeatArray);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mRepeatsSpinner.setAdapter(adapter);
+ mRepeatsSpinner.setSelection(position);
+ }
+
+ // Adds a reminder to the displayed list of reminders.
+ // Returns true if successfully added reminder, false if no reminders can
+ // be added.
+ static boolean addReminder(Activity activity, View.OnClickListener listener,
+ ArrayList<LinearLayout> items, ArrayList<Integer> values,
+ ArrayList<String> labels, int minutes) {
+
+ if (items.size() >= MAX_REMINDERS) {
+ return false;
+ }
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+ 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();
+ int resource = android.R.layout.simple_spinner_item;
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(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);
+
+ int index = findMinutesInReminderList(values, minutes);
+ spinner.setSelection(index);
+ items.add(reminderItem);
+
+ return true;
+ }
+
+ static void addMinutesToList(Context context, ArrayList<Integer> values,
+ ArrayList<String> 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++) {
+ if (minutes < values.get(i)) {
+ values.add(i, minutes);
+ labels.add(i, label);
+ 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
+ */
+ private static int findMinutesInReminderList(ArrayList<Integer> values, int minutes) {
+ int index = values.indexOf(minutes);
+ if (index == -1) {
+ // This should never happen.
+ Log.e("Cal", "Cannot find minutes (" + minutes + ") in list");
+ return 0;
+ }
+ 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
+ // "2 hours".
+ static String constructReminderLabel(Context context, int minutes, boolean abbrev) {
+ Resources resources = context.getResources();
+ int value, resId;
+
+ if (minutes % 60 != 0) {
+ value = minutes;
+ if (abbrev) {
+ resId = R.plurals.Nmins;
+ } else {
+ resId = R.plurals.Nminutes;
+ }
+ } else if (minutes % (24 * 60) != 0) {
+ value = minutes / 60;
+ resId = R.plurals.Nhours;
+ } else {
+ value = minutes / ( 24 * 60);
+ resId = R.plurals.Ndays;
+ }
+
+ String format = resources.getQuantityString(resId, value);
+ return String.format(format, value);
+ }
+
+ private void updateRemindersVisibility() {
+ if (mReminderItems.size() == 0) {
+ mRemindersSeparator.setVisibility(View.GONE);
+ mRemindersContainer.setVisibility(View.GONE);
+ } else {
+ mRemindersSeparator.setVisibility(View.VISIBLE);
+ mRemindersContainer.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void setDate(TextView view, long millis) {
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+ DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH |
+ DateUtils.FORMAT_ABBREV_WEEKDAY;
+ view.setText(DateUtils.formatDateRange(millis, millis, flags));
+ }
+
+ private void setTime(TextView view, long millis) {
+ int flags = DateUtils.FORMAT_SHOW_TIME;
+ if (DateFormat.is24HourFormat(this)) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+ view.setText(DateUtils.formatDateRange(millis, millis, flags));
+ }
+
+ private void save() {
+ // Avoid saving if the calendars cursor is empty. This shouldn't ever
+ // happen since the setup wizard should ensure the user has a calendar.
+ if (mCalendarsCursor == null || mCalendarsCursor.getCount() == 0) {
+ Log.w("Cal", "The calendars table does not contain any calendars. New event was not "
+ + "created.");
+ return;
+ }
+
+ ContentResolver cr = getContentResolver();
+ ContentValues values = getContentValuesFromUi();
+ Uri uri = mUri;
+
+ // 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);
+
+ } else if (mRrule == null) {
+ // Modify contents of a non-repeating event
+ addRecurrenceRule(values);
+ checkTimeDependentFields(values);
+ cr.update(uri, values, null, null);
+
+ } 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);
+
+ } else if (mModification == MODIFY_SELECTED) {
+ // Modify contents of the current instance of repeating event
+
+ // Create a recurrence exception
+ long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
+ values.put(Events.ORIGINAL_EVENT, mEventCursor.getString(EVENT_INDEX_SYNC_ID));
+ values.put(Events.ORIGINAL_INSTANCE_TIME, begin);
+
+ uri = cr.insert(Events.CONTENT_URI, values);
+
+ } else if (mModification == MODIFY_ALL_FOLLOWING) {
+ // Modify contents of all future instances of repeating event
+
+ // Update the current repeating event to end at the new start time
+ updatePastEvents(cr, uri);
+
+ // Create a new event that has a begin time of now
+ mEventRecurrence.parse(mRrule);
+ addRecurrenceRule(values);
+ values.remove(Events.DTEND);
+ uri = cr.insert(Events.CONTENT_URI, values);
+
+ } else if (mModification == MODIFY_ALL) {
+
+ // Modify all instances of repeating event
+ addRecurrenceRule(values);
+
+ if (mRrule == null) {
+
+ // We've changed a recurring event to non recurring
+ // End the previous events and create a new event
+ // If we're the first even though we just delete and
+ // create a new one.
+ if (isFirstEventInSeries()) {
+ cr.delete(uri, null, null);
+ } else {
+ updatePastEvents(cr, uri);
+ }
+ uri = cr.insert(Events.CONTENT_URI, values);
+ } else {
+ checkTimeDependentFields(values);
+ values.remove(Events.DTEND);
+ cr.update(uri, values, null, null);
+ }
+ }
+
+ if (uri != null) {
+ long eventId = ContentUris.parseId(uri);
+ ArrayList<Integer> reminderMinutes = reminderItemsToMinutes(mReminderItems,
+ mReminderValues);
+ saveReminders(cr, eventId, reminderMinutes, mOriginalMinutes);
+ }
+ }
+
+ private boolean isFirstEventInSeries() {
+ int dtStart = mEventCursor.getColumnIndexOrThrow(Events.DTSTART);
+ long start = mEventCursor.getLong(dtStart);
+ return start == mStartTime.toMillis(true);
+ }
+
+ private void updatePastEvents(ContentResolver cr, Uri uri) {
+ long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
+ String oldDuration = mEventCursor.getString(EVENT_INDEX_DURATION);
+
+ Time oldUntilTime = new Time();
+ long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
+ if (mInitialValues.getAsBoolean(Events.ALL_DAY)) {
+ oldUntilTime.timezone = Time.TIMEZONE_UTC;
+ }
+ oldUntilTime.set(begin);
+ oldUntilTime.second--;
+ oldUntilTime.normalize(false);
+ mEventRecurrence.until = oldUntilTime.format2445();
+
+ ContentValues oldValues = new ContentValues();
+ oldValues.put(Events.DTSTART, oldStartMillis);
+ oldValues.put(Events.DURATION, oldDuration);
+ oldValues.put(Events.RRULE, mEventRecurrence.toString());
+ cr.update(uri, oldValues, null, null);
+ }
+
+ private void checkTimeDependentFields(ContentValues values) {
+ long oldBegin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
+ long oldEnd = mInitialValues.getAsLong(EVENT_END_TIME);
+ boolean oldAllDay = mInitialValues.getAsBoolean(Events.ALL_DAY);
+ 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) == 1;
+ 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)
+ && TextUtils.equals(oldTimezone, newTimezone)) {
+ values.remove(Events.DTSTART);
+ values.remove(Events.DTEND);
+ values.remove(Events.DURATION);
+ values.remove(Events.ALL_DAY);
+ values.remove(Events.RRULE);
+ values.remove(Events.EVENT_TIMEZONE);
+ return;
+ }
+
+ if (oldRrule == null || newRrule == null) {
+ return;
+ }
+
+ // If we are modifying all events then we need to set DTSTART to the
+ // start time of the first event in the series, not the current
+ // date and time. If the start time of the event was changed
+ // (from, say, 3pm to 4pm), then we want to add the time difference
+ // to the start time of the first event in the series (the DTSTART
+ // value). If we are modifying one instance or all following instances,
+ // then we leave the DTSTART field alone.
+ if (mModification == MODIFY_ALL) {
+ long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
+ if (oldBegin != newBegin) {
+ // The user changed the start time of this event
+ long offset = newBegin - oldBegin;
+ oldStartMillis += offset;
+ }
+ values.put(Events.DTSTART, oldStartMillis);
+ }
+ }
+
+ static ArrayList<Integer> reminderItemsToMinutes(ArrayList<LinearLayout> reminderItems,
+ ArrayList<Integer> reminderValues) {
+ int len = reminderItems.size();
+ ArrayList<Integer> reminderMinutes = new ArrayList<Integer>(len);
+ for (int index = 0; index < len; index++) {
+ LinearLayout layout = reminderItems.get(index);
+ Spinner spinner = (Spinner) layout.findViewById(R.id.reminder_value);
+ int minutes = reminderValues.get(spinner.getSelectedItemPosition());
+ reminderMinutes.add(minutes);
+ }
+ return reminderMinutes;
+ }
+
+ static void saveReminders(ContentResolver cr, long eventId,
+ ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes) {
+ // If the reminders have not changed, then don't update the database
+ if (reminderMinutes.equals(originalMinutes)) {
+ return;
+ }
+
+ // 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);
+
+ // 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++) {
+ int minutes = reminderMinutes.get(i);
+
+ values.clear();
+ values.put(Reminders.MINUTES, minutes);
+ values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+ values.put(Reminders.EVENT_ID, eventId);
+ cr.insert(Reminders.CONTENT_URI, values);
+ }
+ }
+
+ private void addRecurrenceRule(ContentValues values) {
+ updateRecurrenceRule();
+
+ if (mRrule == null) {
+ return;
+ }
+
+ values.put(Events.RRULE, mRrule);
+ long end = mEndTime.toMillis(true /* ignore dst */);
+ long start = mStartTime.toMillis(true /* ignore dst */);
+ String duration;
+
+ boolean isAllDay = mAllDayCheckBox.isChecked();
+ if (isAllDay) {
+ long days = (end - start + DateUtils.DAY_IN_MILLIS - 1) / DateUtils.DAY_IN_MILLIS;
+ duration = "P" + days + "D";
+ } else {
+ long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS;
+ duration = "P" + seconds + "S";
+ }
+ values.put(Events.DURATION, duration);
+ }
+
+ private void updateRecurrenceRule() {
+ int position = mRepeatsSpinner.getSelectedItemPosition();
+ int selection = mRecurrenceIndexes.get(position);
+
+ if (selection == DOES_NOT_REPEAT) {
+ mRrule = null;
+ return;
+ } else if (selection == REPEATS_CUSTOM) {
+ // Keep custom recurrence as before.
+ return;
+ } else if (selection == REPEATS_DAILY) {
+ mEventRecurrence.freq = EventRecurrence.DAILY;
+ } else if (selection == REPEATS_EVERY_WEEKDAY) {
+ mEventRecurrence.freq = EventRecurrence.WEEKLY;
+ int dayCount = 5;
+ int[] byday = new int[dayCount];
+ int[] bydayNum = new int[dayCount];
+
+ byday[0] = EventRecurrence.MO;
+ byday[1] = EventRecurrence.TU;
+ byday[2] = EventRecurrence.WE;
+ byday[3] = EventRecurrence.TH;
+ byday[4] = EventRecurrence.FR;
+ for (int day = 0; day < dayCount; day++) {
+ bydayNum[day] = 0;
+ }
+
+ mEventRecurrence.byday = byday;
+ mEventRecurrence.bydayNum = bydayNum;
+ mEventRecurrence.bydayCount = dayCount;
+ } else if (selection == REPEATS_WEEKLY_ON_DAY) {
+ mEventRecurrence.freq = EventRecurrence.WEEKLY;
+ int[] days = new int[1];
+ int dayCount = 1;
+ int[] dayNum = new int[dayCount];
+
+ days[0] = EventRecurrence.timeDay2Day(mStartTime.weekDay);
+ // not sure why this needs to be zero, but set it for now.
+ dayNum[0] = 0;
+
+ mEventRecurrence.byday = days;
+ mEventRecurrence.bydayNum = dayNum;
+ mEventRecurrence.bydayCount = dayCount;
+ } else if (selection == REPEATS_MONTHLY_ON_DAY) {
+ mEventRecurrence.freq = EventRecurrence.MONTHLY;
+ mEventRecurrence.bydayCount = 0;
+ mEventRecurrence.bymonthdayCount = 1;
+ int[] bymonthday = new int[1];
+ bymonthday[0] = mStartTime.monthDay;
+ mEventRecurrence.bymonthday = bymonthday;
+ } else if (selection == REPEATS_MONTHLY_ON_DAY_COUNT) {
+ mEventRecurrence.freq = EventRecurrence.MONTHLY;
+ mEventRecurrence.bydayCount = 1;
+ mEventRecurrence.bymonthdayCount = 0;
+
+ int[] byday = new int[1];
+ int[] bydayNum = new int[1];
+ // Compute the week number (for example, the "2nd" Monday)
+ int dayCount = 1 + ((mStartTime.monthDay - 1) / 7);
+ if (dayCount == 5) {
+ dayCount = -1;
+ }
+ bydayNum[0] = dayCount;
+ byday[0] = EventRecurrence.timeDay2Day(mStartTime.weekDay);
+ mEventRecurrence.byday = byday;
+ mEventRecurrence.bydayNum = bydayNum;
+ } else if (selection == REPEATS_YEARLY) {
+ mEventRecurrence.freq = EventRecurrence.YEARLY;
+ }
+
+ // Set the week start day.
+ mEventRecurrence.wkst = EventRecurrence.calendarDay2Day(mFirstDayOfWeek);
+ mRrule = mEventRecurrence.toString();
+ }
+
+ private ContentValues getContentValuesFromUi() {
+ String title = mTitleTextView.getText().toString();
+ boolean isAllDay = mAllDayCheckBox.isChecked();
+ String location = mLocationTextView.getText().toString();
+ String description = mDescriptionTextView.getText().toString();
+ long calendarId = mCalendarsSpinner.getSelectedItemId();
+ Cursor calendarCursor = (Cursor) mCalendarsSpinner.getSelectedItem();
+
+ ContentValues values = new ContentValues();
+
+ String timezone = null;
+ long startMillis;
+ long endMillis;
+ if (isAllDay) {
+ // Reset start and end time, increment the monthDay by 1, and set
+ // the timezone to UTC, as required for all-day events.
+ timezone = Time.TIMEZONE_UTC;
+ mStartTime.hour = 0;
+ mStartTime.minute = 0;
+ mStartTime.second = 0;
+ mStartTime.timezone = timezone;
+ startMillis = mStartTime.normalize(true);
+
+ mEndTime.hour = 0;
+ mEndTime.minute = 0;
+ mEndTime.second = 0;
+ mEndTime.monthDay++;
+ mEndTime.timezone = timezone;
+ endMillis = mEndTime.normalize(true);
+ } else {
+ startMillis = mStartTime.toMillis(true);
+ endMillis = mEndTime.toMillis(true);
+ if (mEventCursor != null) {
+ timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
+ } else if (calendarCursor != null) {
+ timezone = calendarCursor.getString(CALENDARS_INDEX_TIMEZONE);
+ }
+ }
+
+ values.put(Events.EVENT_TIMEZONE, timezone);
+ values.put(Events.CALENDAR_ID, calendarId);
+ values.put(Events.TITLE, title);
+ values.put(Events.ALL_DAY, isAllDay ? 1 : 0);
+ values.put(Events.DTSTART, startMillis);
+ values.put(Events.DTEND, endMillis);
+ values.put(Events.DESCRIPTION, description);
+ values.put(Events.EVENT_LOCATION, location);
+ values.put(Events.TRANSPARENCY, mAvailabilitySpinner.getSelectedItemPosition());
+
+ int visibility = mVisibilitySpinner.getSelectedItemPosition();
+ if (visibility > 0) {
+ // For now we the array contains the values 0, 2, and 3. We add one to match.
+ visibility++;
+ }
+ values.put(Events.VISIBILITY, visibility);
+
+ return values;
+ }
+
+ private boolean isEmpty() {
+ String title = mTitleTextView.getText().toString();
+ if (title.length() > 0) {
+ return false;
+ }
+
+ String location = mLocationTextView.getText().toString();
+ if (location.length() > 0) {
+ return false;
+ }
+
+ String description = mDescriptionTextView.getText().toString();
+ if (description.length() > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isCustomRecurrence() {
+
+ if (mEventRecurrence.until != null || mEventRecurrence.interval != 0) {
+ return true;
+ }
+
+ if (mEventRecurrence.freq == 0) {
+ return false;
+ }
+
+ switch (mEventRecurrence.freq) {
+ case EventRecurrence.DAILY:
+ return false;
+ case EventRecurrence.WEEKLY:
+ if (mEventRecurrence.repeatsOnEveryWeekDay() && isWeekdayEvent()) {
+ return false;
+ } else if (mEventRecurrence.bydayCount == 1) {
+ return false;
+ }
+ break;
+ case EventRecurrence.MONTHLY:
+ if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
+ return false;
+ } else if (mEventRecurrence.bydayCount == 0 && mEventRecurrence.bymonthdayCount == 1) {
+ return false;
+ }
+ break;
+ case EventRecurrence.YEARLY:
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isWeekdayEvent() {
+ if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) {
+ return true;
+ }
+ return false;
+ }
+}