| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.calendar; |
| |
| import static android.provider.Calendar.EVENT_BEGIN_TIME; |
| import static android.provider.Calendar.EVENT_END_TIME; |
| import static android.provider.Calendar.AttendeesColumns.ATTENDEE_STATUS; |
| |
| import android.app.Activity; |
| import android.content.ActivityNotFoundException; |
| import android.content.AsyncQueryHandler; |
| import android.content.ContentProviderOperation; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.OperationApplicationException; |
| import android.content.SharedPreferences; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| import android.graphics.PorterDuff; |
| import android.graphics.Rect; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.pim.ContactsAsyncHelper; |
| import android.pim.EventRecurrence; |
| import android.preference.PreferenceManager; |
| import android.provider.Calendar; |
| import android.provider.ContactsContract; |
| import android.provider.Calendar.Attendees; |
| import android.provider.Calendar.Calendars; |
| import android.provider.Calendar.Events; |
| import android.provider.Calendar.Reminders; |
| import android.provider.ContactsContract.CommonDataKinds; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.Data; |
| import android.provider.ContactsContract.QuickContact; |
| import android.provider.ContactsContract.Intents; |
| import android.provider.ContactsContract.Presence; |
| import android.provider.ContactsContract.CommonDataKinds.Email; |
| import android.text.TextUtils; |
| import android.text.format.DateFormat; |
| import android.text.format.DateUtils; |
| import android.text.format.Time; |
| import android.text.util.Linkify; |
| import android.text.util.Rfc822Token; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnTouchListener; |
| import android.widget.AdapterView; |
| import android.widget.ArrayAdapter; |
| import android.widget.QuickContactBadge; |
| import android.widget.ImageButton; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.Spinner; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.TimeZone; |
| import java.util.regex.Pattern; |
| |
| public class EventInfoActivity extends Activity implements View.OnClickListener, |
| AdapterView.OnItemSelectedListener { |
| public static final boolean DEBUG = false; |
| |
| public static final String TAG = "EventInfoActivity"; |
| |
| private static final int MAX_REMINDERS = 5; |
| |
| /** |
| * These are the corresponding indices into the array of strings |
| * "R.array.change_response_labels" in the resource file. |
| */ |
| static final int UPDATE_SINGLE = 0; |
| static final int UPDATE_ALL = 1; |
| |
| private static final String[] EVENT_PROJECTION = new String[] { |
| Events._ID, // 0 do not remove; used in DeleteEventHelper |
| Events.TITLE, // 1 do not remove; used in DeleteEventHelper |
| Events.RRULE, // 2 do not remove; used in DeleteEventHelper |
| Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper |
| Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper |
| Events.DTSTART, // 5 do not remove; used in DeleteEventHelper |
| Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper |
| Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper |
| Events.DESCRIPTION, // 8 |
| Events.EVENT_LOCATION, // 9 |
| Events.HAS_ALARM, // 10 |
| Events.ACCESS_LEVEL, // 11 |
| Events.COLOR, // 12 |
| Events.HAS_ATTENDEE_DATA, // 13 |
| Events.GUESTS_CAN_MODIFY, // 14 |
| // TODO Events.GUESTS_CAN_INVITE_OTHERS has not been implemented in calendar provider |
| Events.GUESTS_CAN_INVITE_OTHERS, // 15 |
| Events.ORGANIZER, // 16 |
| }; |
| private static final int EVENT_INDEX_ID = 0; |
| private static final int EVENT_INDEX_TITLE = 1; |
| private static final int EVENT_INDEX_RRULE = 2; |
| private static final int EVENT_INDEX_ALL_DAY = 3; |
| private static final int EVENT_INDEX_CALENDAR_ID = 4; |
| private static final int EVENT_INDEX_SYNC_ID = 6; |
| private static final int EVENT_INDEX_EVENT_TIMEZONE = 7; |
| private static final int EVENT_INDEX_DESCRIPTION = 8; |
| private static final int EVENT_INDEX_EVENT_LOCATION = 9; |
| private static final int EVENT_INDEX_HAS_ALARM = 10; |
| private static final int EVENT_INDEX_ACCESS_LEVEL = 11; |
| private static final int EVENT_INDEX_COLOR = 12; |
| private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13; |
| private static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 14; |
| private static final int EVENT_INDEX_CAN_INVITE_OTHERS = 15; |
| private static final int EVENT_INDEX_ORGANIZER = 16; |
| |
| private static final String[] ATTENDEES_PROJECTION = new String[] { |
| Attendees._ID, // 0 |
| Attendees.ATTENDEE_NAME, // 1 |
| Attendees.ATTENDEE_EMAIL, // 2 |
| Attendees.ATTENDEE_RELATIONSHIP, // 3 |
| Attendees.ATTENDEE_STATUS, // 4 |
| }; |
| private static final int ATTENDEES_INDEX_ID = 0; |
| private static final int ATTENDEES_INDEX_NAME = 1; |
| private static final int ATTENDEES_INDEX_EMAIL = 2; |
| private static final int ATTENDEES_INDEX_RELATIONSHIP = 3; |
| private static final int ATTENDEES_INDEX_STATUS = 4; |
| |
| private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=%d"; |
| |
| private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, " |
| + Attendees.ATTENDEE_EMAIL + " ASC"; |
| |
| static final String[] CALENDARS_PROJECTION = new String[] { |
| Calendars._ID, // 0 |
| Calendars.DISPLAY_NAME, // 1 |
| Calendars.OWNER_ACCOUNT, // 2 |
| }; |
| static final int CALENDARS_INDEX_DISPLAY_NAME = 1; |
| static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2; |
| |
| static final String CALENDARS_WHERE = Calendars._ID + "=%d"; |
| |
| private static final String[] REMINDERS_PROJECTION = new String[] { |
| Reminders._ID, // 0 |
| Reminders.MINUTES, // 1 |
| }; |
| private static final int REMINDERS_INDEX_MINUTES = 1; |
| private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=%d AND (" + |
| Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" + |
| Reminders.METHOD_DEFAULT + ")"; |
| private static final String REMINDERS_SORT = Reminders.MINUTES; |
| |
| private static final int MENU_GROUP_REMINDER = 1; |
| private static final int MENU_GROUP_EDIT = 2; |
| private static final int MENU_GROUP_DELETE = 3; |
| |
| private static final int MENU_ADD_REMINDER = 1; |
| private static final int MENU_EDIT = 2; |
| private static final int MENU_DELETE = 3; |
| |
| private static final int ATTENDEE_NO_RESPONSE = -1; |
| private static final int[] ATTENDEE_VALUES = { |
| ATTENDEE_NO_RESPONSE, |
| Attendees.ATTENDEE_STATUS_ACCEPTED, |
| Attendees.ATTENDEE_STATUS_TENTATIVE, |
| Attendees.ATTENDEE_STATUS_DECLINED, |
| }; |
| |
| private LinearLayout mRemindersContainer; |
| private LinearLayout mOrganizerContainer; |
| private TextView mOrganizerView; |
| |
| private Uri mUri; |
| private long mEventId; |
| private Cursor mEventCursor; |
| private Cursor mAttendeesCursor; |
| private Cursor mCalendarsCursor; |
| |
| private long mStartMillis; |
| private long mEndMillis; |
| |
| private boolean mHasAttendeeData; |
| private boolean mIsOrganizer; |
| private long mCalendarOwnerAttendeeId = -1; |
| private String mCalendarOwnerAccount; |
| private boolean mCanModifyCalendar; |
| private boolean mIsBusyFreeCalendar; |
| private boolean mCanModifyEvent; |
| private int mNumOfAttendees; |
| private String mOrganizer; |
| |
| private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>(); |
| private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0); |
| private ArrayList<Integer> mReminderValues; |
| private ArrayList<String> mReminderLabels; |
| private int mDefaultReminderMinutes; |
| |
| private DeleteEventHelper mDeleteEventHelper; |
| private EditResponseHelper mEditResponseHelper; |
| |
| private int mResponseOffset; |
| private int mOriginalAttendeeResponse; |
| private int mAttendeeResponseFromIntent = ATTENDEE_NO_RESPONSE; |
| private boolean mIsRepeating; |
| |
| private Pattern mWildcardPattern = Pattern.compile("^.*$"); |
| private LayoutInflater mLayoutInflater; |
| private LinearLayout mReminderAdder; |
| |
| // TODO This can be removed when the contacts content provider doesn't return duplicates |
| private int mUpdateCounts; |
| private static class ViewHolder { |
| QuickContactBadge badge; |
| ImageView presence; |
| int updateCounts; |
| } |
| private HashMap<String, ViewHolder> mViewHolders = new HashMap<String, ViewHolder>(); |
| private PresenceQueryHandler mPresenceQueryHandler; |
| |
| private static final Uri CONTACT_DATA_WITH_PRESENCE_URI = Data.CONTENT_URI; |
| |
| int PRESENCE_PROJECTION_CONTACT_ID_INDEX = 0; |
| int PRESENCE_PROJECTION_PRESENCE_INDEX = 1; |
| int PRESENCE_PROJECTION_EMAIL_INDEX = 2; |
| int PRESENCE_PROJECTION_PHOTO_ID_INDEX = 3; |
| |
| private static final String[] PRESENCE_PROJECTION = new String[] { |
| Email.CONTACT_ID, // 0 |
| Email.CONTACT_PRESENCE, // 1 |
| Email.DATA, // 2 |
| Email.PHOTO_ID, // 3 |
| }; |
| |
| ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>(); |
| ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>(); |
| ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>(); |
| private int mColor; |
| |
| // This is called when one of the "remove reminder" buttons is selected. |
| public void onClick(View v) { |
| LinearLayout reminderItem = (LinearLayout) v.getParent(); |
| LinearLayout parent = (LinearLayout) reminderItem.getParent(); |
| parent.removeView(reminderItem); |
| mReminderItems.remove(reminderItem); |
| updateRemindersVisibility(); |
| } |
| |
| public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { |
| // If they selected the "No response" option, then don't display the |
| // dialog asking which events to change. |
| if (id == 0 && mResponseOffset == 0) { |
| return; |
| } |
| |
| // If this is not a repeating event, then don't display the dialog |
| // asking which events to change. |
| if (!mIsRepeating) { |
| return; |
| } |
| |
| // If the selection is the same as the original, then don't display the |
| // dialog asking which events to change. |
| int index = findResponseIndexFor(mOriginalAttendeeResponse); |
| if (position == index + mResponseOffset) { |
| return; |
| } |
| |
| // This is a repeating event. We need to ask the user if they mean to |
| // change just this one instance or all instances. |
| mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents()); |
| } |
| |
| public void onNothingSelected(AdapterView<?> parent) { |
| } |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| // Event cursor |
| Intent intent = getIntent(); |
| mUri = intent.getData(); |
| ContentResolver cr = getContentResolver(); |
| mStartMillis = intent.getLongExtra(EVENT_BEGIN_TIME, 0); |
| mEndMillis = intent.getLongExtra(EVENT_END_TIME, 0); |
| mAttendeeResponseFromIntent = intent.getIntExtra(ATTENDEE_STATUS, ATTENDEE_NO_RESPONSE); |
| mEventCursor = managedQuery(mUri, EVENT_PROJECTION, null, null); |
| if (initEventCursor()) { |
| // The cursor is empty. This can happen if the event was deleted. |
| finish(); |
| return; |
| } |
| |
| setContentView(R.layout.event_info_activity); |
| mPresenceQueryHandler = new PresenceQueryHandler(this, cr); |
| mLayoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| mRemindersContainer = (LinearLayout) findViewById(R.id.reminders_container); |
| mOrganizerContainer = (LinearLayout) findViewById(R.id.organizer_container); |
| mOrganizerView = (TextView) findViewById(R.id.organizer); |
| |
| // Calendars cursor |
| Uri uri = Calendars.CONTENT_URI; |
| String where = String.format(CALENDARS_WHERE, mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID)); |
| mCalendarsCursor = managedQuery(uri, CALENDARS_PROJECTION, where, null); |
| mCalendarOwnerAccount = ""; |
| if (mCalendarsCursor != null) { |
| mCalendarsCursor.moveToFirst(); |
| mCalendarOwnerAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); |
| } |
| String eventOrganizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER); |
| mIsOrganizer = mCalendarOwnerAccount.equals(eventOrganizer); |
| mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0; |
| |
| updateView(); |
| |
| // Attendees cursor |
| uri = Attendees.CONTENT_URI; |
| where = String.format(ATTENDEES_WHERE, mEventId); |
| mAttendeesCursor = managedQuery(uri, ATTENDEES_PROJECTION, where, ATTENDEES_SORT_ORDER); |
| initAttendeesCursor(); |
| |
| mOrganizer = eventOrganizer; |
| mCanModifyCalendar = |
| mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) >= Calendars.CONTRIBUTOR_ACCESS; |
| mIsBusyFreeCalendar = |
| mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.FREEBUSY_ACCESS; |
| |
| mCanModifyEvent = mCanModifyCalendar |
| && (mIsOrganizer || (mEventCursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0)); |
| |
| // Initialize the reminder values array. |
| Resources r = getResources(); |
| String[] strings = r.getStringArray(R.array.reminder_minutes_values); |
| int size = strings.length; |
| ArrayList<Integer> list = new ArrayList<Integer>(size); |
| for (int i = 0 ; i < size ; i++) { |
| list.add(Integer.parseInt(strings[i])); |
| } |
| mReminderValues = list; |
| String[] labels = r.getStringArray(R.array.reminder_minutes_labels); |
| mReminderLabels = new ArrayList<String>(Arrays.asList(labels)); |
| |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
| String durationString = |
| prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0"); |
| mDefaultReminderMinutes = Integer.parseInt(durationString); |
| |
| // Reminders cursor |
| boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0; |
| if (hasAlarm) { |
| uri = Reminders.CONTENT_URI; |
| where = String.format(REMINDERS_WHERE, mEventId); |
| Cursor reminderCursor = cr.query(uri, REMINDERS_PROJECTION, where, null, |
| REMINDERS_SORT); |
| try { |
| // First pass: collect all the custom reminder minutes (e.g., |
| // a reminder of 8 minutes) into a global list. |
| while (reminderCursor.moveToNext()) { |
| int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES); |
| EditEvent.addMinutesToList(this, mReminderValues, mReminderLabels, minutes); |
| } |
| |
| // Second pass: create the reminder spinners |
| reminderCursor.moveToPosition(-1); |
| while (reminderCursor.moveToNext()) { |
| int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES); |
| mOriginalMinutes.add(minutes); |
| EditEvent.addReminder(this, this, mReminderItems, mReminderValues, |
| mReminderLabels, minutes); |
| } |
| } finally { |
| reminderCursor.close(); |
| } |
| } |
| |
| // Setup the + Add Reminder Button |
| View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { |
| public void onClick(View v) { |
| addReminder(); |
| } |
| }; |
| ImageButton reminderAddButton = (ImageButton) findViewById(R.id.reminder_add); |
| reminderAddButton.setOnClickListener(addReminderOnClickListener); |
| |
| mReminderAdder = (LinearLayout) findViewById(R.id.reminder_adder); |
| updateRemindersVisibility(); |
| |
| mDeleteEventHelper = new DeleteEventHelper(this, true /* exit when done */); |
| mEditResponseHelper = new EditResponseHelper(this); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| if (initEventCursor()) { |
| // The cursor is empty. This can happen if the event was deleted. |
| finish(); |
| return; |
| } |
| initCalendarsCursor(); |
| updateResponse(); |
| updateTitle(); |
| } |
| |
| private void updateTitle() { |
| Resources res = getResources(); |
| if (mCanModifyCalendar && !mIsOrganizer) { |
| setTitle(res.getString(R.string.event_info_title_invite)); |
| } else { |
| setTitle(res.getString(R.string.event_info_title)); |
| } |
| } |
| |
| /** |
| * Initializes the event cursor, which is expected to point to the first |
| * (and only) result from a query. |
| * @return true if the cursor is empty. |
| */ |
| private boolean initEventCursor() { |
| if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) { |
| return true; |
| } |
| mEventCursor.moveToFirst(); |
| mEventId = mEventCursor.getInt(EVENT_INDEX_ID); |
| String rRule = mEventCursor.getString(EVENT_INDEX_RRULE); |
| mIsRepeating = (rRule != null); |
| return false; |
| } |
| |
| private static class Attendee { |
| String mName; |
| String mEmail; |
| |
| Attendee(String name, String email) { |
| mName = name; |
| mEmail = email; |
| } |
| } |
| |
| private void initAttendeesCursor() { |
| mOriginalAttendeeResponse = ATTENDEE_NO_RESPONSE; |
| mCalendarOwnerAttendeeId = -1; |
| mNumOfAttendees = 0; |
| if (mAttendeesCursor != null) { |
| mNumOfAttendees = mAttendeesCursor.getCount(); |
| if (mAttendeesCursor.moveToFirst()) { |
| mAcceptedAttendees.clear(); |
| mDeclinedAttendees.clear(); |
| mTentativeAttendees.clear(); |
| |
| do { |
| int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); |
| String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME); |
| String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL); |
| |
| if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) == |
| Attendees.RELATIONSHIP_ORGANIZER) { |
| // Overwrites the one from Event table if available |
| if (name != null && name.length() > 0) { |
| mOrganizer = name; |
| } else if (email != null && email.length() > 0) { |
| mOrganizer = email; |
| } |
| } |
| |
| if (mCalendarOwnerAttendeeId == -1 && mCalendarOwnerAccount.equals(email)) { |
| mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID); |
| mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); |
| } else { |
| // Don't show your own status in the list because: |
| // 1) it doesn't make sense for event without other guests. |
| // 2) there's a spinner for that for events with guests. |
| switch(status) { |
| case Attendees.ATTENDEE_STATUS_ACCEPTED: |
| mAcceptedAttendees.add(new Attendee(name, email)); |
| break; |
| case Attendees.ATTENDEE_STATUS_DECLINED: |
| mDeclinedAttendees.add(new Attendee(name, email)); |
| break; |
| default: |
| mTentativeAttendees.add(new Attendee(name, email)); |
| } |
| } |
| } while (mAttendeesCursor.moveToNext()); |
| mAttendeesCursor.moveToFirst(); |
| |
| updateAttendees(); |
| } |
| } |
| // only show the organizer if we're not the organizer and if |
| // we have attendee data (might have been removed by the server |
| // for events with a lot of attendees). |
| if (!mIsOrganizer && mHasAttendeeData) { |
| mOrganizerContainer.setVisibility(View.VISIBLE); |
| mOrganizerView.setText(mOrganizer); |
| } else { |
| mOrganizerContainer.setVisibility(View.GONE); |
| } |
| } |
| |
| private void initCalendarsCursor() { |
| if (mCalendarsCursor != null) { |
| mCalendarsCursor.moveToFirst(); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| if (!isFinishing()) { |
| return; |
| } |
| ContentResolver cr = getContentResolver(); |
| ArrayList<Integer> reminderMinutes = EditEvent.reminderItemsToMinutes(mReminderItems, |
| mReminderValues); |
| ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3); |
| boolean changed = EditEvent.saveReminders(ops, mEventId, reminderMinutes, mOriginalMinutes, |
| false /* no force save */); |
| try { |
| cr.applyBatch(Calendars.CONTENT_URI.getAuthority(), ops); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Ignoring exception: ", e); |
| } catch (OperationApplicationException e) { |
| Log.w(TAG, "Ignoring exception: ", e); |
| } |
| |
| changed |= saveResponse(cr); |
| if (changed) { |
| Toast.makeText(this, R.string.saving_event, Toast.LENGTH_SHORT).show(); |
| } |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| MenuItem item; |
| item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0, |
| R.string.add_new_reminder); |
| item.setIcon(R.drawable.ic_menu_reminder); |
| item.setAlphabeticShortcut('r'); |
| |
| item = menu.add(MENU_GROUP_EDIT, MENU_EDIT, 0, R.string.edit_event_label); |
| item.setIcon(android.R.drawable.ic_menu_edit); |
| item.setAlphabeticShortcut('e'); |
| |
| item = menu.add(MENU_GROUP_DELETE, MENU_DELETE, 0, R.string.delete_event_label); |
| item.setIcon(android.R.drawable.ic_menu_delete); |
| |
| return super.onCreateOptionsMenu(menu); |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| boolean canAddReminders = canAddReminders(); |
| menu.setGroupVisible(MENU_GROUP_REMINDER, canAddReminders); |
| menu.setGroupEnabled(MENU_GROUP_REMINDER, canAddReminders); |
| |
| menu.setGroupVisible(MENU_GROUP_EDIT, mCanModifyEvent); |
| menu.setGroupEnabled(MENU_GROUP_EDIT, mCanModifyEvent); |
| menu.setGroupVisible(MENU_GROUP_DELETE, mCanModifyCalendar); |
| menu.setGroupEnabled(MENU_GROUP_DELETE, mCanModifyCalendar); |
| |
| return super.onPrepareOptionsMenu(menu); |
| } |
| |
| private boolean canAddReminders() { |
| return !mIsBusyFreeCalendar && mReminderItems.size() < MAX_REMINDERS; |
| } |
| |
| private void addReminder() { |
| // TODO: when adding a new reminder, make it different from the |
| // last one in the list (if any). |
| if (mDefaultReminderMinutes == 0) { |
| EditEvent.addReminder(this, this, mReminderItems, |
| mReminderValues, mReminderLabels, 10 /* minutes */); |
| } else { |
| EditEvent.addReminder(this, this, mReminderItems, |
| mReminderValues, mReminderLabels, mDefaultReminderMinutes); |
| } |
| updateRemindersVisibility(); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| super.onOptionsItemSelected(item); |
| switch (item.getItemId()) { |
| case MENU_ADD_REMINDER: |
| addReminder(); |
| break; |
| case MENU_EDIT: |
| doEdit(); |
| break; |
| case MENU_DELETE: |
| doDelete(); |
| break; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| if (keyCode == KeyEvent.KEYCODE_DEL) { |
| doDelete(); |
| return true; |
| } |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| private void updateRemindersVisibility() { |
| if (mIsBusyFreeCalendar) { |
| mRemindersContainer.setVisibility(View.GONE); |
| } else { |
| mRemindersContainer.setVisibility(View.VISIBLE); |
| mReminderAdder.setVisibility(canAddReminders() ? View.VISIBLE : View.GONE); |
| } |
| } |
| |
| /** |
| * Saves the response to an invitation if the user changed the response. |
| * Returns true if the database was updated. |
| * |
| * @param cr the ContentResolver |
| * @return true if the database was changed |
| */ |
| private boolean saveResponse(ContentResolver cr) { |
| if (mAttendeesCursor == null || mEventCursor == null) { |
| return false; |
| } |
| Spinner spinner = (Spinner) findViewById(R.id.response_value); |
| int position = spinner.getSelectedItemPosition() - mResponseOffset; |
| if (position <= 0) { |
| return false; |
| } |
| |
| int status = ATTENDEE_VALUES[position]; |
| |
| // If the status has not changed, then don't update the database |
| if (status == mOriginalAttendeeResponse) { |
| return false; |
| } |
| |
| if (!mIsRepeating) { |
| // This is a non-repeating event |
| updateResponse(cr, mEventId, mCalendarOwnerAttendeeId, status); |
| return true; |
| } |
| |
| // This is a repeating event |
| int whichEvents = mEditResponseHelper.getWhichEvents(); |
| switch (whichEvents) { |
| case -1: |
| return false; |
| case UPDATE_SINGLE: |
| createExceptionResponse(cr, mEventId, mCalendarOwnerAttendeeId, status); |
| return true; |
| case UPDATE_ALL: |
| updateResponse(cr, mEventId, mCalendarOwnerAttendeeId, status); |
| return true; |
| default: |
| Log.e(TAG, "Unexpected choice for updating invitation response"); |
| break; |
| } |
| return false; |
| } |
| |
| private void updateResponse(ContentResolver cr, long eventId, long attendeeId, int status) { |
| // Update the attendee status in the attendees table. the provider |
| // takes care of updating the self attendance status. |
| ContentValues values = new ContentValues(); |
| |
| if (!TextUtils.isEmpty(mCalendarOwnerAccount)) { |
| values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount); |
| } |
| values.put(Attendees.ATTENDEE_STATUS, status); |
| values.put(Attendees.EVENT_ID, eventId); |
| |
| Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId); |
| cr.update(uri, values, null /* where */, null /* selection args */); |
| } |
| |
| private void createExceptionResponse(ContentResolver cr, long eventId, |
| long attendeeId, int status) { |
| // Fetch information about the repeating event. |
| Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); |
| Cursor cursor = cr.query(uri, EVENT_PROJECTION, null, null, null); |
| if (cursor == null) { |
| return; |
| } |
| |
| try { |
| cursor.moveToFirst(); |
| ContentValues values = new ContentValues(); |
| |
| String title = cursor.getString(EVENT_INDEX_TITLE); |
| String timezone = cursor.getString(EVENT_INDEX_EVENT_TIMEZONE); |
| int calendarId = cursor.getInt(EVENT_INDEX_CALENDAR_ID); |
| boolean allDay = cursor.getInt(EVENT_INDEX_ALL_DAY) != 0; |
| String syncId = cursor.getString(EVENT_INDEX_SYNC_ID); |
| |
| values.put(Events.TITLE, title); |
| values.put(Events.EVENT_TIMEZONE, timezone); |
| values.put(Events.ALL_DAY, allDay ? 1 : 0); |
| values.put(Events.CALENDAR_ID, calendarId); |
| values.put(Events.DTSTART, mStartMillis); |
| values.put(Events.DTEND, mEndMillis); |
| values.put(Events.ORIGINAL_EVENT, syncId); |
| values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis); |
| values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0); |
| values.put(Events.STATUS, Events.STATUS_CONFIRMED); |
| values.put(Events.SELF_ATTENDEE_STATUS, status); |
| |
| // Create a recurrence exception |
| cr.insert(Events.CONTENT_URI, values); |
| } finally { |
| cursor.close(); |
| } |
| } |
| |
| private int findResponseIndexFor(int response) { |
| int size = ATTENDEE_VALUES.length; |
| for (int index = 0; index < size; index++) { |
| if (ATTENDEE_VALUES[index] == response) { |
| return index; |
| } |
| } |
| return 0; |
| } |
| |
| private void doEdit() { |
| Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); |
| Intent intent = new Intent(Intent.ACTION_EDIT, uri); |
| intent.putExtra(Calendar.EVENT_BEGIN_TIME, mStartMillis); |
| intent.putExtra(Calendar.EVENT_END_TIME, mEndMillis); |
| intent.setClass(EventInfoActivity.this, EditEvent.class); |
| startActivity(intent); |
| finish(); |
| } |
| |
| private void doDelete() { |
| mDeleteEventHelper.delete(mStartMillis, mEndMillis, mEventCursor, -1); |
| } |
| |
| private void updateView() { |
| if (mEventCursor == null) { |
| return; |
| } |
| |
| String eventName = mEventCursor.getString(EVENT_INDEX_TITLE); |
| if (eventName == null || eventName.length() == 0) { |
| Resources res = getResources(); |
| eventName = res.getString(R.string.no_title_label); |
| } |
| |
| boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0; |
| String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION); |
| String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION); |
| String rRule = mEventCursor.getString(EVENT_INDEX_RRULE); |
| boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0; |
| String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE); |
| mColor = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff; |
| |
| View calBackground = findViewById(R.id.cal_background); |
| calBackground.setBackgroundColor(mColor); |
| |
| TextView title = (TextView) findViewById(R.id.title); |
| title.setTextColor(mColor); |
| |
| View divider = findViewById(R.id.divider); |
| divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN); |
| |
| // What |
| if (eventName != null) { |
| setTextCommon(R.id.title, eventName); |
| } |
| |
| // When |
| String when; |
| int flags; |
| if (allDay) { |
| flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE; |
| } else { |
| flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE; |
| if (DateFormat.is24HourFormat(this)) { |
| flags |= DateUtils.FORMAT_24HOUR; |
| } |
| } |
| when = DateUtils.formatDateRange(this, mStartMillis, mEndMillis, flags); |
| setTextCommon(R.id.when, when); |
| |
| // Show the event timezone if it is different from the local timezone |
| Time time = new Time(); |
| String localTimezone = time.timezone; |
| if (allDay) { |
| localTimezone = Time.TIMEZONE_UTC; |
| } |
| if (eventTimezone != null && !localTimezone.equals(eventTimezone) && !allDay) { |
| String displayName; |
| TimeZone tz = TimeZone.getTimeZone(localTimezone); |
| if (tz == null || tz.getID().equals("GMT")) { |
| displayName = localTimezone; |
| } else { |
| displayName = tz.getDisplayName(); |
| } |
| |
| setTextCommon(R.id.timezone, displayName); |
| } else { |
| setVisibilityCommon(R.id.timezone_container, View.GONE); |
| } |
| |
| // Repeat |
| if (rRule != null) { |
| EventRecurrence eventRecurrence = new EventRecurrence(); |
| eventRecurrence.parse(rRule); |
| Time date = new Time(); |
| if (allDay) { |
| date.timezone = Time.TIMEZONE_UTC; |
| } |
| date.set(mStartMillis); |
| eventRecurrence.setStartDate(date); |
| String repeatString = eventRecurrence.getRepeatString(); |
| setTextCommon(R.id.repeat, repeatString); |
| } else { |
| setVisibilityCommon(R.id.repeat_container, View.GONE); |
| } |
| |
| // Where |
| if (location == null || location.length() == 0) { |
| setVisibilityCommon(R.id.where, View.GONE); |
| } else { |
| final TextView textView = (TextView) findViewById(R.id.where); |
| if (textView != null) { |
| textView.setAutoLinkMask(0); |
| textView.setText(location); |
| Linkify.addLinks(textView, mWildcardPattern, "geo:0,0?q="); |
| textView.setOnTouchListener(new OnTouchListener() { |
| public boolean onTouch(View v, MotionEvent event) { |
| try { |
| return v.onTouchEvent(event); |
| } catch (ActivityNotFoundException e) { |
| // ignore |
| return true; |
| } |
| } |
| }); |
| } |
| } |
| |
| // Description |
| if (description == null || description.length() == 0) { |
| setVisibilityCommon(R.id.description, View.GONE); |
| } else { |
| setTextCommon(R.id.description, description); |
| } |
| |
| // Calendar |
| if (mCalendarsCursor != null) { |
| String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); |
| setTextCommon(R.id.calendar, calendarName); |
| } else { |
| setVisibilityCommon(R.id.calendar_container, View.GONE); |
| } |
| } |
| |
| private void updateAttendees() { |
| CharSequence[] entries; |
| entries = getResources().getTextArray(R.array.response_labels2); |
| LinearLayout attendeesLayout = (LinearLayout) findViewById(R.id.attendee_list); |
| attendeesLayout.removeAllViewsInLayout(); |
| ++mUpdateCounts; |
| addAttendeesToLayout(mAcceptedAttendees, attendeesLayout, entries[0]); |
| addAttendeesToLayout(mDeclinedAttendees, attendeesLayout, entries[2]); |
| addAttendeesToLayout(mTentativeAttendees, attendeesLayout, entries[1]); |
| } |
| |
| private void addAttendeesToLayout(ArrayList<Attendee> attendees, LinearLayout attendeeList, |
| CharSequence sectionTitle) { |
| if (attendees.size() == 0) { |
| return; |
| } |
| |
| // Yes/No/Maybe Title |
| View titleView = mLayoutInflater.inflate(R.layout.contact_item, null); |
| titleView.findViewById(R.id.badge).setVisibility(View.GONE); |
| View divider = titleView.findViewById(R.id.separator); |
| divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN); |
| |
| TextView title = (TextView) titleView.findViewById(R.id.name); |
| title.setText(getString(R.string.response_label, sectionTitle, attendees.size())); |
| title.setTextAppearance(this, R.style.TextAppearance_EventInfo_Label); |
| attendeeList.addView(titleView); |
| |
| // Attendees |
| int numOfAttendees = attendees.size(); |
| StringBuilder selection = new StringBuilder(Email.DATA + " IN ("); |
| String[] selectionArgs = new String[numOfAttendees]; |
| |
| for (int i = 0; i < numOfAttendees; ++i) { |
| Attendee attendee = attendees.get(i); |
| selectionArgs[i] = attendee.mEmail; |
| |
| View v = mLayoutInflater.inflate(R.layout.contact_item, null); |
| v.setTag(attendee); |
| |
| View separator = v.findViewById(R.id.separator); |
| separator.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN); |
| |
| // Text |
| TextView tv = (TextView) v.findViewById(R.id.name); |
| String name = attendee.mName; |
| if (name == null || name.length() == 0) { |
| name = attendee.mEmail; |
| } |
| tv.setText(name); |
| |
| ViewHolder vh = new ViewHolder(); |
| vh.badge = (QuickContactBadge) v.findViewById(R.id.badge); |
| vh.badge.assignContactFromEmail(attendee.mEmail, true); |
| vh.presence = (ImageView) v.findViewById(R.id.presence); |
| mViewHolders.put(attendee.mEmail, vh); |
| |
| if (i == 0) { |
| selection.append('?'); |
| } else { |
| selection.append(", ?"); |
| } |
| |
| attendeeList.addView(v); |
| } |
| selection.append(')'); |
| |
| mPresenceQueryHandler.startQuery(mUpdateCounts, attendees, CONTACT_DATA_WITH_PRESENCE_URI, |
| PRESENCE_PROJECTION, selection.toString(), selectionArgs, null); |
| } |
| |
| private class PresenceQueryHandler extends AsyncQueryHandler { |
| Context mContext; |
| |
| public PresenceQueryHandler(Context context, ContentResolver cr) { |
| super(cr); |
| mContext = context; |
| } |
| |
| @Override |
| protected void onQueryComplete(int queryIndex, Object cookie, Cursor cursor) { |
| if (cursor == null) { |
| if (DEBUG) { |
| Log.e(TAG, "onQueryComplete: cursor == null"); |
| } |
| return; |
| } |
| |
| try { |
| cursor.moveToPosition(-1); |
| while (cursor.moveToNext()) { |
| String email = cursor.getString(PRESENCE_PROJECTION_EMAIL_INDEX); |
| int contactId = cursor.getInt(PRESENCE_PROJECTION_CONTACT_ID_INDEX); |
| ViewHolder vh = mViewHolders.get(email); |
| int photoId = cursor.getInt(PRESENCE_PROJECTION_PHOTO_ID_INDEX); |
| if (DEBUG) { |
| Log.e(TAG, "onQueryComplete Id: " + contactId + " PhotoId: " + photoId |
| + " Email: " + email); |
| } |
| if (vh == null) { |
| continue; |
| } |
| ImageView presenceView = vh.presence; |
| if (presenceView != null) { |
| int status = cursor.getInt(PRESENCE_PROJECTION_PRESENCE_INDEX); |
| presenceView.setImageResource(Presence.getPresenceIconResourceId(status)); |
| presenceView.setVisibility(View.VISIBLE); |
| } |
| |
| if (photoId > 0 && vh.updateCounts < queryIndex) { |
| vh.updateCounts = queryIndex; |
| Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); |
| ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(mContext, vh.badge, |
| personUri, R.drawable.ic_contact_picture); |
| } |
| } |
| } finally { |
| cursor.close(); |
| } |
| } |
| } |
| |
| void updateResponse() { |
| // we only let the user accept/reject/etc. a meeting if: |
| // a) you can edit the event's containing calendar AND |
| // b) you're not the organizer and only attendee |
| // (if the attendee data has been hidden, the visible number of attendees |
| // will be 1 -- the calendar owner's). |
| // (there are more cases involved to be 100% accurate, such as |
| // paying attention to whether or not an attendee status was |
| // included in the feed, but we're currently omitting those corner cases |
| // for simplicity). |
| if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1)) { |
| setVisibilityCommon(R.id.response_container, View.GONE); |
| return; |
| } |
| |
| setVisibilityCommon(R.id.response_container, View.VISIBLE); |
| |
| Spinner spinner = (Spinner) findViewById(R.id.response_value); |
| |
| mResponseOffset = 0; |
| |
| /* If the user has previously responded to this event |
| * we should not allow them to select no response again. |
| * Switch the entries to a set of entries without the |
| * no response option. |
| */ |
| if ((mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_INVITED) |
| && (mOriginalAttendeeResponse != ATTENDEE_NO_RESPONSE) |
| && (mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_NONE)) { |
| CharSequence[] entries; |
| entries = getResources().getTextArray(R.array.response_labels2); |
| mResponseOffset = -1; |
| ArrayAdapter<CharSequence> adapter = |
| new ArrayAdapter<CharSequence>(this, |
| android.R.layout.simple_spinner_item, entries); |
| adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
| spinner.setAdapter(adapter); |
| } |
| |
| int index; |
| if (mAttendeeResponseFromIntent != ATTENDEE_NO_RESPONSE) { |
| index = findResponseIndexFor(mAttendeeResponseFromIntent); |
| } else { |
| index = findResponseIndexFor(mOriginalAttendeeResponse); |
| } |
| spinner.setSelection(index + mResponseOffset); |
| spinner.setOnItemSelectedListener(this); |
| } |
| |
| private void setTextCommon(int id, CharSequence text) { |
| TextView textView = (TextView) findViewById(id); |
| if (textView == null) |
| return; |
| textView.setText(text); |
| } |
| |
| private void setVisibilityCommon(int id, int visibility) { |
| View v = findViewById(id); |
| if (v != null) { |
| v.setVisibility(visibility); |
| } |
| return; |
| } |
| |
| /** |
| * Taken from com.google.android.gm.HtmlConversationActivity |
| * |
| * Send the intent that shows the Contact info corresponding to the email address. |
| */ |
| public void showContactInfo(Attendee attendee, Rect rect) { |
| // First perform lookup query to find existing contact |
| final ContentResolver resolver = getContentResolver(); |
| final String address = attendee.mEmail; |
| final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI, |
| Uri.encode(address)); |
| final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri); |
| |
| if (lookupUri != null) { |
| // Found matching contact, trigger QuickContact |
| QuickContact.showQuickContact(this, rect, lookupUri, QuickContact.MODE_MEDIUM, null); |
| } else { |
| // No matching contact, ask user to create one |
| final Uri mailUri = Uri.fromParts("mailto", address, null); |
| final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri); |
| |
| // Pass along full E-mail string for possible create dialog |
| Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null); |
| intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString()); |
| |
| // Only provide personal name hint if we have one |
| final String senderPersonal = attendee.mName; |
| if (!TextUtils.isEmpty(senderPersonal)) { |
| intent.putExtra(Intents.Insert.NAME, senderPersonal); |
| } |
| |
| startActivity(intent); |
| } |
| } |
| } |