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