import static android.provider.Calendar.EVENT_BEGIN_TIME;
import static android.provider.Calendar.EVENT_END_TIME;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.DatePickerDialog.OnDateSetListener;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.AsyncQueryHandler;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.OperationApplicationException;
import android.content.SharedPreferences;
+import android.content.ContentProviderOperation.Builder;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.os.RemoteException;
import android.pim.EventRecurrence;
import android.preference.PreferenceManager;
+import android.provider.Calendar.Attendees;
import android.provider.Calendar.Calendars;
import android.provider.Calendar.Events;
import android.provider.Calendar.Reminders;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.text.util.Rfc822Validator;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.widget.DatePicker;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.MultiAutoCompleteTextView;
import android.widget.ResourceCursorAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
+import com.google.android.googlelogin.GoogleLoginServiceConstants;
+
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
public class EditEvent extends Activity implements View.OnClickListener,
DialogInterface.OnCancelListener, DialogInterface.OnClickListener {
+ private static final String TAG = "EditEvent";
+ private static final boolean DEBUG = false;
+
/**
* This is the symbolic name for the key used to pass in the boolean
* for creating all-day events that is part of the extra data of the intent.
private static final int EVENT_INDEX_VISIBILITY = 13;
private static final String[] CALENDARS_PROJECTION = new String[] {
- Calendars._ID, // 0
- Calendars.DISPLAY_NAME, // 1
- Calendars.TIMEZONE, // 2
+ Calendars._ID, // 0
+ Calendars.DISPLAY_NAME, // 1
+ Calendars.OWNER_ACCOUNT, // 2
};
private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
- private static final int CALENDARS_INDEX_TIMEZONE = 2;
+ private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
private static final String CALENDARS_WHERE = Calendars.ACCESS_LEVEL + ">=" +
Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SYNC_EVENTS + "=1";
Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" +
Reminders.METHOD_DEFAULT + ")";
+ private static final String[] ATTENDEES_PROJECTION = new String[] {
+ Attendees.ATTENDEE_NAME, // 0
+ Attendees.ATTENDEE_EMAIL, // 1
+ };
+ private static final int ATTENDEES_INDEX_NAME = 0;
+ private static final int ATTENDEES_INDEX_EMAIL = 1;
+ private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=? AND "
+ + Attendees.ATTENDEE_RELATIONSHIP + "<>" + Attendees.RELATIONSHIP_ORGANIZER;
+
private static final int DOES_NOT_REPEAT = 0;
private static final int REPEATS_DAILY = 1;
private static final int REPEATS_EVERY_WEEKDAY = 2;
private static final int MODIFY_SELECTED = 1;
private static final int MODIFY_ALL = 2;
private static final int MODIFY_ALL_FOLLOWING = 3;
-
+
private static final int DAY_IN_SECONDS = 24 * 60 * 60;
private int mFirstDayOfWeek; // cached in onCreate
private LinearLayout mExtraOptions;
private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
+ MultiAutoCompleteTextView mAttendeesList;
+ private EmailAddressAdapter mAddressAdapter;
+ private String mOriginalAttendees = "";
private EventRecurrence mEventRecurrence = new EventRecurrence();
private String mRrule;
private DeleteEventHelper mDeleteEventHelper;
private QueryHandler mQueryHandler;
-
+ private AccountManager mAccountManager;
+
/* This class is used to update the time buttons. */
private class TimeListener implements OnTimeSetListener {
private View mView;
}
}
- private class CalendarsAdapter extends ResourceCursorAdapter {
+ static private class CalendarsAdapter extends ResourceCursorAdapter {
public CalendarsAdapter(Context context, Cursor c) {
super(context, R.layout.calendars_item, c);
setDropDownViewResource(R.layout.calendars_dropdown_item);
}
return;
}
-
+
if (v == mDeleteButton) {
long begin = mStartTime.toMillis(false /* use isDst */);
long end = mEndTime.toMillis(false /* use isDst */);
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();
finish();
}
}
-
+
private class QueryHandler extends AsyncQueryHandler {
public QueryHandler(ContentResolver cr) {
super(cr);
} else {
mCalendarsCursor = cursor;
startManagingCursor(cursor);
-
+
// Stop the spinner
getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
Window.PROGRESS_VISIBILITY_OFF);
if (mSaveAfterQueryComplete) {
mLoadingCalendarsDialog.cancel();
}
-
+
// Create an error message for the user that, when clicked,
// will exit this activity without saving the event.
AlertDialog.Builder builder = new AlertDialog.Builder(EditEvent.this);
return;
}
+ int primaryCalendarPosition = findPrimaryCalendarPosition();
+
// populate the calendars spinner
CalendarsAdapter adapter = new CalendarsAdapter(EditEvent.this, mCalendarsCursor);
mCalendarsSpinner.setAdapter(adapter);
+ mCalendarsSpinner.setSelection(primaryCalendarPosition);
mCalendarsQueryComplete = true;
if (mSaveAfterQueryComplete) {
mLoadingCalendarsDialog.cancel();
save();
finish();
}
+
+ // Find user domain and set it to the validator
+ if(cursor.moveToPosition(primaryCalendarPosition)) {
+ String ownEmail = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+ if (ownEmail != null) {
+ int separator = ownEmail.lastIndexOf('@');
+ if (separator != -1 && ++separator < ownEmail.length()) {
+ mAttendeesList.setValidator(new Rfc822Validator(ownEmail
+ .substring(separator)));
+ }
+ }
+ }
+ }
+ }
+
+ // Find the calendar position in the cursor that matches the signed-in
+ // account
+ private int findPrimaryCalendarPosition() {
+ int primaryCalendarPosition = -1;
+ try {
+ Account[] accounts = mAccountManager.getAccountsByTypeAndFeatures(
+ GoogleLoginServiceConstants.ACCOUNT_TYPE, new String[] {
+ GoogleLoginServiceConstants.FEATURE_LEGACY_HOSTED_OR_GOOGLE
+ }, null, null).getResult();
+ if (accounts.length > 0) {
+ for (int i = 0; i < accounts.length && primaryCalendarPosition == -1; ++i) {
+ String name = accounts[i].name;
+ if (name == null) {
+ continue;
+ }
+
+ int position = 0;
+ mCalendarsCursor.moveToPosition(-1);
+ while (mCalendarsCursor.moveToNext()) {
+ if (name.equals(mCalendarsCursor
+ .getString(CALENDARS_INDEX_OWNER_ACCOUNT))) {
+ primaryCalendarPosition = position;
+ break;
+ }
+ position++;
+ }
+ }
+ }
+ } catch (OperationCanceledException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (AuthenticatorException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ if (primaryCalendarPosition != -1) {
+ return primaryCalendarPosition;
+ } else {
+ return 0;
+ }
}
}
}
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.edit_event);
+ mAccountManager = AccountManager.get(this);
mFirstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
String rrule = mEventCursor.getString(EVENT_INDEX_RRULE);
String timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
long calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID);
-
+
// Remember the initial values
mInitialValues = new ContentValues();
mInitialValues.put(EVENT_BEGIN_TIME, begin);
// We are creating a new event, so set the default from the
// intent (if specified).
allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false);
-
+
// Start the spinner
getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
Window.PROGRESS_VISIBILITY_ON);
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);
+ mRemindersContainer = (LinearLayout) findViewById(R.id.reminder_items_container);
mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container);
+ mAddressAdapter = new EmailAddressAdapter(this);
+ mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees, R.string.hint_attendees);
+
mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
mDefaultReminderMinutes = Integer.parseInt(durationString);
+ long eventId = (mEventCursor == null) ? -1 : mEventCursor.getLong(EVENT_INDEX_ID);
+ ContentResolver cr = getContentResolver();
+
// Reminders cursor
boolean hasAlarm = (mEventCursor != null)
&& (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0);
if (hasAlarm) {
Uri uri = Reminders.CONTENT_URI;
- long eventId = mEventCursor.getLong(EVENT_INDEX_ID);
String where = String.format(REMINDERS_WHERE, eventId);
- ContentResolver cr = getContentResolver();
Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, null);
try {
// First pass: collect all the custom reminder minutes (e.g.,
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()) {
}
updateRemindersVisibility();
+ // Setup the + Add Reminder Button
+ View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ addReminder();
+ }
+ };
+ ImageButton reminderRemoveButton = (ImageButton) findViewById(R.id.reminder_add);
+ reminderRemoveButton.setOnClickListener(addReminderOnClickListener);
+
mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
+ // Attendees cursor
+ if (eventId != -1) {
+ Uri uri = Attendees.CONTENT_URI;
+ String[] whereArgs = {Long.toString(eventId)};
+ Cursor attendeeCursor = cr.query(uri, ATTENDEES_PROJECTION, ATTENDEES_WHERE, whereArgs,
+ null);
+ try {
+ StringBuilder b = new StringBuilder();
+ while (attendeeCursor.moveToNext()) {
+ String name = attendeeCursor.getString(ATTENDEES_INDEX_NAME);
+ String email = attendeeCursor.getString(ATTENDEES_INDEX_EMAIL);
+ if (email != null) {
+ if (name != null && name.length() > 0 && !name.equals(email)) {
+ b.append('"').append(name).append("\" ");
+ }
+ b.append('<').append(email).append(">, ");
+ }
+ }
+ if (b.length() > 0) {
+ mOriginalAttendees = b.toString();
+ mAttendeesList.setText(mOriginalAttendees);
+ }
+ } finally {
+ attendeeCursor.close();
+ }
+ }
if (mEventCursor == null) {
// Allow the intent to specify the fields in the event.
// This will allow other apps to create events easily.
initFromIntent(intent);
}
}
-
+
+ private Rfc822Token[] getAddressesFromList(MultiAutoCompleteTextView list) {
+ list.clearComposingText();
+ return Rfc822Tokenizer.tokenize(list.getText());
+ }
+
+ // From com.google.android.gm.ComposeActivity
+ private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res, int hintId) {
+ MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) findViewById(res);
+ list.setAdapter(mAddressAdapter);
+ list.setTokenizer(new Rfc822Tokenizer());
+
+ // NOTE: assumes no other filters are set
+ list.setFilters(sRecipientFilters);
+
+ return list;
+ }
+
+ /**
+ * From com.google.android.gm.ComposeActivity
+ * Implements special address cleanup rules:
+ * The first space key entry following an "@" symbol that is followed by any combination
+ * of letters and symbols, including one+ dots and zero commas, should insert an extra
+ * comma (followed by the space).
+ */
+ private static InputFilter[] sRecipientFilters = new InputFilter[] { new InputFilter() {
+
+ public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
+ int dstart, int dend) {
+
+ // quick check - did they enter a single space?
+ if (end-start != 1 || source.charAt(start) != ' ') {
+ return null;
+ }
+
+ // determine if the characters before the new space fit the pattern
+ // follow backwards and see if we find a comma, dot, or @
+ int scanBack = dstart;
+ boolean dotFound = false;
+ while (scanBack > 0) {
+ char c = dest.charAt(--scanBack);
+ switch (c) {
+ case '.':
+ dotFound = true; // one or more dots are req'd
+ break;
+ case ',':
+ return null;
+ case '@':
+ if (!dotFound) {
+ return null;
+ }
+ // we have found a comma-insert case. now just do it
+ // in the least expensive way we can.
+ if (source instanceof Spanned) {
+ SpannableStringBuilder sb = new SpannableStringBuilder(",");
+ sb.append(source);
+ return sb;
+ } else {
+ return ", ";
+ }
+ default:
+ // just keep going
+ }
+ }
+
+ // no termination cases were found, so don't edit the input
+ return null;
+ }
+ }};
+
private void initFromIntent(Intent intent) {
String title = intent.getStringExtra(Events.TITLE);
if (title != null) {
mTitleTextView.setText(title);
}
-
+
String location = intent.getStringExtra(Events.EVENT_LOCATION);
if (location != null) {
mLocationTextView.setText(location);
}
-
+
String description = intent.getStringExtra(Events.DESCRIPTION);
if (description != null) {
mDescriptionTextView.setText(description);
}
-
+
int availability = intent.getIntExtra(Events.TRANSPARENCY, -1);
if (availability != -1) {
mAvailabilitySpinner.setSelection(availability);
}
-
+
int visibility = intent.getIntExtra(Events.VISIBILITY, -1);
if (visibility != -1) {
mVisibilitySpinner.setSelection(visibility);
}
-
+
String rrule = intent.getStringExtra(Events.RRULE);
if (rrule != null) {
mRrule = rrule;
return;
}
}
-
+
if (mEventCursor != null) {
Cursor cursor = mEventCursor;
cursor.moveToFirst();
} 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) {
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);
}
}
})
// This is an existing event so hide the calendar spinner
// since we can't change the calendar.
- 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;
+ View calendarGroup = findViewById(R.id.calendar_group);
+ calendarGroup.setVisibility(View.GONE);
+ } else {
+ // New event
+ if (Time.isEpoch(mStartTime) && Time.isEpoch(mEndTime)) {
+ mStartTime.setToNow();
+
+ // Round the time to the nearest half hour.
+ mStartTime.second = 0;
+ int minute = mStartTime.minute;
+ if (minute > 0 && minute <= 30) {
+ mStartTime.minute = 30;
+ } else {
+ mStartTime.minute = 0;
+ mStartTime.hour += 1;
+ }
+
+ long startMillis = mStartTime.normalize(true /* ignore isDst */);
+ mEndTime.set(startMillis + DateUtils.HOUR_IN_MILLIS);
}
- long startMillis = mStartTime.normalize(true /* ignore isDst */);
- mEndTime.set(startMillis + DateUtils.HOUR_IN_MILLIS);
- } else {
// New event - set the default reminder
if (mDefaultReminderMinutes != 0) {
addReminder(this, this, mReminderItems, mReminderValues,
return super.onPrepareOptionsMenu(menu);
}
+ private void addReminder() {
+ // 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();
+ }
+
@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();
+ addReminder();
return true;
case MENU_SHOW_EXTRA_OPTIONS:
mExtraOptions.setVisibility(View.VISIBLE);
Resources r = getResources();
int resource = android.R.layout.simple_spinner_item;
- String[] days = r.getStringArray(R.array.day_labels);
+ String[] days = new String[] {
+ DateUtils.getDayOfWeekString(Calendar.SUNDAY, DateUtils.LENGTH_MEDIUM),
+ DateUtils.getDayOfWeekString(Calendar.MONDAY, DateUtils.LENGTH_MEDIUM),
+ DateUtils.getDayOfWeekString(Calendar.TUESDAY, DateUtils.LENGTH_MEDIUM),
+ DateUtils.getDayOfWeekString(Calendar.WEDNESDAY, DateUtils.LENGTH_MEDIUM),
+ DateUtils.getDayOfWeekString(Calendar.THURSDAY, DateUtils.LENGTH_MEDIUM),
+ DateUtils.getDayOfWeekString(Calendar.FRIDAY, DateUtils.LENGTH_MEDIUM),
+ DateUtils.getDayOfWeekString(Calendar.SATURDAY, DateUtils.LENGTH_MEDIUM),
+ };
String[] ordinals = r.getStringArray(R.array.ordinal_labels);
// Only display "Custom" in the spinner if the device does not support the
LinearLayout parent = (LinearLayout) activity.findViewById(R.id.reminder_items_container);
LinearLayout reminderItem = (LinearLayout) inflater.inflate(R.layout.edit_reminder_item, null);
parent.addView(reminderItem);
-
+
Spinner spinner = (Spinner) reminderItem.findViewById(R.id.reminder_value);
Resources res = activity.getResources();
spinner.setPrompt(res.getString(R.string.reminders_label));
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);
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++) {
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
}
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
static String constructReminderLabel(Context context, int minutes, boolean abbrev) {
Resources resources = context.getResources();
int value, resId;
-
+
if (minutes % 60 != 0) {
value = minutes;
if (abbrev) {
// Saves the event. Returns true if it is okay to exit this activity.
private boolean save() {
+ boolean forceSaveReminders = false;
+
// If we are creating a new event, then make sure we wait until the
// query to fetch the list of calendars has finished.
if (mEventCursor == null) {
Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show();
}
- ContentResolver cr = getContentResolver();
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ int eventIdIndex = -1;
+
ContentValues values = getContentValuesFromUi();
Uri uri = mUri;
+ // Update the "hasAlarm" field for the event
+ ArrayList<Integer> reminderMinutes = reminderItemsToMinutes(mReminderItems,
+ mReminderValues);
+ int len = reminderMinutes.size();
+ values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0);
+
// For recurring events, we must make sure that we use duration rather
// than dtend.
if (uri == null) {
// Create new event with new contents
addRecurrenceRule(values);
- uri = cr.insert(Events.CONTENT_URI, values);
+ eventIdIndex = ops.size();
+ Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values);
+ ops.add(b.build());
+ forceSaveReminders = true;
} else if (mRrule == null) {
// Modify contents of a non-repeating event
addRecurrenceRule(values);
checkTimeDependentFields(values);
- cr.update(uri, values, null, null);
-
+ ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+
} else if (mInitialValues.getAsString(Events.RRULE) == null) {
// This event was changed from a non-repeating event to a
// repeating event.
addRecurrenceRule(values);
values.remove(Events.DTEND);
- cr.update(uri, values, null, null);
+ ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
} else if (mModification == MODIFY_SELECTED) {
// Modify contents of the current instance of repeating event
boolean allDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0;
values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
- uri = cr.insert(Events.CONTENT_URI, values);
+ eventIdIndex = ops.size();
+ Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values);
+ ops.add(b.build());
+ forceSaveReminders = true;
} else if (mModification == MODIFY_ALL_FOLLOWING) {
// Modify this instance and all future instances of repeating event
// then delete the whole series. Otherwise, update the series
// to end at the new start time.
if (isFirstEventInSeries()) {
- cr.delete(uri, null, null);
+ ops.add(ContentProviderOperation.newDelete(uri).build());
} else {
// Update the current repeating event to end at the new
// start time.
- updatePastEvents(cr, uri);
+ updatePastEvents(ops, uri);
}
- uri = cr.insert(Events.CONTENT_URI, values);
+ eventIdIndex = ops.size();
+ ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
+ .build());
} else {
if (isFirstEventInSeries()) {
checkTimeDependentFields(values);
values.remove(Events.DTEND);
- cr.update(uri, values, null, null);
+ Builder b = ContentProviderOperation.newUpdate(uri).withValues(values);
+ ops.add(b.build());
} else {
// Update the current repeating event to end at the new
// start time.
- updatePastEvents(cr, uri);
+ updatePastEvents(ops, uri);
// Create a new event with the user-modified fields
values.remove(Events.DTEND);
- uri = cr.insert(Events.CONTENT_URI, values);
+ eventIdIndex = ops.size();
+ ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(
+ values).build());
}
}
+ forceSaveReminders = true;
} else if (mModification == MODIFY_ALL) {
-
+
// Modify all instances of repeating event
addRecurrenceRule(values);
-
+
if (mRrule == null) {
// We've changed a recurring event to a non-recurring event.
// Delete the whole series and replace it with a new
// non-recurring event.
- cr.delete(uri, null, null);
- uri = cr.insert(Events.CONTENT_URI, values);
+ ops.add(ContentProviderOperation.newDelete(uri).build());
+
+ eventIdIndex = ops.size();
+ ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values)
+ .build());
+ forceSaveReminders = true;
} else {
checkTimeDependentFields(values);
values.remove(Events.DTEND);
- cr.update(uri, values, null, null);
+ ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
}
}
- if (uri != null) {
+ if (eventIdIndex != -1) {
+ saveRemindersWithBackRef(ops, eventIdIndex, reminderMinutes, mOriginalMinutes,
+ forceSaveReminders);
+ } else if (uri != null) {
long eventId = ContentUris.parseId(uri);
- ArrayList<Integer> reminderMinutes = reminderItemsToMinutes(mReminderItems,
- mReminderValues);
- saveReminders(cr, eventId, reminderMinutes, mOriginalMinutes);
+ saveReminders(ops, eventId, reminderMinutes, mOriginalMinutes,
+ forceSaveReminders);
+ }
+
+ Builder b;
+
+ // New event/instance - Set Organizer's response as yes
+ if (eventIdIndex != -1) {
+ values.clear();
+ int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
+ if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
+ String ownerEmail = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+ if (ownerEmail != null) {
+ String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+ if (displayName != null) {
+ values.put(Attendees.ATTENDEE_NAME, displayName);
+ }
+ values.put(Attendees.ATTENDEE_EMAIL, ownerEmail);
+ values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
+ values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+ values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
+
+ b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
+ .withValues(values);
+ b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
+ ops.add(b.build());
+ }
+ }
+ }
+
+ if (eventIdIndex != -1 || uri != null) {
+ Editable attendeesText = mAttendeesList.getText();
+ // Hit the content provider only if the user has changed it
+ if (!mOriginalAttendees.equals(attendeesText.toString())) {
+ // TODO we could do a diff and modify the rows only as needed
+ // Delete all the existing attendees for this event
+ b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
+
+ long eventId = -1;
+ if (eventIdIndex == -1) {
+ eventId = ContentUris.parseId(uri);
+ String[] args = new String[] {
+ Long.toString(eventId)
+ };
+ b.withSelection(ATTENDEES_WHERE, args);
+ } else {
+ // Delete all the existing reminders for this event
+ b.withSelection(ATTENDEES_WHERE, new String[1]);
+ b.withSelectionBackReference(0, eventIdIndex);
+ }
+ ops.add(b.build());
+
+ if (attendeesText.length() > 0) {
+ Rfc822Token[] attendees = getAddressesFromList(mAttendeesList);
+ // Insert the attendees
+ for (Rfc822Token attendee : attendees) {
+ values.clear();
+ values.put(Attendees.ATTENDEE_NAME, attendee.getName());
+ values.put(Attendees.ATTENDEE_EMAIL, attendee.getAddress());
+ values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
+ values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
+ values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
+
+ if (eventIdIndex != -1) {
+ b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
+ .withValues(values);
+ b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
+ } else {
+ values.put(Attendees.EVENT_ID, eventId);
+ b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
+ .withValues(values);
+ }
+ ops.add(b.build());
+ }
+ }
+ }
}
+
+ try {
+ // TODO Move this to background thread
+ ContentProviderResult[] results =
+ getContentResolver().applyBatch(android.provider.Calendar.AUTHORITY, ops);
+ if (DEBUG) {
+ for (int i = 0; i < results.length; i++) {
+ Log.v(TAG, "results = " + results[i].toString());
+ }
+ }
+ } catch (RemoteException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (OperationApplicationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
return true;
}
return start == mStartTime.toMillis(true);
}
- private void updatePastEvents(ContentResolver cr, Uri uri) {
+ private void updatePastEvents(ArrayList<ContentProviderOperation> ops, Uri uri) {
long oldStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
String oldDuration = mEventCursor.getString(EVENT_INDEX_DURATION);
boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
// must include just the date field, and not the time field. The
// repeating events repeat up to and including the "until" time.
untilTime.timezone = Time.TIMEZONE_UTC;
-
+
// Subtract one second from the old begin time to get the new
// "until" time.
- untilTime.set(begin - 1000); // subtract one second (1000 millis)
+ untilTime.set(begin - 1000); // subtract one second (1000 millis)
if (allDay) {
untilTime.hour = 0;
untilTime.minute = 0;
untilTime.second = 0;
untilTime.allDay = true;
untilTime.normalize(false);
-
+
// For all-day events, the duration must be in days, not seconds.
// Otherwise, Google Calendar will (mistakenly) change this event
// into a non-all-day event.
oldValues.put(Events.DTSTART, oldStartMillis);
oldValues.put(Events.DURATION, oldDuration);
oldValues.put(Events.RRULE, mEventRecurrence.toString());
- cr.update(uri, oldValues, null, null);
+ Builder b = ContentProviderOperation.newUpdate(uri).withValues(oldValues);
+ ops.add(b.build());
}
private void checkTimeDependentFields(ContentValues values) {
boolean oldAllDay = mInitialValues.getAsInteger(Events.ALL_DAY) != 0;
String oldRrule = mInitialValues.getAsString(Events.RRULE);
String oldTimezone = mInitialValues.getAsString(Events.EVENT_TIMEZONE);
-
+
long newBegin = values.getAsLong(Events.DTSTART);
long newEnd = values.getAsLong(Events.DTEND);
boolean newAllDay = values.getAsInteger(Events.ALL_DAY) != 0;
String newRrule = values.getAsString(Events.RRULE);
String newTimezone = values.getAsString(Events.EVENT_TIMEZONE);
-
+
// If none of the time-dependent fields changed, then remove them.
if (oldBegin == newBegin && oldEnd == newEnd && oldAllDay == newAllDay
&& TextUtils.equals(oldRrule, newRrule)
values.put(Events.DTSTART, oldStartMillis);
}
}
-
+
static ArrayList<Integer> reminderItemsToMinutes(ArrayList<LinearLayout> reminderItems,
ArrayList<Integer> reminderValues) {
int len = reminderItems.size();
/**
* Saves the reminders, if they changed. Returns true if the database
* was updated.
- *
- * @param cr the ContentResolver
+ *
+ * @param ops the array of ContentProviderOperations
* @param eventId the id of the event whose reminders are being updated
* @param reminderMinutes the array of reminders set by the user
* @param originalMinutes the original array of reminders
+ * @param forceSave if true, then save the reminders even if they didn't
+ * change
* @return true if the database was updated
*/
- static boolean saveReminders(ContentResolver cr, long eventId,
- ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes) {
+ static boolean saveReminders(ArrayList<ContentProviderOperation> ops, long eventId,
+ ArrayList<Integer> reminderMinutes, ArrayList<Integer> originalMinutes,
+ boolean forceSave) {
// If the reminders have not changed, then don't update the database
- if (reminderMinutes.equals(originalMinutes)) {
+ if (reminderMinutes.equals(originalMinutes) && !forceSave) {
return false;
}
// Delete all the existing reminders for this event
String where = Reminders.EVENT_ID + "=?";
String[] args = new String[] { Long.toString(eventId) };
- cr.delete(Reminders.CONTENT_URI, where, args);
+ Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
+ b.withSelection(where, args);
+ ops.add(b.build());
- // Update the "hasAlarm" field for the event
ContentValues values = new ContentValues();
int len = reminderMinutes.size();
- values.put(Events.HAS_ALARM, (len > 0) ? 1 : 0);
- Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
- cr.update(uri, values, null /* where */, null /* selection args */);
// Insert the new reminders, if any
for (int i = 0; i < len; i++) {
values.put(Reminders.MINUTES, minutes);
values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
values.put(Reminders.EVENT_ID, eventId);
- cr.insert(Reminders.CONTENT_URI, values);
+ b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
+ ops.add(b.build());
+ }
+ return true;
+ }
+
+ static boolean saveRemindersWithBackRef(ArrayList<ContentProviderOperation> ops,
+ int eventIdIndex, ArrayList<Integer> reminderMinutes,
+ ArrayList<Integer> originalMinutes, boolean forceSave) {
+ // If the reminders have not changed, then don't update the database
+ if (reminderMinutes.equals(originalMinutes) && !forceSave) {
+ return false;
+ }
+
+ // Delete all the existing reminders for this event
+ Builder b = ContentProviderOperation.newDelete(Reminders.CONTENT_URI);
+ b.withSelection(Reminders.EVENT_ID + "=?", new String[1]);
+ b.withSelectionBackReference(0, eventIdIndex);
+ ops.add(b.build());
+
+ ContentValues values = new ContentValues();
+ int len = reminderMinutes.size();
+
+ // Insert the new reminders, if any
+ for (int i = 0; i < len; i++) {
+ int minutes = reminderMinutes.get(i);
+
+ values.clear();
+ values.put(Reminders.MINUTES, minutes);
+ values.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+ b = ContentProviderOperation.newInsert(Reminders.CONTENT_URI).withValues(values);
+ b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
+ ops.add(b.build());
}
return true;
}
if (mRrule == null) {
return;
}
-
+
values.put(Events.RRULE, mRrule);
long end = mEndTime.toMillis(true /* ignore dst */);
long start = mStartTime.toMillis(true /* ignore dst */);
mEndTime.monthDay++;
mEndTime.timezone = timezone;
endMillis = mEndTime.normalize(true);
-
+
if (mEventCursor == null) {
// This is a new event
calendarId = mCalendarsSpinner.getSelectedItemId();
if (mEventCursor != null) {
// This is an existing event
timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
-
+
// The timezone might be null if we are changing an existing
// all-day event to a non-all-day event. We need to assign
// a timezone to the non-all-day event.
} else {
// This is a new event
calendarId = mCalendarsSpinner.getSelectedItemId();
-
+
// The timezone for a new event is the currently displayed
// timezone, NOT the timezone of the containing calendar.
timezone = TimeZone.getDefault().getID();