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;
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.text.format.Time;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
-import android.text.util.Rfc822Validator;
-import android.text.util.Rfc822InputFilter;
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;
import android.widget.TimePicker;
import android.widget.Toast;
-import com.google.android.googlelogin.GoogleLoginServiceConstants;
-import com.google.android.collect.Lists;
-
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
-import java.util.TimeZone;
+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,
DialogInterface.OnCancelListener, DialogInterface.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.OWNER_ACCOUNT, // 14
+ 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;
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";
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;
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
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;
private DeleteEventHelper mDeleteEventHelper;
private QueryHandler mQueryHandler;
- private AccountManager mAccountManager;
/* This class is used to update the time buttons. */
private class TimeListener implements OnTimeSetListener {
// 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);
@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);
+ }
+ }
+ }
}
}
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();
// 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 (cursor.moveToPosition(primaryCalendarPosition)) {
+ if (mHasAttendeeData && cursor.moveToPosition(defaultCalendarPosition)) {
String ownEmail = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
if (ownEmail != null) {
String domain = extractDomain(ownEmail);
}
}
- // 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) {
- Log.w(TAG, "Ignoring unexpected exception", e);
- } catch (IOException e) {
- Log.w(TAG, "Ignoring unexpected exception", e);
- } catch (AuthenticatorException e) {
- Log.w(TAG, "Ignoring unexpected exception", e);
- } 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;
}
}
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.edit_event);
- mAccountManager = AccountManager.get(this);
boolean newEvent = false;
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();
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);
- String ownerAccount = mEventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
- if (!TextUtils.isEmpty(ownerAccount)) {
- String ownerDomain = extractDomain(ownerAccount);
+ mOwnerAccount = mEventCursor.getString(EVENT_INDEX_OWNER_ACCOUNT);
+ if (!TextUtils.isEmpty(mOwnerAccount)) {
+ String ownerDomain = extractDomain(mOwnerAccount);
if (ownerDomain != null) {
domain = ownerDomain;
}
mRemindersContainer = (LinearLayout) findViewById(R.id.reminder_items_container);
mExtraOptions = (LinearLayout) findViewById(R.id.extra_options_container);
- mAddressAdapter = new EmailAddressAdapter(this);
- mEmailValidator = new Rfc822Validator(domain);
- mAttendeesList = initMultiAutoCompleteTextView(R.id.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) {
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);
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,
// validate the emails, out of paranoia. they should already be
// validated on input, but drop any invalid emails just to be safe.
- for (Rfc822Token address : addresses) {
+ 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);
- addresses.remove(address);
+ addressIterator.remove();
}
}
return addresses;
// 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;
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;
}
// 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());
}
}
- 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) {
Builder b;
// New event/instance - Set Organizer's response as yes
- if (eventIdIndex != -1) {
+ if (mHasAttendeeData && newEvent) {
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());
+
+ // 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 (eventIdIndex != -1 || uri != null) {
+ if (mHasAttendeeData && (newEvent || uri != null)) {
Editable attendeesText = mAttendeesList.getText();
- // Hit the content provider only if the user has changed it
- if (!mOriginalAttendees.equals(attendeesText.toString())) {
+ // 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).
// only compute deltas if this is an existing event.
// new events (being inserted into the Events table) won't
// have any existing attendees.
- if (eventIdIndex == -1) {
+ if (!newEvent) {
HashSet<Rfc822Token> removedAttendees = new HashSet<Rfc822Token>();
HashSet<Rfc822Token> originalAttendees = new HashSet<Rfc822Token>();
Rfc822Tokenizer.tokenize(mOriginalAttendees, originalAttendees);
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(Attendees.EVENT_ID, eventIdIndex);
}
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();
}
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;
}