Merge branch '100616-replace-controllers'
Conflicts: astrid/default.propertiespull/14/head
@ -1,137 +1,208 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.timsu.astrid"
|
||||
android:versionName="2.14.4" android:versionCode="135">
|
||||
package="com.timsu.astrid"
|
||||
android:versionName="3.0.0-beta" android:versionCode="136">
|
||||
|
||||
<!-- ============================ Metadata ============================ -->
|
||||
<!-- ================================================== Used Permissions = -->
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<!-- for notifications -->
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<!-- for synchronization -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<!-- for google calendar integration -->
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<!-- for creating shortcuts -->
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<!-- for backup -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<!-- for analytics -->
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<!-- ============================================== Exported Permissions = -->
|
||||
|
||||
<!-- for tasks provider -->
|
||||
<permission android:name="com.timsu.astrid.permission.READ_TASKS"
|
||||
android:permissionGroup="android.permission-group.MESSAGES"
|
||||
android:protectionLevel="normal"
|
||||
android:label="@string/read_tasks_permission"
|
||||
android:description="@string/read_tasks_permission"/>
|
||||
<uses-permission android:name="com.timsu.astrid.permission.READ_TASKS"/>
|
||||
|
||||
<!-- for reading data from plugins or astrid -->
|
||||
<permission android:name="com.todoroo.astrid.READ"
|
||||
android:description="@string/read_permission_desc"
|
||||
android:protectionLevel="normal"
|
||||
android:label="@string/read_permission_label" />
|
||||
<uses-permission android:name="com.todoroo.astrid.READ" />
|
||||
|
||||
<!-- for writing data to plugins or astrid -->
|
||||
<permission android:name="com.todoroo.astrid.WRITE"
|
||||
android:description="@string/write_permission_desc"
|
||||
android:protectionLevel="normal"
|
||||
android:label="@string/write_permission_label" />
|
||||
<uses-permission android:name="com.todoroo.astrid.WRITE" />
|
||||
|
||||
<!-- ========================================================== Metadata = -->
|
||||
|
||||
<uses-sdk android:minSdkVersion="3"
|
||||
android:targetSdkVersion="4" />
|
||||
<supports-screens />
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name">
|
||||
|
||||
<!-- For Flurry analytics -->
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<!-- ====================================================== Activities = -->
|
||||
|
||||
<!-- Activity that displays task list -->
|
||||
<activity android:name="com.todoroo.astrid.activity.TaskListActivity"
|
||||
android:theme="@style/Theme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Activity that displays filter list -->
|
||||
<activity android:name="com.todoroo.astrid.activity.FilterListActivity"
|
||||
android:theme="@style/Theme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.searchable"
|
||||
android:resource="@xml/filter_list_searchable" />
|
||||
</activity>
|
||||
<!-- Activity that creates or edits tasks -->
|
||||
<activity android:name="com.todoroo.astrid.activity.TaskEditActivity"
|
||||
android:label="@string/taskEdit_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="vnd.android.cursor.item/task" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Activity that lets users log in to sync providers -->
|
||||
<activity android:name=".activities.SyncLoginActivity"/>
|
||||
<!-- Activity that lets users edit app preferences -->
|
||||
<activity android:name="com.todoroo.astrid.activity.EditPreferences"/>
|
||||
<!-- Activity that lets users edit synchronization preferences -->
|
||||
<activity android:name=".activities.SyncPreferences"/>
|
||||
<!-- Activity that Locale displays to edit tag notification settings -->
|
||||
<activity android:name=".activities.LocaleEditAlerts"
|
||||
android:label="@string/locale_edit_alerts_title"
|
||||
android:icon="@drawable/icon_32"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- ======================================================= Receivers = -->
|
||||
|
||||
<receiver android:name="com.todoroo.astrid.reminders.Notifications" />
|
||||
|
||||
<!-- For Tasks provider -->
|
||||
<permission android:name="com.timsu.astrid.permission.READ_TASKS"
|
||||
android:permissionGroup="android.permission-group.MESSAGES"
|
||||
android:protectionLevel="normal"
|
||||
android:label="@string/read_tasks_permission"
|
||||
android:description="@string/read_tasks_permission"/>
|
||||
<uses-permission android:name="com.timsu.astrid.permission.READ_TASKS"/>
|
||||
|
||||
<uses-sdk android:minSdkVersion="3"
|
||||
android:targetSdkVersion="4" />
|
||||
<supports-screens />
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="false">
|
||||
|
||||
<!-- ======================== Activities ========================= -->
|
||||
|
||||
<!-- Activity that displays the task list -->
|
||||
<activity android:name=".activities.TaskList">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Activity that redirects to a task list, invoked by notifications -->
|
||||
<activity android:name=".activities.TaskListNotify"
|
||||
android:launchMode="singleTop" />
|
||||
|
||||
<!-- Activity that creates or edits tasks -->
|
||||
<activity android:name=".activities.TaskEdit"
|
||||
android:icon="@drawable/icon_add"
|
||||
android:label="@string/taskEdit_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="vnd.android.cursor.item/task" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<!-- Activity that views tags -->
|
||||
<activity android:name=".activities.TagView">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Activity that lets users log in to sync providers -->
|
||||
<activity android:name=".activities.SyncLoginActivity"/>
|
||||
|
||||
<!-- Activity that lets users edit app preferences -->
|
||||
<activity android:name=".activities.EditPreferences"/>
|
||||
|
||||
<!-- Activity that lets users edit synchronization preferences -->
|
||||
<activity android:name=".activities.SyncPreferences"/>
|
||||
|
||||
<!-- activity that Locale displays to edit tag notification settings -->
|
||||
<activity
|
||||
android:name=".activities.LocaleEditAlerts"
|
||||
android:label="@string/locale_edit_alerts_title"
|
||||
android:icon="@drawable/icon_32"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- ======================== Receivers ========================= -->
|
||||
|
||||
<receiver android:name=".utilities.Notifications" />
|
||||
|
||||
<receiver android:name=".utilities.LocaleReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="com.timsu.astrid.action.LOCALE_ALERT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".utilities.StartupReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".appwidget.AstridAppWidgetProvider" >
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_provider_info" />
|
||||
</receiver>
|
||||
|
||||
|
||||
<!-- ======================== Services ========================== -->
|
||||
|
||||
<service android:name=".appwidget.AstridAppWidgetProvider$UpdateService"></service>
|
||||
|
||||
<service android:name=".sync.SynchronizationService" />
|
||||
<service android:name=".utilities.BackupService"/>
|
||||
|
||||
<!-- ======================== Providers ========================== -->
|
||||
|
||||
<provider
|
||||
android:name=".provider.TasksProvider"
|
||||
android:authorities="com.timsu.astrid.tasksprovider"
|
||||
android:multiprocess="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:readPermission="com.timsu.astrid.permission.READ_TASKS"
|
||||
/>
|
||||
|
||||
</application>
|
||||
<receiver android:name=".utilities.LocaleReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="com.timsu.astrid.action.LOCALE_ALERT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".appwidget.AstridAppWidgetProvider" >
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_provider_info" />
|
||||
</receiver>
|
||||
|
||||
<!-- ======================================================== Services = -->
|
||||
|
||||
<service android:name=".appwidget.AstridAppWidgetProvider$UpdateService"></service>
|
||||
<service android:name=".sync.SynchronizationService" />
|
||||
<service android:name=".utilities.BackupService"/>
|
||||
|
||||
<!-- ======================================================= Providers = -->
|
||||
|
||||
<provider android:name=".provider.TasksProvider"
|
||||
android:authorities="com.timsu.astrid.tasksprovider"
|
||||
android:multiprocess="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:readPermission="com.timsu.astrid.permission.READ_TASKS"/>
|
||||
|
||||
<!-- ========================================================= Plugins = -->
|
||||
|
||||
<!-- core filters -->
|
||||
<receiver android:name="com.todoroo.astrid.filters.CorePlugin">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.REQUEST_PLUGINS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="com.todoroo.astrid.filters.CoreFilterExposer">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- extended filters -->
|
||||
<receiver android:name="com.todoroo.astrid.filters.ExtendedPlugin">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.REQUEST_PLUGINS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="com.todoroo.astrid.filters.ExtendedFilterExposer">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- tags -->
|
||||
<receiver android:name="com.todoroo.astrid.tags.TagsPlugin">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.REQUEST_PLUGINS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="com.todoroo.astrid.tags.TagFilterExposer">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="com.todoroo.astrid.tags.TagDetailExposer">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- reminders -->
|
||||
<activity android:name="com.todoroo.astrid.reminders.ReminderPreferences"
|
||||
android:label="@string/rmd_EPr_alerts_header">
|
||||
<intent-filter>
|
||||
<action android:name="com.todoroo.astrid.SETTINGS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<receiver android:name="com.todoroo.astrid.reminders.ReminderStartupService">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<activity android:name="com.todoroo.astrid.reminders.NotificationActivity"
|
||||
android:taskAffinity="" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Constants for interfacing with Astrid's Content Providers.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class AstridContentProvider {
|
||||
|
||||
/**
|
||||
* Content Provider
|
||||
*/
|
||||
public static final String PROVIDER = "com.todoroo.astrid.provider";
|
||||
|
||||
// --- methods for generating URI's for accessing Astrid data
|
||||
|
||||
/**
|
||||
* URI for:
|
||||
* <ul>
|
||||
* <li>Queries on multiple tasks
|
||||
* <li>Inserting new tasks
|
||||
* <li>Deleting multiple tasks at a time
|
||||
* <li>Updating multiple tasks at a time
|
||||
* </ul>
|
||||
* If your selection clause contains metadata columns, you need to use
|
||||
* <code>allItemsWithMetadataUri</code> instead of this one.
|
||||
*/
|
||||
public static Uri allItemsUri() {
|
||||
return Uri.parse("content://" + PROVIDER + "/items");
|
||||
}
|
||||
|
||||
/**
|
||||
* URI for:
|
||||
* <ul>
|
||||
* <li>Querying on tasks with metadata columns in selection
|
||||
* <li>Deleting multiple tasks with metadata columns in selection
|
||||
* <li>Updating multiple tasks with metadata columns in selection
|
||||
* </ul>
|
||||
* If, for example, you have defined metadata key 'tag' and wish to delete
|
||||
* all tasks where 'tag' = 'deleteme', you would use this URI. For querying
|
||||
* or insertion, use <code>allItemsUri</code>.
|
||||
* <p>
|
||||
* For queries, <code>allItemsUri</code> will be more efficient, but will
|
||||
* not work if your selection clause contains columns not mentioned in your
|
||||
* projection
|
||||
*
|
||||
* @param metadata
|
||||
* array of metadata columns you wish to select using
|
||||
*
|
||||
*/
|
||||
public static Uri allItemsWithMetadataUri(String[] metadata) {
|
||||
if(metadata == null || metadata.length == 0)
|
||||
throw new IllegalArgumentException("You must provide metadata");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for(int i = 0; i < metadata.length; i++)
|
||||
builder.append(escapeUriSubComponent(metadata[i])).append(
|
||||
SUB_COMPONENT_SEPARATOR);
|
||||
|
||||
return Uri.parse("content://" + PROVIDER + "/itemsWith/" +
|
||||
escapeUriComponent(builder.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* URI for:
|
||||
* <ul>
|
||||
* <li>Queries on a single task
|
||||
* <li>Updating fields for a single task
|
||||
* <li>Deleting a single task
|
||||
* </ul>
|
||||
*
|
||||
* @param id
|
||||
* id of task to fetch
|
||||
*/
|
||||
public static Uri singleItemUri(long id) {
|
||||
return Uri.parse("content://" + PROVIDER + "/" + id);
|
||||
}
|
||||
|
||||
/**
|
||||
* URI for:
|
||||
* <ul>
|
||||
* <li>Queries on multiple tasks, grouped by column
|
||||
* </ul>
|
||||
* @param groupBy
|
||||
* column name to group by
|
||||
*/
|
||||
public static Uri groupByUri(String groupBy) {
|
||||
groupBy = escapeUriComponent(groupBy);
|
||||
return Uri.parse("content://" + PROVIDER + "/groupby/" + groupBy);
|
||||
}
|
||||
|
||||
// --- task built-in columns and constnats
|
||||
|
||||
/**
|
||||
* A task in Astrid represents a single item in a user's task list
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*/
|
||||
public static class AstridTask {
|
||||
|
||||
// --- columns
|
||||
|
||||
/** long: Task id */
|
||||
public static final String ID = "_id";
|
||||
|
||||
/** String: name of Task */
|
||||
public static final String TITLE = "title";
|
||||
|
||||
/** int: Task Urgency setting (see <code>Task.URGENCY_*</code>) */
|
||||
public static final String URGENCY = "urgency";
|
||||
|
||||
/** int: Task Importance setting (see <code>Task.IMPORTANCE_*</code>) */
|
||||
public static final String IMPORTANCE = "importance";
|
||||
|
||||
/** int: unixtime Task is due, 0 if not set */
|
||||
public static final String DUE_DATE = "dueDate";
|
||||
|
||||
/** int: unixtime Task should be hidden until, 0 if not set */
|
||||
public static final String HIDDEN_UNTIL = "hiddenUntil";
|
||||
|
||||
/** int: unixtime Task was created */
|
||||
public static final String CREATION_DATE = "creationDate";
|
||||
|
||||
/** int: unixtime Task was completed, 0 if task not completed */
|
||||
public static final String COMPLETION_DATE = "completionDate";
|
||||
|
||||
/** int: unixtime Task was deleted, 0 if task not deleted */
|
||||
public static final String DELETION_DATE = "deletionDate";
|
||||
|
||||
/** int: unixtime Task was modified */
|
||||
public static final String MODIFICATION_DATE = "modificationDate";
|
||||
|
||||
// --- urgency settings
|
||||
|
||||
public static final int URGENCY_NONE = 0;
|
||||
public static final int URGENCY_TODAY = 1;
|
||||
public static final int URGENCY_THIS_WEEK = 2;
|
||||
public static final int URGENCY_THIS_MONTH = 3;
|
||||
public static final int URGENCY_WITHIN_THREE_MONTHS = 4;
|
||||
public static final int URGENCY_WITHIN_SIX_MONTHS = 5;
|
||||
public static final int URGENCY_WITHIN_A_YEAR = 6;
|
||||
public static final int URGENCY_SPECIFIC_DAY = 7;
|
||||
public static final int URGENCY_SPECIFIC_DAY_TIME = 8;
|
||||
|
||||
// --- importance settings
|
||||
|
||||
public static final int IMPORTANCE_DO_OR_DIE = 0;
|
||||
public static final int IMPORTANCE_MUST_DO = 1;
|
||||
public static final int IMPORTANCE_SHOULD_DO = 2;
|
||||
public static final int IMPORTANCE_NONE = 3;
|
||||
|
||||
|
||||
}
|
||||
|
||||
// --- internal methods
|
||||
|
||||
/**
|
||||
* Escapes a string for use in a URI. Used internally to pass extra data
|
||||
* to the content provider.
|
||||
* @param component
|
||||
* @return
|
||||
*/
|
||||
private static String escapeUriComponent(String component) {
|
||||
return component.replace("%", "%o").replace("/", "%s");
|
||||
}
|
||||
|
||||
private static final String SUB_COMPONENT_SEPARATOR = "|";
|
||||
|
||||
/**
|
||||
* Escapes a string for use as part of a URI string. Used internally to pass extra data
|
||||
* to the content provider.
|
||||
* @param component
|
||||
* @return
|
||||
*/
|
||||
private static String escapeUriSubComponent(String component) {
|
||||
return component.replace("$", "$o").replace(SUB_COMPONENT_SEPARATOR, "$s");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents a plug-in for Astrid. Users can enable or disable plug-ins,
|
||||
* which affect all other extension points that share the same identifier.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class Plugin implements Parcelable {
|
||||
|
||||
/**
|
||||
* Plug-in Identifier
|
||||
*/
|
||||
public String plugin = null;
|
||||
|
||||
/**
|
||||
* Plug-in Title
|
||||
*/
|
||||
public String title = null;
|
||||
|
||||
/**
|
||||
* Plug-in Author
|
||||
*/
|
||||
public String author = null;
|
||||
|
||||
/**
|
||||
* Plug-in Description
|
||||
*/
|
||||
public String description = null;
|
||||
|
||||
/**
|
||||
* Convenience constructor to generate a plug-in object
|
||||
*
|
||||
* @param plugin
|
||||
* @param title
|
||||
* @param author
|
||||
* @param description
|
||||
*/
|
||||
public Plugin(String plugin, String title, String author, String description) {
|
||||
this.plugin = plugin;
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(plugin);
|
||||
dest.writeString(title);
|
||||
dest.writeString(author);
|
||||
dest.writeString(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable creator
|
||||
*/
|
||||
public static final Parcelable.Creator<Plugin> CREATOR = new Parcelable.Creator<Plugin>() {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Plugin createFromParcel(Parcel source) {
|
||||
return new Plugin(source.readString(), source.readString(),
|
||||
source.readString(), source.readString());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Plugin[] newArray(int size) {
|
||||
return new Plugin[size];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.android.ide.eclipse.adt.debug.LaunchConfigType">
|
||||
<stringAttribute key="bad_container_name" value="/astrid.launch"/>
|
||||
<intAttribute key="com.android.ide.eclipse.adt.action" value="0"/>
|
||||
<stringAttribute key="com.android.ide.eclipse.adt.commandline" value="-scale 0.7"/>
|
||||
<intAttribute key="com.android.ide.eclipse.adt.delay" value="0"/>
|
||||
<booleanAttribute key="com.android.ide.eclipse.adt.nobootanim" value="true"/>
|
||||
<intAttribute key="com.android.ide.eclipse.adt.speed" value="0"/>
|
||||
<booleanAttribute key="com.android.ide.eclipse.adt.target" value="true"/>
|
||||
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/astrid"/>
|
||||
<listEntry value="/astrid/AndroidManifest.xml"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ALLOW_TERMINATE" value="true"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="astrid"/>
|
||||
</launchConfiguration>
|
||||
@ -1,5 +0,0 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
|
||||
public enum OrderType {
|
||||
DESC, ASC
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.data.sql.Constants.AS;
|
||||
import static com.todoroo.andlib.data.sql.Constants.SPACE;
|
||||
import static com.todoroo.andlib.sql.Constants.AS;
|
||||
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||
|
||||
public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
|
||||
protected String alias;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public class EqCriterion extends UnaryCriterion {
|
||||
EqCriterion(Field field, Object value) {
|
||||
@ -0,0 +1,15 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public final class Functions {
|
||||
|
||||
public static String caseStatement(Criterion when, Object ifTrue, Object ifFalse) {
|
||||
return new StringBuilder("CASE WHEN ").
|
||||
append(when.toString()).append(" THEN ").append(value(ifTrue)).
|
||||
append(" ELSE ").append(value(ifFalse)).append(" END").toString();
|
||||
}
|
||||
|
||||
private static String value(Object value) {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
@ -1,33 +1,33 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.data.sql.Constants.JOIN;
|
||||
import static com.todoroo.andlib.data.sql.Constants.ON;
|
||||
import static com.todoroo.andlib.data.sql.Constants.SPACE;
|
||||
import static com.todoroo.andlib.sql.Constants.JOIN;
|
||||
import static com.todoroo.andlib.sql.Constants.ON;
|
||||
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||
|
||||
public class Join {
|
||||
private final Table joinTable;
|
||||
private final SqlTable joinTable;
|
||||
private final JoinType joinType;
|
||||
private final Criterion[] criterions;
|
||||
|
||||
private Join(Table table, JoinType joinType, Criterion... criterions) {
|
||||
private Join(SqlTable table, JoinType joinType, Criterion... criterions) {
|
||||
joinTable = table;
|
||||
this.joinType = joinType;
|
||||
this.criterions = criterions;
|
||||
}
|
||||
|
||||
public static Join inner(Table expression, Criterion... criterions) {
|
||||
public static Join inner(SqlTable expression, Criterion... criterions) {
|
||||
return new Join(expression, JoinType.INNER, criterions);
|
||||
}
|
||||
|
||||
public static Join left(Table table, Criterion... criterions) {
|
||||
public static Join left(SqlTable table, Criterion... criterions) {
|
||||
return new Join(table, JoinType.LEFT, criterions);
|
||||
}
|
||||
|
||||
public static Join right(Table table, Criterion... criterions) {
|
||||
public static Join right(SqlTable table, Criterion... criterions) {
|
||||
return new Join(table, JoinType.RIGHT, criterions);
|
||||
}
|
||||
|
||||
public static Join out(Table table, Criterion... criterions) {
|
||||
public static Join out(SqlTable table, Criterion... criterions) {
|
||||
return new Join(table, JoinType.OUT, criterions);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public enum JoinType {
|
||||
INNER, LEFT, RIGHT, OUT
|
||||
@ -1,6 +1,6 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.data.sql.Constants.SPACE;
|
||||
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -1,25 +1,25 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.data.sql.Constants.SPACE;
|
||||
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||
|
||||
public class Order {
|
||||
private final Field expression;
|
||||
private final Object expression;
|
||||
private final OrderType orderType;
|
||||
|
||||
private Order(Field expression) {
|
||||
private Order(Object expression) {
|
||||
this(expression, OrderType.ASC);
|
||||
}
|
||||
|
||||
private Order(Field expression, OrderType orderType) {
|
||||
private Order(Object expression, OrderType orderType) {
|
||||
this.expression = expression;
|
||||
this.orderType = orderType;
|
||||
}
|
||||
|
||||
public static Order asc(Field expression) {
|
||||
public static Order asc(Object expression) {
|
||||
return new Order(expression);
|
||||
}
|
||||
|
||||
public static Order desc(Field expression) {
|
||||
public static Order desc(Object expression) {
|
||||
return new Order(expression, OrderType.DESC);
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public enum OrderType {
|
||||
DESC, ASC
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.Constants.COMMA;
|
||||
import static com.todoroo.andlib.sql.Constants.GROUP_BY;
|
||||
import static com.todoroo.andlib.sql.Constants.LIMIT;
|
||||
import static com.todoroo.andlib.sql.Constants.ORDER_BY;
|
||||
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||
import static com.todoroo.andlib.sql.Constants.WHERE;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Query Template returns a bunch of criteria that allows a query to be
|
||||
* constructed
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class QueryTemplate {
|
||||
|
||||
private final ArrayList<Criterion> criterions = new ArrayList<Criterion>();
|
||||
private final ArrayList<Join> joins = new ArrayList<Join>();
|
||||
private final ArrayList<Field> groupBies = new ArrayList<Field>();
|
||||
private final ArrayList<Order> orders = new ArrayList<Order>();
|
||||
private final ArrayList<Criterion> havings = new ArrayList<Criterion>();
|
||||
private Integer limit = null;
|
||||
|
||||
public QueryTemplate join(Join... join) {
|
||||
joins.addAll(asList(join));
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate where(Criterion criterion) {
|
||||
criterions.add(criterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate groupBy(Field... groupBy) {
|
||||
groupBies.addAll(asList(groupBy));
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate orderBy(Order... order) {
|
||||
orders.addAll(asList(order));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
visitJoinClause(sql);
|
||||
visitWhereClause(sql);
|
||||
visitGroupByClause(sql);
|
||||
visitOrderByClause(sql);
|
||||
if(limit != null)
|
||||
sql.append(LIMIT).append(SPACE).append(limit);
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private void visitOrderByClause(StringBuilder sql) {
|
||||
if (orders.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(ORDER_BY);
|
||||
for (Order order : orders) {
|
||||
sql.append(SPACE).append(order).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void visitGroupByClause(StringBuilder sql) {
|
||||
if (groupBies.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(GROUP_BY);
|
||||
for (Field groupBy : groupBies) {
|
||||
sql.append(SPACE).append(groupBy).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
if (havings.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append("HAVING");
|
||||
for (Criterion havingCriterion : havings) {
|
||||
sql.append(SPACE).append(havingCriterion).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
}
|
||||
|
||||
private void visitWhereClause(StringBuilder sql) {
|
||||
if (criterions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(WHERE);
|
||||
for (Criterion criterion : criterions) {
|
||||
sql.append(SPACE).append(criterion).append(SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitJoinClause(StringBuilder sql) {
|
||||
for (Join join : joins) {
|
||||
sql.append(join).append(SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
public QueryTemplate having(Criterion criterion) {
|
||||
this.havings.add(criterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate limit(int limitValue) {
|
||||
this.limit = limitValue;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public class Table extends DBObject<Table> {
|
||||
public class SqlTable extends DBObject<SqlTable> {
|
||||
|
||||
protected Table(String expression) {
|
||||
protected SqlTable(String expression) {
|
||||
super(expression);
|
||||
}
|
||||
|
||||
public static Table table(String table) {
|
||||
return new Table(table);
|
||||
public static SqlTable table(String table) {
|
||||
return new SqlTable(table);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@ -1,6 +1,6 @@
|
||||
package com.todoroo.andlib.data.sql;
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.data.sql.Constants.SPACE;
|
||||
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||
|
||||
public class UnaryCriterion extends Criterion {
|
||||
protected final Field expression;
|
||||
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.filters;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Functions;
|
||||
import com.todoroo.andlib.sql.Order;
|
||||
import com.todoroo.andlib.sql.QueryTemplate;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.activity.FilterListActivity;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.api.FilterListItem;
|
||||
import com.todoroo.astrid.api.SearchFilter;
|
||||
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
|
||||
/**
|
||||
* Exposes Astrid's built in filters to the {@link FilterListActivity}
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class CoreFilterExposer extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Resources r = context.getResources();
|
||||
|
||||
// build filters
|
||||
Filter inbox = buildInboxFilter(r);
|
||||
|
||||
Filter all = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_All),
|
||||
r.getString(R.string.BFE_All),
|
||||
new QueryTemplate().where(Criterion.not(TaskCriteria.isDeleted())).
|
||||
orderBy(Order.desc(Task.MODIFICATION_DATE)),
|
||||
null);
|
||||
all.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_all)).getBitmap();
|
||||
|
||||
SearchFilter searchFilter = new SearchFilter(CorePlugin.IDENTIFIER,
|
||||
"Search");
|
||||
searchFilter.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_search)).getBitmap();
|
||||
|
||||
// transmit filter list
|
||||
FilterListItem[] list = new FilterListItem[3];
|
||||
list[0] = inbox;
|
||||
list[1] = all;
|
||||
list[2] = searchFilter;
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, list);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build inbox filter
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static Filter buildInboxFilter(Resources r) {
|
||||
Filter inbox = new Filter(CorePlugin.IDENTIFIER, r.getString(R.string.BFE_Inbox),
|
||||
r.getString(R.string.BFE_Inbox_title),
|
||||
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
|
||||
TaskCriteria.isVisible(DateUtilities.now()))).orderBy(
|
||||
Order.asc(Functions.caseStatement(Task.DUE_DATE.eq(0),
|
||||
String.format("(%d + 1000000 * %s)", DateUtilities.now(), Task.IMPORTANCE),
|
||||
String.format("(%s + 1000000 * %s)", Task.DUE_DATE, Task.IMPORTANCE)))),
|
||||
null);
|
||||
inbox.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_inbox)).getBitmap();
|
||||
return inbox;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.todoroo.astrid.filters;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.Plugin;
|
||||
|
||||
public class CorePlugin extends BroadcastReceiver {
|
||||
|
||||
static final String IDENTIFIER = "core";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Plugin plugin = new Plugin(IDENTIFIER, "Core Filters", "Todoroo",
|
||||
"Provides 'Inbox' and 'All Tasks' Filters");
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.filters;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Order;
|
||||
import com.todoroo.andlib.sql.QueryTemplate;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.activity.FilterListActivity;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.api.FilterListHeader;
|
||||
import com.todoroo.astrid.api.FilterListItem;
|
||||
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
|
||||
/**
|
||||
* Exposes Astrid's built in filters to the {@link FilterListActivity}
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class ExtendedFilterExposer extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Resources r = context.getResources();
|
||||
|
||||
// build filters
|
||||
FilterListHeader header = new FilterListHeader(ExtendedPlugin.IDENTIFIER,
|
||||
"Extended");
|
||||
|
||||
Filter alphabetical = new Filter(ExtendedPlugin.IDENTIFIER,
|
||||
"Alphabetical",
|
||||
"Alphabetical",
|
||||
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
|
||||
TaskCriteria.isVisible(DateUtilities.now()))).
|
||||
orderBy(Order.asc(Task.TITLE)),
|
||||
null);
|
||||
alphabetical.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_alpha)).getBitmap();
|
||||
|
||||
Filter recent = new Filter(ExtendedPlugin.IDENTIFIER,
|
||||
"Recently Modified",
|
||||
"Recently Modified",
|
||||
new QueryTemplate().orderBy(Order.desc(Task.MODIFICATION_DATE)).limit(15),
|
||||
null);
|
||||
recent.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_recent)).getBitmap();
|
||||
|
||||
ContentValues hiddenValues = new ContentValues();
|
||||
hiddenValues.put(Task.HIDE_UNTIL.name, DateUtilities.now() + DateUtilities.ONE_DAY);
|
||||
Filter hidden = new Filter(ExtendedPlugin.IDENTIFIER,
|
||||
"Hidden Tasks",
|
||||
"Hidden Tasks",
|
||||
new QueryTemplate().where(Criterion.and(TaskCriteria.isActive(),
|
||||
Criterion.not(TaskCriteria.isVisible(DateUtilities.now())))).
|
||||
orderBy(Order.asc(Task.HIDE_UNTIL)),
|
||||
hiddenValues);
|
||||
hidden.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_hidden)).getBitmap();
|
||||
|
||||
Filter deleted = new Filter(ExtendedPlugin.IDENTIFIER,
|
||||
"Deleted Tasks",
|
||||
"Deleted Tasks",
|
||||
new QueryTemplate().where(TaskCriteria.isDeleted()).
|
||||
orderBy(Order.desc(Task.DELETION_DATE)),
|
||||
null);
|
||||
deleted.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_deleted)).getBitmap();
|
||||
|
||||
// transmit filter list
|
||||
FilterListItem[] list = new FilterListItem[5];
|
||||
list[0] = header;
|
||||
list[1] = alphabetical;
|
||||
list[2] = recent;
|
||||
list[3] = hidden;
|
||||
list[4] = deleted;
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, list);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.todoroo.astrid.filters;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.Plugin;
|
||||
|
||||
public class ExtendedPlugin extends BroadcastReceiver {
|
||||
|
||||
static final String IDENTIFIER = "extended";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Plugin plugin = new Plugin(IDENTIFIER, "Extended Filters", "Todoroo",
|
||||
"Provides extended filters for viewing subsets of your tasks");
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,249 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.timsu.astrid.data.task.TaskIdentifier;
|
||||
import com.timsu.astrid.utilities.Constants;
|
||||
import com.timsu.astrid.utilities.Preferences;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
import com.todoroo.andlib.service.NotificationManager;
|
||||
import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
|
||||
public class Notifications extends BroadcastReceiver {
|
||||
|
||||
// --- constants
|
||||
|
||||
/** task id extra */
|
||||
static final String ID_KEY = "id"; //$NON-NLS-1$
|
||||
|
||||
/** notification type extra */
|
||||
static final String TYPE_KEY = "type"; //$NON-NLS-1$
|
||||
|
||||
// --- instance variables
|
||||
|
||||
@Autowired
|
||||
private TaskDao taskDao;
|
||||
|
||||
@Autowired
|
||||
private ExceptionService exceptionService;
|
||||
|
||||
public static NotificationManager notificationManager = null;
|
||||
|
||||
// --- alarm handling
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
public Notifications() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
/** Alarm intent */
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
|
||||
long id = intent.getLongExtra(ID_KEY, 0);
|
||||
int type = intent.getIntExtra(TYPE_KEY, (byte) 0);
|
||||
|
||||
Resources r = context.getResources();
|
||||
String reminder;
|
||||
if(type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE)
|
||||
reminder = getRandomReminder(r.getStringArray(R.array.reminders_due));
|
||||
else if(type == ReminderService.TYPE_SNOOZE)
|
||||
reminder = getRandomReminder(r.getStringArray(R.array.reminders_snooze));
|
||||
else
|
||||
reminder = getRandomReminder(r.getStringArray(R.array.reminders));
|
||||
|
||||
if(!showNotification(id, type, reminder)) {
|
||||
notificationManager.cancel((int)id);
|
||||
}
|
||||
}
|
||||
|
||||
// --- notification creation
|
||||
|
||||
/** Clear notifications associated with this application */
|
||||
public static void clearAllNotifications(Context context, TaskIdentifier taskId) {
|
||||
NotificationManager nm = (NotificationManager)
|
||||
context.getSystemService(Activity.NOTIFICATION_SERVICE);
|
||||
nm.cancel((int)taskId.getId());
|
||||
}
|
||||
|
||||
/** @return a random reminder string */
|
||||
static String getRandomReminder(String[] reminders) {
|
||||
int next = ReminderService.random.nextInt(reminders.length);
|
||||
String reminder = reminders[next];
|
||||
return reminder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a new notification about the given task. Returns false if there was
|
||||
* some sort of error or the alarm should be disabled.
|
||||
*/
|
||||
public boolean showNotification(long id, int type, String reminder) {
|
||||
Context context = ContextManager.getContext();
|
||||
if(notificationManager == null)
|
||||
notificationManager = new AndroidNotificationManager(context);
|
||||
|
||||
Task task;
|
||||
try {
|
||||
task = taskDao.fetch(id, Task.TITLE, Task.HIDE_UNTIL, Task.COMPLETION_DATE,
|
||||
Task.DELETION_DATE, Task.REMINDER_FLAGS);
|
||||
if(task == null)
|
||||
throw new IllegalArgumentException("cound not find item with id"); //$NON-NLS-1$
|
||||
|
||||
} catch (Exception e) {
|
||||
exceptionService.reportError("show-notif", e); //$NON-NLS-1$
|
||||
return false;
|
||||
}
|
||||
|
||||
// you're done - don't sound, do delete
|
||||
if(task.isCompleted() || task.isDeleted())
|
||||
return false;
|
||||
|
||||
// it's hidden - don't sound, don't delete
|
||||
if(task.isHidden() && type == ReminderService.TYPE_RANDOM)
|
||||
return true;
|
||||
|
||||
// read properties
|
||||
String taskTitle = task.getValue(Task.TITLE);
|
||||
boolean nonstopMode = task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_NONSTOP);
|
||||
|
||||
// update last reminder time
|
||||
task.setValue(Task.REMINDER_LAST, DateUtilities.now());
|
||||
taskDao.save(task, false);
|
||||
|
||||
// quiet hours? unless alarm clock
|
||||
boolean quietHours = false;
|
||||
Integer quietHoursStart = Preferences.getQuietHourStart(context);
|
||||
Integer quietHoursEnd = Preferences.getQuietHourEnd(context);
|
||||
if(quietHoursStart != null && quietHoursEnd != null && !nonstopMode) {
|
||||
int hour = new Date().getHours();
|
||||
if(quietHoursStart < quietHoursEnd) {
|
||||
if(hour >= quietHoursStart && hour < quietHoursEnd)
|
||||
quietHours = true;
|
||||
} else { // wrap across 24/hour boundary
|
||||
if(hour >= quietHoursStart || hour < quietHoursEnd)
|
||||
quietHours = true;
|
||||
}
|
||||
}
|
||||
|
||||
Resources r = context.getResources();
|
||||
|
||||
Intent notifyIntent = new Intent(context, NotificationActivity.class);
|
||||
notifyIntent.putExtra(NotificationActivity.TOKEN_ID, id);
|
||||
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context,
|
||||
(int)id, notifyIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
// set up properties (name and icon) for the notification
|
||||
String appName = r.getString(R.string.app_name);
|
||||
int icon;
|
||||
switch(Preferences.getNotificationIconTheme(context)) {
|
||||
case Preferences.ICON_SET_PINK:
|
||||
icon = R.drawable.notif_pink_alarm;
|
||||
break;
|
||||
case Preferences.ICON_SET_BORING:
|
||||
icon = R.drawable.notif_boring_alarm;
|
||||
break;
|
||||
default:
|
||||
icon = R.drawable.notif_astrid;
|
||||
}
|
||||
|
||||
// create notification object
|
||||
Notification notification = new Notification(
|
||||
icon, reminder, System.currentTimeMillis());
|
||||
notification.setLatestEventInfo(context,
|
||||
appName,
|
||||
reminder + " " + taskTitle, //$NON-NLS-1$
|
||||
pendingIntent);
|
||||
notification.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
if(Preferences.isPersistenceMode(context)) {
|
||||
notification.flags |= Notification.FLAG_NO_CLEAR |
|
||||
Notification.FLAG_SHOW_LIGHTS;
|
||||
notification.ledOffMS = 5000;
|
||||
notification.ledOnMS = 700;
|
||||
notification.ledARGB = Color.YELLOW;
|
||||
}
|
||||
else
|
||||
notification.defaults = Notification.DEFAULT_LIGHTS;
|
||||
|
||||
AudioManager audioManager = (AudioManager)context.getSystemService(
|
||||
Context.AUDIO_SERVICE);
|
||||
|
||||
// if non-stop mode is activated, set up the flags for insistent
|
||||
// notification, and increase the volume to full volume, so the user
|
||||
// will actually pay attention to the alarm
|
||||
if(nonstopMode && (type != ReminderService.TYPE_RANDOM)) {
|
||||
notification.flags |= Notification.FLAG_INSISTENT;
|
||||
notification.audioStreamType = AudioManager.STREAM_ALARM;
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_ALARM,
|
||||
audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0);
|
||||
} else {
|
||||
notification.audioStreamType = AudioManager.STREAM_NOTIFICATION;
|
||||
}
|
||||
|
||||
// quiet hours = no sound
|
||||
if(quietHours) {
|
||||
notification.sound = null;
|
||||
} else {
|
||||
Uri notificationSound = Preferences.getNotificationRingtone(context);
|
||||
if(audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) {
|
||||
notification.sound = null;
|
||||
} else if(notificationSound != null &&
|
||||
!notificationSound.toString().equals("")) { //$NON-NLS-1$
|
||||
notification.sound = notificationSound;
|
||||
} else {
|
||||
notification.defaults |= Notification.DEFAULT_SOUND;
|
||||
}
|
||||
}
|
||||
|
||||
// quiet hours + periodic = no vibrate
|
||||
if(quietHours && (type == ReminderService.TYPE_RANDOM)) {
|
||||
notification.vibrate = null;
|
||||
} else {
|
||||
if (Preferences.shouldVibrate(context)
|
||||
&& audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
|
||||
notification.vibrate = new long[] {0, 1000, 500, 1000, 500, 1000};
|
||||
} else {
|
||||
notification.vibrate = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(Constants.DEBUG)
|
||||
Log.w("Astrid", "Logging notification: " + reminder); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
notificationManager.notify((int)id, notification);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- notification manager
|
||||
|
||||
public static void setNotificationManager(
|
||||
NotificationManager notificationManager) {
|
||||
Notifications.notificationManager = notificationManager;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class ReminderPlugin extends BroadcastReceiver {
|
||||
|
||||
static final String IDENTIFIER = "reminders"; //$NON-NLS-1$
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
/*Plugin plugin = new Plugin(IDENTIFIER, "Reminders", "Todoroo",
|
||||
"Provides notification reminders for tasks");
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);*/
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
|
||||
/**
|
||||
* Displays the preference screen for users to edit their preferences
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class ReminderPreferences extends PreferenceActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences_reminders);
|
||||
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
initializePreference(screen);
|
||||
|
||||
}
|
||||
|
||||
private void initializePreference(Preference preference) {
|
||||
if(preference instanceof PreferenceGroup) {
|
||||
PreferenceGroup group = (PreferenceGroup)preference;
|
||||
for(int i = 0; i < group.getPreferenceCount(); i++) {
|
||||
initializePreference(group.getPreference(i));
|
||||
}
|
||||
} else {
|
||||
Object value = null;
|
||||
if(preference instanceof ListPreference)
|
||||
value = ((ListPreference)preference).getValue();
|
||||
else if(preference instanceof CheckBoxPreference)
|
||||
value = ((CheckBoxPreference)preference).isChecked();
|
||||
else if(preference instanceof EditTextPreference)
|
||||
value = ((EditTextPreference)preference).getText();
|
||||
|
||||
if(value != null)
|
||||
updatePreferences(preference, value);
|
||||
|
||||
preference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference myPreference, Object newValue) {
|
||||
updatePreferences(myPreference, newValue);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
protected int valueToIndex(String value, String[] array) {
|
||||
for(int i = 0; i < array.length; i++)
|
||||
if(array[i].equals(value))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource if null, updates all resources
|
||||
*/
|
||||
protected void updatePreferences(Preference preference, Object value) {
|
||||
Resources r = getResources();
|
||||
|
||||
if(r.getString(R.string.p_rmd_quietStart).equals(preference.getKey())) {
|
||||
int index = valueToIndex((String)value, r.getStringArray(R.array.EPr_quiet_hours_start_values));
|
||||
if(index == -1)
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_desc_none));
|
||||
else {
|
||||
String duration = r.getStringArray(R.array.EPr_quiet_hours_start)[index];
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_start_desc, duration));
|
||||
}
|
||||
} else if(r.getString(R.string.p_rmd_quietEnd).equals(preference.getKey())) {
|
||||
int index = valueToIndex((String)value, r.getStringArray(R.array.EPr_quiet_hours_end_values));
|
||||
if(index == -1)
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_desc_none));
|
||||
else {
|
||||
String duration = r.getStringArray(R.array.EPr_quiet_hours_end)[index];
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_quiet_hours_end_desc, duration));
|
||||
}
|
||||
} else if(r.getString(R.string.p_rmd_ringtone).equals(preference.getKey())) {
|
||||
if(value == null)
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_ringtone_desc_default));
|
||||
else
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_ringtone_desc_custom));
|
||||
} else if(r.getString(R.string.p_rmd_persistent).equals(preference.getKey())) {
|
||||
if((Boolean)value)
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_persistent_desc_true));
|
||||
else
|
||||
preference.setSummary(r.getString(R.string.rmd_EPr_persistent_desc_false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,301 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
import com.todoroo.astrid.utility.Preferences;
|
||||
|
||||
|
||||
/**
|
||||
* Data service for reminders
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class ReminderService {
|
||||
|
||||
// --- constants
|
||||
|
||||
private static final Property<?>[] PROPERTIES = new Property<?>[] {
|
||||
Task.DUE_DATE,
|
||||
Task.REMINDER_FLAGS,
|
||||
Task.REMINDER_PERIOD,
|
||||
Task.REMINDER_LAST
|
||||
};
|
||||
|
||||
/** flag for due date reminder */
|
||||
static final int TYPE_DUE = 0;
|
||||
/** flag for overdue reminder */
|
||||
static final int TYPE_OVERDUE = 1;
|
||||
/** flag for random reminder */
|
||||
static final int TYPE_RANDOM = 2;
|
||||
/** flag for a snoozed reminder */
|
||||
static final int TYPE_SNOOZE = 3;
|
||||
|
||||
static final Random random = new Random();
|
||||
|
||||
// --- instance variables
|
||||
|
||||
@Autowired
|
||||
private TaskDao taskDao;
|
||||
|
||||
private AlarmScheduler scheduler = new ReminderAlarmScheduler();
|
||||
|
||||
public ReminderService() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
setPreferenceDefaults();
|
||||
}
|
||||
|
||||
// --- preference handling
|
||||
|
||||
private static boolean preferencesInitialized = false;
|
||||
|
||||
/** Set preference defaults, if unset. called at startup */
|
||||
public void setPreferenceDefaults() {
|
||||
if(preferencesInitialized)
|
||||
return;
|
||||
|
||||
Context context = ContextManager.getContext();
|
||||
SharedPreferences prefs = Preferences.getPrefs(context);
|
||||
Editor editor = prefs.edit();
|
||||
Resources r = context.getResources();
|
||||
|
||||
Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_quietStart, 22);
|
||||
Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_quietEnd, 10);
|
||||
Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_default_random_hours, 0);
|
||||
Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_time, 12);
|
||||
Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_nagging, true);
|
||||
Preferences.setIfUnset(prefs, editor, r, R.string.p_rmd_persistent, true);
|
||||
|
||||
editor.commit();
|
||||
preferencesInitialized = true;
|
||||
}
|
||||
|
||||
// --- reminder scheduling logic
|
||||
|
||||
/**
|
||||
* Schedules all alarms
|
||||
*/
|
||||
public void scheduleAllAlarms() {
|
||||
|
||||
TodorooCursor<Task> cursor = getTasksWithReminders(PROPERTIES);
|
||||
try {
|
||||
Task task = new Task();
|
||||
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||
task.readFromCursor(cursor);
|
||||
scheduleAlarm(task, false);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static final long NO_ALARM = Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Schedules alarms for a single task
|
||||
* @param task
|
||||
*/
|
||||
public void scheduleAlarm(Task task) {
|
||||
scheduleAlarm(task, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules alarms for a single task
|
||||
*
|
||||
* @param shouldPerformPropertyCheck
|
||||
* whether to check if task has requisite properties
|
||||
*/
|
||||
private void scheduleAlarm(Task task, boolean shouldPerformPropertyCheck) {
|
||||
if(task == null || !task.isSaved())
|
||||
return;
|
||||
|
||||
// read data if necessary
|
||||
if(shouldPerformPropertyCheck) {
|
||||
for(Property<?> property : PROPERTIES) {
|
||||
if(!task.containsValue(property)) {
|
||||
task = taskDao.fetch(task.getId(), PROPERTIES);
|
||||
if(task == null)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// random reminders
|
||||
long whenRandom = calculateNextRandomReminder(task);
|
||||
|
||||
// notifications at due date
|
||||
long whenDueDate = calculateNextDueDateReminder(task);
|
||||
|
||||
// notifications after due date
|
||||
long whenOverdue = calculateNextOverdueReminder(task);
|
||||
|
||||
if(whenRandom < whenDueDate && whenRandom < whenOverdue)
|
||||
scheduler.createAlarm(task, whenRandom, TYPE_RANDOM);
|
||||
else if(whenDueDate < whenOverdue)
|
||||
scheduler.createAlarm(task, whenDueDate, TYPE_DUE);
|
||||
else if(whenOverdue != NO_ALARM)
|
||||
scheduler.createAlarm(task, whenOverdue, TYPE_OVERDUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next alarm time for overdue reminders. If the due date
|
||||
* has passed, we schedule a reminder some time in the next 4 - 24 hours.
|
||||
*
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
private long calculateNextOverdueReminder(Task task) {
|
||||
if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE)) {
|
||||
long dueDate = task.getValue(Task.DUE_DATE);
|
||||
if(dueDate > DateUtilities.now())
|
||||
return NO_ALARM;
|
||||
return DateUtilities.now() + (long)((4 + 20 * random.nextFloat()) * DateUtilities.ONE_HOUR);
|
||||
}
|
||||
return NO_ALARM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next alarm time for due date reminders. If the due date
|
||||
* has not already passed, we return the due date, altering the time
|
||||
* if the date was indicated to not have a due time
|
||||
*
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
private long calculateNextDueDateReminder(Task task) {
|
||||
if(task.hasDueDate() && task.getFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE)) {
|
||||
long dueDate = task.getValue(Task.DUE_DATE);
|
||||
if(dueDate < DateUtilities.now())
|
||||
return NO_ALARM;
|
||||
else if(task.hasDueTime())
|
||||
// return due date straight up
|
||||
return dueDate;
|
||||
else {
|
||||
// return notification time on this day
|
||||
Date date = new Date(dueDate);
|
||||
date.setHours(Preferences.getIntegerFromString(R.string.p_rmd_time));
|
||||
date.setMinutes(0);
|
||||
return date.getTime();
|
||||
}
|
||||
}
|
||||
return NO_ALARM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next alarm time for random reminders. We take the last
|
||||
* random reminder time and add approximately the reminder period, until
|
||||
* we get a time that's in the future.
|
||||
*
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
private long calculateNextRandomReminder(Task task) {
|
||||
long reminderPeriod = task.getValue(Task.REMINDER_PERIOD);
|
||||
if((reminderPeriod) > 0) {
|
||||
long when = task.getValue(Task.REMINDER_LAST);
|
||||
|
||||
// get or make up a last notification time
|
||||
if(when == 0) {
|
||||
when = DateUtilities.now();
|
||||
task.setValue(Task.REMINDER_LAST, when);
|
||||
taskDao.save(task, false);
|
||||
}
|
||||
|
||||
when += (long)(reminderPeriod * (0.85f + 0.3f * random.nextFloat()));
|
||||
return when;
|
||||
}
|
||||
return NO_ALARM;
|
||||
}
|
||||
|
||||
// --- alarm manager alarm creation
|
||||
|
||||
/**
|
||||
* Interface for testing
|
||||
*/
|
||||
public interface AlarmScheduler {
|
||||
public void createAlarm(Task task, long time, int type);
|
||||
}
|
||||
|
||||
public void setScheduler(AlarmScheduler scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public AlarmScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
private class ReminderAlarmScheduler implements AlarmScheduler {
|
||||
/**
|
||||
* Create an alarm for the given task at the given type
|
||||
*
|
||||
* @param task
|
||||
* @param time
|
||||
* @param type
|
||||
* @param flags
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public void createAlarm(Task task, long time, int type) {
|
||||
if(time == 0 || time == NO_ALARM)
|
||||
return;
|
||||
|
||||
if(time < DateUtilities.now()) {
|
||||
time = DateUtilities.now() + (long)((0.5f +
|
||||
4 * random.nextFloat()) * DateUtilities.ONE_HOUR);
|
||||
}
|
||||
|
||||
Context context = ContextManager.getContext();
|
||||
Intent intent = new Intent(context, Notifications.class);
|
||||
intent.setType(Long.toString(task.getId()));
|
||||
intent.setAction(Integer.toString(type));
|
||||
intent.putExtra(Notifications.ID_KEY, task.getId());
|
||||
intent.putExtra(Notifications.TYPE_KEY, type);
|
||||
|
||||
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
|
||||
intent, 0);
|
||||
|
||||
if(Constants.DEBUG)
|
||||
Log.e("Astrid", "Alarm (" + task.getId() + ", " + type +
|
||||
") set for " + new Date(time));
|
||||
am.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
|
||||
}
|
||||
}
|
||||
|
||||
// --- data fetching classes
|
||||
|
||||
/**
|
||||
* Gets a listing of all tasks that are active &
|
||||
* @param properties
|
||||
* @return todoroo cursor. PLEASE CLOSE THIS CURSOR!
|
||||
*/
|
||||
private TodorooCursor<Task> getTasksWithReminders(Property<?>... properties) {
|
||||
return taskDao.query(Query.select(properties).where(Criterion.and(TaskCriteria.isActive(),
|
||||
Task.REMINDER_FLAGS.gt(0))));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
|
||||
/**
|
||||
* Service which handles jobs that need to be run when phone boots
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class ReminderStartupService extends BroadcastReceiver {
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
// --- system startup
|
||||
|
||||
@Override
|
||||
/** Called when the system is started up */
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
new ReminderService().scheduleAllAlarms();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.tags;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.TaskDetail;
|
||||
|
||||
/**
|
||||
* Exposes {@link TaskDetail} for tags, i.e. "Tags: frogs, animals"
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class TagDetailExposer extends BroadcastReceiver {
|
||||
|
||||
private static TagService tagService = null;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// get tags associated with this task
|
||||
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
if(tagService == null)
|
||||
tagService = new TagService();
|
||||
String tagList = tagService.getTagsAsString(taskId);
|
||||
|
||||
if(tagList.length() == 0)
|
||||
return;
|
||||
|
||||
TaskDetail taskDetail = new TaskDetail(TagsPlugin.IDENTIFIER,
|
||||
context.getString(R.string.tag_TLA_detail, tagList));
|
||||
|
||||
// transmit
|
||||
TaskDetail[] details = new TaskDetail[1];
|
||||
details[0] = taskDetail;
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_DETAILS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, details);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.tags;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.sql.QueryTemplate;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.Filter;
|
||||
import com.todoroo.astrid.api.FilterCategory;
|
||||
import com.todoroo.astrid.api.FilterListHeader;
|
||||
import com.todoroo.astrid.api.FilterListItem;
|
||||
import com.todoroo.astrid.tags.TagService.Tag;
|
||||
|
||||
/**
|
||||
* Exposes filters based on tags
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class TagFilterExposer extends BroadcastReceiver {
|
||||
|
||||
private TagService tagService;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private Filter filterFromTag(Context context, Tag tag) {
|
||||
String listTitle = context.getString(R.string.tag_FEx_tag_w_size).
|
||||
replace("$T", tag.tag).replace("$C", Integer.toString(tag.count));
|
||||
String title = context.getString(R.string.tag_FEx_name, tag.tag);
|
||||
QueryTemplate tagTemplate = tag.queryTemplate();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(TagService.KEY, tag.tag);
|
||||
|
||||
Filter filter = new Filter(TagsPlugin.IDENTIFIER,
|
||||
listTitle, title,
|
||||
tagTemplate,
|
||||
contentValues);
|
||||
|
||||
// filters[0].contextMenuLabels = new String[] {
|
||||
// "Rename Tag",
|
||||
// "Delete Tag"
|
||||
// };
|
||||
// filters[0].contextMenuIntents = new Intent[] {
|
||||
// new Intent(),
|
||||
// new Intent()
|
||||
// };
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
tagService = new TagService();
|
||||
Tag[] tagsByAlpha = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_ALPHA);
|
||||
|
||||
// If user does not have any tags, don't show this section at all
|
||||
if(tagsByAlpha.length == 0)
|
||||
return;
|
||||
|
||||
Resources r = context.getResources();
|
||||
|
||||
Tag[] tagsBySize = tagService.getGroupedTags(TagService.GROUPED_TAGS_BY_SIZE);
|
||||
Filter[] filtersByAlpha = new Filter[tagsByAlpha.length];
|
||||
for(int i = 0; i < tagsByAlpha.length; i++)
|
||||
filtersByAlpha[i] = filterFromTag(context, tagsByAlpha[i]);
|
||||
|
||||
Filter[] filtersBySize = new Filter[tagsBySize.length];
|
||||
for(int i = 0; i < tagsBySize.length; i++)
|
||||
filtersBySize[i] = filterFromTag(context, tagsBySize[i]);
|
||||
|
||||
FilterListHeader tagsHeader = new FilterListHeader(TagsPlugin.IDENTIFIER,
|
||||
context.getString(R.string.tag_FEx_header));
|
||||
Filter untagged = new Filter(TagsPlugin.IDENTIFIER,
|
||||
"Untagged",
|
||||
"Untagged",
|
||||
tagService.untaggedTemplate(),
|
||||
null);
|
||||
untagged.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_untagged)).getBitmap();
|
||||
FilterCategory tagsCategoryBySize = new FilterCategory(TagsPlugin.IDENTIFIER,
|
||||
context.getString(R.string.tag_FEx_by_size), filtersBySize);
|
||||
tagsCategoryBySize.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags1)).getBitmap();
|
||||
FilterCategory tagsCategoryByAlpha = new FilterCategory(TagsPlugin.IDENTIFIER,
|
||||
context.getString(R.string.tag_FEx_alpha), filtersByAlpha);
|
||||
tagsCategoryByAlpha.listingIcon = ((BitmapDrawable)r.getDrawable(R.drawable.filter_tags2)).getBitmap();
|
||||
|
||||
// transmit filter list
|
||||
FilterListItem[] list = new FilterListItem[4];
|
||||
list[0] = tagsHeader;
|
||||
list[1] = untagged;
|
||||
list[2] = tagsCategoryBySize;
|
||||
list[3] = tagsCategoryByAlpha;
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_FILTERS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ITEMS, list);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.todoroo.astrid.tags;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.Plugin;
|
||||
|
||||
public class TagsPlugin extends BroadcastReceiver {
|
||||
|
||||
static final String IDENTIFIER = "tags";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Plugin plugin = new Plugin(IDENTIFIER, "Tags", "Todoroo",
|
||||
"Provides tagging support for tasks.");
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_PLUGINS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_PLUGIN, plugin);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:startColor="#373737"
|
||||
android:endColor="#000000"
|
||||
android:gradientRadius="300"
|
||||
android:centerX="0.5"
|
||||
android:centerY="0.5" />
|
||||
</shape>
|
||||
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:startColor="#ffffffff"
|
||||
android:centerColor="#ff000000"
|
||||
android:endColor="#ffffffff"
|
||||
android:angle="0" />
|
||||
</shape>
|
||||
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 666 B |
@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 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.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Enabled states -->
|
||||
|
||||
<item android:state_checked="true" android:state_pressed="true"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_on_pressed" />
|
||||
<item android:state_checked="false" android:state_pressed="true"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_off_pressed" />
|
||||
|
||||
<item android:state_checked="false"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_50" />
|
||||
<item android:state_checked="true"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_on" />
|
||||
|
||||
</selector>
|
||||
@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 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
|
||||
t
|
||||
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.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Enabled states -->
|
||||
|
||||
<item android:state_checked="true" android:state_pressed="true"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_on_pressed" />
|
||||
<item android:state_checked="false" android:state_pressed="true"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_off_pressed" />
|
||||
|
||||
<item android:state_checked="false"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_75" />
|
||||
<item android:state_checked="true"
|
||||
android:state_enabled="true"
|
||||
android:drawable="@drawable/btn_check_on" />
|
||||
|
||||
</selector>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 390 B |
|
After Width: | Height: | Size: 567 B |
|
After Width: | Height: | Size: 655 B |
|
After Width: | Height: | Size: 853 B |
|
After Width: | Height: | Size: 806 B |
|
After Width: | Height: | Size: 378 B |
|
After Width: | Height: | Size: 615 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 599 B |
|
After Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 169 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |