2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.calendar;
19 import static android.provider.Calendar.EVENT_BEGIN_TIME;
20 import static android.provider.Calendar.EVENT_END_TIME;
22 import android.accounts.Account;
23 import android.accounts.AccountManager;
24 import android.accounts.AuthenticatorException;
25 import android.accounts.OperationCanceledException;
26 import android.app.Activity;
27 import android.app.AlertDialog;
28 import android.app.DatePickerDialog;
29 import android.app.ProgressDialog;
30 import android.app.TimePickerDialog;
31 import android.app.DatePickerDialog.OnDateSetListener;
32 import android.app.TimePickerDialog.OnTimeSetListener;
33 import android.content.AsyncQueryHandler;
34 import android.content.ContentProviderOperation;
35 import android.content.ContentProviderResult;
36 import android.content.ContentResolver;
37 import android.content.ContentUris;
38 import android.content.ContentValues;
39 import android.content.Context;
40 import android.content.DialogInterface;
41 import android.content.Intent;
42 import android.content.OperationApplicationException;
43 import android.content.SharedPreferences;
44 import android.content.ContentProviderOperation.Builder;
45 import android.content.DialogInterface.OnCancelListener;
46 import android.content.DialogInterface.OnClickListener;
47 import android.content.res.Resources;
48 import android.database.Cursor;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.os.RemoteException;
52 import android.pim.EventRecurrence;
53 import android.preference.PreferenceManager;
54 import android.provider.Calendar.Attendees;
55 import android.provider.Calendar.Calendars;
56 import android.provider.Calendar.Events;
57 import android.provider.Calendar.Reminders;
58 import android.text.Editable;
59 import android.text.InputFilter;
60 import android.text.SpannableStringBuilder;
61 import android.text.Spanned;
62 import android.text.TextUtils;
63 import android.text.format.DateFormat;
64 import android.text.format.DateUtils;
65 import android.text.format.Time;
66 import android.text.util.Rfc822Token;
67 import android.text.util.Rfc822Tokenizer;
68 import android.text.util.Rfc822Validator;
69 import android.util.Log;
70 import android.view.KeyEvent;
71 import android.view.LayoutInflater;
72 import android.view.Menu;
73 import android.view.MenuItem;
74 import android.view.View;
75 import android.view.Window;
76 import android.widget.ArrayAdapter;
77 import android.widget.Button;
78 import android.widget.CheckBox;
79 import android.widget.CompoundButton;
80 import android.widget.DatePicker;
81 import android.widget.ImageButton;
82 import android.widget.LinearLayout;
83 import android.widget.MultiAutoCompleteTextView;
84 import android.widget.ResourceCursorAdapter;
85 import android.widget.Spinner;
86 import android.widget.TextView;
87 import android.widget.TimePicker;
88 import android.widget.Toast;
90 import com.google.android.googlelogin.GoogleLoginServiceConstants;
92 import java.io.IOException;
93 import java.util.ArrayList;
94 import java.util.Arrays;
95 import java.util.Calendar;
96 import java.util.TimeZone;
98 public class EditEvent extends Activity implements View.OnClickListener,
99 DialogInterface.OnCancelListener, DialogInterface.OnClickListener {
100 private static final String TAG = "EditEvent";
101 private static final boolean DEBUG = false;
104 * This is the symbolic name for the key used to pass in the boolean
105 * for creating all-day events that is part of the extra data of the intent.
106 * This is used only for creating new events and is set to true if
107 * the default for the new event should be an all-day event.
109 public static final String EVENT_ALL_DAY = "allDay";
111 private static final int MAX_REMINDERS = 5;
113 private static final int MENU_GROUP_REMINDER = 1;
114 private static final int MENU_GROUP_SHOW_OPTIONS = 2;
115 private static final int MENU_GROUP_HIDE_OPTIONS = 3;
117 private static final int MENU_ADD_REMINDER = 1;
118 private static final int MENU_SHOW_EXTRA_OPTIONS = 2;
119 private static final int MENU_HIDE_EXTRA_OPTIONS = 3;
121 private static final String[] EVENT_PROJECTION = new String[] {
124 Events.DESCRIPTION, // 2
125 Events.EVENT_LOCATION, // 3
127 Events.HAS_ALARM, // 5
128 Events.CALENDAR_ID, // 6
130 Events.DURATION, // 8
131 Events.EVENT_TIMEZONE, // 9
133 Events._SYNC_ID, // 11
134 Events.TRANSPARENCY, // 12
135 Events.VISIBILITY, // 13
137 private static final int EVENT_INDEX_ID = 0;
138 private static final int EVENT_INDEX_TITLE = 1;
139 private static final int EVENT_INDEX_DESCRIPTION = 2;
140 private static final int EVENT_INDEX_EVENT_LOCATION = 3;
141 private static final int EVENT_INDEX_ALL_DAY = 4;
142 private static final int EVENT_INDEX_HAS_ALARM = 5;
143 private static final int EVENT_INDEX_CALENDAR_ID = 6;
144 private static final int EVENT_INDEX_DTSTART = 7;
145 private static final int EVENT_INDEX_DURATION = 8;
146 private static final int EVENT_INDEX_TIMEZONE = 9;
147 private static final int EVENT_INDEX_RRULE = 10;
148 private static final int EVENT_INDEX_SYNC_ID = 11;
149 private static final int EVENT_INDEX_TRANSPARENCY = 12;
150 private static final int EVENT_INDEX_VISIBILITY = 13;
152 private static final String[] CALENDARS_PROJECTION = new String[] {
154 Calendars.DISPLAY_NAME, // 1
155 Calendars.OWNER_ACCOUNT, // 2
157 private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
158 private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
159 private static final String CALENDARS_WHERE = Calendars.ACCESS_LEVEL + ">=" +
160 Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SYNC_EVENTS + "=1";
162 private static final String[] REMINDERS_PROJECTION = new String[] {
164 Reminders.MINUTES, // 1
166 private static final int REMINDERS_INDEX_MINUTES = 1;
167 private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=%d AND (" +
168 Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
169 Reminders.METHOD_DEFAULT + ")";
171 private static final String[] ATTENDEES_PROJECTION = new String[] {
172 Attendees.ATTENDEE_NAME, // 0
173 Attendees.ATTENDEE_EMAIL, // 1
175 private static final int ATTENDEES_INDEX_NAME = 0;
176 private static final int ATTENDEES_INDEX_EMAIL = 1;
177 private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=? AND "
178 + Attendees.ATTENDEE_RELATIONSHIP + "<>" + Attendees.RELATIONSHIP_ORGANIZER;
180 private static final int DOES_NOT_REPEAT = 0;
181 private static final int REPEATS_DAILY = 1;
182 private static final int REPEATS_EVERY_WEEKDAY = 2;
183 private static final int REPEATS_WEEKLY_ON_DAY = 3;
184 private static final int REPEATS_MONTHLY_ON_DAY_COUNT = 4;
185 private static final int REPEATS_MONTHLY_ON_DAY = 5;
186 private static final int REPEATS_YEARLY = 6;
187 private static final int REPEATS_CUSTOM = 7;
189 private static final int MODIFY_UNINITIALIZED = 0;
190 private static final int MODIFY_SELECTED = 1;
191 private static final int MODIFY_ALL = 2;
192 private static final int MODIFY_ALL_FOLLOWING = 3;
194 private static final int DAY_IN_SECONDS = 24 * 60 * 60;
196 private int mFirstDayOfWeek; // cached in onCreate
198 private Cursor mEventCursor;
199 private Cursor mCalendarsCursor;
201 private Button mStartDateButton;
202 private Button mEndDateButton;
203 private Button mStartTimeButton;
204 private Button mEndTimeButton;
205 private Button mSaveButton;
206 private Button mDeleteButton;
207 private Button mDiscardButton;
208 private CheckBox mAllDayCheckBox;
209 private Spinner mCalendarsSpinner;
210 private Spinner mRepeatsSpinner;
211 private Spinner mAvailabilitySpinner;
212 private Spinner mVisibilitySpinner;
213 private TextView mTitleTextView;
214 private TextView mLocationTextView;
215 private TextView mDescriptionTextView;
216 private View mRemindersSeparator;
217 private LinearLayout mRemindersContainer;
218 private LinearLayout mExtraOptions;
219 private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
220 private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
221 MultiAutoCompleteTextView mAttendeesList;
222 private EmailAddressAdapter mAddressAdapter;
223 private String mOriginalAttendees = "";
225 private EventRecurrence mEventRecurrence = new EventRecurrence();
226 private String mRrule;
227 private boolean mCalendarsQueryComplete;
228 private boolean mSaveAfterQueryComplete;
229 private ProgressDialog mLoadingCalendarsDialog;
230 private AlertDialog mNoCalendarsDialog;
231 private ContentValues mInitialValues;
234 * If the repeating event is created on the phone and it hasn't been
235 * synced yet to the web server, then there is a bug where you can't
236 * delete or change an instance of the repeating event. This case
237 * can be detected with mSyncId. If mSyncId == null, then the repeating
238 * event has not been synced to the phone, in which case we won't allow
239 * the user to change one instance.
241 private String mSyncId;
243 private ArrayList<Integer> mRecurrenceIndexes = new ArrayList<Integer> (0);
244 private ArrayList<Integer> mReminderValues;
245 private ArrayList<String> mReminderLabels;
247 private Time mStartTime;
248 private Time mEndTime;
249 private int mModification = MODIFY_UNINITIALIZED;
250 private int mDefaultReminderMinutes;
252 private DeleteEventHelper mDeleteEventHelper;
253 private QueryHandler mQueryHandler;
254 private AccountManager mAccountManager;
256 /* This class is used to update the time buttons. */
257 private class TimeListener implements OnTimeSetListener {
260 public TimeListener(View view) {
264 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
265 // Cache the member variables locally to avoid inner class overhead.
266 Time startTime = mStartTime;
267 Time endTime = mEndTime;
269 // Cache the start and end millis so that we limit the number
270 // of calls to normalize() and toMillis(), which are fairly
274 if (mView == mStartTimeButton) {
275 // The start time was changed.
276 int hourDuration = endTime.hour - startTime.hour;
277 int minuteDuration = endTime.minute - startTime.minute;
279 startTime.hour = hourOfDay;
280 startTime.minute = minute;
281 startMillis = startTime.normalize(true);
283 // Also update the end time to keep the duration constant.
284 endTime.hour = hourOfDay + hourDuration;
285 endTime.minute = minute + minuteDuration;
286 endMillis = endTime.normalize(true);
288 // The end time was changed.
289 startMillis = startTime.toMillis(true);
290 endTime.hour = hourOfDay;
291 endTime.minute = minute;
292 endMillis = endTime.normalize(true);
294 // Do not allow an event to have an end time before the start time.
295 if (endTime.before(startTime)) {
296 endTime.set(startTime);
297 endMillis = startMillis;
301 setDate(mEndDateButton, endMillis);
302 setTime(mStartTimeButton, startMillis);
303 setTime(mEndTimeButton, endMillis);
307 private class TimeClickListener implements View.OnClickListener {
310 public TimeClickListener(Time time) {
314 public void onClick(View v) {
315 new TimePickerDialog(EditEvent.this, new TimeListener(v),
316 mTime.hour, mTime.minute,
317 DateFormat.is24HourFormat(EditEvent.this)).show();
321 private class DateListener implements OnDateSetListener {
324 public DateListener(View view) {
328 public void onDateSet(DatePicker view, int year, int month, int monthDay) {
329 // Cache the member variables locally to avoid inner class overhead.
330 Time startTime = mStartTime;
331 Time endTime = mEndTime;
333 // Cache the start and end millis so that we limit the number
334 // of calls to normalize() and toMillis(), which are fairly
338 if (mView == mStartDateButton) {
339 // The start date was changed.
340 int yearDuration = endTime.year - startTime.year;
341 int monthDuration = endTime.month - startTime.month;
342 int monthDayDuration = endTime.monthDay - startTime.monthDay;
344 startTime.year = year;
345 startTime.month = month;
346 startTime.monthDay = monthDay;
347 startMillis = startTime.normalize(true);
349 // Also update the end date to keep the duration constant.
350 endTime.year = year + yearDuration;
351 endTime.month = month + monthDuration;
352 endTime.monthDay = monthDay + monthDayDuration;
353 endMillis = endTime.normalize(true);
355 // If the start date has changed then update the repeats.
358 // The end date was changed.
359 startMillis = startTime.toMillis(true);
361 endTime.month = month;
362 endTime.monthDay = monthDay;
363 endMillis = endTime.normalize(true);
365 // Do not allow an event to have an end time before the start time.
366 if (endTime.before(startTime)) {
367 endTime.set(startTime);
368 endMillis = startMillis;
372 setDate(mStartDateButton, startMillis);
373 setDate(mEndDateButton, endMillis);
374 setTime(mEndTimeButton, endMillis); // In case end time had to be reset
378 private class DateClickListener implements View.OnClickListener {
381 public DateClickListener(Time time) {
385 public void onClick(View v) {
386 new DatePickerDialog(EditEvent.this, new DateListener(v), mTime.year,
387 mTime.month, mTime.monthDay).show();
391 static private class CalendarsAdapter extends ResourceCursorAdapter {
392 public CalendarsAdapter(Context context, Cursor c) {
393 super(context, R.layout.calendars_item, c);
394 setDropDownViewResource(R.layout.calendars_dropdown_item);
398 public void bindView(View view, Context context, Cursor cursor) {
399 TextView name = (TextView) view.findViewById(R.id.calendar_name);
400 name.setText(cursor.getString(CALENDARS_INDEX_DISPLAY_NAME));
404 // This is called if the user clicks on one of the buttons: "Save",
405 // "Discard", or "Delete". This is also called if the user clicks
406 // on the "remove reminder" button.
407 public void onClick(View v) {
408 if (v == mSaveButton) {
415 if (v == mDeleteButton) {
416 long begin = mStartTime.toMillis(false /* use isDst */);
417 long end = mEndTime.toMillis(false /* use isDst */);
419 switch (mModification) {
420 case MODIFY_SELECTED:
421 which = DeleteEventHelper.DELETE_SELECTED;
423 case MODIFY_ALL_FOLLOWING:
424 which = DeleteEventHelper.DELETE_ALL_FOLLOWING;
427 which = DeleteEventHelper.DELETE_ALL;
430 mDeleteEventHelper.delete(begin, end, mEventCursor, which);
434 if (v == mDiscardButton) {
439 // This must be a click on one of the "remove reminder" buttons
440 LinearLayout reminderItem = (LinearLayout) v.getParent();
441 LinearLayout parent = (LinearLayout) reminderItem.getParent();
442 parent.removeView(reminderItem);
443 mReminderItems.remove(reminderItem);
444 updateRemindersVisibility();
447 // This is called if the user cancels a popup dialog. There are two
448 // dialogs: the "Loading calendars" dialog, and the "No calendars"
449 // dialog. The "Loading calendars" dialog is shown if there is a delay
450 // in loading the calendars (needed when creating an event) and the user
451 // tries to save the event before the calendars have finished loading.
452 // The "No calendars" dialog is shown if there are no syncable calendars.
453 public void onCancel(DialogInterface dialog) {
454 if (dialog == mLoadingCalendarsDialog) {
455 mSaveAfterQueryComplete = false;
456 } else if (dialog == mNoCalendarsDialog) {
461 // This is called if the user clicks on a dialog button.
462 public void onClick(DialogInterface dialog, int which) {
463 if (dialog == mNoCalendarsDialog) {
468 private class QueryHandler extends AsyncQueryHandler {
469 public QueryHandler(ContentResolver cr) {
474 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
475 // If the Activity is finishing, then close the cursor.
476 // Otherwise, use the new cursor in the adapter.
478 stopManagingCursor(cursor);
481 mCalendarsCursor = cursor;
482 startManagingCursor(cursor);
485 getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
486 Window.PROGRESS_VISIBILITY_OFF);
488 // If there are no syncable calendars, then we cannot allow
489 // creating a new event.
490 if (cursor.getCount() == 0) {
491 // Cancel the "loading calendars" dialog if it exists
492 if (mSaveAfterQueryComplete) {
493 mLoadingCalendarsDialog.cancel();
496 // Create an error message for the user that, when clicked,
497 // will exit this activity without saving the event.
498 AlertDialog.Builder builder = new AlertDialog.Builder(EditEvent.this);
499 builder.setTitle(R.string.no_syncable_calendars)
500 .setIcon(android.R.drawable.ic_dialog_alert)
501 .setMessage(R.string.no_calendars_found)
502 .setPositiveButton(android.R.string.ok, EditEvent.this)
503 .setOnCancelListener(EditEvent.this);
504 mNoCalendarsDialog = builder.show();
508 int primaryCalendarPosition = findPrimaryCalendarPosition();
510 // populate the calendars spinner
511 CalendarsAdapter adapter = new CalendarsAdapter(EditEvent.this, mCalendarsCursor);
512 mCalendarsSpinner.setAdapter(adapter);
513 mCalendarsSpinner.setSelection(primaryCalendarPosition);
514 mCalendarsQueryComplete = true;
515 if (mSaveAfterQueryComplete) {
516 mLoadingCalendarsDialog.cancel();
521 // Find user domain and set it to the validator
522 if(cursor.moveToPosition(primaryCalendarPosition)) {
523 String ownEmail = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
524 if (ownEmail != null) {
525 int separator = ownEmail.lastIndexOf('@');
526 if (separator != -1 && ++separator < ownEmail.length()) {
527 mAttendeesList.setValidator(new Rfc822Validator(ownEmail
528 .substring(separator)));
535 // Find the calendar position in the cursor that matches the signed-in
537 private int findPrimaryCalendarPosition() {
538 int primaryCalendarPosition = -1;
540 Account[] accounts = mAccountManager.getAccountsByTypeAndFeatures(
541 GoogleLoginServiceConstants.ACCOUNT_TYPE, new String[] {
542 GoogleLoginServiceConstants.FEATURE_LEGACY_HOSTED_OR_GOOGLE
543 }, null, null).getResult();
544 if (accounts.length > 0) {
545 for (int i = 0; i < accounts.length && primaryCalendarPosition == -1; ++i) {
546 String name = accounts[i].name;
552 mCalendarsCursor.moveToPosition(-1);
553 while (mCalendarsCursor.moveToNext()) {
554 if (name.equals(mCalendarsCursor
555 .getString(CALENDARS_INDEX_OWNER_ACCOUNT))) {
556 primaryCalendarPosition = position;
563 } catch (OperationCanceledException e) {
564 // TODO Auto-generated catch block
566 } catch (IOException e) {
567 // TODO Auto-generated catch block
569 } catch (AuthenticatorException e) {
570 // TODO Auto-generated catch block
573 if (primaryCalendarPosition != -1) {
574 return primaryCalendarPosition;
583 protected void onCreate(Bundle icicle) {
584 super.onCreate(icicle);
585 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
586 setContentView(R.layout.edit_event);
587 mAccountManager = AccountManager.get(this);
589 mFirstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
591 mStartTime = new Time();
592 mEndTime = new Time();
594 Intent intent = getIntent();
595 mUri = intent.getData();
598 mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null);
599 if (mEventCursor == null || mEventCursor.getCount() == 0) {
600 // The cursor is empty. This can happen if the event was deleted.
606 long begin = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
607 long end = intent.getLongExtra(EVENT_END_TIME, 0);
609 boolean allDay = false;
610 if (mEventCursor != null) {
611 // The event already exists so fetch the all-day status
612 mEventCursor.moveToFirst();
613 allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
614 String rrule = mEventCursor.getString(EVENT_INDEX_RRULE);
615 String timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
616 long calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID);
618 // Remember the initial values
619 mInitialValues = new ContentValues();
620 mInitialValues.put(EVENT_BEGIN_TIME, begin);
621 mInitialValues.put(EVENT_END_TIME, end);
622 mInitialValues.put(Events.ALL_DAY, allDay ? 1 : 0);
623 mInitialValues.put(Events.RRULE, rrule);
624 mInitialValues.put(Events.EVENT_TIMEZONE, timezone);
625 mInitialValues.put(Events.CALENDAR_ID, calendarId);
627 // We are creating a new event, so set the default from the
628 // intent (if specified).
629 allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false);
632 getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
633 Window.PROGRESS_VISIBILITY_ON);
635 // Start a query in the background to read the list of calendars
636 mQueryHandler = new QueryHandler(getContentResolver());
637 mQueryHandler.startQuery(0, null, Calendars.CONTENT_URI, CALENDARS_PROJECTION,
638 CALENDARS_WHERE, null /* selection args */, null /* sort order */);
641 // If the event is all-day, read the times in UTC timezone
644 String tz = mStartTime.timezone;
645 mStartTime.timezone = Time.TIMEZONE_UTC;
646 mStartTime.set(begin);
647 mStartTime.timezone = tz;
649 // Calling normalize to calculate isDst
650 mStartTime.normalize(true);
652 mStartTime.set(begin);
658 String tz = mStartTime.timezone;
659 mEndTime.timezone = Time.TIMEZONE_UTC;
661 mEndTime.timezone = tz;
663 // Calling normalize to calculate isDst
664 mEndTime.normalize(true);
670 // cache all the widgets
671 mTitleTextView = (TextView) findViewById(R.id.title);
672 mLocationTextView = (TextView) findViewById(R.id.location);
673 mDescriptionTextView = (TextView) findViewById(R.id.description);
674 mStartDateButton = (Button) findViewById(R.id.start_date);
675 mEndDateButton = (Button) findViewById(R.id.end_date);
676 mStartTimeButton = (Button) findViewById(R.id.start_time);
677 mEndTimeButton = (Button) findViewById(R.id.end_time);
678 mAllDayCheckBox = (CheckBox) findViewById(R.id.is_all_day);
679 mCalendarsSpinner = (Spinner) findViewById(R.id.calendars);
680 mRepeatsSpinner = (Spinner) findViewById(R.id.repeats);
681 mAvailabilitySpinner = (Spinner) findViewById(R.id.availability);
682 mVisibilitySpinner = (Spinner) findViewById(R.id.visibility);
683 mRemindersSeparator = findViewById(R.id.reminders_separator);
684 mRemindersContainer = (LinearLayout) findViewById(R.id.reminder_items_container);
685 mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container);
687 mAddressAdapter = new EmailAddressAdapter(this);
688 mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees, R.string.hint_attendees);
690 mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
691 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
693 if (mEndTime.hour == 0 && mEndTime.minute == 0) {
695 long endMillis = mEndTime.normalize(true);
697 // Do not allow an event to have an end time before the start time.
698 if (mEndTime.before(mStartTime)) {
699 mEndTime.set(mStartTime);
700 endMillis = mEndTime.normalize(true);
702 setDate(mEndDateButton, endMillis);
703 setTime(mEndTimeButton, endMillis);
706 mStartTimeButton.setVisibility(View.GONE);
707 mEndTimeButton.setVisibility(View.GONE);
709 if (mEndTime.hour == 0 && mEndTime.minute == 0) {
711 long endMillis = mEndTime.normalize(true);
712 setDate(mEndDateButton, endMillis);
713 setTime(mEndTimeButton, endMillis);
716 mStartTimeButton.setVisibility(View.VISIBLE);
717 mEndTimeButton.setVisibility(View.VISIBLE);
723 mAllDayCheckBox.setChecked(true);
725 mAllDayCheckBox.setChecked(false);
728 mSaveButton = (Button) findViewById(R.id.save);
729 mSaveButton.setOnClickListener(this);
731 mDeleteButton = (Button) findViewById(R.id.delete);
732 mDeleteButton.setOnClickListener(this);
734 mDiscardButton = (Button) findViewById(R.id.discard);
735 mDiscardButton.setOnClickListener(this);
737 // Initialize the reminder values array.
738 Resources r = getResources();
739 String[] strings = r.getStringArray(R.array.reminder_minutes_values);
740 int size = strings.length;
741 ArrayList<Integer> list = new ArrayList<Integer>(size);
742 for (int i = 0 ; i < size ; i++) {
743 list.add(Integer.parseInt(strings[i]));
745 mReminderValues = list;
746 String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
747 mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
749 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
750 String durationString =
751 prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
752 mDefaultReminderMinutes = Integer.parseInt(durationString);
754 long eventId = (mEventCursor == null) ? -1 : mEventCursor.getLong(EVENT_INDEX_ID);
755 ContentResolver cr = getContentResolver();
758 boolean hasAlarm = (mEventCursor != null)
759 && (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0);
761 Uri uri = Reminders.CONTENT_URI;
762 String where = String.format(REMINDERS_WHERE, eventId);
763 Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null);
765 // First pass: collect all the custom reminder minutes (e.g.,
766 // a reminder of 8 minutes) into a global list.
767 while (reminderCursor.moveToNext()) {
768 int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
769 EditEvent.addMinutesToList(this, mReminderValues, mReminderLabels, minutes);
772 // Second pass: create the reminder spinners
773 reminderCursor.moveToPosition(-1);
774 while (reminderCursor.moveToNext()) {
775 int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES);
776 mOriginalMinutes.add(minutes);
777 EditEvent.addReminder(this, this, mReminderItems, mReminderValues,
778 mReminderLabels, minutes);
781 reminderCursor.close();
784 updateRemindersVisibility();
786 // Setup the + Add Reminder Button
787 View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
788 public void onClick(View v) {
792 ImageButton reminderRemoveButton = (ImageButton) findViewById(R.id.reminder_add);
793 reminderRemoveButton.setOnClickListener(addReminderOnClickListener);
795 mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
799 Uri uri = Attendees.CONTENT_URI;
800 String[] whereArgs = {Long.toString(eventId)};
801 Cursor attendeeCursor = cr.query(uri, ATTENDEES_PROJECTION, ATTENDEES_WHERE, whereArgs,
804 StringBuilder b = new StringBuilder();
805 while (attendeeCursor.moveToNext()) {
806 String name = attendeeCursor.getString(ATTENDEES_INDEX_NAME);
807 String email = attendeeCursor.getString(ATTENDEES_INDEX_EMAIL);
809 if (name != null && name.length() > 0 && !name.equals(email)) {
810 b.append('"').append(name).append("\" ");
812 b.append('<').append(email).append(">, ");
815 if (b.length() > 0) {
816 mOriginalAttendees = b.toString();
817 mAttendeesList.setText(mOriginalAttendees);
820 attendeeCursor.close();
823 if (mEventCursor == null) {
824 // Allow the intent to specify the fields in the event.
825 // This will allow other apps to create events easily.
826 initFromIntent(intent);
830 private Rfc822Token[] getAddressesFromList(MultiAutoCompleteTextView list) {
831 list.clearComposingText();
832 return Rfc822Tokenizer.tokenize(list.getText());
835 // From com.google.android.gm.ComposeActivity
836 private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res, int hintId) {
837 MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) findViewById(res);
838 list.setAdapter(mAddressAdapter);
839 list.setTokenizer(new Rfc822Tokenizer());
841 // NOTE: assumes no other filters are set
842 list.setFilters(sRecipientFilters);
848 * From com.google.android.gm.ComposeActivity
849 * Implements special address cleanup rules:
850 * The first space key entry following an "@" symbol that is followed by any combination
851 * of letters and symbols, including one+ dots and zero commas, should insert an extra
852 * comma (followed by the space).
854 private static InputFilter[] sRecipientFilters = new InputFilter[] { new InputFilter() {
856 public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
857 int dstart, int dend) {
859 // quick check - did they enter a single space?
860 if (end-start != 1 || source.charAt(start) != ' ') {
864 // determine if the characters before the new space fit the pattern
865 // follow backwards and see if we find a comma, dot, or @
866 int scanBack = dstart;
867 boolean dotFound = false;
868 while (scanBack > 0) {
869 char c = dest.charAt(--scanBack);
872 dotFound = true; // one or more dots are req'd
880 // we have found a comma-insert case. now just do it
881 // in the least expensive way we can.
882 if (source instanceof Spanned) {
883 SpannableStringBuilder sb = new SpannableStringBuilder(",");
894 // no termination cases were found, so don't edit the input
899 private void initFromIntent(Intent intent) {
900 String title = intent.getStringExtra(Events.TITLE);
902 mTitleTextView.setText(title);
905 String location = intent.getStringExtra(Events.EVENT_LOCATION);
906 if (location != null) {
907 mLocationTextView.setText(location);
910 String description = intent.getStringExtra(Events.DESCRIPTION);
911 if (description != null) {
912 mDescriptionTextView.setText(description);
915 int availability = intent.getIntExtra(Events.TRANSPARENCY, -1);
916 if (availability != -1) {
917 mAvailabilitySpinner.setSelection(availability);
920 int visibility = intent.getIntExtra(Events.VISIBILITY, -1);
921 if (visibility != -1) {
922 mVisibilitySpinner.setSelection(visibility);
925 String rrule = intent.getStringExtra(Events.RRULE);
928 mEventRecurrence.parse(rrule);
933 protected void onResume() {
937 if (mEventCursor == null || mEventCursor.getCount() == 0) {
938 // The cursor is empty. This can happen if the event was deleted.
944 if (mEventCursor != null) {
945 Cursor cursor = mEventCursor;
946 cursor.moveToFirst();
948 mRrule = cursor.getString(EVENT_INDEX_RRULE);
949 String title = cursor.getString(EVENT_INDEX_TITLE);
950 String description = cursor.getString(EVENT_INDEX_DESCRIPTION);
951 String location = cursor.getString(EVENT_INDEX_EVENT_LOCATION);
952 int availability = cursor.getInt(EVENT_INDEX_TRANSPARENCY);
953 int visibility = cursor.getInt(EVENT_INDEX_VISIBILITY);
954 if (visibility > 0) {
955 // For now we the array contains the values 0, 2, and 3. We subtract one to match.
959 if (!TextUtils.isEmpty(mRrule) && mModification == MODIFY_UNINITIALIZED) {
960 // If this event has not been synced, then don't allow deleting
961 // or changing a single instance.
962 mSyncId = cursor.getString(EVENT_INDEX_SYNC_ID);
963 mEventRecurrence.parse(mRrule);
965 // If we haven't synced this repeating event yet, then don't
966 // allow the user to change just one instance.
968 CharSequence[] items;
969 if (mSyncId == null) {
970 items = new CharSequence[2];
972 items = new CharSequence[3];
973 items[itemIndex++] = getText(R.string.modify_event);
975 items[itemIndex++] = getText(R.string.modify_all);
976 items[itemIndex++] = getText(R.string.modify_all_following);
978 // Display the modification dialog.
979 new AlertDialog.Builder(this)
980 .setOnCancelListener(new OnCancelListener() {
981 public void onCancel(DialogInterface dialog) {
985 .setTitle(R.string.edit_event_label)
986 .setItems(items, new OnClickListener() {
987 public void onClick(DialogInterface dialog, int which) {
990 (mSyncId == null) ? MODIFY_ALL : MODIFY_SELECTED;
991 } else if (which == 1) {
993 (mSyncId == null) ? MODIFY_ALL_FOLLOWING : MODIFY_ALL;
994 } else if (which == 2) {
995 mModification = MODIFY_ALL_FOLLOWING;
998 // If we are modifying all the events in a
999 // series then disable and ignore the date.
1000 if (mModification == MODIFY_ALL) {
1001 mStartDateButton.setEnabled(false);
1002 mEndDateButton.setEnabled(false);
1003 } else if (mModification == MODIFY_SELECTED) {
1004 mRepeatsSpinner.setEnabled(false);
1011 mTitleTextView.setText(title);
1012 mLocationTextView.setText(location);
1013 mDescriptionTextView.setText(description);
1014 mAvailabilitySpinner.setSelection(availability);
1015 mVisibilitySpinner.setSelection(visibility);
1017 // This is an existing event so hide the calendar spinner
1018 // since we can't change the calendar.
1019 View calendarGroup = findViewById(R.id.calendar_group);
1020 calendarGroup.setVisibility(View.GONE);
1023 if (Time.isEpoch(mStartTime) && Time.isEpoch(mEndTime)) {
1024 mStartTime.setToNow();
1026 // Round the time to the nearest half hour.
1027 mStartTime.second = 0;
1028 int minute = mStartTime.minute;
1029 if (minute > 0 && minute <= 30) {
1030 mStartTime.minute = 30;
1032 mStartTime.minute = 0;
1033 mStartTime.hour += 1;
1036 long startMillis = mStartTime.normalize(true /* ignore isDst */);
1037 mEndTime.set(startMillis + DateUtils.HOUR_IN_MILLIS);
1040 // New event - set the default reminder
1041 if (mDefaultReminderMinutes != 0) {
1042 addReminder(this, this, mReminderItems, mReminderValues,
1043 mReminderLabels, mDefaultReminderMinutes);
1046 // Hide delete button
1047 mDeleteButton.setVisibility(View.GONE);
1050 updateRemindersVisibility();
1056 public boolean onCreateOptionsMenu(Menu menu) {
1058 item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0,
1059 R.string.add_new_reminder);
1060 item.setIcon(R.drawable.ic_menu_reminder);
1061 item.setAlphabeticShortcut('r');
1063 item = menu.add(MENU_GROUP_SHOW_OPTIONS, MENU_SHOW_EXTRA_OPTIONS, 0,
1064 R.string.edit_event_show_extra_options);
1065 item.setIcon(R.drawable.ic_menu_show_list);
1066 item = menu.add(MENU_GROUP_HIDE_OPTIONS, MENU_HIDE_EXTRA_OPTIONS, 0,
1067 R.string.edit_event_hide_extra_options);
1068 item.setIcon(R.drawable.ic_menu_show_list);
1070 return super.onCreateOptionsMenu(menu);
1074 public boolean onPrepareOptionsMenu(Menu menu) {
1075 if (mReminderItems.size() < MAX_REMINDERS) {
1076 menu.setGroupVisible(MENU_GROUP_REMINDER, true);
1077 menu.setGroupEnabled(MENU_GROUP_REMINDER, true);
1079 menu.setGroupVisible(MENU_GROUP_REMINDER, false);
1080 menu.setGroupEnabled(MENU_GROUP_REMINDER, false);
1083 if (mExtraOptions.getVisibility() == View.VISIBLE) {
1084 menu.setGroupVisible(MENU_GROUP_SHOW_OPTIONS, false);
1085 menu.setGroupVisible(MENU_GROUP_HIDE_OPTIONS, true);
1087 menu.setGroupVisible(MENU_GROUP_SHOW_OPTIONS, true);
1088 menu.setGroupVisible(MENU_GROUP_HIDE_OPTIONS, false);
1091 return super.onPrepareOptionsMenu(menu);
1094 private void addReminder() {
1095 // TODO: when adding a new reminder, make it different from the
1096 // last one in the list (if any).
1097 if (mDefaultReminderMinutes == 0) {
1098 addReminder(this, this, mReminderItems, mReminderValues,
1099 mReminderLabels, 10 /* minutes */);
1101 addReminder(this, this, mReminderItems, mReminderValues,
1102 mReminderLabels, mDefaultReminderMinutes);
1104 updateRemindersVisibility();
1108 public boolean onOptionsItemSelected(MenuItem item) {
1109 switch (item.getItemId()) {
1110 case MENU_ADD_REMINDER:
1113 case MENU_SHOW_EXTRA_OPTIONS:
1114 mExtraOptions.setVisibility(View.VISIBLE);
1116 case MENU_HIDE_EXTRA_OPTIONS:
1117 mExtraOptions.setVisibility(View.GONE);
1120 return super.onOptionsItemSelected(item);
1124 public boolean onKeyDown(int keyCode, KeyEvent event) {
1126 case KeyEvent.KEYCODE_BACK:
1127 // If we are creating a new event, do not create it if the
1128 // title, location and description are all empty, in order to
1129 // prevent accidental "no subject" event creations.
1130 if (mUri != null || !isEmpty()) {
1132 // We cannot exit this activity because the calendars
1133 // are still loading.
1140 return super.onKeyDown(keyCode, event);
1143 private void populateWhen() {
1144 long startMillis = mStartTime.toMillis(false /* use isDst */);
1145 long endMillis = mEndTime.toMillis(false /* use isDst */);
1146 setDate(mStartDateButton, startMillis);
1147 setDate(mEndDateButton, endMillis);
1149 setTime(mStartTimeButton, startMillis);
1150 setTime(mEndTimeButton, endMillis);
1152 mStartDateButton.setOnClickListener(new DateClickListener(mStartTime));
1153 mEndDateButton.setOnClickListener(new DateClickListener(mEndTime));
1155 mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime));
1156 mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime));
1159 private void populateRepeats() {
1160 Time time = mStartTime;
1161 Resources r = getResources();
1162 int resource = android.R.layout.simple_spinner_item;
1164 String[] days = new String[] {
1165 DateUtils.getDayOfWeekString(Calendar.SUNDAY, DateUtils.LENGTH_MEDIUM),
1166 DateUtils.getDayOfWeekString(Calendar.MONDAY, DateUtils.LENGTH_MEDIUM),
1167 DateUtils.getDayOfWeekString(Calendar.TUESDAY, DateUtils.LENGTH_MEDIUM),
1168 DateUtils.getDayOfWeekString(Calendar.WEDNESDAY, DateUtils.LENGTH_MEDIUM),
1169 DateUtils.getDayOfWeekString(Calendar.THURSDAY, DateUtils.LENGTH_MEDIUM),
1170 DateUtils.getDayOfWeekString(Calendar.FRIDAY, DateUtils.LENGTH_MEDIUM),
1171 DateUtils.getDayOfWeekString(Calendar.SATURDAY, DateUtils.LENGTH_MEDIUM),
1173 String[] ordinals = r.getStringArray(R.array.ordinal_labels);
1175 // Only display "Custom" in the spinner if the device does not support the
1176 // recurrence functionality of the event. Only display every weekday if
1177 // the event starts on a weekday.
1178 boolean isCustomRecurrence = isCustomRecurrence();
1179 boolean isWeekdayEvent = isWeekdayEvent();
1181 ArrayList<String> repeatArray = new ArrayList<String>(0);
1182 ArrayList<Integer> recurrenceIndexes = new ArrayList<Integer>(0);
1184 repeatArray.add(r.getString(R.string.does_not_repeat));
1185 recurrenceIndexes.add(DOES_NOT_REPEAT);
1187 repeatArray.add(r.getString(R.string.daily));
1188 recurrenceIndexes.add(REPEATS_DAILY);
1190 if (isWeekdayEvent) {
1191 repeatArray.add(r.getString(R.string.every_weekday));
1192 recurrenceIndexes.add(REPEATS_EVERY_WEEKDAY);
1195 String format = r.getString(R.string.weekly);
1196 repeatArray.add(String.format(format, time.format("%A")));
1197 recurrenceIndexes.add(REPEATS_WEEKLY_ON_DAY);
1199 // Calculate whether this is the 1st, 2nd, 3rd, 4th, or last appearance of the given day.
1200 int dayNumber = (time.monthDay - 1) / 7;
1201 format = r.getString(R.string.monthly_on_day_count);
1202 repeatArray.add(String.format(format, ordinals[dayNumber], days[time.weekDay]));
1203 recurrenceIndexes.add(REPEATS_MONTHLY_ON_DAY_COUNT);
1205 format = r.getString(R.string.monthly_on_day);
1206 repeatArray.add(String.format(format, time.monthDay));
1207 recurrenceIndexes.add(REPEATS_MONTHLY_ON_DAY);
1209 long when = time.toMillis(false);
1210 format = r.getString(R.string.yearly);
1212 if (DateFormat.is24HourFormat(this)) {
1213 flags |= DateUtils.FORMAT_24HOUR;
1215 repeatArray.add(String.format(format, DateUtils.formatDateTime(this, when, flags)));
1216 recurrenceIndexes.add(REPEATS_YEARLY);
1218 if (isCustomRecurrence) {
1219 repeatArray.add(r.getString(R.string.custom));
1220 recurrenceIndexes.add(REPEATS_CUSTOM);
1222 mRecurrenceIndexes = recurrenceIndexes;
1224 int position = recurrenceIndexes.indexOf(DOES_NOT_REPEAT);
1225 if (mRrule != null) {
1226 if (isCustomRecurrence) {
1227 position = recurrenceIndexes.indexOf(REPEATS_CUSTOM);
1229 switch (mEventRecurrence.freq) {
1230 case EventRecurrence.DAILY:
1231 position = recurrenceIndexes.indexOf(REPEATS_DAILY);
1233 case EventRecurrence.WEEKLY:
1234 if (mEventRecurrence.repeatsOnEveryWeekDay()) {
1235 position = recurrenceIndexes.indexOf(REPEATS_EVERY_WEEKDAY);
1237 position = recurrenceIndexes.indexOf(REPEATS_WEEKLY_ON_DAY);
1240 case EventRecurrence.MONTHLY:
1241 if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
1242 position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY_COUNT);
1244 position = recurrenceIndexes.indexOf(REPEATS_MONTHLY_ON_DAY);
1247 case EventRecurrence.YEARLY:
1248 position = recurrenceIndexes.indexOf(REPEATS_YEARLY);
1253 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, resource, repeatArray);
1254 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
1255 mRepeatsSpinner.setAdapter(adapter);
1256 mRepeatsSpinner.setSelection(position);
1259 // Adds a reminder to the displayed list of reminders.
1260 // Returns true if successfully added reminder, false if no reminders can
1262 static boolean addReminder(Activity activity, View.OnClickListener listener,
1263 ArrayList<LinearLayout> items, ArrayList<Integer> values,
1264 ArrayList<String> labels, int minutes) {
1266 if (items.size() >= MAX_REMINDERS) {
1270 LayoutInflater inflater = activity.getLayoutInflater();
1271 LinearLayout parent = (LinearLayout) activity.findViewById(R.id.reminder_items_container);
1272 LinearLayout reminderItem = (LinearLayout) inflater.inflate(R.layout.edit_reminder_item, null);
1273 parent.addView(reminderItem);
1275 Spinner spinner = (Spinner) reminderItem.findViewById(R.id.reminder_value);
1276 Resources res = activity.getResources();
1277 spinner.setPrompt(res.getString(R.string.reminders_label));
1278 int resource = android.R.layout.simple_spinner_item;
1279 ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, resource, labels);
1280 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
1281 spinner.setAdapter(adapter);
1283 ImageButton reminderRemoveButton;
1284 reminderRemoveButton = (ImageButton) reminderItem.findViewById(R.id.reminder_remove);
1285 reminderRemoveButton.setOnClickListener(listener);
1287 int index = findMinutesInReminderList(values, minutes);
1288 spinner.setSelection(index);
1289 items.add(reminderItem);
1294 static void addMinutesToList(Context context, ArrayList<Integer> values,
1295 ArrayList<String> labels, int minutes) {
1296 int index = values.indexOf(minutes);
1301 // The requested "minutes" does not exist in the list, so insert it
1304 String label = constructReminderLabel(context, minutes, false);
1305 int len = values.size();
1306 for (int i = 0; i < len; i++) {
1307 if (minutes < values.get(i)) {
1308 values.add(i, minutes);
1309 labels.add(i, label);
1314 values.add(minutes);
1315 labels.add(len, label);
1319 * Finds the index of the given "minutes" in the "values" list.
1321 * @param values the list of minutes corresponding to the spinner choices
1322 * @param minutes the minutes to search for in the values list
1323 * @return the index of "minutes" in the "values" list
1325 private static int findMinutesInReminderList(ArrayList<Integer> values, int minutes) {
1326 int index = values.indexOf(minutes);
1328 // This should never happen.
1329 Log.e("Cal", "Cannot find minutes (" + minutes + ") in list");
1335 // Constructs a label given an arbitrary number of minutes. For example,
1336 // if the given minutes is 63, then this returns the string "63 minutes".
1337 // As another example, if the given minutes is 120, then this returns
1339 static String constructReminderLabel(Context context, int minutes, boolean abbrev) {
1340 Resources resources = context.getResources();
1343 if (minutes % 60 != 0) {
1346 resId = R.plurals.Nmins;
1348 resId = R.plurals.Nminutes;
1350 } else if (minutes % (24 * 60) != 0) {
1351 value = minutes / 60;
1352 resId = R.plurals.Nhours;
1354 value = minutes / ( 24 * 60);
1355 resId = R.plurals.Ndays;
1358 String format = resources.getQuantityString(resId, value);
1359 return String.format(format, value);
1362 private void updateRemindersVisibility() {
1363 if (mReminderItems.size() == 0) {
1364 mRemindersSeparator.setVisibility(View.GONE);
1365 mRemindersContainer.setVisibility(View.GONE);
1367 mRemindersSeparator.setVisibility(View.VISIBLE);
1368 mRemindersContainer.setVisibility(View.VISIBLE);
1372 private void setDate(TextView view, long millis) {
1373 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
1374 DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH |
1375 DateUtils.FORMAT_ABBREV_WEEKDAY;
1376 view.setText(DateUtils.formatDateTime(this, millis, flags));
1379 private void setTime(TextView view, long millis) {
1380 int flags = DateUtils.FORMAT_SHOW_TIME;
1381 if (DateFormat.is24HourFormat(this)) {
1382 flags |= DateUtils.FORMAT_24HOUR;
1384 view.setText(DateUtils.formatDateTime(this, millis, flags));
1387 // Saves the event. Returns true if it is okay to exit this activity.
1388 private boolean save() {
1389 boolean forceSaveReminders = false;
1391 // If we are creating a new event, then make sure we wait until the
1392 // query to fetch the list of calendars has finished.
1393 if (mEventCursor == null) {
1394 if (!mCalendarsQueryComplete) {
1395 // Wait for the calendars query to finish.
1396 if (mLoadingCalendarsDialog == null) {
1397 // Create the progress dialog
1398 mLoadingCalendarsDialog = ProgressDialog.show(this,
1399 getText(R.string.loading_calendars_title),
1400 getText(R.string.loading_calendars_message),
1402 mSaveAfterQueryComplete = true;
1407 // Avoid creating a new event if the calendars cursor is empty. This
1408 // shouldn't ever happen since the setup wizard should ensure the user
1410 if (mCalendarsCursor == null || mCalendarsCursor.getCount() == 0) {
1411 Log.w("Cal", "The calendars table does not contain any calendars."
1412 + " New event was not created.");
1415 Toast.makeText(this, R.string.creating_event, Toast.LENGTH_SHORT).show();
1417 Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show();
1420 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1421 int eventIdIndex = -1;
1423 ContentValues values = getContentValuesFromUi();
1426 // Update the "hasAlarm" field for the event
1427 ArrayList<Integer> reminderMinutes = reminderItemsToMinutes(mReminderItems,
1429 int len = reminderMinutes.size();
1430 values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0);
1432 // For recurring events, we must make sure that we use duration rather
1435 // Create new event with new contents
1436 addRecurrenceRule(values);
1437 eventIdIndex = ops.size();
1438 Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values);
1440 forceSaveReminders = true;
1442 } else if (mRrule == null) {
1443 // Modify contents of a non-repeating event
1444 addRecurrenceRule(values);
1445 checkTimeDependentFields(values);
1446 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
1448 } else if (mInitialValues.getAsString(Events.RRULE) == null) {
1449 // This event was changed from a non-repeating event to a
1451 addRecurrenceRule(values);
1452 values.remove(Events.DTEND);
1453 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
1455 } else if (mModification == MODIFY_SELECTED) {
1456 // Modify contents of the current instance of repeating event
1458 // Create a recurrence exception
1459 long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
1460 values.put(Events.ORIGINAL_EVENT, mEventCursor.getString(EVENT_INDEX_SYNC_ID));
1461 values.put(Events.ORIGINAL_INSTANCE_TIME, begin);
1462 boolean allDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0;
1463 values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
1465 eventIdIndex = ops.size();
1466 Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values);
1468 forceSaveReminders = true;
1470 } else if (mModification == MODIFY_ALL_FOLLOWING) {
1471 // Modify this instance and all future instances of repeating event
1472 addRecurrenceRule(values);
1474 if (mRrule == null) {
1475 // We've changed a recurring event to a non-recurring event.
1476 // If the event we are editing is the first in the series,
1477 // then delete the whole series. Otherwise, update the series
1478 // to end at the new start time.
1479 if (isFirstEventInSeries()) {
1480 ops.add(ContentProviderOperation.newDelete(uri).build());
1482 // Update the current repeating event to end at the new
1484 updatePastEvents(ops, uri);
1486 eventIdIndex = ops.size();
1487 ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
1490 if (isFirstEventInSeries()) {
1491 checkTimeDependentFields(values);
1492 values.remove(Events.DTEND);
1493 Builder b = ContentProviderOperation.newUpdate(uri).withValues(values);
1496 // Update the current repeating event to end at the new
1498 updatePastEvents(ops, uri);
1500 // Create a new event with the user-modified fields
1501 values.remove(Events.DTEND);
1502 eventIdIndex = ops.size();
1503 ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(
1507 forceSaveReminders = true;
1509 } else if (mModification == MODIFY_ALL) {
1511 // Modify all instances of repeating event
1512 addRecurrenceRule(values);
1514 if (mRrule == null) {
1515 // We've changed a recurring event to a non-recurring event.
1516 // Delete the whole series and replace it with a new
1517 // non-recurring event.
1518 ops.add(ContentProviderOperation.newDelete(uri).build());
1520 eventIdIndex = ops.size();
1521 ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
1523 forceSaveReminders = true;
1525 checkTimeDependentFields(values);
1526 values.remove(Events.DTEND);
1527 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
1531 if (eventIdIndex != -1) {
1532 saveRemindersWithBackRef(ops, eventIdIndex, reminderMinutes, mOriginalMinutes,
1533 forceSaveReminders);
1534 } else if (uri != null) {
1535 long eventId = ContentUris.parseId(uri);
1536 saveReminders(ops, eventId, reminderMinutes, mOriginalMinutes,
1537 forceSaveReminders);
1542 // New event/instance - Set Organizer's response as yes
1543 if (eventIdIndex != -1) {
1545 int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
1546 if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
1547 String ownerEmail = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
1548 if (ownerEmail != null) {
1549 String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
1550 if (displayName != null) {
1551 values.put(Attendees.ATTENDEE_NAME, displayName);
1553 values.put(Attendees.ATTENDEE_EMAIL, ownerEmail);
1554 values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
1555 values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
1556 values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
1558 b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
1559 .withValues(values);
1560 b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
1566 if (eventIdIndex != -1 || uri != null) {
1567 Editable attendeesText = mAttendeesList.getText();
1568 // Hit the content provider only if the user has changed it
1569 if (!mOriginalAttendees.equals(attendeesText.toString())) {
1570 // TODO we could do a diff and modify the rows only as needed
1571 // Delete all the existing attendees for this event
1572 b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
1575 if (eventIdIndex == -1) {
1576 eventId = ContentUris.parseId(uri);
1577 String[] args = new String[] {
1578 Long.toString(eventId)
1580 b.withSelection(ATTENDEES_WHERE, args);
1582 // Delete all the existing reminders for this event
1583 b.withSelection(ATTENDEES_WHERE, new String[1]);
1584 b.withSelectionBackReference(0, eventIdIndex);
1588 if (attendeesText.length() > 0) {
1589 Rfc822Token[] attendees = getAddressesFromList(mAttendeesList);
1590 // Insert the attendees
1591 for (Rfc822Token attendee : attendees) {
1593 values.put(Attendees.ATTENDEE_NAME, attendee.getName());
1594 values.put(Attendees.ATTENDEE_EMAIL, attendee.getAddress());
1595 values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
1596 values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
1597 values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
1599 if (eventIdIndex != -1) {
1600 b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
1601 .withValues(values);
1602 b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
1604 values.put(Attendees.EVENT_ID, eventId);
1605 b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
1606 .withValues(values);
1615 // TODO Move this to background thread
1616 ContentProviderResult[] results =
1617 getContentResolver().applyBatch(android.provider.Calendar.AUTHORITY, ops);
1619 for (int i = 0; i < results.length; i++) {
1620 Log.v(TAG, "results = " + results[i].toString());
1623 } catch (RemoteException e) {
1624 // TODO Auto-generated catch block
1625 e.printStackTrace();
1626 } catch (OperationApplicationException e) {
1627 // TODO Auto-generated catch block
1628 e.printStackTrace();
1634 private boolean isFirstEventInSeries() {
1635 int dtStart = mEventCursor.getColumnIndexOrThrow(Events.DTSTART);
1636 long start = mEventCursor.getLong(dtStart);
1637 return start == mStartTime.toMillis(true);
1640 private void updatePastEvents(ArrayList<ContentProviderOperation> ops, Uri uri) {
1641 long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
1642 String oldDuration = mEventCursor.getString(EVENT_INDEX_DURATION);
1643 boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
1644 String oldRrule = mEventCursor.getString(EVENT_INDEX_RRULE);
1645 mEventRecurrence.parse(oldRrule);
1647 Time untilTime = new Time();
1648 long begin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
1649 ContentValues oldValues = new ContentValues();
1651 // The "until" time must be in UTC time in order for Google calendar
1652 // to display it properly. For all-day events, the "until" time string
1653 // must include just the date field, and not the time field. The
1654 // repeating events repeat up to and including the "until" time.
1655 untilTime.timezone = Time.TIMEZONE_UTC;
1657 // Subtract one second from the old begin time to get the new
1659 untilTime.set(begin - 1000); // subtract one second (1000 millis)
1662 untilTime.minute = 0;
1663 untilTime.second = 0;
1664 untilTime.allDay = true;
1665 untilTime.normalize(false);
1667 // For all-day events, the duration must be in days, not seconds.
1668 // Otherwise, Google Calendar will (mistakenly) change this event
1669 // into a non-all-day event.
1670 int len = oldDuration.length();
1671 if (oldDuration.charAt(0) == 'P' && oldDuration.charAt(len - 1) == 'S') {
1672 int seconds = Integer.parseInt(oldDuration.substring(1, len - 1));
1673 int days = (seconds + DAY_IN_SECONDS - 1) / DAY_IN_SECONDS;
1674 oldDuration = "P" + days + "D";
1677 mEventRecurrence.until = untilTime.format2445();
1679 oldValues.put(Events.DTSTART, oldStartMillis);
1680 oldValues.put(Events.DURATION, oldDuration);
1681 oldValues.put(Events.RRULE, mEventRecurrence.toString());
1682 Builder b = ContentProviderOperation.newUpdate(uri).withValues(oldValues);
1686 private void checkTimeDependentFields(ContentValues values) {
1687 long oldBegin = mInitialValues.getAsLong(EVENT_BEGIN_TIME);
1688 long oldEnd = mInitialValues.getAsLong(EVENT_END_TIME);
1689 boolean oldAllDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0;
1690 String oldRrule = mInitialValues.getAsString(Events.RRULE);
1691 String oldTimezone = mInitialValues.getAsString(Events.EVENT_TIMEZONE);
1693 long newBegin = values.getAsLong(Events.DTSTART);
1694 long newEnd = values.getAsLong(Events.DTEND);
1695 boolean newAllDay = values.getAsInteger(Events.ALL_DAY) != 0;
1696 String newRrule = values.getAsString(Events.RRULE);
1697 String newTimezone = values.getAsString(Events.EVENT_TIMEZONE);
1699 // If none of the time-dependent fields changed, then remove them.
1700 if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay
1701 && TextUtils.equals(oldRrule, newRrule)
1702 && TextUtils.equals(oldTimezone, newTimezone)) {
1703 values.remove(Events.DTSTART);
1704 values.remove(Events.DTEND);
1705 values.remove(Events.DURATION);
1706 values.remove(Events.ALL_DAY);
1707 values.remove(Events.RRULE);
1708 values.remove(Events.EVENT_TIMEZONE);
1712 if (oldRrule == null || newRrule == null) {
1716 // If we are modifying all events then we need to set DTSTART to the
1717 // start time of the first event in the series, not the current
1718 // date and time. If the start time of the event was changed
1719 // (from, say, 3pm to 4pm), then we want to add the time difference
1720 // to the start time of the first event in the series (the DTSTART
1721 // value). If we are modifying one instance or all following instances,
1722 // then we leave the DTSTART field alone.
1723 if (mModification == MODIFY_ALL) {
1724 long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
1725 if (oldBegin != newBegin) {
1726 // The user changed the start time of this event
1727 long offset = newBegin - oldBegin;
1728 oldStartMillis += offset;
1730 values.put(Events.DTSTART, oldStartMillis);
1734 static ArrayList<Integer> reminderItemsToMinutes(ArrayList<LinearLayout> reminderItems,
1735 ArrayList<Integer> reminderValues) {
1736 int len = reminderItems.size();
1737 ArrayList<Integer> reminderMinutes = new ArrayList<Integer>(len);
1738 for (int index = 0; index < len; index++) {
1739 LinearLayout layout = reminderItems.get(index);
1740 Spinner spinner = (Spinner) layout.findViewById(R.id.reminder_value);
1741 int minutes = reminderValues.get(spinner.getSelectedItemPosition());
1742 reminderMinutes.add(minutes);
1744 return reminderMinutes;
1748 * Saves the reminders, if they changed. Returns true if the database
1751 * @param ops the array of ContentProviderOperations
1752 * @param eventId the id of the event whose reminders are being updated
1753 * @param reminderMinutes the array of reminders set by the user
1754 * @param originalMinutes the original array of reminders
1755 * @param forceSave if true, then save the reminders even if they didn't
1757 * @return true if the database was updated
1759 static boolean saveReminders(ArrayList<ContentProviderOperation> ops, long eventId,
1760 ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes,
1761 boolean forceSave) {
1762 // If the reminders have not changed, then don't update the database
1763 if (reminderMinutes.equals(originalMinutes) && !forceSave) {
1767 // Delete all the existing reminders for this event
1768 String where = Reminders.EVENT_ID + "=?";
1769 String[] args = new String[] { Long.toString(eventId) };
1770 Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
1771 b.withSelection(where, args);
1774 ContentValues values = new ContentValues();
1775 int len = reminderMinutes.size();
1777 // Insert the new reminders, if any
1778 for (int i = 0; i < len; i++) {
1779 int minutes = reminderMinutes.get(i);
1782 values.put(Reminders.MINUTES, minutes);
1783 values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1784 values.put(Reminders.EVENT_ID, eventId);
1785 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
1791 static boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops,
1792 int eventIdIndex, ArrayList<Integer> reminderMinutes,
1793 ArrayList<Integer> originalMinutes, boolean forceSave) {
1794 // If the reminders have not changed, then don't update the database
1795 if (reminderMinutes.equals(originalMinutes) && !forceSave) {
1799 // Delete all the existing reminders for this event
1800 Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
1801 b.withSelection(Reminders.EVENT_ID + "=?", new String[1]);
1802 b.withSelectionBackReference(0, eventIdIndex);
1805 ContentValues values = new ContentValues();
1806 int len = reminderMinutes.size();
1808 // Insert the new reminders, if any
1809 for (int i = 0; i < len; i++) {
1810 int minutes = reminderMinutes.get(i);
1813 values.put(Reminders.MINUTES, minutes);
1814 values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
1815 b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
1816 b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
1822 private void addRecurrenceRule(ContentValues values) {
1823 updateRecurrenceRule();
1825 if (mRrule == null) {
1829 values.put(Events.RRULE, mRrule);
1830 long end = mEndTime.toMillis(true /* ignore dst */);
1831 long start = mStartTime.toMillis(true /* ignore dst */);
1834 boolean isAllDay = mAllDayCheckBox.isChecked();
1836 long days = (end - start + DateUtils.DAY_IN_MILLIS - 1) / DateUtils.DAY_IN_MILLIS;
1837 duration = "P" + days + "D";
1839 long seconds = (end - start) / DateUtils.SECOND_IN_MILLIS;
1840 duration = "P" + seconds + "S";
1842 values.put(Events.DURATION, duration);
1845 private void updateRecurrenceRule() {
1846 int position = mRepeatsSpinner.getSelectedItemPosition();
1847 int selection = mRecurrenceIndexes.get(position);
1849 if (selection == DOES_NOT_REPEAT) {
1852 } else if (selection == REPEATS_CUSTOM) {
1853 // Keep custom recurrence as before.
1855 } else if (selection == REPEATS_DAILY) {
1856 mEventRecurrence.freq = EventRecurrence.DAILY;
1857 } else if (selection == REPEATS_EVERY_WEEKDAY) {
1858 mEventRecurrence.freq = EventRecurrence.WEEKLY;
1860 int[] byday = new int[dayCount];
1861 int[] bydayNum = new int[dayCount];
1863 byday[0] = EventRecurrence.MO;
1864 byday[1] = EventRecurrence.TU;
1865 byday[2] = EventRecurrence.WE;
1866 byday[3] = EventRecurrence.TH;
1867 byday[4] = EventRecurrence.FR;
1868 for (int day = 0; day < dayCount; day++) {
1872 mEventRecurrence.byday = byday;
1873 mEventRecurrence.bydayNum = bydayNum;
1874 mEventRecurrence.bydayCount = dayCount;
1875 } else if (selection == REPEATS_WEEKLY_ON_DAY) {
1876 mEventRecurrence.freq = EventRecurrence.WEEKLY;
1877 int[] days = new int[1];
1879 int[] dayNum = new int[dayCount];
1881 days[0] = EventRecurrence.timeDay2Day(mStartTime.weekDay);
1882 // not sure why this needs to be zero, but set it for now.
1885 mEventRecurrence.byday = days;
1886 mEventRecurrence.bydayNum = dayNum;
1887 mEventRecurrence.bydayCount = dayCount;
1888 } else if (selection == REPEATS_MONTHLY_ON_DAY) {
1889 mEventRecurrence.freq = EventRecurrence.MONTHLY;
1890 mEventRecurrence.bydayCount = 0;
1891 mEventRecurrence.bymonthdayCount = 1;
1892 int[] bymonthday = new int[1];
1893 bymonthday[0] = mStartTime.monthDay;
1894 mEventRecurrence.bymonthday = bymonthday;
1895 } else if (selection == REPEATS_MONTHLY_ON_DAY_COUNT) {
1896 mEventRecurrence.freq = EventRecurrence.MONTHLY;
1897 mEventRecurrence.bydayCount = 1;
1898 mEventRecurrence.bymonthdayCount = 0;
1900 int[] byday = new int[1];
1901 int[] bydayNum = new int[1];
1902 // Compute the week number (for example, the "2nd" Monday)
1903 int dayCount = 1 + ((mStartTime.monthDay - 1) / 7);
1904 if (dayCount == 5) {
1907 bydayNum[0] = dayCount;
1908 byday[0] = EventRecurrence.timeDay2Day(mStartTime.weekDay);
1909 mEventRecurrence.byday = byday;
1910 mEventRecurrence.bydayNum = bydayNum;
1911 } else if (selection == REPEATS_YEARLY) {
1912 mEventRecurrence.freq = EventRecurrence.YEARLY;
1915 // Set the week start day.
1916 mEventRecurrence.wkst = EventRecurrence.calendarDay2Day(mFirstDayOfWeek);
1917 mRrule = mEventRecurrence.toString();
1920 private ContentValues getContentValuesFromUi() {
1921 String title = mTitleTextView.getText().toString();
1922 boolean isAllDay = mAllDayCheckBox.isChecked();
1923 String location = mLocationTextView.getText().toString();
1924 String description = mDescriptionTextView.getText().toString();
1926 ContentValues values = new ContentValues();
1928 String timezone = null;
1933 // Reset start and end time, increment the monthDay by 1, and set
1934 // the timezone to UTC, as required for all-day events.
1935 timezone = Time.TIMEZONE_UTC;
1936 mStartTime.hour = 0;
1937 mStartTime.minute = 0;
1938 mStartTime.second = 0;
1939 mStartTime.timezone = timezone;
1940 startMillis = mStartTime.normalize(true);
1943 mEndTime.minute = 0;
1944 mEndTime.second = 0;
1945 mEndTime.monthDay++;
1946 mEndTime.timezone = timezone;
1947 endMillis = mEndTime.normalize(true);
1949 if (mEventCursor == null) {
1950 // This is a new event
1951 calendarId = mCalendarsSpinner.getSelectedItemId();
1953 calendarId = mInitialValues.getAsLong(Events.CALENDAR_ID);
1956 startMillis = mStartTime.toMillis(true);
1957 endMillis = mEndTime.toMillis(true);
1958 if (mEventCursor != null) {
1959 // This is an existing event
1960 timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
1962 // The timezone might be null if we are changing an existing
1963 // all-day event to a non-all-day event. We need to assign
1964 // a timezone to the non-all-day event.
1965 if (TextUtils.isEmpty(timezone)) {
1966 timezone = TimeZone.getDefault().getID();
1968 calendarId = mInitialValues.getAsLong(Events.CALENDAR_ID);
1970 // This is a new event
1971 calendarId = mCalendarsSpinner.getSelectedItemId();
1973 // The timezone for a new event is the currently displayed
1974 // timezone, NOT the timezone of the containing calendar.
1975 timezone = TimeZone.getDefault().getID();
1979 values.put(Events.CALENDAR_ID, calendarId);
1980 values.put(Events.EVENT_TIMEZONE, timezone);
1981 values.put(Events.TITLE, title);
1982 values.put(Events.ALL_DAY, isAllDay ? 1 : 0);
1983 values.put(Events.DTSTART, startMillis);
1984 values.put(Events.DTEND, endMillis);
1985 values.put(Events.DESCRIPTION, description);
1986 values.put(Events.EVENT_LOCATION, location);
1987 values.put(Events.TRANSPARENCY, mAvailabilitySpinner.getSelectedItemPosition());
1989 int visibility = mVisibilitySpinner.getSelectedItemPosition();
1990 if (visibility > 0) {
1991 // For now we the array contains the values 0, 2, and 3. We add one to match.
1994 values.put(Events.VISIBILITY, visibility);
1999 private boolean isEmpty() {
2000 String title = mTitleTextView.getText().toString();
2001 if (title.length() > 0) {
2005 String location = mLocationTextView.getText().toString();
2006 if (location.length() > 0) {
2010 String description = mDescriptionTextView.getText().toString();
2011 if (description.length() > 0) {
2018 private boolean isCustomRecurrence() {
2020 if (mEventRecurrence.until != null || mEventRecurrence.interval != 0) {
2024 if (mEventRecurrence.freq == 0) {
2028 switch (mEventRecurrence.freq) {
2029 case EventRecurrence.DAILY:
2031 case EventRecurrence.WEEKLY:
2032 if (mEventRecurrence.repeatsOnEveryWeekDay() && isWeekdayEvent()) {
2034 } else if (mEventRecurrence.bydayCount == 1) {
2038 case EventRecurrence.MONTHLY:
2039 if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
2041 } else if (mEventRecurrence.bydayCount == 0 && mEventRecurrence.bymonthdayCount == 1) {
2045 case EventRecurrence.YEARLY:
2052 private boolean isWeekdayEvent() {
2053 if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) {