Merge branch '100616-replace-controllers'
Conflicts: astrid/default.propertiespull/14/head
@ -1,137 +1,208 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.timsu.astrid"
|
package="com.timsu.astrid"
|
||||||
android:versionName="2.14.4" android:versionCode="135">
|
android:versionName="3.0.0-beta" android:versionCode="136">
|
||||||
|
|
||||||
<!-- ============================ Metadata ============================ -->
|
<!-- ================================================== Used Permissions = -->
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<!-- for notifications -->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
<!-- for synchronization -->
|
||||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
<!-- for google calendar integration -->
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<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 -->
|
<!-- ====================================================== Activities = -->
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<!-- 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 -->
|
<receiver android:name=".utilities.LocaleReceiver">
|
||||||
<permission android:name="com.timsu.astrid.permission.READ_TASKS"
|
<intent-filter>
|
||||||
android:permissionGroup="android.permission-group.MESSAGES"
|
<action android:name="com.timsu.astrid.action.LOCALE_ALERT" />
|
||||||
android:protectionLevel="normal"
|
</intent-filter>
|
||||||
android:label="@string/read_tasks_permission"
|
</receiver>
|
||||||
android:description="@string/read_tasks_permission"/>
|
|
||||||
<uses-permission android:name="com.timsu.astrid.permission.READ_TASKS"/>
|
<receiver android:name=".appwidget.AstridAppWidgetProvider" >
|
||||||
|
<intent-filter>
|
||||||
<uses-sdk android:minSdkVersion="3"
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
android:targetSdkVersion="4" />
|
</intent-filter>
|
||||||
<supports-screens />
|
<meta-data android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/widget_provider_info" />
|
||||||
<application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="false">
|
</receiver>
|
||||||
|
|
||||||
<!-- ======================== Activities ========================= -->
|
<!-- ======================================================== Services = -->
|
||||||
|
|
||||||
<!-- Activity that displays the task list -->
|
<service android:name=".appwidget.AstridAppWidgetProvider$UpdateService"></service>
|
||||||
<activity android:name=".activities.TaskList">
|
<service android:name=".sync.SynchronizationService" />
|
||||||
<intent-filter>
|
<service android:name=".utilities.BackupService"/>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<!-- ======================================================= Providers = -->
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
<provider android:name=".provider.TasksProvider"
|
||||||
|
android:authorities="com.timsu.astrid.tasksprovider"
|
||||||
<!-- Activity that redirects to a task list, invoked by notifications -->
|
android:multiprocess="true"
|
||||||
<activity android:name=".activities.TaskListNotify"
|
android:grantUriPermissions="true"
|
||||||
android:launchMode="singleTop" />
|
android:readPermission="com.timsu.astrid.permission.READ_TASKS"/>
|
||||||
|
|
||||||
<!-- Activity that creates or edits tasks -->
|
<!-- ========================================================= Plugins = -->
|
||||||
<activity android:name=".activities.TaskEdit"
|
|
||||||
android:icon="@drawable/icon_add"
|
<!-- core filters -->
|
||||||
android:label="@string/taskEdit_label">
|
<receiver android:name="com.todoroo.astrid.filters.CorePlugin">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="com.todoroo.astrid.REQUEST_PLUGINS" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
<intent-filter>
|
<receiver android:name="com.todoroo.astrid.filters.CoreFilterExposer">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<intent-filter>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
|
||||||
<data android:mimeType="vnd.android.cursor.item/task" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</activity>
|
|
||||||
|
<!-- extended filters -->
|
||||||
<!-- Activity that views tags -->
|
<receiver android:name="com.todoroo.astrid.filters.ExtendedPlugin">
|
||||||
<activity android:name=".activities.TagView">
|
<intent-filter>
|
||||||
<intent-filter>
|
<action android:name="com.todoroo.astrid.REQUEST_PLUGINS" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
</intent-filter>
|
||||||
</intent-filter>
|
</receiver>
|
||||||
</activity>
|
<receiver android:name="com.todoroo.astrid.filters.ExtendedFilterExposer">
|
||||||
|
<intent-filter>
|
||||||
<!-- Activity that lets users log in to sync providers -->
|
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
|
||||||
<activity android:name=".activities.SyncLoginActivity"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
<!-- Activity that lets users edit app preferences -->
|
</receiver>
|
||||||
<activity android:name=".activities.EditPreferences"/>
|
|
||||||
|
<!-- tags -->
|
||||||
<!-- Activity that lets users edit synchronization preferences -->
|
<receiver android:name="com.todoroo.astrid.tags.TagsPlugin">
|
||||||
<activity android:name=".activities.SyncPreferences"/>
|
<intent-filter>
|
||||||
|
<action android:name="com.todoroo.astrid.REQUEST_PLUGINS" />
|
||||||
<!-- activity that Locale displays to edit tag notification settings -->
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<activity
|
</intent-filter>
|
||||||
android:name=".activities.LocaleEditAlerts"
|
</receiver>
|
||||||
android:label="@string/locale_edit_alerts_title"
|
<receiver android:name="com.todoroo.astrid.tags.TagFilterExposer">
|
||||||
android:icon="@drawable/icon_32"
|
<intent-filter>
|
||||||
android:exported="true" >
|
<action android:name="com.todoroo.astrid.REQUEST_FILTERS" />
|
||||||
<intent-filter>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
</intent-filter>
|
||||||
</intent-filter>
|
</receiver>
|
||||||
</activity>
|
<receiver android:name="com.todoroo.astrid.tags.TagDetailExposer">
|
||||||
|
<intent-filter>
|
||||||
<!-- ======================== Receivers ========================= -->
|
<action android:name="com.todoroo.astrid.REQUEST_DETAILS" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<receiver android:name=".utilities.Notifications" />
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
<receiver android:name=".utilities.LocaleReceiver">
|
|
||||||
<intent-filter>
|
<!-- reminders -->
|
||||||
<action android:name="com.timsu.astrid.action.LOCALE_ALERT" />
|
<activity android:name="com.todoroo.astrid.reminders.ReminderPreferences"
|
||||||
</intent-filter>
|
android:label="@string/rmd_EPr_alerts_header">
|
||||||
</receiver>
|
<intent-filter>
|
||||||
|
<action android:name="com.todoroo.astrid.SETTINGS" />
|
||||||
<receiver android:name=".utilities.StartupReceiver">
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<intent-filter>
|
</intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
</activity>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<receiver android:name="com.todoroo.astrid.reminders.ReminderStartupService">
|
||||||
</intent-filter>
|
<intent-filter>
|
||||||
</receiver>
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<receiver android:name=".appwidget.AstridAppWidgetProvider" >
|
</intent-filter>
|
||||||
<intent-filter>
|
</receiver>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<activity android:name="com.todoroo.astrid.reminders.NotificationActivity"
|
||||||
</intent-filter>
|
android:taskAffinity="" />
|
||||||
<meta-data android:name="android.appwidget.provider"
|
</application>
|
||||||
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>
|
|
||||||
|
|
||||||
</manifest>
|
</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.sql.Constants.AS;
|
||||||
import static com.todoroo.andlib.data.sql.Constants.SPACE;
|
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||||
|
|
||||||
public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
|
public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
|
||||||
protected String alias;
|
protected String alias;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.todoroo.andlib.data.sql;
|
package com.todoroo.andlib.sql;
|
||||||
|
|
||||||
public class EqCriterion extends UnaryCriterion {
|
public class EqCriterion extends UnaryCriterion {
|
||||||
EqCriterion(Field field, Object value) {
|
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.List;
|
||||||
import java.util.ArrayList;
|
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.sql.Constants.JOIN;
|
||||||
import static com.todoroo.andlib.data.sql.Constants.ON;
|
import static com.todoroo.andlib.sql.Constants.ON;
|
||||||
import static com.todoroo.andlib.data.sql.Constants.SPACE;
|
import static com.todoroo.andlib.sql.Constants.SPACE;
|
||||||
|
|
||||||
public class Join {
|
public class Join {
|
||||||
private final Table joinTable;
|
private final SqlTable joinTable;
|
||||||
private final JoinType joinType;
|
private final JoinType joinType;
|
||||||
private final Criterion[] criterions;
|
private final Criterion[] criterions;
|
||||||
|
|
||||||
private Join(Table table, JoinType joinType, Criterion... criterions) {
|
private Join(SqlTable table, JoinType joinType, Criterion... criterions) {
|
||||||
joinTable = table;
|
joinTable = table;
|
||||||
this.joinType = joinType;
|
this.joinType = joinType;
|
||||||
this.criterions = criterions;
|
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);
|
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);
|
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);
|
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);
|
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 {
|
public enum JoinType {
|
||||||
INNER, LEFT, RIGHT, OUT
|
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.HashMap;
|
||||||
import java.util.Map;
|
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 {
|
public class Order {
|
||||||
private final Field expression;
|
private final Object expression;
|
||||||
private final OrderType orderType;
|
private final OrderType orderType;
|
||||||
|
|
||||||
private Order(Field expression) {
|
private Order(Object expression) {
|
||||||
this(expression, OrderType.ASC);
|
this(expression, OrderType.ASC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Order(Field expression, OrderType orderType) {
|
private Order(Object expression, OrderType orderType) {
|
||||||
this.expression = expression;
|
this.expression = expression;
|
||||||
this.orderType = orderType;
|
this.orderType = orderType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Order asc(Field expression) {
|
public static Order asc(Object expression) {
|
||||||
return new Order(expression);
|
return new Order(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Order desc(Field expression) {
|
public static Order desc(Object expression) {
|
||||||
return new Order(expression, OrderType.DESC);
|
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);
|
super(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Table table(String table) {
|
public static SqlTable table(String table) {
|
||||||
return new Table(table);
|
return new SqlTable(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("nls")
|
@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 {
|
public class UnaryCriterion extends Criterion {
|
||||||
protected final Field expression;
|
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 |