mirror of https://github.com/tasks/tasks
Beginnings of google calendar plugin
parent
14bb13f023
commit
b333a8243c
@ -0,0 +1,131 @@
|
||||
package com.todoroo.astrid.gcal;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.astrid.utility.Preferences;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public class Calendars {
|
||||
|
||||
private static final String ID_COLUMN_NAME = "_id";
|
||||
private static final String DISPLAY_COLUMN_NAME = "displayName";
|
||||
private static final String ACCES_LEVEL_COLUMN_NAME = "access_level";
|
||||
|
||||
private static final String[] CALENDARS_PROJECTION = new String[] {
|
||||
ID_COLUMN_NAME, // Calendars._ID,
|
||||
DISPLAY_COLUMN_NAME // Calendars.DISPLAY_NAME
|
||||
};
|
||||
|
||||
// Only show calendars that the user can modify. Access level 500
|
||||
// corresponds to Calendars.CONTRIBUTOR_ACCESS
|
||||
private static final String CALENDARS_WHERE = ACCES_LEVEL_COLUMN_NAME + ">= 500";
|
||||
|
||||
private static final String CALENDARS_SORT = "displayName ASC";
|
||||
|
||||
// --- api access
|
||||
|
||||
/** Return content uri for calendars */
|
||||
public static Uri getCalendarContentUri() {
|
||||
if(android.os.Build.VERSION.SDK_INT >= 8)
|
||||
return Uri.parse("content://com.android.calendar/calendars");
|
||||
else
|
||||
return Uri.parse("content://calendar/calendars");
|
||||
}
|
||||
|
||||
/** Return calendar package name */
|
||||
public static String getCalendarPackage() {
|
||||
if(android.os.Build.VERSION.SDK_INT >= 8)
|
||||
return "com.google.android.calendar";
|
||||
else
|
||||
return "com.android.calendar";
|
||||
}
|
||||
|
||||
// --- helper data structure
|
||||
|
||||
/**
|
||||
* Helper class for working with the results of getCalendars
|
||||
*/
|
||||
public static class CalendarResult {
|
||||
/** calendar names */
|
||||
public String[] calendars;
|
||||
|
||||
/** calendar ids. null entry -> use default */
|
||||
public String[] calendarIds;
|
||||
|
||||
/** selected index */
|
||||
public int selectedIndex = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends all user-modifiable calendars to listPreference. Always includes
|
||||
* entry called "Astrid default" with calendar id of
|
||||
* prefs_defaultCalendar_default.
|
||||
*
|
||||
* @param context
|
||||
* context
|
||||
* @param listPreference
|
||||
* preference to init
|
||||
*/
|
||||
public static CalendarResult getCalendars(Context context) {
|
||||
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
Resources r = context.getResources();
|
||||
Cursor c = cr.query(getCalendarContentUri(), CALENDARS_PROJECTION,
|
||||
CALENDARS_WHERE, null, CALENDARS_SORT);
|
||||
|
||||
// Fetch the current setting. Invalid calendar id will
|
||||
// be changed to default value.
|
||||
String defaultSetting = Preferences.getStringValue(R.string.gcal_p_default);
|
||||
|
||||
CalendarResult result = new CalendarResult();
|
||||
|
||||
if (c == null || c.getCount() == 0) {
|
||||
// Something went wrong when querying calendars. Only offer them
|
||||
// the system default choice
|
||||
result.calendars = new String[] {
|
||||
r.getString(R.string.gcal_GCP_default) };
|
||||
result.calendarIds = new String[] { null };
|
||||
result.selectedIndex = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
int calendarCount = c.getCount();
|
||||
|
||||
result.calendars = new String[calendarCount];
|
||||
result.calendarIds = new String[calendarCount];
|
||||
|
||||
// Iterate calendars one by one, and fill up the list preference
|
||||
try {
|
||||
int row = 0;
|
||||
int idColumn = c.getColumnIndex(ID_COLUMN_NAME);
|
||||
int nameColumn = c.getColumnIndex(DISPLAY_COLUMN_NAME);
|
||||
while (c.moveToNext()) {
|
||||
String id = c.getString(idColumn);
|
||||
String name = c.getString(nameColumn);
|
||||
result.calendars[row] = name;
|
||||
result.calendarIds[row] = id;
|
||||
|
||||
// We found currently selected calendar
|
||||
if (defaultSetting != null && defaultSetting.equals(id)) {
|
||||
result.selectedIndex = row;
|
||||
}
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
if (result.selectedIndex == -1 || result.selectedIndex >= calendarCount) {
|
||||
result.selectedIndex = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
c.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
package com.todoroo.astrid.gcal;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentValues;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import com.flurry.android.FlurryAgent;
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.astrid.activity.TaskEditActivity.TaskEditControlSet;
|
||||
import com.todoroo.astrid.gcal.Calendars.CalendarResult;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
|
||||
/**
|
||||
* Control Set for managing repeats
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class GCalControlSet implements TaskEditControlSet {
|
||||
|
||||
/** If task has no estimated time, how early to set a task in calendar (seconds)*/
|
||||
private static final int DEFAULT_CAL_TIME = 3600;
|
||||
|
||||
// --- instance variables
|
||||
|
||||
private final Activity activity;
|
||||
|
||||
private final CalendarResult calendars;
|
||||
|
||||
private final CheckBox addToCalendar;
|
||||
|
||||
private final Spinner calendarSelector;
|
||||
|
||||
private final Button viewCalendarEvent;
|
||||
|
||||
public GCalControlSet(Activity activity, ViewGroup parent) {
|
||||
this.activity = activity;
|
||||
LayoutInflater.from(activity).inflate(R.layout.gcal_control, parent, true);
|
||||
|
||||
this.addToCalendar = (CheckBox) activity.findViewById(R.id.add_to_calendar);
|
||||
this.calendarSelector = (Spinner) activity.findViewById(R.id.calendars);
|
||||
this.viewCalendarEvent = (Button) activity.findViewById(R.id.view_calendar_event);
|
||||
|
||||
calendars = Calendars.getCalendars(activity);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity,
|
||||
android.R.layout.simple_spinner_item, calendars.calendars);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
calendarSelector.setAdapter(adapter);
|
||||
|
||||
addToCalendar.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
calendarSelector.setVisibility(isChecked ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/** Take the values from the model and set the calendar start and end times
|
||||
* based on these. Sets keys 'dtstart' and 'dtend'.
|
||||
*
|
||||
* @param preferred preferred due date or null
|
||||
* @param definite definite due date or null
|
||||
* @param estimatedSeconds estimated duration or null
|
||||
* @param values
|
||||
*/
|
||||
@SuppressWarnings({ "nls", "unused" })
|
||||
public void createCalendarStartEndTimes(Date preferred, Date definite,
|
||||
Integer estimatedSeconds, ContentValues values) {
|
||||
FlurryAgent.onEvent("create-calendar-event");
|
||||
|
||||
Long deadlineDate = null;
|
||||
if (preferred != null && preferred.after(new Date()))
|
||||
deadlineDate = preferred.getTime();
|
||||
else if (definite != null)
|
||||
deadlineDate = definite.getTime();
|
||||
else
|
||||
deadlineDate = System.currentTimeMillis() + 24*3600*1000L;
|
||||
|
||||
int estimatedTime = DEFAULT_CAL_TIME;
|
||||
if(estimatedSeconds != null && estimatedSeconds > 0) {
|
||||
estimatedTime = estimatedSeconds;
|
||||
}
|
||||
values.put("dtstart", deadlineDate - estimatedTime * 1000L);
|
||||
values.put("dtend", deadlineDate);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void onPause() {
|
||||
// create calendar event
|
||||
/*if(addToCalendar.isChecked() && model.getCalendarUri() == null) {
|
||||
|
||||
Uri uri = Uri.parse("content://calendar/events");
|
||||
ContentResolver cr = getContentResolver();
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("title", title.getText().toString());
|
||||
values.put("calendar_id", Preferences.getDefaultCalendarIDSafe(this));
|
||||
values.put("description", notes.getText().toString());
|
||||
values.put("hasAlarm", 0);
|
||||
values.put("transparency", 0);
|
||||
values.put("visibility", 0);
|
||||
|
||||
createCalendarStartEndTimes(model.getPreferredDueDate(),
|
||||
model.getDefiniteDueDate(), model.getEstimatedSeconds(),
|
||||
values);
|
||||
|
||||
Uri result = null;
|
||||
try{
|
||||
result = cr.insert(uri, values);
|
||||
model.setCalendarUri(result.toString());
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e("astrid", "Error creating calendar event!", e);
|
||||
}
|
||||
} */
|
||||
|
||||
// save save save
|
||||
|
||||
/* if(addToCalendar.isChecked() && model.getCalendarUri() != null) {
|
||||
Uri result = Uri.parse(model.getCalendarUri());
|
||||
Intent intent = new Intent(Intent.ACTION_EDIT, result);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
createCalendarStartEndTimes(model.getPreferredDueDate(),
|
||||
model.getDefiniteDueDate(), model.getEstimatedSeconds(),
|
||||
values);
|
||||
|
||||
intent.putExtra("beginTime", values.getAsLong("dtstart"));
|
||||
intent.putExtra("endTime", values.getAsLong("dtend"));
|
||||
|
||||
startActivity(intent);
|
||||
} */
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFromTask(Task task) {
|
||||
//
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToModel(Task task) {
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package com.todoroo.astrid.gcal;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.google.ical.iter.RecurrenceIterator;
|
||||
import com.google.ical.iter.RecurrenceIteratorFactory;
|
||||
import com.google.ical.values.DateTimeValueImpl;
|
||||
import com.google.ical.values.DateValue;
|
||||
import com.google.ical.values.DateValueImpl;
|
||||
import com.google.ical.values.Frequency;
|
||||
import com.google.ical.values.RRule;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
|
||||
public class GCalTaskCompleteListener extends BroadcastReceiver {
|
||||
|
||||
@Autowired
|
||||
private TaskService taskService;
|
||||
|
||||
@Autowired
|
||||
private ExceptionService exceptionService;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
|
||||
Task task = taskService.fetchById(taskId, Task.ID, Task.RECURRENCE,
|
||||
Task.DUE_DATE, Task.FLAGS, Task.HIDE_UNTIL);
|
||||
if(task == null)
|
||||
return;
|
||||
|
||||
String recurrence = task.getValue(Task.RECURRENCE);
|
||||
if(recurrence != null && recurrence.length() > 0) {
|
||||
DateValue repeatFrom;
|
||||
Date repeatFromDate = new Date();
|
||||
|
||||
DateValue today = new DateValueImpl(repeatFromDate.getYear() + 1900,
|
||||
repeatFromDate.getMonth() + 1, repeatFromDate.getDate());
|
||||
if(task.hasDueDate() && !task.getFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION)) {
|
||||
repeatFromDate = new Date(task.getValue(Task.DUE_DATE));
|
||||
if(task.hasDueTime()) {
|
||||
repeatFrom = new DateTimeValueImpl(repeatFromDate.getYear() + 1900,
|
||||
repeatFromDate.getMonth() + 1, repeatFromDate.getDate(),
|
||||
repeatFromDate.getHours(), repeatFromDate.getMinutes(), repeatFromDate.getSeconds());
|
||||
} else {
|
||||
repeatFrom = new DateValueImpl(repeatFromDate.getYear() + 1900,
|
||||
repeatFromDate.getMonth() + 1, repeatFromDate.getDate());
|
||||
}
|
||||
} else {
|
||||
repeatFrom = today;
|
||||
}
|
||||
|
||||
// invoke the recurrence iterator
|
||||
try {
|
||||
long newDueDate;
|
||||
RRule rrule = new RRule(recurrence);
|
||||
if(rrule.getFreq() == Frequency.HOURLY) {
|
||||
newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
|
||||
repeatFromDate.getTime() + DateUtilities.ONE_HOUR * rrule.getInterval());
|
||||
} else {
|
||||
RecurrenceIterator iterator = RecurrenceIteratorFactory.createRecurrenceIterator(rrule,
|
||||
repeatFrom, TimeZone.getDefault());
|
||||
DateValue nextDate;
|
||||
if(repeatFrom.compareTo(today) < 0) {
|
||||
iterator.advanceTo(today);
|
||||
if(!iterator.hasNext())
|
||||
return;
|
||||
nextDate = iterator.next();
|
||||
} else {
|
||||
iterator.advanceTo(repeatFrom);
|
||||
if(!iterator.hasNext())
|
||||
return;
|
||||
nextDate = iterator.next();
|
||||
nextDate = iterator.next();
|
||||
}
|
||||
|
||||
if(nextDate instanceof DateTimeValueImpl) {
|
||||
DateTimeValueImpl newDateTime = (DateTimeValueImpl)nextDate;
|
||||
newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
|
||||
Date.UTC(newDateTime.year() - 1900, newDateTime.month() - 1,
|
||||
newDateTime.day(), newDateTime.hour(),
|
||||
newDateTime.minute(), newDateTime.second()));
|
||||
} else {
|
||||
newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY,
|
||||
new Date(nextDate.year() - 1900, nextDate.month() - 1,
|
||||
nextDate.day()).getTime());
|
||||
}
|
||||
}
|
||||
|
||||
long hideUntil = task.getValue(Task.HIDE_UNTIL);
|
||||
if(hideUntil > 0 && task.getValue(Task.DUE_DATE) > 0) {
|
||||
hideUntil += newDueDate - task.getValue(Task.DUE_DATE);
|
||||
}
|
||||
|
||||
task = taskService.clone(task);
|
||||
task.setValue(Task.DUE_DATE, newDueDate);
|
||||
task.setValue(Task.HIDE_UNTIL, hideUntil);
|
||||
task.setValue(Task.COMPLETION_DATE, 0L);
|
||||
taskService.save(task, false);
|
||||
} catch (ParseException e) {
|
||||
exceptionService.reportError("recurrence-rule: " + recurrence, e); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- calendar integration -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/TEA_calendar_label"
|
||||
style="@style/TextAppearance.GEN_EditLabel" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/add_to_calendar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/TEA_addToCalendar_label" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/calendars"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/view_calendar_event"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/TEA_showCalendar_label"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
||||
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Resources for built-in tag plug-in -->
|
||||
|
||||
<!-- =============================================== Task Edit Controls == -->
|
||||
|
||||
|
||||
<!-- ================================================== Preference Keys == -->
|
||||
|
||||
<!-- Calendar Setting Title -->
|
||||
<string name="gcal_GCP_title">Default Calendar</string>
|
||||
|
||||
<!-- Calendar Setting Summary (%s -> calendar summary) -->
|
||||
<string name="gcal_GCP_summary">Current Setting: %s</string>
|
||||
|
||||
<!-- System Default Calendar (displayed if we can't figure out calendars) -->
|
||||
<string name="gcal_GCP_default">Default Calendar</string>
|
||||
|
||||
<!-- Default Calendar Preference Key (do not translate) -->
|
||||
<string name="gcal_p_default">default_calendar_id</string>
|
||||
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue