summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRohit Yengisetty <rohit@cyngn.com>2015-01-20 16:56:13 -0800
committerSteve Kondik <steve@cyngn.com>2015-10-18 13:52:42 -0700
commit87b24fb1aa6f98d5202397cfd8207d0d428c1adf (patch)
treea08b544964290d8013b8ea89e32a758bef1e408e /src
parent3d46380f62c2daf9d9bc6f7ed744144552d1014a (diff)
downloadandroid_packages_apps_Calendar-87b24fb1aa6f98d5202397cfd8207d0d428c1adf.tar.gz
android_packages_apps_Calendar-87b24fb1aa6f98d5202397cfd8207d0d428c1adf.tar.bz2
android_packages_apps_Calendar-87b24fb1aa6f98d5202397cfd8207d0d428c1adf.zip
Calendar : Respond to calendar events share intent
Retrofit existing classes to support launching Calendar in "shareable" mode. User will have the ability to select calendar events for whom vcs files will be generated and sent to the requester. Change-Id: I2b0162ea53a4392149aa49765b8b070db36daa8a
Diffstat (limited to 'src')
-rw-r--r--src/com/android/calendar/CalendarUtils.java77
-rw-r--r--src/com/android/calendar/EventInfoFragment.java251
-rw-r--r--src/com/android/calendar/ShareCalendarActivity.java229
-rw-r--r--src/com/android/calendar/agenda/AgendaAdapter.java3
-rw-r--r--src/com/android/calendar/agenda/AgendaFragment.java33
-rw-r--r--src/com/android/calendar/agenda/AgendaListView.java43
-rw-r--r--src/com/android/calendar/agenda/AgendaWindowAdapter.java60
-rw-r--r--src/com/android/calendar/icalendar/VCalendar.java21
8 files changed, 603 insertions, 114 deletions
diff --git a/src/com/android/calendar/CalendarUtils.java b/src/com/android/calendar/CalendarUtils.java
index 0238c321..7b9ffc10 100644
--- a/src/com/android/calendar/CalendarUtils.java
+++ b/src/com/android/calendar/CalendarUtils.java
@@ -103,7 +103,7 @@ public class CalendarUtils {
// Check the values in the db
int keyColumn = cursor.getColumnIndexOrThrow(CalendarCache.KEY);
int valueColumn = cursor.getColumnIndexOrThrow(CalendarCache.VALUE);
- while(cursor.moveToNext()) {
+ while (cursor.moveToNext()) {
String key = cursor.getString(keyColumn);
String value = cursor.getString(valueColumn);
if (TextUtils.equals(key, CalendarCache.KEY_TIMEZONE_TYPE)) {
@@ -353,4 +353,79 @@ public class CalendarUtils {
public static SharedPreferences getSharedPreferences(Context context, String prefsName) {
return context.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
}
+
+ /**
+ * For those interested in changes to the list of shared calendar events
+ */
+ public static interface ShareEventListener {
+
+ /**
+ * Called whenever a calendar event is added to the list of events about to be shared
+ *
+ * @param eventInfo A Triple containing event related information
+ * Triple (event id, startMillis, endMillis)
+ */
+ public void onEventShared(Triple<Long, Long, Long> eventInfo);
+
+
+ /**
+ * called when an event is removed from the share list
+ */
+ public void onEventRemoval(long eventId);
+
+ /**
+ * called to signal that the current share list has been discarded
+ */
+ public void onResetShareList();
+ }
+
+ /**
+ * Implementation of a 3-tuple
+ * Modeled after {@link android.util.Pair} for passing calendar event information
+ *
+ * The first element is perceived to be the unique identifier with the others serving as storage
+ * for ancillary information
+ */
+ public static class Triple<X, Y, Z> {
+ public X first; // dominating element used in equals() and hashCode() implementations
+ public Y second;
+ public Z third;
+
+ public Triple(X a, Y b, Z c) {
+ first = a;
+ second = b;
+ third = c;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ builder.append(first.toString());
+ builder.append(" , ");
+ builder.append(second.toString());
+ builder.append(" , ");
+ builder.append(third.toString());
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /**
+ * First element in the triple is used for comparison, as the other are used to store
+ * ancillary information
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Triple)) return false;
+ return ((Triple)o).first.equals(first);
+ }
+
+ /**
+ * Only the first element contributes
+ */
+ @Override
+ public int hashCode() {
+ return first.hashCode();
+ }
+ }
}
diff --git a/src/com/android/calendar/EventInfoFragment.java b/src/com/android/calendar/EventInfoFragment.java
index 4374226e..dd4d17a6 100644
--- a/src/com/android/calendar/EventInfoFragment.java
+++ b/src/com/android/calendar/EventInfoFragment.java
@@ -427,6 +427,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
private QueryHandler mHandler;
+ // used to signal the completion of querying calendar event data
+ // note: runnable is executed on the ui thread
+ private Runnable mQueryCompleteRunnable;
+
private final Runnable mTZUpdater = new Runnable() {
@Override
public void run() {
@@ -463,6 +467,8 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
private CalendarController mController;
+ private boolean mInNonUiMode;
+
private class QueryHandler extends AsyncQueryService {
public QueryHandler(Context context) {
super(context);
@@ -471,7 +477,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// if the activity is finishing, then close the cursor and return
- final Activity activity = getActivity();
+ final Activity activity = (mActivity != null) ? mActivity : getActivity();
if (activity == null || activity.isFinishing()) {
if (cursor != null) {
cursor.close();
@@ -517,9 +523,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
case TOKEN_QUERY_CALENDARS:
mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
updateCalendar(mView);
- // FRAG_TODO fragments shouldn't set the title anymore
- updateTitle();
-
+ if (!mInNonUiMode) {
+ // FRAG_TODO fragments shouldn't set the title anymore
+ updateTitle();
+ }
args = new String[] {
mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME),
mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) };
@@ -535,7 +542,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
} else {
- sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES);
+ assessQueryCompletion(TOKEN_QUERY_ATTENDEES);
}
if (mHasAlarm) {
// start reminders query
@@ -544,10 +551,11 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
startQuery(TOKEN_QUERY_REMINDERS, null, uri,
REMINDERS_PROJECTION, REMINDERS_WHERE, args, null);
} else {
- sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS);
+ assessQueryCompletion(TOKEN_QUERY_REMINDERS);
}
break;
case TOKEN_QUERY_COLORS:
+ if (mInNonUiMode) break;
ArrayList<Integer> colors = new ArrayList<Integer>();
if (cursor.moveToFirst()) {
do
@@ -584,11 +592,11 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
case TOKEN_QUERY_ATTENDEES:
mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
initAttendeesCursor(mView);
- updateResponse(mView);
+ if (!mInNonUiMode) updateResponse(mView);
break;
case TOKEN_QUERY_REMINDERS:
mRemindersCursor = Utils.matrixCursorFromCursor(cursor);
- initReminders(mView, mRemindersCursor);
+ if (!mInNonUiMode) initReminders(mView, mRemindersCursor);
break;
case TOKEN_QUERY_VISIBLE_CALENDARS:
if (cursor.getCount() > 1) {
@@ -601,34 +609,38 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
} else {
// Don't need to display the calendar owner when there is only a single
// calendar. Skip the duplicate calendars query.
- setVisibilityCommon(mView, R.id.calendar_container, View.GONE);
+ if (!mInNonUiMode) {
+ setVisibilityCommon(mView, R.id.calendar_container, View.GONE);
+ }
mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS;
}
break;
case TOKEN_QUERY_DUPLICATE_CALENDARS:
- SpannableStringBuilder sb = new SpannableStringBuilder();
-
- // Calendar display name
- String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
- sb.append(calendarName);
-
- // Show email account if display name is not unique and
- // display name != email
- String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
- if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) &&
- Utils.isValidEmail(email)) {
- sb.append(" (").append(email).append(")");
- }
+ if (!mInNonUiMode) {
+ SpannableStringBuilder sb = new SpannableStringBuilder();
+
+ // Calendar display name
+ String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
+ sb.append(calendarName);
+
+ // Show email account if display name is not unique and
+ // display name != email
+ String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
+ if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) &&
+ Utils.isValidEmail(email)) {
+ sb.append(" (").append(email).append(")");
+ }
- setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE);
- setTextCommon(mView, R.id.calendar_name, sb);
+ setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE);
+ setTextCommon(mView, R.id.calendar_name, sb);
+ }
break;
}
cursor.close();
- sendAccessibilityEventIfQueryDone(token);
+ assessQueryCompletion(token);
// All queries are done, show the view.
- if (mCurrentQuery == TOKEN_QUERY_ALL) {
+ if (mCurrentQuery == TOKEN_QUERY_ALL && !mInNonUiMode) {
if (mLoadingMsgView.getAlpha() == 1) {
// Loading message is showing, let it stay a bit more (to prevent
// flashing) by adding a start delay to the event animation
@@ -648,10 +660,18 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
}
}
- private void sendAccessibilityEventIfQueryDone(int token) {
+ private void assessQueryCompletion(int token) {
mCurrentQuery |= token;
if (mCurrentQuery == TOKEN_QUERY_ALL) {
- sendAccessibilityEvent();
+
+ // signal query completion
+ if (mQueryCompleteRunnable != null) {
+ mActivity.runOnUiThread(mQueryCompleteRunnable);
+ }
+ if (!mInNonUiMode) {
+ sendAccessibilityEvent();
+ }
+
}
}
@@ -699,6 +719,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
mEventId = eventId;
}
+ public void launchInNonUiMode() {
+ mInNonUiMode = true;
+ }
+
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -739,6 +763,10 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
}
}
+ public void setQueryCompleteRunnable(Runnable runnable) {
+ mQueryCompleteRunnable = runnable;
+ }
+
private void applyDialogParams() {
Dialog dialog = getDialog();
dialog.setCanceledOnTouchOutside(true);
@@ -870,6 +898,21 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
}
}
+ /**
+ * Initiate the querying of the calendar event data.
+ * For when this component is started in a non-ui mode
+ */
+ public void startQueryingData(Context context) {
+ mContext = context;
+ if (context instanceof Activity) {
+ mActivity = (Activity) context;
+ }
+
+ mHandler = new QueryHandler(context);
+ mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
+ null, null, null);
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -1112,7 +1155,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
// Overwrites the one from Event table if available
if (!TextUtils.isEmpty(name)) {
mEventOrganizerDisplayName = name;
- if (!mIsOrganizer) {
+ if (!mIsOrganizer && view != null) {
setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
}
@@ -1160,7 +1203,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
} while (mAttendeesCursor.moveToNext());
mAttendeesCursor.moveToFirst();
- updateAttendees(view);
+ if (view != null) updateAttendees(view);
}
}
}
@@ -1278,64 +1321,13 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
* Generates an .ics formatted file with the event info and launches intent chooser to
* share said file
*/
- private void shareEvent(ShareType type) {
- // Create the respective ICalendar objects from the event info
- VCalendar calendar = new VCalendar();
- calendar.addProperty(VCalendar.VERSION, "2.0");
- calendar.addProperty(VCalendar.PRODID, VCalendar.PRODUCT_IDENTIFIER);
- calendar.addProperty(VCalendar.CALSCALE, "GREGORIAN");
- calendar.addProperty(VCalendar.METHOD, "REQUEST");
-
- VEvent event = new VEvent();
- mEventCursor.moveToFirst();
- // add event start and end datetime
- if (!mAllDay) {
- String eventTimeZone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
- event.addEventStart(mStartMillis, eventTimeZone);
- event.addEventEnd(mEndMillis, eventTimeZone);
- } else {
- // All-day events' start and end time are stored as UTC.
- // Treat the event start and end time as being in the local time zone and convert them
- // to the corresponding UTC datetime. If the UTC time is used as is, the ical recipients
- // will report the wrong start and end time (+/- 1 day) for the event as they will
- // convert the UTC time to their respective local time-zones
- String localTimeZone = Utils.getTimeZone(mActivity, mTZUpdater);
- long eventStart = IcalendarUtils.convertTimeToUtc(mStartMillis, localTimeZone);
- long eventEnd = IcalendarUtils.convertTimeToUtc(mEndMillis, localTimeZone);
- event.addEventStart(eventStart, "UTC");
- event.addEventEnd(eventEnd, "UTC");
- }
-
- event.addProperty(VEvent.LOCATION, mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION));
- event.addProperty(VEvent.DESCRIPTION, mEventCursor.getString(EVENT_INDEX_DESCRIPTION));
- event.addProperty(VEvent.SUMMARY, mEventCursor.getString(EVENT_INDEX_TITLE));
- event.addOrganizer(new Organizer(mEventOrganizerDisplayName, mEventOrganizerEmail));
-
- // Add Attendees to event
- for (Attendee attendee : mAcceptedAttendees) {
- IcalendarUtils.addAttendeeToEvent(attendee, event);
- }
-
- for (Attendee attendee : mDeclinedAttendees) {
- IcalendarUtils.addAttendeeToEvent(attendee, event);
- }
-
- for (Attendee attendee : mTentativeAttendees) {
- IcalendarUtils.addAttendeeToEvent(attendee, event);
- }
-
- for (Attendee attendee : mNoResponseAttendees) {
- IcalendarUtils.addAttendeeToEvent(attendee, event);
- }
-
- // compose all of the ICalendar objects
- calendar.addEvent(event);
-
+ public void shareEvent(ShareType type) {
+ VCalendar calendar = generateVCalendar();
// create and share ics file
boolean isShareSuccessful = false;
try {
// event title serves as the file name prefix
- String filePrefix = event.getProperty(VEvent.SUMMARY);
+ String filePrefix = calendar.getFirstEvent().getProperty(VEvent.SUMMARY);
if (filePrefix == null || filePrefix.length() < 3) {
// default to a generic filename if event title doesn't qualify
// prefix length constraint is imposed by File#createTempFile
@@ -1358,8 +1350,7 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
dir = mActivity.getExternalCacheDir();
}
- File inviteFile = IcalendarUtils.createTempFile(filePrefix, ".ics",
- dir);
+ File inviteFile = IcalendarUtils.createTempFile(filePrefix, ".ics", dir);
if (IcalendarUtils.writeCalendarToFile(calendar, inviteFile)) {
if (type == ShareType.INTENT) {
@@ -1393,9 +1384,11 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
}
startActivity(chooserIntent);
} else {
- String msg = getString(R.string.cal_export_succ_msg);
- Toast.makeText(mActivity, String.format(msg, inviteFile),
- Toast.LENGTH_SHORT).show();
+ if (! mInNonUiMode) {
+ String msg = getString(R.string.cal_export_succ_msg);
+ Toast.makeText(mActivity, String.format(msg, inviteFile),
+ Toast.LENGTH_SHORT).show();
+ }
}
isShareSuccessful = true;
@@ -1414,6 +1407,66 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
}
}
+ /**
+ * Creates a calendar object (VCalendar) that adheres to the ICalendar specification
+ */
+ public VCalendar generateVCalendar() {
+
+ // Create the respective ICalendar objects from the event info
+ VCalendar calendar = new VCalendar();
+ calendar.addProperty(VCalendar.VERSION, "2.0");
+ calendar.addProperty(VCalendar.PRODID, VCalendar.PRODUCT_IDENTIFIER);
+ calendar.addProperty(VCalendar.CALSCALE, "GREGORIAN");
+ calendar.addProperty(VCalendar.METHOD, "REQUEST");
+
+ VEvent event = new VEvent();
+ mEventCursor.moveToFirst();
+ // add event start and end datetime
+ if (!mAllDay) {
+ String eventTimeZone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
+ event.addEventStart(mStartMillis, eventTimeZone);
+ event.addEventEnd(mEndMillis, eventTimeZone);
+ } else {
+ // All-day events' start and end time are stored as UTC.
+ // Treat the event start and end time as being in the local time zone and convert them
+ // to the corresponding UTC datetime. If the UTC time is used as is, the ical recipients
+ // will report the wrong start and end time (+/- 1 day) for the event as they will
+ // convert the UTC time to their respective local time-zones
+ String localTimeZone = Utils.getTimeZone(mActivity, mTZUpdater);
+ long eventStart = IcalendarUtils.convertTimeToUtc(mStartMillis, localTimeZone);
+ long eventEnd = IcalendarUtils.convertTimeToUtc(mEndMillis, localTimeZone);
+ event.addEventStart(eventStart, "UTC");
+ event.addEventEnd(eventEnd, "UTC");
+ }
+
+ event.addProperty(VEvent.LOCATION, mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION));
+ event.addProperty(VEvent.DESCRIPTION, mEventCursor.getString(EVENT_INDEX_DESCRIPTION));
+ event.addProperty(VEvent.SUMMARY, mEventCursor.getString(EVENT_INDEX_TITLE));
+ event.addOrganizer(new Organizer(mEventOrganizerDisplayName, mEventOrganizerEmail));
+
+ // Add Attendees to event
+ for (Attendee attendee : mAcceptedAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ for (Attendee attendee : mDeclinedAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ for (Attendee attendee : mTentativeAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ for (Attendee attendee : mNoResponseAttendees) {
+ IcalendarUtils.addAttendeeToEvent(attendee, event);
+ }
+
+ // compose all of the ICalendar objects
+ calendar.addEvent(event);
+
+ return calendar;
+ }
+
private void showEventColorPickerDialog() {
if (mColorPickerDialog == null) {
mColorPickerDialog = EventColorPickerDialog.newInstance(mColors, mCurrentColor,
@@ -1925,12 +1978,15 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
mEventOrganizerDisplayName = mEventOrganizerEmail;
}
- if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) {
- setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
- setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
- } else {
- setVisibilityCommon(view, R.id.organizer_container, View.GONE);
+ if (!mInNonUiMode) {
+ if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) {
+ setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
+ setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
+ } else {
+ setVisibilityCommon(view, R.id.organizer_container, View.GONE);
+ }
}
+
mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL)
>= Calendars.CAL_ACCESS_CONTRIBUTOR;
@@ -1939,6 +1995,9 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
mIsBusyFreeCalendar =
mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
+ // no more work to be done if launched in non-ui mode
+ if (mInNonUiMode) return;
+
if (!mIsBusyFreeCalendar) {
View b = mView.findViewById(R.id.edit);
@@ -1978,8 +2037,8 @@ public class EventInfoFragment extends DialogFragment implements OnCheckedChange
mActivity.invalidateOptionsMenu();
}
} else {
- setVisibilityCommon(view, R.id.calendar, View.GONE);
- sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
+ if (!mInNonUiMode) setVisibilityCommon(view, R.id.calendar, View.GONE);
+ assessQueryCompletion(TOKEN_QUERY_DUPLICATE_CALENDARS);
}
}
diff --git a/src/com/android/calendar/ShareCalendarActivity.java b/src/com/android/calendar/ShareCalendarActivity.java
new file mode 100644
index 00000000..cd538ca6
--- /dev/null
+++ b/src/com/android/calendar/ShareCalendarActivity.java
@@ -0,0 +1,229 @@
+package com.android.calendar;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.CalendarContract;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import com.android.calendar.agenda.AgendaFragment;
+import com.android.calendar.icalendar.IcalendarUtils;
+import com.android.calendar.icalendar.VCalendar;
+import com.android.calendar.icalendar.VEvent;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Handles requests for sharing calendar events
+ * This activity returns a vcs formatted file
+ */
+public class ShareCalendarActivity extends Activity implements CalendarUtils.ShareEventListener {
+
+ public static final String EXTRA_LIMIT_TO_ONE_EVENT = "EXTRA_LIMIT_TO_ONE_EVENT";
+
+ private static final String TAG = "ShareCalendarActivity";
+
+ private HashSet<CalendarUtils.Triple<Long, Long, Long>> mShareEventsList =
+ new HashSet<CalendarUtils.Triple<Long, Long, Long>>();
+
+ private ArrayList<EventInfoFragment> mEventDataFragments = new ArrayList<EventInfoFragment>();
+
+ private ActionBar mActionBar;
+ private AgendaFragment mAgendaFragment;
+ private long mStartMillis;
+ private ArrayList<Uri> mFileUris = new ArrayList<Uri>();
+ private int mNumQueriesCompleted;
+ private boolean mShouldSelectSingleEvent;
+ private Resources mResources;
+ private String mNoSelectionMsg;
+
+ @Override
+ public void onEventShared(CalendarUtils.Triple<Long, Long, Long> eventInfo) {
+ if (eventInfo.first < 0) return;
+
+ mShareEventsList.add(eventInfo);
+ updateSubtitle();
+ }
+
+ @Override
+ public void onEventRemoval(long eventId) {
+ if (eventId < 0) return;
+
+ CalendarUtils.Triple<Long, Long, Long> eventInfo =
+ new CalendarUtils.Triple<Long, Long, Long>(eventId, 0l, 0l);
+ mShareEventsList.remove(eventInfo);
+ updateSubtitle();
+ }
+
+ @Override
+ public void onResetShareList() {
+ // undo
+ mShareEventsList.clear();
+ updateSubtitle();
+ }
+
+ public void updateSubtitle() {
+ int listSize = mShareEventsList.size();
+ String subtitle;
+ // workaround for Android's poor pluralization treatment of 'zero' quantity in English
+ if (listSize == 0) {
+ subtitle = mNoSelectionMsg;
+ } else {
+ subtitle = mResources.getQuantityString(R.plurals.events_selected,
+ listSize, listSize);
+ }
+ mActionBar.setSubtitle(subtitle);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ Bundle args = intent.getExtras();
+ if (args != null) {
+ mShouldSelectSingleEvent = args.getBoolean(EXTRA_LIMIT_TO_ONE_EVENT, false);
+ }
+
+ mStartMillis = System.currentTimeMillis();
+ setContentView(R.layout.simple_frame_layout);
+ mResources = getResources();
+
+ // create agenda fragment displaying the list of calendar events
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction ft = fragmentManager.beginTransaction();
+ mAgendaFragment = new AgendaFragment(mStartMillis, false, true);
+ mAgendaFragment.setShareModeOptions(this, mShouldSelectSingleEvent);
+ ft.replace(R.id.main_frame, mAgendaFragment);
+ ft.commit();
+
+ mActionBar = getActionBar();
+ mActionBar.setDisplayShowTitleEnabled(true);
+ String title = mShouldSelectSingleEvent ?
+ mResources.getQuantityString(R.plurals.select_events_to_share, 1) :
+ mResources.getQuantityString(R.plurals.select_events_to_share, 2) ;
+
+ mActionBar.setTitle(title);
+ mNoSelectionMsg = mResources.getString(R.string.no_events_selected);
+ updateSubtitle();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.share_event_title_bar, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case(R.id.action_done):
+ generateEventData();
+ evalIfComplete();
+ break;
+
+ case (R.id.action_cancel):
+ setResultAndFinish(false);
+ break;
+
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ // called after the data fragment's is done loading
+ public void queryComplete() {
+ mNumQueriesCompleted++;
+ evalIfComplete();
+ }
+
+ // used load event information for the list of selected events
+ private void generateEventData() {
+ for (CalendarUtils.Triple<Long, Long, Long> event : mShareEventsList) {
+
+ long eventId = event.first;
+ long eventStartMillis = event.second;
+ long eventEndMillis = event.third;
+
+ // EventInfoFragment just serves as a data fragment and is initialized with
+ // default arguments for parameters that don't affect model loading
+ EventInfoFragment eif = new EventInfoFragment(this, eventId, eventStartMillis,
+ eventEndMillis, CalendarContract.Attendees.ATTENDEE_STATUS_NONE, false, 0,
+ null);
+ eif.launchInNonUiMode();
+ eif.startQueryingData(this);
+ eif.setQueryCompleteRunnable(new Runnable() {
+ @Override
+ public void run() {
+ // indicate model loading is complete
+ queryComplete();
+ }
+ });
+
+ mEventDataFragments.add(eif);
+ }
+
+ }
+
+ // generates the vcs files if the data for selected events has been successfully queried
+ private void evalIfComplete() {
+ if (mNumQueriesCompleted != 0 && mNumQueriesCompleted == mEventDataFragments.size()) {
+
+ for(EventInfoFragment event : mEventDataFragments) {
+ try {
+ // generate vcs file
+ VCalendar calendar = event.generateVCalendar();
+ // event title serves as the file name prefix
+ String filePrefix = calendar.getFirstEvent().getProperty(VEvent.SUMMARY);
+ if (filePrefix == null || filePrefix.length() < 3) {
+ // default to a generic filename if event title doesn't qualify
+ // prefix length constraint is imposed by File#createTempFile
+ filePrefix = "invite";
+ }
+
+ filePrefix = filePrefix.replaceAll("\\W+", " ");
+
+ if (!filePrefix.endsWith(" ")) {
+ filePrefix += " ";
+ }
+ File dir = getExternalCacheDir();
+ File inviteFile = IcalendarUtils.createTempFile(filePrefix, ".vcs", dir);
+ inviteFile.setReadable(true, false); // set world-readable
+ if (IcalendarUtils.writeCalendarToFile(calendar, inviteFile)) {
+ mFileUris.add(Uri.fromFile(inviteFile));
+ }
+ } catch (IOException ioe) {
+ break;
+ }
+ }
+
+ setResultAndFinish(true);
+
+ } else if (mEventDataFragments.size() < 1) { // if no events have been selected
+ setResultAndFinish(false);
+ }
+ }
+
+ private void setResultAndFinish(boolean isResultAvailable) {
+ if (isResultAvailable) {
+ Intent intent = new Intent();
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, mFileUris);
+ setResult(RESULT_OK, intent);
+ finish();
+ } else {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+}
diff --git a/src/com/android/calendar/agenda/AgendaAdapter.java b/src/com/android/calendar/agenda/AgendaAdapter.java
index 9e492832..0b42bb9d 100644
--- a/src/com/android/calendar/agenda/AgendaAdapter.java
+++ b/src/com/android/calendar/agenda/AgendaAdapter.java
@@ -26,6 +26,7 @@ import android.text.format.DateUtils;
import android.text.format.Time;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
@@ -78,6 +79,7 @@ public class AgendaAdapter extends ResourceCursorAdapter {
boolean allDay;
boolean grayed;
int julianDay;
+ CheckBox selectedForSharing;
}
public AgendaAdapter(Context context, int resource) {
@@ -125,6 +127,7 @@ public class AgendaAdapter extends ResourceCursorAdapter {
view.findViewById(R.id.agenda_item_text_container);
holder.selectedMarker = view.findViewById(R.id.selected_marker);
holder.colorChip = (ColorChipView)view.findViewById(R.id.agenda_item_color);
+ holder.selectedForSharing = (CheckBox) view.findViewById(R.id.shareCheckbox);
}
holder.startTimeMilli = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN);
diff --git a/src/com/android/calendar/agenda/AgendaFragment.java b/src/com/android/calendar/agenda/AgendaFragment.java
index ff5c47cc..6328bd63 100644
--- a/src/com/android/calendar/agenda/AgendaFragment.java
+++ b/src/com/android/calendar/agenda/AgendaFragment.java
@@ -41,6 +41,7 @@ import com.android.calendar.CalendarController.ViewType;
import com.android.calendar.EventInfoFragment;
import com.android.calendar.GeneralPreferences;
import com.android.calendar.R;
+import com.android.calendar.CalendarUtils.ShareEventListener;
import com.android.calendar.StickyHeaderListView;
import com.android.calendar.Utils;
@@ -71,8 +72,9 @@ public class AgendaFragment extends Fragment implements CalendarController.Event
private AgendaWindowAdapter mAdapter = null;
private boolean mForceReplace = true;
private long mLastShownEventId = -1;
-
-
+ private boolean mLaunchedInShareMode;
+ private boolean mShouldSelectSingleEvent;
+ private ShareEventListener mShareEventListener;
// Tracks the time of the top visible view in order to send UPDATE_TITLE messages to the action
// bar.
@@ -87,13 +89,17 @@ public class AgendaFragment extends Fragment implements CalendarController.Event
};
public AgendaFragment() {
- this(0, false);
+ this(0, false, false);
}
+ public AgendaFragment(long timeMillis, boolean usedForSearch) {
+ this(0, usedForSearch, false);
+ }
// timeMillis - time of first event to show
// usedForSearch - indicates if this fragment is used in the search fragment
- public AgendaFragment(long timeMillis, boolean usedForSearch) {
+ // inShareMode - indicates whether the fragment was started to share calendar events
+ public AgendaFragment(long timeMillis, boolean usedForSearch, boolean inShareMode) {
mInitialTimeMillis = timeMillis;
mTime = new Time();
mLastHandledEventTime = new Time();
@@ -105,6 +111,7 @@ public class AgendaFragment extends Fragment implements CalendarController.Event
}
mLastHandledEventTime.set(mTime);
mUsedForSearch = usedForSearch;
+ mLaunchedInShareMode = inShareMode;
}
@Override
@@ -142,7 +149,6 @@ public class AgendaFragment extends Fragment implements CalendarController.Event
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
-
int screenWidth = mActivity.getResources().getDisplayMetrics().widthPixels;
View v = inflater.inflate(R.layout.agenda_fragment, null);
@@ -168,14 +174,24 @@ public class AgendaFragment extends Fragment implements CalendarController.Event
if (lv != null) {
Adapter a = mAgendaListView.getAdapter();
lv.setAdapter(a);
+
if (a instanceof HeaderViewListAdapter) {
mAdapter = (AgendaWindowAdapter) ((HeaderViewListAdapter)a).getWrappedAdapter();
+ if (mLaunchedInShareMode) {
+ mAdapter.launchInShareMode(true, mShouldSelectSingleEvent);
+ mAgendaListView.launchInShareMode(true, mShouldSelectSingleEvent);
+ if (mShareEventListener != null) {
+ mAgendaListView.setShareEventListener(mShareEventListener);
+ }
+ }
lv.setIndexer(mAdapter);
lv.setHeaderHeightListener(mAdapter);
+
} else if (a instanceof AgendaWindowAdapter) {
mAdapter = (AgendaWindowAdapter)a;
lv.setIndexer(mAdapter);
lv.setHeaderHeightListener(mAdapter);
+
} else {
Log.wtf(TAG, "Cannot find HeaderIndexer for StickyHeaderListView");
}
@@ -208,6 +224,13 @@ public class AgendaFragment extends Fragment implements CalendarController.Event
return v;
}
+ // configure share mode launch options
+ public void setShareModeOptions(ShareEventListener listener, boolean selectSingleEvent) {
+ mShareEventListener = listener;
+ mShouldSelectSingleEvent = selectSingleEvent;
+
+ }
+
@Override
public void onResume() {
super.onResume();
diff --git a/src/com/android/calendar/agenda/AgendaListView.java b/src/com/android/calendar/agenda/AgendaListView.java
index 6cfc7e5b..f3270e66 100644
--- a/src/com/android/calendar/agenda/AgendaListView.java
+++ b/src/com/android/calendar/agenda/AgendaListView.java
@@ -20,6 +20,8 @@ import com.android.calendar.CalendarController;
import com.android.calendar.CalendarController.EventType;
import com.android.calendar.DeleteEventHelper;
import com.android.calendar.R;
+import com.android.calendar.CalendarUtils.ShareEventListener;
+import com.android.calendar.CalendarUtils.Triple;
import com.android.calendar.Utils;
import com.android.calendar.agenda.AgendaAdapter.ViewHolder;
import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo;
@@ -51,6 +53,9 @@ public class AgendaListView extends ListView implements OnItemClickListener {
private Time mTime;
private boolean mShowEventDetailsWithAgenda;
private Handler mHandler = null;
+ private boolean mLaunchedInShareMode;
+ private boolean mShouldSelectSingleEvent;
+ private ShareEventListener mShareEventListener;
private final Runnable mTZUpdater = new Runnable() {
@Override
@@ -86,6 +91,12 @@ public class AgendaListView extends ListView implements OnItemClickListener {
initView(context);
}
+ // "share mode" is off by default
+ public void launchInShareMode(boolean inShareMode, boolean selectSingleEvent) {
+ mLaunchedInShareMode = true;
+ mShouldSelectSingleEvent = selectSingleEvent;
+ }
+
private void initView(Context context) {
mContext = context;
mTimeZone = Utils.getTimeZone(context, mTZUpdater);
@@ -108,6 +119,10 @@ public class AgendaListView extends ListView implements OnItemClickListener {
mHandler = new Handler();
}
+ public void setShareEventListener(ShareEventListener listener) {
+ mShareEventListener = listener;
+ }
+
// Sets a thread to run every EVENT_UPDATE_TIME in order to update the list
// with grayed out past events
private void setPastEventsUpdater() {
@@ -197,10 +212,30 @@ public class AgendaListView extends ListView implements OnItemClickListener {
endTime = Utils.convertAlldayLocalToUTC(mTime, endTime, mTimeZone);
}
mTime.set(startTime);
- CalendarController controller = CalendarController.getInstance(mContext);
- controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, item.id,
- startTime, endTime, 0, 0, CalendarController.EventInfo.buildViewExtraLong(
- Attendees.ATTENDEE_STATUS_NONE, item.allDay), holderStartTime);
+
+ // divert click action to either a ShareEventListener or the Controller
+ if (mShareEventListener != null && mLaunchedInShareMode) {
+ long viewId = ((ViewHolder) holder).instanceId;
+
+ if (mWindowAdapter.isEventInShareList(viewId)) {
+ Triple<Long, Long, Long> eventinfo =
+ new Triple<Long, Long, Long>(item.id, item.begin, item.end);
+ if (mShouldSelectSingleEvent) mShareEventListener.onResetShareList();
+ mShareEventListener.onEventShared(eventinfo);
+
+ } else {
+ // submit event removal
+ mShareEventListener.onEventRemoval(item.id);
+ }
+
+ } else {
+ CalendarController controller = CalendarController.getInstance(mContext);
+ controller.sendEventRelatedEventWithExtra(this, EventType.VIEW_EVENT, item.id,
+ startTime, endTime, 0, 0,
+ CalendarController.EventInfo.buildViewExtraLong(
+ Attendees.ATTENDEE_STATUS_NONE, item.allDay),
+ holderStartTime);
+ }
}
}
}
diff --git a/src/com/android/calendar/agenda/AgendaWindowAdapter.java b/src/com/android/calendar/agenda/AgendaWindowAdapter.java
index 9fd59f0f..97822f98 100644
--- a/src/com/android/calendar/agenda/AgendaWindowAdapter.java
+++ b/src/com/android/calendar/agenda/AgendaWindowAdapter.java
@@ -50,6 +50,7 @@ import com.android.calendar.Utils;
import java.util.Date;
import java.util.Formatter;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
@@ -74,7 +75,7 @@ Check for leaks and excessive allocations
*/
public class AgendaWindowAdapter extends BaseAdapter
- implements StickyHeaderListView.HeaderIndexer, StickyHeaderListView.HeaderHeightListener{
+ implements StickyHeaderListView.HeaderIndexer, StickyHeaderListView.HeaderHeightListener {
static final boolean BASICLOG = false;
static final boolean DEBUGLOG = false;
@@ -228,6 +229,10 @@ public class AgendaWindowAdapter extends BaseAdapter
private final int mSelectedItemTextColor;
private final float mItemRightMargin;
+ private boolean mLaunchedInShareMode;
+ private boolean mShouldSelectSingleEvent;
+ private HashSet<Long> mSharedEvents = new HashSet<Long>();
+
// Types of Query
private static final int QUERY_TYPE_OLDER = 0; // Query for older events
private static final int QUERY_TYPE_NEWER = 1; // Query for newer events
@@ -369,6 +374,12 @@ public class AgendaWindowAdapter extends BaseAdapter
mAgendaListView.addHeaderView(mHeaderView);
}
+ // "share mode" is off by default
+ public void launchInShareMode(boolean inShareMode, boolean selectSingleEvent) {
+ mLaunchedInShareMode = inShareMode;
+ mShouldSelectSingleEvent = selectSingleEvent;
+ }
+
// Method in Adapter
@Override
public int getViewTypeCount() {
@@ -485,6 +496,25 @@ public class AgendaWindowAdapter extends BaseAdapter
pastPresentDivider.setVisibility(View.GONE);
}
}
+
+ if (mLaunchedInShareMode) {
+ Object tag = v.getTag();
+ if (tag instanceof AgendaAdapter.ViewHolder) {
+ AgendaAdapter.ViewHolder vh = (AgendaAdapter.ViewHolder) tag;
+
+ // toggle visibility of share checkbox
+ vh.selectedForSharing.setVisibility(View.VISIBLE);
+ vh.selectedForSharing.setClickable(false);
+
+ // set 'checked' status
+ if (mSharedEvents.contains(vh.instanceId)) {
+ vh.selectedForSharing.setChecked(true);
+ } else {
+ vh.selectedForSharing.setChecked(false);
+ }
+ }
+ }
+
} else {
// TODO
Log.e(TAG, "BUG: getAdapterInfoByPosition returned null!!! " + position);
@@ -1310,10 +1340,32 @@ public class AgendaWindowAdapter extends BaseAdapter
Object vh = v.getTag();
if (vh instanceof AgendaAdapter.ViewHolder) {
mSelectedVH = (AgendaAdapter.ViewHolder) vh;
+ boolean datasetChanged = false;
+
+ if (mLaunchedInShareMode) {
+
+ if (mShouldSelectSingleEvent) {
+ mSharedEvents.clear();
+ mSharedEvents.add(mSelectedVH.instanceId);
+ } else {
+
+ if (mSharedEvents.contains(mSelectedVH.instanceId)) {
+ mSharedEvents.remove(mSelectedVH.instanceId);
+ } else {
+ mSharedEvents.add(mSelectedVH.instanceId);
+ }
+
+ }
+
+ datasetChanged = true;
+ }
+
if (mSelectedInstanceId != mSelectedVH.instanceId) {
mSelectedInstanceId = mSelectedVH.instanceId;
- notifyDataSetChanged();
+ datasetChanged = true;
}
+
+ if (datasetChanged) notifyDataSetChanged();
}
}
}
@@ -1397,6 +1449,10 @@ public class AgendaWindowAdapter extends BaseAdapter
return -1;
}
+ public boolean isEventInShareList(long id) {
+ return mSharedEvents.contains(id);
+ }
+
@Override
public void OnHeaderHeightChanged(int height) {
mStickyHeaderSize = height;
diff --git a/src/com/android/calendar/icalendar/VCalendar.java b/src/com/android/calendar/icalendar/VCalendar.java
index 9f422e6f..140d9358 100644
--- a/src/com/android/calendar/icalendar/VCalendar.java
+++ b/src/com/android/calendar/icalendar/VCalendar.java
@@ -52,11 +52,11 @@ public class VCalendar {
* Add specified property
* @param property
* @param value
- * @return
*/
public boolean addProperty(String property, String value) {
- // since all the required mProperties are unary (only one can exist) , taking a shortcut here
- // when multiples of a property can exist , enforce that here .. cleverly
+ // since all the required mProperties are unary (only one can exist) , taking a shortcut
+ // here
+ // TODO: when multiple attributes of a property can exist , enforce that here
if (sPropertyList.containsKey(property) && value != null) {
mProperties.put(property, IcalendarUtils.cleanseString(value));
return true;
@@ -73,16 +73,25 @@ public class VCalendar {
}
/**
- *
- * @return
+ * Returns all the events that are part of this calendar
*/
public LinkedList<VEvent> getAllEvents() {
return mEvents;
}
/**
+ * Returns the first event of the calendar
+ */
+ public VEvent getFirstEvent() {
+ if (mEvents != null && mEvents.size() > 0) {
+ return mEvents.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Returns the iCal representation of the calendar and all of its inherent components
- * @return
*/
public String getICalFormattedString() {
StringBuilder output = new StringBuilder();