OSDN Git Service

Remove dtend when recurring event is created.
[android-x86/packages-apps-Calendar.git] / src / com / android / calendar / EditEvent.java
index d235456..2fb2d9b 100644 (file)
@@ -19,10 +19,9 @@ package com.android.calendar;
 import static android.provider.Calendar.EVENT_BEGIN_TIME;
 import static android.provider.Calendar.EVENT_END_TIME;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
+import com.android.common.Rfc822InputFilter;
+import com.android.common.Rfc822Validator;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.DatePickerDialog;
@@ -50,29 +49,25 @@ 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.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.Window;
+import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.CheckBox;
@@ -87,12 +82,13 @@ 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;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.TimeZone;
 
 public class EditEvent extends Activity implements View.OnClickListener,
@@ -119,20 +115,22 @@ public class EditEvent extends Activity implements View.OnClickListener,
     private static final int MENU_HIDE_EXTRA_OPTIONS = 3;
 
     private static final String[] EVENT_PROJECTION = new String[] {
-            Events._ID,             // 0
-            Events.TITLE,           // 1
-            Events.DESCRIPTION,     // 2
-            Events.EVENT_LOCATION,  // 3
-            Events.ALL_DAY,         // 4
-            Events.HAS_ALARM,       // 5
-            Events.CALENDAR_ID,     // 6
-            Events.DTSTART,         // 7
-            Events.DURATION,        // 8
-            Events.EVENT_TIMEZONE,  // 9
-            Events.RRULE,           // 10
-            Events._SYNC_ID,        // 11
-            Events.TRANSPARENCY,    // 12
-            Events.VISIBILITY,      // 13
+            Events._ID,               // 0
+            Events.TITLE,             // 1
+            Events.DESCRIPTION,       // 2
+            Events.EVENT_LOCATION,    // 3
+            Events.ALL_DAY,           // 4
+            Events.HAS_ALARM,         // 5
+            Events.CALENDAR_ID,       // 6
+            Events.DTSTART,           // 7
+            Events.DURATION,          // 8
+            Events.EVENT_TIMEZONE,    // 9
+            Events.RRULE,             // 10
+            Events._SYNC_ID,          // 11
+            Events.TRANSPARENCY,      // 12
+            Events.VISIBILITY,        // 13
+            Events.OWNER_ACCOUNT,     // 14
+            Events.HAS_ATTENDEE_DATA, // 15
     };
     private static final int EVENT_INDEX_ID = 0;
     private static final int EVENT_INDEX_TITLE = 1;
@@ -148,14 +146,18 @@ public class EditEvent extends Activity implements View.OnClickListener,
     private static final int EVENT_INDEX_SYNC_ID = 11;
     private static final int EVENT_INDEX_TRANSPARENCY = 12;
     private static final int EVENT_INDEX_VISIBILITY = 13;
+    private static final int EVENT_INDEX_OWNER_ACCOUNT = 14;
+    private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 15;
 
     private static final String[] CALENDARS_PROJECTION = new String[] {
             Calendars._ID,           // 0
             Calendars.DISPLAY_NAME,  // 1
             Calendars.OWNER_ACCOUNT, // 2
+            Calendars.COLOR,         // 3
     };
     private static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
     private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
+    private static final int CALENDARS_INDEX_COLOR = 3;
     private static final String CALENDARS_WHERE = Calendars.ACCESS_LEVEL + ">=" +
             Calendars.CONTRIBUTOR_ACCESS + " AND " + Calendars.SYNC_EVENTS + "=1";
 
@@ -176,6 +178,8 @@ public class EditEvent extends Activity implements View.OnClickListener,
     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 String ATTENDEES_DELETE_PREFIX = Attendees.EVENT_ID + "=? AND " +
+            Attendees.ATTENDEE_EMAIL + " IN (";
 
     private static final int DOES_NOT_REPEAT = 0;
     private static final int REPEATS_DAILY = 1;
@@ -218,10 +222,14 @@ public class EditEvent extends Activity implements View.OnClickListener,
     private LinearLayout mExtraOptions;
     private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>();
     private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
-    MultiAutoCompleteTextView mAttendeesList;
+    private Rfc822Validator mEmailValidator;
+    private MultiAutoCompleteTextView mAttendeesList;
     private EmailAddressAdapter mAddressAdapter;
     private String mOriginalAttendees = "";
 
+    // Used to control the visibility of the Guests textview. Default to true
+    private boolean mHasAttendeeData = true;
+
     private EventRecurrence mEventRecurrence = new EventRecurrence();
     private String mRrule;
     private boolean mCalendarsQueryComplete;
@@ -229,6 +237,7 @@ public class EditEvent extends Activity implements View.OnClickListener,
     private ProgressDialog mLoadingCalendarsDialog;
     private AlertDialog mNoCalendarsDialog;
     private ContentValues mInitialValues;
+    private String mOwnerAccount;
 
     /**
      * If the repeating event is created on the phone and it hasn't been
@@ -244,6 +253,9 @@ public class EditEvent extends Activity implements View.OnClickListener,
     private ArrayList<Integer> mReminderValues;
     private ArrayList<String> mReminderLabels;
 
+    // This is to keep track of whether or not multiple calendars have the same display name
+    private static HashMap<String,Boolean> mIsDuplicateName = new HashMap<String,Boolean>();
+
     private Time mStartTime;
     private Time mEndTime;
     private int mModification = MODIFY_UNINITIALIZED;
@@ -251,7 +263,6 @@ public class EditEvent extends Activity implements View.OnClickListener,
 
     private DeleteEventHelper mDeleteEventHelper;
     private QueryHandler mQueryHandler;
-    private AccountManager mAccountManager;
 
     /* This class is used to update the time buttons. */
     private class TimeListener implements OnTimeSetListener {
@@ -283,21 +294,20 @@ public class EditEvent extends Activity implements View.OnClickListener,
                 // Also update the end time to keep the duration constant.
                 endTime.hour = hourOfDay + hourDuration;
                 endTime.minute = minute + minuteDuration;
-                endMillis = endTime.normalize(true);
             } else {
                 // The end time was changed.
                 startMillis = startTime.toMillis(true);
                 endTime.hour = hourOfDay;
                 endTime.minute = minute;
-                endMillis = endTime.normalize(true);
 
-                // Do not allow an event to have an end time before the start time.
+                // Move to the next day if the end time is before the start time.
                 if (endTime.before(startTime)) {
-                    endTime.set(startTime);
-                    endMillis = startMillis;
+                    endTime.monthDay = startTime.monthDay + 1;
                 }
             }
 
+            endMillis = endTime.normalize(true);
+
             setDate(mEndDateButton, endMillis);
             setTime(mStartTimeButton, startMillis);
             setTime(mEndTimeButton, endMillis);
@@ -396,8 +406,29 @@ public class EditEvent extends Activity implements View.OnClickListener,
 
         @Override
         public void bindView(View view, Context context, Cursor cursor) {
+            View colorBar = view.findViewById(R.id.color);
+            if (colorBar != null) {
+                colorBar.setBackgroundDrawable(
+                        Utils.getColorChip(cursor.getInt(CALENDARS_INDEX_COLOR)));
+            }
+
             TextView name = (TextView) view.findViewById(R.id.calendar_name);
-            name.setText(cursor.getString(CALENDARS_INDEX_DISPLAY_NAME));
+            if (name != null) {
+                String displayName = cursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+                name.setText(displayName);
+                name.setTextColor(0xFF000000);
+
+                TextView accountName = (TextView) view.findViewById(R.id.account_name);
+                if(accountName != null) {
+                    if (mIsDuplicateName.containsKey(displayName)
+                            && mIsDuplicateName.get(displayName)) {
+                        accountName.setText(cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT));
+                        accountName.setVisibility(TextView.VISIBLE);
+                    } else {
+                        accountName.setVisibility(TextView.GONE);
+                    }
+                }
+            }
         }
     }
 
@@ -505,12 +536,15 @@ public class EditEvent extends Activity implements View.OnClickListener,
                     return;
                 }
 
-                int primaryCalendarPosition = findPrimaryCalendarPosition();
+                Utils.checkForDuplicateNames(mIsDuplicateName, cursor,
+                        CALENDARS_INDEX_DISPLAY_NAME);
+
+                int defaultCalendarPosition = findDefaultCalendarPosition(mCalendarsCursor);
 
                 // populate the calendars spinner
                 CalendarsAdapter adapter = new CalendarsAdapter(EditEvent.this, mCalendarsCursor);
                 mCalendarsSpinner.setAdapter(adapter);
-                mCalendarsSpinner.setSelection(primaryCalendarPosition);
+                mCalendarsSpinner.setSelection(defaultCalendarPosition);
                 mCalendarsQueryComplete = true;
                 if (mSaveAfterQueryComplete) {
                     mLoadingCalendarsDialog.cancel();
@@ -518,73 +552,65 @@ public class EditEvent extends Activity implements View.OnClickListener,
                     finish();
                 }
 
-                // Find user domain and set it to the validator
-                if(cursor.moveToPosition(primaryCalendarPosition)) {
+                // Find user domain and set it to the validator.
+                // TODO: we may want to update this validator if the user actually picks
+                // a different calendar.  maybe not.  depends on what we want for the
+                // user experience.  this may change when we add support for multiple
+                // accounts, anyway.
+                if (mHasAttendeeData && cursor.moveToPosition(defaultCalendarPosition)) {
                     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)));
+                        String domain = extractDomain(ownEmail);
+                        if (domain != null) {
+                            mEmailValidator = new Rfc822Validator(domain);
+                            mAttendeesList.setValidator(mEmailValidator);
                         }
                     }
                 }
             }
         }
 
-        // 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;
-                        }
+        // Find the calendar position in the cursor that matches calendar in preference
+        private int findDefaultCalendarPosition(Cursor calendarsCursor) {
+            if (calendarsCursor.getCount() <= 0) {
+                return -1;
+            }
 
-                        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;
+            String defaultCalendar = Utils.getSharedPreference(EditEvent.this,
+                    CalendarPreferenceActivity.KEY_DEFAULT_CALENDAR, null);
+
+            if (defaultCalendar == null) {
+                return 0;
+            }
+
+            int position = 0;
+            calendarsCursor.moveToPosition(-1);
+            while(calendarsCursor.moveToNext()) {
+                if (defaultCalendar.equals(mCalendarsCursor
+                        .getString(CALENDARS_INDEX_OWNER_ACCOUNT))) {
+                    return position;
                 }
+                position++;
             }
+            return 0;
         }
     }
 
+    private static String extractDomain(String email) {
+        int separator = email.lastIndexOf('@');
+        if (separator != -1 && ++separator < email.length()) {
+            return email.substring(separator);
+        }
+        return null;
+    }
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
         setContentView(R.layout.edit_event);
-        mAccountManager = AccountManager.get(this);
+
+        boolean newEvent = false;
 
         mFirstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
 
@@ -595,7 +621,7 @@ public class EditEvent extends Activity implements View.OnClickListener,
         mUri = intent.getData();
 
         if (mUri != null) {
-            mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null);
+            mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null, null);
             if (mEventCursor == null || mEventCursor.getCount() == 0) {
                 // The cursor is empty. This can happen if the event was deleted.
                 finish();
@@ -606,14 +632,24 @@ public class EditEvent extends Activity implements View.OnClickListener,
         long begin = intent.getLongExtra(EVENT_BEGIN_TIME, 0);
         long end = intent.getLongExtra(EVENT_END_TIME, 0);
 
+        String domain = "gmail.com";
+
         boolean allDay = false;
         if (mEventCursor != null) {
             // The event already exists so fetch the all-day status
             mEventCursor.moveToFirst();
+            mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
             allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
             String rrule = mEventCursor.getString(EVENT_INDEX_RRULE);
             String timezone = mEventCursor.getString(EVENT_INDEX_TIMEZONE);
             long calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID);
+            mOwnerAccount = mEventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
+            if (!TextUtils.isEmpty(mOwnerAccount)) {
+                String ownerDomain = extractDomain(mOwnerAccount);
+                if (ownerDomain != null) {
+                    domain = ownerDomain;
+                }
+            }
 
             // Remember the initial values
             mInitialValues = new ContentValues();
@@ -624,6 +660,7 @@ public class EditEvent extends Activity implements View.OnClickListener,
             mInitialValues.put(Events.EVENT_TIMEZONE, timezone);
             mInitialValues.put(Events.CALENDAR_ID, calendarId);
         } else {
+            newEvent = true;
             // We are creating a new event, so set the default from the
             // intent (if specified).
             allDay = intent.getBooleanExtra(EVENT_ALL_DAY, false);
@@ -684,8 +721,13 @@ public class EditEvent extends Activity implements View.OnClickListener,
         mRemindersContainer = (LinearLayout) findViewById(R.id.reminder_items_container);
         mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container);
 
-        mAddressAdapter = new EmailAddressAdapter(this);
-        mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees, R.string.hint_attendees);
+        if (mHasAttendeeData) {
+            mAddressAdapter = new EmailAddressAdapter(this);
+            mEmailValidator = new Rfc822Validator(domain);
+            mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees);
+        } else {
+            findViewById(R.id.attendees_group).setVisibility(View.GONE);
+        }
 
         mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -746,11 +788,16 @@ public class EditEvent extends Activity implements View.OnClickListener,
         String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
         mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
 
-        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(this);
         String durationString =
                 prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0");
         mDefaultReminderMinutes = Integer.parseInt(durationString);
 
+        if (newEvent && mDefaultReminderMinutes != 0) {
+            addReminder(this, this, mReminderItems, mReminderValues,
+                    mReminderLabels, mDefaultReminderMinutes);
+        }
+
         long eventId = (mEventCursor == null) ? -1 : mEventCursor.getLong(EVENT_INDEX_ID);
         ContentResolver cr = getContentResolver();
 
@@ -795,7 +842,7 @@ public class EditEvent extends Activity implements View.OnClickListener,
         mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */);
 
        // Attendees cursor
-        if (eventId != -1) {
+        if (mHasAttendeeData && eventId != -1) {
             Uri uri = Attendees.CONTENT_URI;
             String[] whereArgs = {Long.toString(eventId)};
             Cursor attendeeCursor = cr.query(uri, ATTENDEES_PROJECTION, ATTENDEES_WHERE, whereArgs,
@@ -827,16 +874,30 @@ public class EditEvent extends Activity implements View.OnClickListener,
         }
     }
 
-    private Rfc822Token[] getAddressesFromList(MultiAutoCompleteTextView list) {
+    private LinkedHashSet<Rfc822Token> getAddressesFromList(MultiAutoCompleteTextView list) {
         list.clearComposingText();
-        return Rfc822Tokenizer.tokenize(list.getText());
+        LinkedHashSet<Rfc822Token> addresses = new LinkedHashSet<Rfc822Token>();
+        Rfc822Tokenizer.tokenize(list.getText(), addresses);
+
+        // validate the emails, out of paranoia.  they should already be
+        // validated on input, but drop any invalid emails just to be safe.
+        Iterator<Rfc822Token> addressIterator = addresses.iterator();
+        while (addressIterator.hasNext()) {
+            Rfc822Token address = addressIterator.next();
+            if (!mEmailValidator.isValid(address.getAddress())) {
+                Log.w(TAG, "Dropping invalid attendee email address: " + address);
+                addressIterator.remove();
+            }
+        }
+        return addresses;
     }
 
     // From com.google.android.gm.ComposeActivity
-    private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res, int hintId) {
+    private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res) {
         MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) findViewById(res);
         list.setAdapter(mAddressAdapter);
         list.setTokenizer(new Rfc822Tokenizer());
+        list.setValidator(mEmailValidator);
 
         // NOTE: assumes no other filters are set
         list.setFilters(sRecipientFilters);
@@ -851,50 +912,7 @@ public class EditEvent extends Activity implements View.OnClickListener,
      * 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 static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() };
 
     private void initFromIntent(Intent intent) {
         String title = intent.getStringExtra(Events.TITLE);
@@ -1026,7 +1044,9 @@ public class EditEvent extends Activity implements View.OnClickListener,
                 // Round the time to the nearest half hour.
                 mStartTime.second = 0;
                 int minute = mStartTime.minute;
-                if (minute > 0 && minute <= 30) {
+                if (minute == 0) {
+                    // We are already on a half hour increment
+                } else if (minute > 0 && minute <= 30) {
                     mStartTime.minute = 30;
                 } else {
                     mStartTime.minute = 0;
@@ -1037,12 +1057,6 @@ public class EditEvent extends Activity implements View.OnClickListener,
                 mEndTime.set(startMillis + DateUtils.HOUR_IN_MILLIS);
             }
 
-            // New event - set the default reminder
-            if (mDefaultReminderMinutes != 0) {
-                addReminder(this, this, mReminderItems, mReminderValues,
-                        mReminderLabels, mDefaultReminderMinutes);
-            }
-
             // Hide delete button
             mDeleteButton.setVisibility(View.GONE);
         }
@@ -1121,23 +1135,18 @@ public class EditEvent extends Activity implements View.OnClickListener,
     }
 
     @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_BACK:
-                // If we are creating a new event, do not create it if the
-                // title, location and description are all empty, in order to
-                // prevent accidental "no subject" event creations.
-                if (mUri != null || !isEmpty()) {
-                    if (!save()) {
-                        // We cannot exit this activity because the calendars
-                        // are still loading.
-                        return true;
-                    }
-                }
-                break;
+    public void onBackPressed() {
+        // If we are creating a new event, do not create it if the
+        // title, location and description are all empty, in order to
+        // prevent accidental "no subject" event creations.
+        if (mUri != null || !isEmpty()) {
+            if (!save()) {
+                // We cannot exit this activity because the calendars
+                // are still loading.
+                return;
+            }
         }
-
-        return super.onKeyDown(keyCode, event);
+        finish();
     }
 
     private void populateWhen() {
@@ -1404,11 +1413,12 @@ public class EditEvent extends Activity implements View.OnClickListener,
                 return false;
             }
 
-            // Avoid creating a new event if the calendars cursor is empty. This
-            // shouldn't ever happen since the setup wizard should ensure the user
-            // has a calendar.
-            if (mCalendarsCursor == null || mCalendarsCursor.getCount() == 0) {
-                Log.w("Cal", "The calendars table does not contain any calendars."
+            // Avoid creating a new event if the calendars cursor is empty or we clicked through
+            // too quickly and no calendar was selected (blame the monkey)
+            if (mCalendarsCursor == null || mCalendarsCursor.getCount() == 0 ||
+                    mCalendarsSpinner.getSelectedItemId() == AdapterView.INVALID_ROW_ID) {
+                Log.w("Cal", "The calendars table does not contain any calendars"
+                        + " or no calendar was selected."
                         + " New event was not created.");
                 return true;
             }
@@ -1432,8 +1442,13 @@ public class EditEvent extends Activity implements View.OnClickListener,
         // For recurring events, we must make sure that we use duration rather
         // than dtend.
         if (uri == null) {
+            // Add hasAttendeeData for a new event
+            values.put(Events.HAS_ATTENDEE_DATA, 1);
             // Create new event with new contents
             addRecurrenceRule(values);
+            if (mRrule != null) {
+                values.remove(Events.DTEND);
+            }
             eventIdIndex = ops.size();
             Builder b = ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values);
             ops.add(b.build());
@@ -1528,7 +1543,10 @@ public class EditEvent extends Activity implements View.OnClickListener,
             }
         }
 
-        if (eventIdIndex != -1) {
+        // New Event or New Exception to an existing event
+        boolean newEvent = (eventIdIndex != -1);
+
+        if (newEvent) {
             saveRemindersWithBackRef(ops, eventIdIndex, reminderMinutes, mOriginalMinutes,
                     forceSaveReminders);
         } else if (uri != null) {
@@ -1537,32 +1555,96 @@ public class EditEvent extends Activity implements View.OnClickListener,
                     forceSaveReminders);
         }
 
-        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
-                Builder 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);
+        Builder b;
+
+        // New event/instance - Set Organizer's response as yes
+        if (mHasAttendeeData && newEvent) {
+            values.clear();
+            int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
+
+            // Save the default calendar for new events
+            if (mCalendarsCursor != null) {
+                if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
+                    String defaultCalendar = mCalendarsCursor
+                            .getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+                    Utils.setSharedPreference(this,
+                            CalendarPreferenceActivity.KEY_DEFAULT_CALENDAR, defaultCalendar);
                 }
+            }
+
+            String ownerEmail = mOwnerAccount;
+            // Just in case mOwnerAccount is null, try to get owner from mCalendarsCursor
+            if (ownerEmail == null && mCalendarsCursor != null &&
+                    mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
+                ownerEmail = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+            }
+            if (ownerEmail != null) {
+                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());
+            }
+        }
+
+        // TODO: is this the right test?  this currently checks if this is
+        // a new event or an existing event.  or is this a paranoia check?
+        if (mHasAttendeeData && (newEvent || uri != null)) {
+            Editable attendeesText = mAttendeesList.getText();
+            // Hit the content provider only if this is a new event or the user has changed it
+            if (newEvent || !mOriginalAttendees.equals(attendeesText.toString())) {
+                // figure out which attendees need to be added and which ones
+                // need to be deleted.  use a linked hash set, so we maintain
+                // order (but also remove duplicates).
+                LinkedHashSet<Rfc822Token> newAttendees = getAddressesFromList(mAttendeesList);
+
+                // the eventId is only used if eventIdIndex is -1.
+                // TODO: clean up this code.
+                long eventId = uri != null ? ContentUris.parseId(uri) : -1;
+
+                // only compute deltas if this is an existing event.
+                // new events (being inserted into the Events table) won't
+                // have any existing attendees.
+                if (!newEvent) {
+                    HashSet<Rfc822Token> removedAttendees = new HashSet<Rfc822Token>();
+                    HashSet<Rfc822Token> originalAttendees = new HashSet<Rfc822Token>();
+                    Rfc822Tokenizer.tokenize(mOriginalAttendees, originalAttendees);
+                    for (Rfc822Token originalAttendee : originalAttendees) {
+                        if (newAttendees.contains(originalAttendee)) {
+                            // existing attendee.  remove from new attendees set.
+                            newAttendees.remove(originalAttendee);
+                        } else {
+                            // no longer in attendees.  mark as removed.
+                            removedAttendees.add(originalAttendee);
+                        }
+                    }
+
+                    // delete removed attendees
+                    b = ContentProviderOperation.newDelete(Attendees.CONTENT_URI);
+
+                    String[] args = new String[removedAttendees.size() + 1];
+                    args[0] = Long.toString(eventId);
+                    int i = 1;
+                    StringBuilder deleteWhere = new StringBuilder(ATTENDEES_DELETE_PREFIX);
+                    for (Rfc822Token removedAttendee : removedAttendees) {
+                        if (i > 1) {
+                            deleteWhere.append(",");
+                        }
+                        deleteWhere.append("?");
+                        args[i++] = removedAttendee.getAddress();
+                    }
+                    deleteWhere.append(")");
+                    b.withSelection(deleteWhere.toString(), args);
+                    ops.add(b.build());
+                }
 
-                if (attendeesText.length() > 0) {
-                    Rfc822Token[] attendees = getAddressesFromList(mAttendeesList);
-                    // Insert the attendees
-                    for (Rfc822Token attendee : attendees) {
+                if (newAttendees.size() > 0) {
+                    // Insert the new attendees
+                    for (Rfc822Token attendee : newAttendees) {
                         values.clear();
                         values.put(Attendees.ATTENDEE_NAME, attendee.getName());
                         values.put(Attendees.ATTENDEE_EMAIL, attendee.getAddress());
@@ -1570,10 +1652,10 @@ public class EditEvent extends Activity implements View.OnClickListener,
                         values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
                         values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE);
 
-                        if (eventIdIndex != -1) {
+                        if (newEvent) {
                             b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
                                     .withValues(values);
-                            b.withValueBackReference(Reminders.EVENT_ID, eventIdIndex);
+                            b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
                         } else {
                             values.put(Attendees.EVENT_ID, eventId);
                             b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI)
@@ -1595,11 +1677,9 @@ public class EditEvent extends Activity implements View.OnClickListener,
                 }
             }
         } catch (RemoteException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
+            Log.w(TAG, "Ignoring unexpected remote exception", e);
         } catch (OperationApplicationException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
+            Log.w(TAG, "Ignoring unexpected exception", e);
         }
 
         return true;
@@ -1892,10 +1972,10 @@ public class EditEvent extends Activity implements View.OnClickListener,
     }
 
     private ContentValues getContentValuesFromUi() {
-        String title = mTitleTextView.getText().toString();
+        String title = mTitleTextView.getText().toString().trim();
         boolean isAllDay = mAllDayCheckBox.isChecked();
-        String location = mLocationTextView.getText().toString();
-        String description = mDescriptionTextView.getText().toString();
+        String location = mLocationTextView.getText().toString().trim();
+        String description = mDescriptionTextView.getText().toString().trim();
 
         ContentValues values = new ContentValues();
 
@@ -1971,17 +2051,17 @@ public class EditEvent extends Activity implements View.OnClickListener,
     }
 
     private boolean isEmpty() {
-        String title = mTitleTextView.getText().toString();
+        String title = mTitleTextView.getText().toString().trim();
         if (title.length() > 0) {
             return false;
         }
 
-        String location = mLocationTextView.getText().toString();
+        String location = mLocationTextView.getText().toString().trim();
         if (location.length() > 0) {
             return false;
         }
 
-        String description = mDescriptionTextView.getText().toString();
+        String description = mDescriptionTextView.getText().toString().trim();
         if (description.length() > 0) {
             return false;
         }