mirror of https://github.com/tasks/tasks
Moved a bunch of API files into the API project. Yay for code reuse.
Please import the github project todoroo/astridApi. :)pull/14/head
parent
be125eca89
commit
c6da0907ff
@ -1,93 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 an add-onn for Astrid. Users can enable or disable add-ons,
|
|
||||||
* which affect all other extension points that share the same identifier.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class Addon implements Parcelable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add-on Identifier
|
|
||||||
*/
|
|
||||||
public String addon = 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 addon
|
|
||||||
* @param title
|
|
||||||
* @param author
|
|
||||||
* @param description
|
|
||||||
*/
|
|
||||||
public Addon(String addon, String title, String author, String description) {
|
|
||||||
this.addon = addon;
|
|
||||||
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(addon);
|
|
||||||
dest.writeString(title);
|
|
||||||
dest.writeString(author);
|
|
||||||
dest.writeString(description);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable creator
|
|
||||||
*/
|
|
||||||
public static final Parcelable.Creator<Addon> CREATOR = new Parcelable.Creator<Addon>() {
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public Addon createFromParcel(Parcel source) {
|
|
||||||
return new Addon(source.readString(), source.readString(),
|
|
||||||
source.readString(), source.readString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public Addon[] newArray(int size) {
|
|
||||||
return new Addon[size];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,242 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.widget.RemoteViews;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constants for interfacing with Astrid.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public class AstridApiConstants {
|
|
||||||
|
|
||||||
// --- General Constants
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Astrid broadcast base package name
|
|
||||||
*/
|
|
||||||
public static final String PACKAGE = "com.todoroo.astrid";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permission for reading tasks and receiving to GET_FILTERS intent
|
|
||||||
*/
|
|
||||||
public static final String PERMISSION_READ = PACKAGE + ".READ";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permission for writing and creating tasks
|
|
||||||
*/
|
|
||||||
public static final String PERMISSION_WRITE = PACKAGE + ".WRITE";
|
|
||||||
|
|
||||||
// --- Content Provider
|
|
||||||
|
|
||||||
/**
|
|
||||||
* URI to append to base content URI for making group-by queries
|
|
||||||
*/
|
|
||||||
public static final String GROUP_BY_URI = "/groupby/";
|
|
||||||
|
|
||||||
// --- Broadcast Extras
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extras name for task id
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_TASK_ID = "task";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extras name for a response item broadcast to astrid
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_RESPONSE = "response";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extras name for plug-in identifier
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_ADDON = "addon";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extras name for whether task detail request is extended
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_EXTENDED = "extended";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extras name for old task due date
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_OLD_DUE_DATE= "oldDueDate";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extras name for new task due date
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_NEW_DUE_DATE = "newDueDate";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extras name for sync provider name
|
|
||||||
*/
|
|
||||||
public static final String EXTRAS_NAME = "name";
|
|
||||||
|
|
||||||
// --- Add-ons API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent requesting add-ons
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_REQUEST_ADDONS = PACKAGE + ".REQUEST_ADDONS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent sending add-ons back to Astrid
|
|
||||||
* @extra EXTRAS_RESPONSE an {@link Addon} object
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_SEND_ADDONS = PACKAGE + ".SEND_ADDONS";
|
|
||||||
|
|
||||||
// --- Filters API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent requesting filters
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_REQUEST_FILTERS = PACKAGE + ".REQUEST_FILTERS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent sending filters back to Astrid
|
|
||||||
* @extra EXTRAS_ADDON your add-on identifier
|
|
||||||
* @extra EXTRAS_RESPONSE an array of {@link FilterListItem}s
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_SEND_FILTERS = PACKAGE + ".SEND_FILTERS";
|
|
||||||
|
|
||||||
// --- Edit Controls API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent requesting task edit controls
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task user is editing
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_REQUEST_EDIT_CONTROLS = PACKAGE + ".REQUEST_EDIT_CONTROLS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent sending task edit controls back to Astrid
|
|
||||||
* @extra EXTRAS_ADDON your add-on identifier
|
|
||||||
* @extra EXTRAS_RESPONSE a {@link RemoteViews} with your edit controls
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_SEND_EDIT_CONTROLS = PACKAGE + ".SEND_EDIT_CONTROLS";
|
|
||||||
|
|
||||||
// --- Task Details API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent requesting details for a task.
|
|
||||||
* Extended details are displayed when a user presses on a task.
|
|
||||||
*
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
* @extra EXTRAS_EXTENDED whether request is for standard or extended details
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_REQUEST_DETAILS = PACKAGE + ".REQUEST_DETAILS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent sending details back to Astrid
|
|
||||||
* @extra EXTRAS_ADDON your add-on identifier
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
* @extra EXTRAS_EXTENDED whether request is for standard or extended details
|
|
||||||
* @extra EXTRAS_RESPONSE a String
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_SEND_DETAILS = PACKAGE + ".SEND_DETAILS";
|
|
||||||
|
|
||||||
// --- Sync Action API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent requesting a listing of active
|
|
||||||
* sync actions users can activate from the menu
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_REQUEST_SYNC_ACTIONS = PACKAGE + ".REQUEST_SYNC_ACTIONS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent sending sync provider information back to Astrid
|
|
||||||
* @extra EXTRAS_ADDON your add-on identifier
|
|
||||||
* @extra EXTRAS_RESPONSE a {@link SyncAction} to invoke synchronization
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_SEND_SYNC_ACTIONS = PACKAGE + ".SEND_SYNC_ACTIONS";
|
|
||||||
|
|
||||||
// --- Task Actions API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent requesting actions for a task
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_REQUEST_ACTIONS = PACKAGE + ".REQUEST_ACTIONS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent sending actions back to Astrid
|
|
||||||
* @extra EXTRAS_ADDON your add-on identifier
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
* @extra EXTRAS_RESPONSE a String
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_SEND_ACTIONS = PACKAGE + ".SEND_ACTIONS";
|
|
||||||
|
|
||||||
// --- Task Decorations API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent requesting task list decorations for a task
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_REQUEST_DECORATIONS = PACKAGE + ".REQUEST_DECORATIONS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent sending decorations back to Astrid
|
|
||||||
* @extra EXTRAS_ADDON your add-on identifier
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
* @extra EXTRAS_RESPONSE a {@link TaskDecoration}
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_SEND_DECORATIONS = PACKAGE + ".SEND_DECORATIONS";
|
|
||||||
|
|
||||||
// --- Actions API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for intents to be displayed on task context menu
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
*/
|
|
||||||
public static final String ACTION_TASK_CONTEXT_MENU = PACKAGE + ".CONTEXT_MENU";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for intents to be displayed on Astrid's task list menu
|
|
||||||
* @extra EXTRAS_ADDON your add-on identifier
|
|
||||||
* @extra EXTRAS_RESPONSE an array of {@link Intent}s
|
|
||||||
*/
|
|
||||||
public static final String ACTION_TASK_LIST_MENU = PACKAGE + ".TASK_LIST_MENU";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for intents to be displayed in Astrid's settings. By default,
|
|
||||||
* your application will be put into the category named by your application,
|
|
||||||
* but you can add a string meta-data with name "category" to override this.
|
|
||||||
*/
|
|
||||||
public static final String ACTION_SETTINGS = PACKAGE + ".SETTINGS";
|
|
||||||
|
|
||||||
// --- Events API
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent notifying add-ons that Astrid started up
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_EVENT_STARTUP = PACKAGE + ".STARTUP";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent notifying Astrid task list to refresh
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_EVENT_REFRESH = PACKAGE + ".REFRESH";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent notifying Astrid to clear detail cache
|
|
||||||
* because an event occurred that potentially affects all tasks (e.g.
|
|
||||||
* logging out of a sync provider). Use this call carefully, as loading
|
|
||||||
* details can degrade the performance of Astrid.
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_EVENT_FLUSH_DETAILS = PACKAGE + ".FLUSH_DETAILS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent notifying that task was completed
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_EVENT_TASK_COMPLETED = PACKAGE + ".TASK_COMPLETED";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action name for broadcast intent notifying that task was created from repeating template
|
|
||||||
* @extra EXTRAS_TASK_ID id of the task
|
|
||||||
* @extra EXTRAS_OLD_DUE_DATE task old due date (could be 0)
|
|
||||||
* @extra EXTRAS_NEW_DUE_DATE task new due date (will not be 0)
|
|
||||||
*/
|
|
||||||
public static final String BROADCAST_EVENT_TASK_REPEATED = PACKAGE + ".TASK_REPEATED";
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,159 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CustomFilterCriteria allow users to build a custom filter by chaining
|
|
||||||
* together criteria
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class CustomFilterCriterion implements Parcelable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Criteria Identifier. This identifier allows saved filters to be reloaded.
|
|
||||||
* <p>
|
|
||||||
* e.g "duedate"
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String identifier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Criteria Title. If the title contains ?, this is replaced by the entry
|
|
||||||
* label string selected.
|
|
||||||
* <p>
|
|
||||||
* e.g "Due: ?"
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Criterion SQL. This query should return task id's. If this contains
|
|
||||||
* ?, it will be replaced by the entry value
|
|
||||||
* <p>
|
|
||||||
* Examples:
|
|
||||||
* <ul>
|
|
||||||
* <li><code>SELECT _id FROM tasks WHERE dueDate <= ?</code>
|
|
||||||
* <li><code>SELECT task FROM metadata WHERE value = '?'</code>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String sql;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Values to apply to a task when quick-adding a task from a filter
|
|
||||||
* created from this criterion. ? will be replaced with the entry value.
|
|
||||||
* For example, when a user views tasks tagged 'ABC', the
|
|
||||||
* tasks they create should also be tagged 'ABC'. If set to null, no
|
|
||||||
* additional values will be stored for a task.
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public ContentValues valuesForNewTasks = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of entries for user to select from
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String[] entryTitles;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of entry values corresponding to entries
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String[] entryValues;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon for this criteria. Can be null for no bitmap
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public Bitmap icon;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Criteria name. This is displayed when users are selecting a criteria
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new CustomFilterCriteria object
|
|
||||||
*
|
|
||||||
* @param title
|
|
||||||
* @param sql
|
|
||||||
* @param valuesForNewTasks
|
|
||||||
* @param entryTitles
|
|
||||||
* @param entryValues
|
|
||||||
* @param icon
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
public CustomFilterCriterion(String identifier, String title, String sql,
|
|
||||||
ContentValues valuesForNewTasks, String[] entryTitles,
|
|
||||||
String[] entryValues, Bitmap icon, String name) {
|
|
||||||
this.identifier = identifier;
|
|
||||||
this.text = title;
|
|
||||||
this.sql = sql;
|
|
||||||
this.valuesForNewTasks = valuesForNewTasks;
|
|
||||||
this.entryTitles = entryTitles;
|
|
||||||
this.entryValues = entryValues;
|
|
||||||
this.icon = icon;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeString(identifier);
|
|
||||||
dest.writeString(text);
|
|
||||||
dest.writeString(sql);
|
|
||||||
dest.writeParcelable(valuesForNewTasks, 0);
|
|
||||||
dest.writeStringArray(entryTitles);
|
|
||||||
dest.writeStringArray(entryValues);
|
|
||||||
dest.writeParcelable(icon, 0);
|
|
||||||
dest.writeString(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable Creator Object
|
|
||||||
*/
|
|
||||||
public static final Parcelable.Creator<CustomFilterCriterion> CREATOR = new Parcelable.Creator<CustomFilterCriterion>() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public CustomFilterCriterion createFromParcel(Parcel source) {
|
|
||||||
CustomFilterCriterion item = new CustomFilterCriterion(
|
|
||||||
source.readString(), source.readString(), source.readString(),
|
|
||||||
(ContentValues)source.readParcelable(ContentValues.class.getClassLoader()),
|
|
||||||
source.createStringArray(), source.createStringArray(),
|
|
||||||
(Bitmap)source.readParcelable(Bitmap.class.getClassLoader()),
|
|
||||||
source.readString());
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public CustomFilterCriterion[] newArray(int size) {
|
|
||||||
return new CustomFilterCriterion[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,164 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.sql.QueryTemplate;
|
|
||||||
|
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A <code>FilterListFilter</code> allows users to display tasks that have
|
|
||||||
* something in common.
|
|
||||||
* <p>
|
|
||||||
* A plug-in can expose new <code>FilterListFilter</code>s to the system by
|
|
||||||
* responding to the <code>com.todoroo.astrid.GET_FILTERS</code> broadcast
|
|
||||||
* intent.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class Filter extends FilterListItem {
|
|
||||||
|
|
||||||
// --- constants
|
|
||||||
|
|
||||||
/** Constant for valuesForNewTasks to indicate the value should be replaced
|
|
||||||
* with the current time as long */
|
|
||||||
public static final long VALUE_NOW = Long.MIN_VALUE + 1;
|
|
||||||
|
|
||||||
// --- instance variables
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expanded title of this filter. This is displayed at the top
|
|
||||||
* of the screen when user is viewing this filter.
|
|
||||||
* <p>
|
|
||||||
* e.g "Tasks With Notes"
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String title;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link PermaSql} query for this filter. The query will be appended to the select
|
|
||||||
* statement after "<code>SELECT fields FROM table %s</code>". It is
|
|
||||||
* recommended that you use a {@link QueryTemplate} to construct your
|
|
||||||
* query.
|
|
||||||
* <p>
|
|
||||||
* Examples:
|
|
||||||
* <ul>
|
|
||||||
* <li><code>"WHERE completionDate = 0"</code>
|
|
||||||
* <li><code>"INNER JOIN " +
|
|
||||||
* Constants.TABLE_METADATA + " ON metadata.task = tasks.id WHERE
|
|
||||||
* metadata.namespace = " + NAMESPACE + " AND metadata.key = 'a' AND
|
|
||||||
* metadata.value = 'b' GROUP BY tasks.id ORDER BY tasks.title"</code>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String sqlQuery;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Values to apply to a task when quick-adding a task from this filter.
|
|
||||||
* For example, when a user views tasks tagged 'ABC', the
|
|
||||||
* tasks they create should also be tagged 'ABC'. If set to null, no
|
|
||||||
* additional values will be stored for a task. Can use {@link PermaSql}
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public ContentValues valuesForNewTasks = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor for creating a Filter object
|
|
||||||
* @param listingTitle
|
|
||||||
* Title of this item as displayed on the lists page, e.g. Inbox
|
|
||||||
* @param title
|
|
||||||
* Expanded title of this filter when user is viewing this
|
|
||||||
* filter, e.g. Inbox (20 tasks)
|
|
||||||
* @param sqlQuery
|
|
||||||
* SQL query for this list (see {@link sqlQuery} for examples).
|
|
||||||
* @param valuesForNewTasks
|
|
||||||
* see {@link sqlForNewTasks}
|
|
||||||
*/
|
|
||||||
public Filter(String listingTitle, String title,
|
|
||||||
QueryTemplate sqlQuery, ContentValues valuesForNewTasks) {
|
|
||||||
this(listingTitle, title, sqlQuery == null ? null : sqlQuery.toString(),
|
|
||||||
valuesForNewTasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor for creating a Filter object
|
|
||||||
* @param listingTitle
|
|
||||||
* Title of this item as displayed on the lists page, e.g. Inbox
|
|
||||||
* @param title
|
|
||||||
* Expanded title of this filter when user is viewing this
|
|
||||||
* filter, e.g. Inbox (20 tasks)
|
|
||||||
* @param sqlQuery
|
|
||||||
* SQL query for this list (see {@link sqlQuery} for examples).
|
|
||||||
* @param valuesForNewTasks
|
|
||||||
* see {@link sqlForNewTasks}
|
|
||||||
*/
|
|
||||||
public Filter(String listingTitle, String title,
|
|
||||||
String sqlQuery, ContentValues valuesForNewTasks) {
|
|
||||||
this.listingTitle = listingTitle;
|
|
||||||
this.title = title;
|
|
||||||
this.sqlQuery = sqlQuery;
|
|
||||||
this.valuesForNewTasks = valuesForNewTasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor
|
|
||||||
*
|
|
||||||
* @param plugin
|
|
||||||
* {@link Addon} identifier that encompasses object
|
|
||||||
*/
|
|
||||||
protected Filter() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
super.writeToParcel(dest, flags);
|
|
||||||
dest.writeString(title);
|
|
||||||
dest.writeString(sqlQuery);
|
|
||||||
dest.writeParcelable(valuesForNewTasks, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable Creator Object
|
|
||||||
*/
|
|
||||||
public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public Filter createFromParcel(Parcel source) {
|
|
||||||
Filter item = new Filter();
|
|
||||||
item.readFromParcel(source);
|
|
||||||
item.title = source.readString();
|
|
||||||
item.sqlQuery = source.readString();
|
|
||||||
item.valuesForNewTasks = source.readParcelable(ContentValues.class.getClassLoader());
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public Filter[] newArray(int size) {
|
|
||||||
return new Filter[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A <code>FilterCategory</code> groups common {@link Filter}s and allows
|
|
||||||
* a user to show/hide all of its children.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class FilterCategory extends FilterListItem {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Filter}s contained by this category
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public Filter[] children;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for creating a new FilterCategory
|
|
||||||
* @param listingTitle
|
|
||||||
* Title of this item as displayed on the lists page, e.g. Inbox
|
|
||||||
* @param children
|
|
||||||
* filters belonging to this category
|
|
||||||
*/
|
|
||||||
public FilterCategory(String listingTitle, Filter[] children) {
|
|
||||||
this.listingTitle = listingTitle;
|
|
||||||
this.children = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for creating a new FilterCategory
|
|
||||||
*
|
|
||||||
* @param plugin
|
|
||||||
* {@link Addon} identifier that encompasses object
|
|
||||||
*/
|
|
||||||
protected FilterCategory() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
super.writeToParcel(dest, flags);
|
|
||||||
dest.writeParcelableArray(children, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable creator
|
|
||||||
*/
|
|
||||||
public static final Parcelable.Creator<FilterCategory> CREATOR = new Parcelable.Creator<FilterCategory>() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public FilterCategory createFromParcel(Parcel source) {
|
|
||||||
FilterCategory item = new FilterCategory();
|
|
||||||
item.readFromParcel(source);
|
|
||||||
|
|
||||||
Parcelable[] parcelableChildren = source.readParcelableArray(
|
|
||||||
FilterCategory.class.getClassLoader());
|
|
||||||
item.children = new Filter[parcelableChildren.length];
|
|
||||||
for(int i = 0; i < item.children.length; i++) {
|
|
||||||
if(parcelableChildren[i] instanceof FilterListItem)
|
|
||||||
item.children[i] = (Filter) parcelableChildren[i];
|
|
||||||
else
|
|
||||||
item.children[i] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public FilterCategory[] newArray(int size) {
|
|
||||||
return new FilterCategory[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Section Header for Filter List
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class FilterListHeader extends FilterListItem {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for creating a new FilterListHeader
|
|
||||||
* @param listingTitle
|
|
||||||
* @param listingIconResource
|
|
||||||
* @param priority
|
|
||||||
*/
|
|
||||||
public FilterListHeader(String listingTitle) {
|
|
||||||
this.listingTitle = listingTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for creating a new FilterListHeader
|
|
||||||
*
|
|
||||||
* @param plugin
|
|
||||||
* {@link Addon} identifier that encompasses object
|
|
||||||
*/
|
|
||||||
protected FilterListHeader() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable
|
|
||||||
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
super.writeToParcel(dest, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Parcelable.Creator<FilterListHeader> CREATOR = new Parcelable.Creator<FilterListHeader>() {
|
|
||||||
|
|
||||||
public FilterListHeader createFromParcel(Parcel source) {
|
|
||||||
FilterListHeader item = new FilterListHeader();
|
|
||||||
item.readFromParcel(source);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterListHeader[] newArray(int size) {
|
|
||||||
return new FilterListHeader[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an item displayed by Astrid's FilterListActivity
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
abstract public class FilterListItem implements Parcelable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title of this item displayed on the Filters page
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String listingTitle = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bitmap for icon used on listing page. <code>null</code> => no icon
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public Bitmap listingIcon = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text Color. <code>0</code> => default color
|
|
||||||
*/
|
|
||||||
public int color = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context Menu labels. The context menu will be displayed when users
|
|
||||||
* long-press on this filter list item.
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public String contextMenuLabels[] = new String[0];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context menu intents. This intent will be started when the corresponding
|
|
||||||
* content menu label is invoked. This array must be the same size as
|
|
||||||
* the contextMenuLabels array.
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public Intent contextMenuIntents[] = new Intent[0];
|
|
||||||
|
|
||||||
// --- parcelable helpers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeString(listingTitle);
|
|
||||||
dest.writeParcelable(listingIcon, 0);
|
|
||||||
dest.writeInt(color);
|
|
||||||
|
|
||||||
// write array lengths before arrays
|
|
||||||
dest.writeStringArray(contextMenuLabels);
|
|
||||||
dest.writeTypedArray(contextMenuIntents, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method to read FilterListItem properties from a parcel.
|
|
||||||
*
|
|
||||||
* @param source
|
|
||||||
*/
|
|
||||||
public void readFromParcel(Parcel source) {
|
|
||||||
listingTitle = source.readString();
|
|
||||||
listingIcon = source.readParcelable(Bitmap.class.getClassLoader());
|
|
||||||
color = source.readInt();
|
|
||||||
|
|
||||||
contextMenuLabels = source.createStringArray();
|
|
||||||
contextMenuIntents = source.createTypedArray(Intent.CREATOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PermaSql allows for creating SQL statements that can be saved and used
|
|
||||||
* later without dates getting stale. It also allows these values to be
|
|
||||||
* used in
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class PermaSql {
|
|
||||||
|
|
||||||
// --- placeholder strings
|
|
||||||
|
|
||||||
/** value to be replaced with the current time as long */
|
|
||||||
public static final String VALUE_NOW = "NOW()"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** value to be replaced by end of day as long */
|
|
||||||
public static final String VALUE_EOD = "EOD()"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** value to be replaced by end of day yesterday as long */
|
|
||||||
public static final String VALUE_EOD_YESTERDAY = "EODY()"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** value to be replaced by end of day tomorrow as long */
|
|
||||||
public static final String VALUE_EOD_TOMORROW = "EODT()"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** value to be replaced by end of day day after tomorrow as long */
|
|
||||||
public static final String VALUE_EOD_DAY_AFTER = "EODTT()"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** value to be replaced by end of day next week as long */
|
|
||||||
public static final String VALUE_EOD_NEXT_WEEK = "EODW()"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** Replace placeholder strings with actual */
|
|
||||||
public static String replacePlaceholders(String value) {
|
|
||||||
if(value.contains(VALUE_NOW))
|
|
||||||
value = value.replace(VALUE_NOW, Long.toString(DateUtilities.now()));
|
|
||||||
if(value.contains(VALUE_EOD) || value.contains(VALUE_EOD_DAY_AFTER) ||
|
|
||||||
value.contains(VALUE_EOD_NEXT_WEEK) || value.contains(VALUE_EOD_TOMORROW) ||
|
|
||||||
value.contains(VALUE_EOD_YESTERDAY)) {
|
|
||||||
Date date = new Date();
|
|
||||||
date.setHours(23);
|
|
||||||
date.setMinutes(59);
|
|
||||||
date.setSeconds(59);
|
|
||||||
long time = date.getTime();
|
|
||||||
value = value.replace(VALUE_EOD_YESTERDAY, Long.toString(time - DateUtilities.ONE_DAY));
|
|
||||||
value = value.replace(VALUE_EOD, Long.toString(time));
|
|
||||||
value = value.replace(VALUE_EOD_TOMORROW, Long.toString(time + DateUtilities.ONE_DAY));
|
|
||||||
value = value.replace(VALUE_EOD_DAY_AFTER, Long.toString(time + 2 * DateUtilities.ONE_DAY));
|
|
||||||
value = value.replace(VALUE_EOD_NEXT_WEEK, Long.toString(time + 7 * DateUtilities.ONE_DAY));
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an intent that can be called to perform synchronization
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SyncAction implements Parcelable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label
|
|
||||||
*/
|
|
||||||
public String label = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intent to call when invoking this operation
|
|
||||||
*/
|
|
||||||
public PendingIntent intent = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an EditOperation object
|
|
||||||
*
|
|
||||||
* @param label
|
|
||||||
* label to display
|
|
||||||
* @param intent
|
|
||||||
* intent to invoke. {@link EXTRAS_TASK_ID} will be passed
|
|
||||||
*/
|
|
||||||
public SyncAction(String label, PendingIntent intent) {
|
|
||||||
super();
|
|
||||||
this.label = label;
|
|
||||||
this.intent = intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the label of this action
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable helpers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeString(label);
|
|
||||||
dest.writeParcelable(intent, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable creator
|
|
||||||
*/
|
|
||||||
public static final Parcelable.Creator<SyncAction> CREATOR = new Parcelable.Creator<SyncAction>() {
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public SyncAction createFromParcel(Parcel source) {
|
|
||||||
return new SyncAction(source.readString(), (PendingIntent)source.readParcelable(
|
|
||||||
PendingIntent.class.getClassLoader()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public SyncAction[] newArray(int size) {
|
|
||||||
return new SyncAction[size];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an intent that can be called on a task
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TaskAction implements Parcelable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label
|
|
||||||
*/
|
|
||||||
public String text = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intent to call when invoking this operation
|
|
||||||
*/
|
|
||||||
public PendingIntent intent = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an EditOperation object
|
|
||||||
*
|
|
||||||
* @param text
|
|
||||||
* label to display
|
|
||||||
* @param intent
|
|
||||||
* intent to invoke. {@link EXTRAS_TASK_ID} will be passed
|
|
||||||
*/
|
|
||||||
public TaskAction(String text, PendingIntent intent) {
|
|
||||||
super();
|
|
||||||
this.text = text;
|
|
||||||
this.intent = intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable helpers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeString(text);
|
|
||||||
dest.writeParcelable(intent, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable creator
|
|
||||||
*/
|
|
||||||
public static final Parcelable.Creator<TaskAction> CREATOR = new Parcelable.Creator<TaskAction>() {
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public TaskAction createFromParcel(Parcel source) {
|
|
||||||
return new TaskAction(source.readString(), (PendingIntent)source.readParcelable(
|
|
||||||
PendingIntent.class.getClassLoader()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public TaskAction[] newArray(int size) {
|
|
||||||
return new TaskAction[size];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.api;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.widget.RemoteViews;
|
|
||||||
import android.widget.RemoteViews.RemoteView;
|
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a line of text displayed in the Task List
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class TaskDecoration implements Parcelable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Place decoration between completion box and task title
|
|
||||||
*/
|
|
||||||
public static final int POSITION_LEFT = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Place decoration between task title and importance bar
|
|
||||||
*/
|
|
||||||
public static final int POSITION_RIGHT = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link RemoteView} decoration
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
public RemoteViews decoration = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decoration position
|
|
||||||
*/
|
|
||||||
public int position = POSITION_LEFT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decorated task background color. 0 is default
|
|
||||||
*/
|
|
||||||
public int color = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a TaskDetail object
|
|
||||||
* @param text
|
|
||||||
* text to display
|
|
||||||
* @param color
|
|
||||||
* color to use for text. Use <code>0</code> for default color
|
|
||||||
*/
|
|
||||||
public TaskDecoration(RemoteViews decoration, int position, int color) {
|
|
||||||
this.decoration = decoration;
|
|
||||||
this.position = position;
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable helpers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeParcelable(decoration, 0);
|
|
||||||
dest.writeInt(position);
|
|
||||||
dest.writeInt(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable creator
|
|
||||||
*/
|
|
||||||
public static final Parcelable.Creator<TaskDecoration> CREATOR = new Parcelable.Creator<TaskDecoration>() {
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public TaskDecoration createFromParcel(Parcel source) {
|
|
||||||
return new TaskDecoration((RemoteViews)source.readParcelable(
|
|
||||||
RemoteViews.class.getClassLoader()),
|
|
||||||
source.readInt(), source.readInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public TaskDecoration[] newArray(int size) {
|
|
||||||
return new TaskDecoration[size];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,296 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009, Todoroo Inc
|
|
||||||
* All Rights Reserved
|
|
||||||
* http://www.todoroo.com
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.data;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteDatabase.CursorFactory;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
|
||||||
import com.todoroo.andlib.service.ContextManager;
|
|
||||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AbstractDatabase is a database abstraction which wraps a SQLite database.
|
|
||||||
* <p>
|
|
||||||
* Users of this class are in charge of the database's lifecycle - ensuring that
|
|
||||||
* the database is open when needed and closed when usage is finished. Within an
|
|
||||||
* activity, this is typically accomplished through the onResume and onPause
|
|
||||||
* methods, though if the database is not needed for the activity's entire
|
|
||||||
* lifecycle, it can be closed earlier.
|
|
||||||
* <p>
|
|
||||||
* Direct querying is not recommended for type safety reasons. Instead, use one
|
|
||||||
* of the service classes to issue the request and return a {@link TodorooCursor}.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
abstract public class AbstractDatabase {
|
|
||||||
|
|
||||||
// --- abstract methods
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return database name
|
|
||||||
*/
|
|
||||||
protected abstract String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all tables in this database
|
|
||||||
*/
|
|
||||||
protected abstract Table[] getTables();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return database version
|
|
||||||
*/
|
|
||||||
protected abstract int getVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after database and tables are created. Use this method to
|
|
||||||
* create indices and perform other database maintenance
|
|
||||||
*/
|
|
||||||
protected abstract void onCreateTables();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upgrades an open database from one version to the next
|
|
||||||
* @param oldVersion
|
|
||||||
* @param newVersion
|
|
||||||
* @return true if upgrade was handled, false otherwise
|
|
||||||
*/
|
|
||||||
protected abstract boolean onUpgrade(int oldVersion, int newVersion);
|
|
||||||
|
|
||||||
// --- protected variables
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQLiteOpenHelper that takes care of database operations
|
|
||||||
*/
|
|
||||||
protected SQLiteOpenHelper helper = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal pointer to open database. Hides the fact that there is a
|
|
||||||
* database and a wrapper by making a single monolithic interface
|
|
||||||
*/
|
|
||||||
protected SQLiteDatabase database = null;
|
|
||||||
|
|
||||||
// --- internal implementation
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the name of the table containing these models
|
|
||||||
* @param modelType
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public final Table getTable(Class<? extends AbstractModel> modelType) {
|
|
||||||
for(Table table : getTables()) {
|
|
||||||
if(table.modelClass.equals(modelType))
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
throw new UnsupportedOperationException("Unknown model class " + modelType); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
|
|
||||||
protected synchronized final void initializeHelper() {
|
|
||||||
if(helper == null) {
|
|
||||||
if(ContextManager.getContext() == null)
|
|
||||||
throw new NullPointerException("Null context creating database helper");
|
|
||||||
helper = new DatabaseHelper(ContextManager.getContext(),
|
|
||||||
getName(), null, getVersion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the database for writing. Must be closed afterwards. If user is
|
|
||||||
* out of disk space, database may be opened for reading instead
|
|
||||||
*/
|
|
||||||
public synchronized final void openForWriting() {
|
|
||||||
initializeHelper();
|
|
||||||
|
|
||||||
if(database != null && !database.isReadOnly() && database.isOpen())
|
|
||||||
return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
database = helper.getWritableDatabase();
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
// don't know why this happens
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
} catch (final RuntimeException original) {
|
|
||||||
Log.e("database-" + getName(), "Error opening db",
|
|
||||||
original);
|
|
||||||
try {
|
|
||||||
// provide read-only database
|
|
||||||
openForReading();
|
|
||||||
} catch (Exception readException) {
|
|
||||||
// throw original write exception
|
|
||||||
throw original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the database for reading. Must be closed afterwards
|
|
||||||
*/
|
|
||||||
public synchronized final void openForReading() {
|
|
||||||
initializeHelper();
|
|
||||||
if(database != null && database.isOpen())
|
|
||||||
return;
|
|
||||||
database = helper.getReadableDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the database if it has been opened previously
|
|
||||||
*/
|
|
||||||
public synchronized final void close() {
|
|
||||||
if(database != null) {
|
|
||||||
database.close();
|
|
||||||
}
|
|
||||||
database = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all data in database. Warning: this does what it says. Any open
|
|
||||||
* database resources will be abruptly closed.
|
|
||||||
*/
|
|
||||||
public synchronized final void clear() {
|
|
||||||
close();
|
|
||||||
ContextManager.getContext().deleteDatabase(getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return sql database. opens database if not yet open
|
|
||||||
*/
|
|
||||||
public synchronized final SQLiteDatabase getDatabase() {
|
|
||||||
if(database == null) {
|
|
||||||
AndroidUtilities.sleepDeep(300L);
|
|
||||||
openForWriting();
|
|
||||||
}
|
|
||||||
return database;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return human-readable database name for debugging
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "DB:" + getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- database wrapper
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @see android.database.sqlite.SQLiteDatabase#rawQuery(String sql, String[] selectionArgs)
|
|
||||||
*/
|
|
||||||
public synchronized Cursor rawQuery(String sql, String[] selectionArgs) {
|
|
||||||
return getDatabase().rawQuery(sql, selectionArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @see android.database.sqlite.SQLiteDatabase#insert(String table, String nullColumnHack, ContentValues values)
|
|
||||||
*/
|
|
||||||
public synchronized long insert(String table, String nullColumnHack, ContentValues values) {
|
|
||||||
return getDatabase().insert(table, nullColumnHack, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @see android.database.sqlite.SQLiteDatabase#delete(String table, String whereClause, String[] whereArgs)
|
|
||||||
*/
|
|
||||||
public synchronized int delete(String table, String whereClause, String[] whereArgs) {
|
|
||||||
return getDatabase().delete(table, whereClause, whereArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @see android.database.sqlite.SQLiteDatabase#update(String table, ContentValues values, String whereClause, String[] whereArgs)
|
|
||||||
*/
|
|
||||||
public synchronized int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
|
|
||||||
return getDatabase().update(table, values, whereClause, whereArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helper classes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default implementation of Astrid database helper
|
|
||||||
*/
|
|
||||||
private class DatabaseHelper extends SQLiteOpenHelper {
|
|
||||||
|
|
||||||
public DatabaseHelper(Context context, String name,
|
|
||||||
CursorFactory factory, int version) {
|
|
||||||
super(context, name, factory, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to create the database tables
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public synchronized void onCreate(SQLiteDatabase db) {
|
|
||||||
StringBuilder sql = new StringBuilder();
|
|
||||||
SqlConstructorVisitor sqlVisitor = new SqlConstructorVisitor();
|
|
||||||
|
|
||||||
// create tables
|
|
||||||
for(Table table : getTables()) {
|
|
||||||
sql.append("CREATE TABLE IF NOT EXISTS ").append(table.name).append('(').
|
|
||||||
append(AbstractModel.ID_PROPERTY).append(" INTEGER PRIMARY KEY AUTOINCREMENT");
|
|
||||||
for(Property<?> property : table.getProperties()) {
|
|
||||||
if(AbstractModel.ID_PROPERTY.name.equals(property.name))
|
|
||||||
continue;
|
|
||||||
sql.append(',').append(property.accept(sqlVisitor, null));
|
|
||||||
}
|
|
||||||
sql.append(')');
|
|
||||||
db.execSQL(sql.toString());
|
|
||||||
sql.setLength(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// post-table-creation
|
|
||||||
database = db;
|
|
||||||
onCreateTables();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to upgrade the database to a new version
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
Log.w("database-" + getName(), String.format("Upgrading database from version %d to %d.",
|
|
||||||
oldVersion, newVersion));
|
|
||||||
|
|
||||||
database = db;
|
|
||||||
if(!AbstractDatabase.this.onUpgrade(oldVersion, newVersion)) {
|
|
||||||
// We don't know how to handle this case because someone forgot to
|
|
||||||
// implement the upgrade. We can't drop tables, we can only
|
|
||||||
// throw a nasty exception at this time
|
|
||||||
|
|
||||||
throw new IllegalStateException("Missing database migration " +
|
|
||||||
"from " + oldVersion + " to " + newVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visitor that returns SQL constructor for this property
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class SqlConstructorVisitor implements PropertyVisitor<String, Void> {
|
|
||||||
|
|
||||||
public String visitDouble(Property<Double> property, Void data) {
|
|
||||||
return String.format("%s REAL", property.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String visitInteger(Property<Integer> property, Void data) {
|
|
||||||
return String.format("%s INTEGER", property.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String visitLong(Property<Long> property, Void data) {
|
|
||||||
return String.format("%s INTEGER", property.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String visitString(Property<String> property, Void data) {
|
|
||||||
return String.format("%s TEXT", property.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,431 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009, Todoroo Inc
|
|
||||||
* All Rights Reserved
|
|
||||||
* http://www.todoroo.com
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.data;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.data.Property.DoubleProperty;
|
|
||||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
|
||||||
import com.todoroo.andlib.data.Property.LongProperty;
|
|
||||||
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <code>AbstractModel</code> represents a row in a database.
|
|
||||||
* <p>
|
|
||||||
* A single database can be represented by multiple <code>AbstractModel</code>s
|
|
||||||
* corresponding to different queries that return a different set of columns.
|
|
||||||
* Each model exposes a set of properties that it contains.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class AbstractModel implements Parcelable {
|
|
||||||
|
|
||||||
// --- static variables
|
|
||||||
|
|
||||||
private static final ContentValuesSavingVisitor saver = new ContentValuesSavingVisitor();
|
|
||||||
|
|
||||||
// --- constants
|
|
||||||
|
|
||||||
/** id property common to all models */
|
|
||||||
protected static final String ID_PROPERTY_NAME = "_id"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** id field common to all models */
|
|
||||||
public static final IntegerProperty ID_PROPERTY = new IntegerProperty(null, ID_PROPERTY_NAME);
|
|
||||||
|
|
||||||
/** sentinel for objects without an id */
|
|
||||||
public static final long NO_ID = 0;
|
|
||||||
|
|
||||||
// --- abstract methods
|
|
||||||
|
|
||||||
/** Get the default values for this object */
|
|
||||||
abstract public ContentValues getDefaultValues();
|
|
||||||
|
|
||||||
// --- data store variables and management
|
|
||||||
|
|
||||||
/* Data Source Ordering:
|
|
||||||
*
|
|
||||||
* In order to return the best data, we want to check first what the user
|
|
||||||
* has explicitly set (setValues), then the values we have read out of
|
|
||||||
* the database (values), then defaults (getDefaultValues)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** User set values */
|
|
||||||
protected ContentValues setValues = null;
|
|
||||||
|
|
||||||
/** Values from database */
|
|
||||||
protected ContentValues values = null;
|
|
||||||
|
|
||||||
/** Get database-read values for this object */
|
|
||||||
public ContentValues getDatabaseValues() {
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the user-set values for this object */
|
|
||||||
public ContentValues getSetValues() {
|
|
||||||
return setValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get a list of all field/value pairs merged across data sources */
|
|
||||||
public ContentValues getMergedValues() {
|
|
||||||
ContentValues mergedValues = new ContentValues();
|
|
||||||
|
|
||||||
ContentValues defaultValues = getDefaultValues();
|
|
||||||
if(defaultValues != null)
|
|
||||||
mergedValues.putAll(defaultValues);
|
|
||||||
if(values != null)
|
|
||||||
mergedValues.putAll(values);
|
|
||||||
if(setValues != null)
|
|
||||||
mergedValues.putAll(setValues);
|
|
||||||
|
|
||||||
return mergedValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all data on this model
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
values = null;
|
|
||||||
setValues = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transfers all set values into values. This occurs when a task is
|
|
||||||
* saved - future saves will not need to write all the data as before.
|
|
||||||
*/
|
|
||||||
public void markSaved() {
|
|
||||||
if(values == null)
|
|
||||||
values = setValues;
|
|
||||||
else if(setValues != null)
|
|
||||||
values.putAll(setValues);
|
|
||||||
setValues = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use merged values to compare two models to each other. Must be of
|
|
||||||
* exactly the same class.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
if(other == null || other.getClass() != getClass())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return getMergedValues().equals(((AbstractModel)other).getMergedValues());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getMergedValues().hashCode() ^ getClass().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- data retrieval
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads all properties from the supplied cursor and store
|
|
||||||
*/
|
|
||||||
protected synchronized void readPropertiesFromCursor(TodorooCursor<? extends AbstractModel> cursor) {
|
|
||||||
if (values == null)
|
|
||||||
values = new ContentValues();
|
|
||||||
|
|
||||||
// clears user-set values
|
|
||||||
setValues = null;
|
|
||||||
|
|
||||||
for (Property<?> property : cursor.getProperties()) {
|
|
||||||
saver.save(property, values, cursor.get(property));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the given property. Make sure this model has this property!
|
|
||||||
*/
|
|
||||||
public synchronized <TYPE> TYPE getValue(Property<TYPE> property) {
|
|
||||||
Object value;
|
|
||||||
if(setValues != null && setValues.containsKey(property.name))
|
|
||||||
value = setValues.get(property.name);
|
|
||||||
|
|
||||||
else if(values != null && values.containsKey(property.name))
|
|
||||||
value = values.get(property.name);
|
|
||||||
|
|
||||||
else if(getDefaultValues().containsKey(property.name))
|
|
||||||
value = getDefaultValues().get(property.name);
|
|
||||||
|
|
||||||
else
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Model Error: Did not read property " + property.name); //$NON-NLS-1$
|
|
||||||
|
|
||||||
// resolve properties that were retrieved with a different type than accessed
|
|
||||||
if(value instanceof String && property instanceof LongProperty)
|
|
||||||
return (TYPE) Long.valueOf((String)value);
|
|
||||||
else if(value instanceof String && property instanceof IntegerProperty)
|
|
||||||
return (TYPE) Integer.valueOf((String)value);
|
|
||||||
else if(value instanceof String && property instanceof DoubleProperty)
|
|
||||||
return (TYPE) Double.valueOf((String)value);
|
|
||||||
else if(value instanceof Integer && property instanceof LongProperty)
|
|
||||||
return (TYPE) Long.valueOf(((Number)value).longValue());
|
|
||||||
return (TYPE) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method to get the identifier of the model, if it exists.
|
|
||||||
*
|
|
||||||
* @return {@value #NO_ID} if this model was not added to the database
|
|
||||||
*/
|
|
||||||
abstract public long getId();
|
|
||||||
|
|
||||||
protected long getIdHelper(LongProperty id) {
|
|
||||||
if(setValues != null && setValues.containsKey(id.name))
|
|
||||||
return setValues.getAsLong(id.name);
|
|
||||||
else if(values != null && values.containsKey(id.name))
|
|
||||||
return values.getAsLong(id.name);
|
|
||||||
else
|
|
||||||
return NO_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(long id) {
|
|
||||||
if (setValues == null)
|
|
||||||
setValues = new ContentValues();
|
|
||||||
|
|
||||||
if(id == NO_ID)
|
|
||||||
setValues.remove(ID_PROPERTY_NAME);
|
|
||||||
else
|
|
||||||
setValues.put(ID_PROPERTY_NAME, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if this model has found Jesus (i.e. the database)
|
|
||||||
*/
|
|
||||||
public boolean isSaved() {
|
|
||||||
return getId() != NO_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param property
|
|
||||||
* @return true if setValues or values contains this property
|
|
||||||
*/
|
|
||||||
public boolean containsValue(Property<?> property) {
|
|
||||||
if(setValues != null && setValues.containsKey(property.name))
|
|
||||||
return true;
|
|
||||||
if(values != null && values.containsKey(property.name))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param property
|
|
||||||
* @return true if setValues or values contains this property, and the value
|
|
||||||
* stored is not null
|
|
||||||
*/
|
|
||||||
public boolean containsNonNullValue(Property<?> property) {
|
|
||||||
if(setValues != null && setValues.containsKey(property.name))
|
|
||||||
return setValues.get(property.name) != null;
|
|
||||||
if(values != null && values.containsKey(property.name))
|
|
||||||
return values.get(property.name) != null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- data storage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the user has changed this property value and it should be
|
|
||||||
* stored for saving in the database
|
|
||||||
*/
|
|
||||||
protected synchronized <TYPE> boolean shouldSaveValue(
|
|
||||||
Property<TYPE> property, TYPE newValue) {
|
|
||||||
|
|
||||||
// we've already decided to save it, so overwrite old value
|
|
||||||
if (setValues.containsKey(property.name))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// values contains this key, we should check it out
|
|
||||||
if(values != null && values.containsKey(property.name)) {
|
|
||||||
TYPE value = getValue(property);
|
|
||||||
if (value == null) {
|
|
||||||
if (newValue == null)
|
|
||||||
return false;
|
|
||||||
} else if (value.equals(newValue))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, good to save
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the given property. Make sure this model has this property!
|
|
||||||
*/
|
|
||||||
public synchronized <TYPE> void setValue(Property<TYPE> property,
|
|
||||||
TYPE value) {
|
|
||||||
if (setValues == null)
|
|
||||||
setValues = new ContentValues();
|
|
||||||
if (!shouldSaveValue(property, value))
|
|
||||||
return;
|
|
||||||
|
|
||||||
saver.save(property, setValues, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges content values with those coming from another source
|
|
||||||
*/
|
|
||||||
public synchronized <TYPE> void mergeWith(ContentValues other) {
|
|
||||||
if (setValues == null)
|
|
||||||
setValues = new ContentValues();
|
|
||||||
setValues.putAll(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the key for the given property
|
|
||||||
* @param property
|
|
||||||
*/
|
|
||||||
public synchronized void clearValue(Property<?> property) {
|
|
||||||
if(setValues != null && setValues.containsKey(property.name))
|
|
||||||
setValues.remove(property.name);
|
|
||||||
else if(values != null && values.containsKey(property.name))
|
|
||||||
values.remove(property.name);
|
|
||||||
else if(getDefaultValues().containsKey(property.name))
|
|
||||||
throw new IllegalArgumentException("Property has a default value"); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- property management
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks inside the given class and finds all declared properties
|
|
||||||
*/
|
|
||||||
protected static Property<?>[] generateProperties(Class<? extends AbstractModel> cls) {
|
|
||||||
ArrayList<Property<?>> properties = new ArrayList<Property<?>>();
|
|
||||||
if(cls.getSuperclass() != AbstractModel.class)
|
|
||||||
properties.addAll(Arrays.asList(generateProperties(
|
|
||||||
(Class<? extends AbstractModel>) cls.getSuperclass())));
|
|
||||||
|
|
||||||
// a property is public, static & extends Property
|
|
||||||
for(Field field : cls.getFields()) {
|
|
||||||
if((field.getModifiers() & Modifier.STATIC) == 0)
|
|
||||||
continue;
|
|
||||||
if(!Property.class.isAssignableFrom(field.getType()))
|
|
||||||
continue;
|
|
||||||
try {
|
|
||||||
properties.add((Property<?>) field.get(null));
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return properties.toArray(new Property<?>[properties.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visitor that saves a value into a content values store
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class ContentValuesSavingVisitor implements PropertyVisitor<Void, Object> {
|
|
||||||
|
|
||||||
private ContentValues store;
|
|
||||||
|
|
||||||
public synchronized void save(Property<?> property, ContentValues newStore, Object value) {
|
|
||||||
this.store = newStore;
|
|
||||||
|
|
||||||
// we don't allow null values, as they indicate unset properties
|
|
||||||
// when the database was written
|
|
||||||
|
|
||||||
if(value != null)
|
|
||||||
property.accept(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Void visitDouble(Property<Double> property, Object value) {
|
|
||||||
store.put(property.name, (Double) value);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Void visitInteger(Property<Integer> property, Object value) {
|
|
||||||
store.put(property.name, (Integer) value);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Void visitLong(Property<Long> property, Object value) {
|
|
||||||
store.put(property.name, (Long) value);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Void visitString(Property<String> property, Object value) {
|
|
||||||
store.put(property.name, (String) value);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- parcelable helpers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeParcelable(setValues, 0);
|
|
||||||
dest.writeParcelable(values, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In addition to overriding this class, model classes should create
|
|
||||||
* a static final variable named "CREATOR" in order to satisfy the
|
|
||||||
* requirements of the Parcelable interface.
|
|
||||||
*/
|
|
||||||
abstract protected Parcelable.Creator<? extends AbstractModel> getCreator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parcelable creator helper
|
|
||||||
*/
|
|
||||||
protected static final class ModelCreator<TYPE extends AbstractModel>
|
|
||||||
implements Parcelable.Creator<TYPE> {
|
|
||||||
|
|
||||||
private final Class<TYPE> cls;
|
|
||||||
|
|
||||||
public ModelCreator(Class<TYPE> cls) {
|
|
||||||
super();
|
|
||||||
this.cls = cls;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public TYPE createFromParcel(Parcel source) {
|
|
||||||
TYPE model;
|
|
||||||
try {
|
|
||||||
model = cls.newInstance();
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (InstantiationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
model.setValues = source.readParcelable(ContentValues.class.getClassLoader());
|
|
||||||
model.values = source.readParcelable(ContentValues.class.getClassLoader());
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
public TYPE[] newArray(int size) {
|
|
||||||
return (TYPE[]) Array.newInstance(cls, size);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,250 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009, Todoroo Inc
|
|
||||||
* All Rights Reserved
|
|
||||||
* http://www.todoroo.com
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.data;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.sql.Criterion;
|
|
||||||
import com.todoroo.andlib.sql.Query;
|
|
||||||
import com.todoroo.astrid.utility.Constants;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract data access object
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class GenericDao<TYPE extends AbstractModel> {
|
|
||||||
|
|
||||||
private final Class<TYPE> modelClass;
|
|
||||||
|
|
||||||
private Table table;
|
|
||||||
|
|
||||||
private AbstractDatabase database;
|
|
||||||
|
|
||||||
public GenericDao(Class<TYPE> modelClass) {
|
|
||||||
this.modelClass = modelClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GenericDao(Class<TYPE> modelClass, AbstractDatabase database) {
|
|
||||||
this.modelClass = modelClass;
|
|
||||||
setDatabase(database);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets table associated with this DAO */
|
|
||||||
public Table getTable() {
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets database accessed by this DAO. Used for dependency-injected
|
|
||||||
* initialization by child classes and unit tests
|
|
||||||
*
|
|
||||||
* @param database
|
|
||||||
*/
|
|
||||||
public void setDatabase(AbstractDatabase database) {
|
|
||||||
if(database == this.database)
|
|
||||||
return;
|
|
||||||
this.database = database;
|
|
||||||
table = database.getTable(modelClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- dao methods
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a query with SQL DSL objects
|
|
||||||
*
|
|
||||||
* @param query
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public TodorooCursor<TYPE> query(Query query) {
|
|
||||||
query.from(table);
|
|
||||||
if(Constants.DEBUG)
|
|
||||||
Log.i("SQL-" + modelClass.getSimpleName(), query.toString()); //$NON-NLS-1$
|
|
||||||
Cursor cursor = database.rawQuery(query.toString(), null);
|
|
||||||
return new TodorooCursor<TYPE>(cursor, query.getFields());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a query with raw SQL
|
|
||||||
*
|
|
||||||
* @param properties
|
|
||||||
* @param selection
|
|
||||||
* @param selectionArgs
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public TodorooCursor<TYPE> rawQuery(String selection, String[] selectionArgs, Property<?>... properties) {
|
|
||||||
String[] fields = new String[properties.length];
|
|
||||||
for(int i = 0; i < properties.length; i++)
|
|
||||||
fields[i] = properties[i].name;
|
|
||||||
return new TodorooCursor<TYPE>(database.getDatabase().query(table.name,
|
|
||||||
fields, selection, selectionArgs, null, null, null),
|
|
||||||
properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns object corresponding to the given identifier
|
|
||||||
*
|
|
||||||
* @param database
|
|
||||||
* @param table
|
|
||||||
* name of table
|
|
||||||
* @param properties
|
|
||||||
* properties to read
|
|
||||||
* @param id
|
|
||||||
* id of item
|
|
||||||
* @return null if no item found
|
|
||||||
*/
|
|
||||||
public TYPE fetch(long id, Property<?>... properties) {
|
|
||||||
TodorooCursor<TYPE> cursor = fetchItem(id, properties);
|
|
||||||
try {
|
|
||||||
if (cursor.getCount() == 0)
|
|
||||||
return null;
|
|
||||||
Constructor<TYPE> constructor = modelClass.getConstructor(TodorooCursor.class);
|
|
||||||
return constructor.newInstance(cursor);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (InstantiationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the given id
|
|
||||||
*
|
|
||||||
* @param database
|
|
||||||
* @param id
|
|
||||||
* @return true if delete was successful
|
|
||||||
*/
|
|
||||||
public boolean delete(long id) {
|
|
||||||
return database.delete(table.name,
|
|
||||||
AbstractModel.ID_PROPERTY.eq(id).toString(), null) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete all matching a clause
|
|
||||||
* @param database
|
|
||||||
* @param where
|
|
||||||
* @return # of deleted items
|
|
||||||
*/
|
|
||||||
public int deleteWhere(Criterion where) {
|
|
||||||
return database.delete(table.name,
|
|
||||||
where.toString(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the given object to the database. Creates a new object if
|
|
||||||
* model id property has not been set
|
|
||||||
*
|
|
||||||
* @return true on success.
|
|
||||||
*/
|
|
||||||
public boolean persist(TYPE item) {
|
|
||||||
if (item.getId() == AbstractModel.NO_ID) {
|
|
||||||
return createNew(item);
|
|
||||||
} else {
|
|
||||||
ContentValues values = item.getSetValues();
|
|
||||||
|
|
||||||
if (values.size() == 0) // nothing changed
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return saveExisting(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the given item.
|
|
||||||
*
|
|
||||||
* @param database
|
|
||||||
* @param table
|
|
||||||
* table name
|
|
||||||
* @param item
|
|
||||||
* item model
|
|
||||||
* @return returns true on success.
|
|
||||||
*/
|
|
||||||
public boolean createNew(TYPE item) {
|
|
||||||
long newRow = database.insert(table.name,
|
|
||||||
AbstractModel.ID_PROPERTY.name, item.getMergedValues());
|
|
||||||
boolean result = newRow >= 0;
|
|
||||||
if(result) {
|
|
||||||
item.markSaved();
|
|
||||||
item.setId(newRow);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the given item. Will not create a new item!
|
|
||||||
*
|
|
||||||
* @param database
|
|
||||||
* @param table
|
|
||||||
* table name
|
|
||||||
* @param item
|
|
||||||
* item model
|
|
||||||
* @return returns true on success.
|
|
||||||
*/
|
|
||||||
public boolean saveExisting(TYPE item) {
|
|
||||||
ContentValues values = item.getSetValues();
|
|
||||||
if(values == null || values.size() == 0) // nothing changed
|
|
||||||
return true;
|
|
||||||
boolean result = database.update(table.name, values,
|
|
||||||
AbstractModel.ID_PROPERTY.eq(item.getId()).toString(), null) > 0;
|
|
||||||
if(result)
|
|
||||||
item.markSaved();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates multiple rows of the database based on model set values
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
* item model
|
|
||||||
* @param criterion
|
|
||||||
* @return returns true on success.
|
|
||||||
*/
|
|
||||||
public int updateMultiple(ContentValues values, Criterion criterion) {
|
|
||||||
if(values.size() == 0) // nothing changed
|
|
||||||
return 0;
|
|
||||||
return database.update(table.name, values, criterion.toString(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helper methods
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns cursor to object corresponding to the given identifier
|
|
||||||
*
|
|
||||||
* @param database
|
|
||||||
* @param table
|
|
||||||
* name of table
|
|
||||||
* @param properties
|
|
||||||
* properties to read
|
|
||||||
* @param id
|
|
||||||
* id of item
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected TodorooCursor<TYPE> fetchItem(long id, Property<?>... properties) {
|
|
||||||
TodorooCursor<TYPE> cursor = query(
|
|
||||||
Query.select(properties).where(AbstractModel.ID_PROPERTY.eq(id)));
|
|
||||||
cursor.moveToFirst();
|
|
||||||
return new TodorooCursor<TYPE>(cursor, properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,207 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009, Todoroo Inc
|
|
||||||
* All Rights Reserved
|
|
||||||
* http://www.todoroo.com
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.data;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.sql.Field;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Property represents a typed column in a database.
|
|
||||||
*
|
|
||||||
* Within a given database row, the parameter may not exist, in which case the
|
|
||||||
* value is null, it may be of an incorrect type, in which case an exception is
|
|
||||||
* thrown, or the correct type, in which case the value is returned.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
* @param <TYPE>
|
|
||||||
* a database supported type, such as String or Integer
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public abstract class Property<TYPE> extends Field implements Cloneable {
|
|
||||||
|
|
||||||
// --- implementation
|
|
||||||
|
|
||||||
/** The database table name this property */
|
|
||||||
public final Table table;
|
|
||||||
|
|
||||||
/** The database column name for this property */
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a property by table and column name. Uses the default property
|
|
||||||
* expression which is derived from default table name
|
|
||||||
*/
|
|
||||||
protected Property(Table table, String columnName) {
|
|
||||||
this(table, columnName, (table == null) ? (columnName) : (table.name + "." + columnName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a property by table and column name, manually specifying an
|
|
||||||
* expression to use in SQL
|
|
||||||
*/
|
|
||||||
protected Property(Table table, String columnName, String expression) {
|
|
||||||
super(expression);
|
|
||||||
this.table = table;
|
|
||||||
this.name = columnName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept a visitor
|
|
||||||
*/
|
|
||||||
abstract public <RETURN, PARAMETER> RETURN accept(
|
|
||||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a clone of this property
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Property<TYPE> clone() {
|
|
||||||
try {
|
|
||||||
return (Property<TYPE>) super.clone();
|
|
||||||
} catch (CloneNotSupportedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helper classes and interfaces
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visitor interface for property classes
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface PropertyVisitor<RETURN, PARAMETER> {
|
|
||||||
public RETURN visitInteger(Property<Integer> property, PARAMETER data);
|
|
||||||
|
|
||||||
public RETURN visitLong(Property<Long> property, PARAMETER data);
|
|
||||||
|
|
||||||
public RETURN visitDouble(Property<Double> property, PARAMETER data);
|
|
||||||
|
|
||||||
public RETURN visitString(Property<String> property, PARAMETER data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- children
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integer property type. See {@link Property}
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class IntegerProperty extends Property<Integer> {
|
|
||||||
|
|
||||||
public IntegerProperty(Table table, String name) {
|
|
||||||
super(table, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IntegerProperty(Table table, String name, String expression) {
|
|
||||||
super(table, name, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RETURN, PARAMETER> RETURN accept(
|
|
||||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
|
||||||
return visitor.visitInteger(this, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String property type. See {@link Property}
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class StringProperty extends Property<String> {
|
|
||||||
|
|
||||||
public StringProperty(Table table, String name) {
|
|
||||||
super(table, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected StringProperty(Table table, String name, String expression) {
|
|
||||||
super(table, name, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RETURN, PARAMETER> RETURN accept(
|
|
||||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
|
||||||
return visitor.visitString(this, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Double property type. See {@link Property}
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class DoubleProperty extends Property<Double> {
|
|
||||||
|
|
||||||
public DoubleProperty(Table table, String name) {
|
|
||||||
super(table, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DoubleProperty(Table table, String name, String expression) {
|
|
||||||
super(table, name, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RETURN, PARAMETER> RETURN accept(
|
|
||||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
|
||||||
return visitor.visitDouble(this, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Long property type. See {@link Property}
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class LongProperty extends Property<Long> {
|
|
||||||
|
|
||||||
public LongProperty(Table table, String name) {
|
|
||||||
super(table, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected LongProperty(Table table, String name, String expression) {
|
|
||||||
super(table, name, expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RETURN, PARAMETER> RETURN accept(
|
|
||||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
|
||||||
return visitor.visitLong(this, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- pseudo-properties
|
|
||||||
|
|
||||||
/** Runs a SQL function and returns the result as a string */
|
|
||||||
public static class StringFunctionProperty extends StringProperty {
|
|
||||||
public StringFunctionProperty(String function, String columnName) {
|
|
||||||
super(null, columnName, function);
|
|
||||||
alias = columnName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Runs a SQL function and returns the result as a string */
|
|
||||||
public static class IntegerFunctionProperty extends IntegerProperty {
|
|
||||||
public IntegerFunctionProperty(String function, String columnName) {
|
|
||||||
super(null, columnName, function);
|
|
||||||
alias = columnName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Counting in aggregated tables. Returns the result of COUNT(1) */
|
|
||||||
public static final class CountProperty extends IntegerFunctionProperty {
|
|
||||||
public CountProperty() {
|
|
||||||
super("COUNT(1)", "count");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
package com.todoroo.andlib.data;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.sql.Field;
|
|
||||||
import com.todoroo.andlib.sql.SqlTable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Table class. Most fields are final, so methods such as <code>as</code> will
|
|
||||||
* clone the table when it returns.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class Table extends SqlTable {
|
|
||||||
public final String name;
|
|
||||||
public final Class<? extends AbstractModel> modelClass;
|
|
||||||
|
|
||||||
public Table(String name, Class<? extends AbstractModel> modelClass) {
|
|
||||||
this(name, modelClass, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Table(String name, Class<? extends AbstractModel> modelClass, String alias) {
|
|
||||||
super(name);
|
|
||||||
this.name = name;
|
|
||||||
this.alias = alias;
|
|
||||||
this.modelClass = modelClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a list of properties from model class by reflection
|
|
||||||
* @return property array
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public Property<?>[] getProperties() {
|
|
||||||
try {
|
|
||||||
return (Property<?>[])modelClass.getField("PROPERTIES").get(null);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- for sql-dsl
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new join table based on this table, but with an alias
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Table as(String newAlias) {
|
|
||||||
return new Table(name, modelClass, newAlias);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a field object based on the given property
|
|
||||||
* @param property
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public Field field(Property<?> property) {
|
|
||||||
if(alias != null)
|
|
||||||
return Field.field(alias + "." + property.name);
|
|
||||||
return Field.field(name + "." + property.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.data;
|
|
||||||
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.CursorWrapper;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AstridCursor wraps a cursor and allows users to query for individual
|
|
||||||
* {@link Property} types or read an entire {@link AbstractModel} from
|
|
||||||
* a database row.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
* @param <TYPE> a model type that is returned by this cursor
|
|
||||||
*/
|
|
||||||
public class TodorooCursor<TYPE extends AbstractModel> extends CursorWrapper {
|
|
||||||
|
|
||||||
/** Properties read by this cursor */
|
|
||||||
private final Property<?>[] properties;
|
|
||||||
|
|
||||||
/** Weakly cache field name to column id references for this cursor.
|
|
||||||
* Because it's a weak hash map, entire keys can be discarded by GC */
|
|
||||||
private final WeakHashMap<String, Integer> columnIndexCache;
|
|
||||||
|
|
||||||
/** Property reading visitor */
|
|
||||||
private static final CursorReadingVisitor reader = new CursorReadingVisitor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an <code>AstridCursor</code> from the supplied {@link Cursor}
|
|
||||||
* object.
|
|
||||||
*
|
|
||||||
* @param cursor
|
|
||||||
* @param properties properties read from this cursor
|
|
||||||
*/
|
|
||||||
public TodorooCursor(Cursor cursor, Property<?>[] properties) {
|
|
||||||
super(cursor);
|
|
||||||
|
|
||||||
this.properties = properties;
|
|
||||||
columnIndexCache = new WeakHashMap<String, Integer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value for the given property on the underlying {@link Cursor}
|
|
||||||
*
|
|
||||||
* @param <PROPERTY_TYPE> type to return
|
|
||||||
* @param property to retrieve
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public <PROPERTY_TYPE> PROPERTY_TYPE get(Property<PROPERTY_TYPE> property) {
|
|
||||||
return (PROPERTY_TYPE)property.accept(reader, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets entire property list
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Property<?>[] getProperties() {
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use cache to get the column index for the given field name
|
|
||||||
*/
|
|
||||||
public synchronized int getColumnIndexFromCache(String field) {
|
|
||||||
Integer index = columnIndexCache.get(field);
|
|
||||||
if(index == null) {
|
|
||||||
index = getColumnIndexOrThrow(field);
|
|
||||||
columnIndexCache.put(field, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visitor that reads the given property from a cursor
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class CursorReadingVisitor implements PropertyVisitor<Object, TodorooCursor<?>> {
|
|
||||||
|
|
||||||
public Object visitDouble(Property<Double> property,
|
|
||||||
TodorooCursor<?> cursor) {
|
|
||||||
return cursor.getDouble(cursor.getColumnIndexFromCache(property.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object visitInteger(Property<Integer> property,
|
|
||||||
TodorooCursor<?> cursor) {
|
|
||||||
return cursor.getInt(cursor.getColumnIndexFromCache(property.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object visitLong(Property<Long> property, TodorooCursor<?> cursor) {
|
|
||||||
return cursor.getLong(cursor.getColumnIndexFromCache(property.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object visitString(Property<String> property,
|
|
||||||
TodorooCursor<?> cursor) {
|
|
||||||
return cursor.getString(cursor.getColumnIndexFromCache(property.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Dependency Injector knows how to inject certain dependencies based
|
|
||||||
* on the field that is passed in.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface AbstractDependencyInjector {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the injected object for this field. If implementing class does not
|
|
||||||
* know how to handle this dependency, it should return null
|
|
||||||
*
|
|
||||||
* @param object
|
|
||||||
* object to perform dependency injection on
|
|
||||||
* @param field
|
|
||||||
* field tagged with {link Autowired} annotation
|
|
||||||
* @return object to assign to this field, or null
|
|
||||||
*/
|
|
||||||
abstract Object getInjection(Object object, Field field);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Autowired is an annotation that tells the dependency injector to
|
|
||||||
* set up the class as appropriate.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Target({ElementType.FIELD})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface Autowired {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton class to manage current application context
|
|
||||||
* b
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public final class ContextManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global application context
|
|
||||||
*/
|
|
||||||
private static Context context = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the global context
|
|
||||||
* @param context
|
|
||||||
*/
|
|
||||||
public static void setContext(Context context) {
|
|
||||||
ContextManager.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the global context
|
|
||||||
*/
|
|
||||||
public static Context getContext() {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method to read a string from the resources
|
|
||||||
*
|
|
||||||
* @param resid
|
|
||||||
* @param parameters
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getString(int resId, Object... formatArgs) {
|
|
||||||
return context.getString(resId, formatArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method to read resources
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static Resources getResources() {
|
|
||||||
return context.getResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.todoroo.astrid.utility.Constants;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple Dependency Injection Service for Android.
|
|
||||||
* <p>
|
|
||||||
* Add dependency injectors to the injector chain, then invoke this method
|
|
||||||
* against classes you wish to perform dependency injection for.
|
|
||||||
* <p>
|
|
||||||
* All errors encountered are handled as warnings, so if dependency injection
|
|
||||||
* seems to be failing, check the logs for more information.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class DependencyInjectionService {
|
|
||||||
|
|
||||||
private static final String QUALIFIED_PACKAGE = "com.t"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dependency injectors. Use getters and setters to modify this list
|
|
||||||
*/
|
|
||||||
private AbstractDependencyInjector[] injectors = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform dependency injection in the caller object
|
|
||||||
*
|
|
||||||
* @param caller
|
|
||||||
* object to perform DI on
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public void inject(Object caller) {
|
|
||||||
|
|
||||||
// Traverse through class and all parent classes, looking for
|
|
||||||
// fields declared with the @Autowired annotation and using
|
|
||||||
// dependency injection to set them as appropriate
|
|
||||||
|
|
||||||
Class<?> cls = caller.getClass();
|
|
||||||
while(cls != null) {
|
|
||||||
String packageName = cls.getPackage().getName();
|
|
||||||
if(!packageName.startsWith(QUALIFIED_PACKAGE))
|
|
||||||
break;
|
|
||||||
|
|
||||||
for(Field field : cls.getDeclaredFields()) {
|
|
||||||
if(field.getAnnotation(Autowired.class) != null) {
|
|
||||||
field.setAccessible(true);
|
|
||||||
try {
|
|
||||||
handleField(caller, field);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
throw new RuntimeException(String.format("Unable to set field '%s' of type '%s'",
|
|
||||||
field.getName(), field.getType()), e);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new RuntimeException(String.format("Unable to set field '%s' of type '%s'",
|
|
||||||
field.getName(), field.getType()), e);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(String.format("Unable to set field '%s' of type '%s'",
|
|
||||||
field.getName(), field.getType()), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cls = cls.getSuperclass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the appropriate dependency object based on the type
|
|
||||||
* that this autowired field accepts
|
|
||||||
*
|
|
||||||
* @param caller
|
|
||||||
* calling object
|
|
||||||
* @param field
|
|
||||||
* field to inject
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
private synchronized void handleField(Object caller, Field field)
|
|
||||||
throws IllegalStateException, IllegalArgumentException,
|
|
||||||
IllegalAccessException {
|
|
||||||
|
|
||||||
if(field.getType().isPrimitive())
|
|
||||||
throw new IllegalStateException(String.format(
|
|
||||||
"Tried to dependency-inject primative field '%s' of type '%s'",
|
|
||||||
field.getName(), field.getType()));
|
|
||||||
|
|
||||||
// field has already been processed, ignore
|
|
||||||
if (field.get(caller) != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (AbstractDependencyInjector injector : injectors) {
|
|
||||||
Object injection = injector.getInjection(caller, field);
|
|
||||||
if (injection != null) {
|
|
||||||
if(Constants.DEBUG)
|
|
||||||
Log.d("INJECTOR", injector + ":" + caller + "." + field.getName() + " => " + injection);
|
|
||||||
field.set(caller, injection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format("No dependency injector found for autowired " +
|
|
||||||
"field '%s' in class '%s'. Injectors: %s",
|
|
||||||
field.getName(), caller.getClass().getName(),
|
|
||||||
Arrays.asList(getInjectors())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- static methods
|
|
||||||
|
|
||||||
private static DependencyInjectionService instance = null;
|
|
||||||
|
|
||||||
DependencyInjectionService() {
|
|
||||||
// prevent instantiation
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the singleton instance of the dependency injection service.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public synchronized static DependencyInjectionService getInstance() {
|
|
||||||
if(instance == null)
|
|
||||||
instance = new DependencyInjectionService();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the array of installed injectors
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public synchronized AbstractDependencyInjector[] getInjectors() {
|
|
||||||
return injectors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the array of installed injectors
|
|
||||||
* @param injectors
|
|
||||||
*/
|
|
||||||
public synchronized void setInjectors(AbstractDependencyInjector[] injectors) {
|
|
||||||
this.injectors = injectors;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception handling utility class - reports and logs errors
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ExceptionService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ErrorReporter[] errorReporters;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer errorDialogTitleResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer errorDialogBodyGeneric;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer errorDialogBodyNullError;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer errorDialogBodySocketTimeout;
|
|
||||||
|
|
||||||
public ExceptionService() {
|
|
||||||
DependencyInjectionService.getInstance().inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report the error via registered error handlers
|
|
||||||
*
|
|
||||||
* @param name Internal error name. Not displayed to user
|
|
||||||
* @param error Exception encountered. Message will be displayed to user
|
|
||||||
*/
|
|
||||||
public void reportError(String name, Throwable error) {
|
|
||||||
if(errorReporters == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for(ErrorReporter reporter : errorReporters)
|
|
||||||
reporter.handleError(name, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display error dialog if context is activity and report error
|
|
||||||
*
|
|
||||||
* @param context Application Context
|
|
||||||
* @param name Internal error name. Not displayed to user
|
|
||||||
* @param error Exception encountered. Message will be displayed to user
|
|
||||||
*/
|
|
||||||
public void displayAndReportError(final Context context, String name, Throwable error) {
|
|
||||||
if(context instanceof Activity) {
|
|
||||||
final String messageToDisplay;
|
|
||||||
|
|
||||||
// pretty up the message when displaying to user
|
|
||||||
if(error == null)
|
|
||||||
messageToDisplay = context.getString(errorDialogBodyNullError);
|
|
||||||
else if(error instanceof SocketTimeoutException)
|
|
||||||
messageToDisplay = context.getString(errorDialogBodySocketTimeout);
|
|
||||||
else
|
|
||||||
messageToDisplay = context.getString(errorDialogBodyGeneric, error);
|
|
||||||
|
|
||||||
((Activity)context).runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
new AlertDialog.Builder(context)
|
|
||||||
.setTitle(errorDialogTitleResource)
|
|
||||||
.setMessage(messageToDisplay)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// suppress errors during dialog creation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reportError(name, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error reporter interface
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface ErrorReporter {
|
|
||||||
public void handleError(String name, Throwable error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AndroidLogReporter reports errors to LogCat
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class AndroidLogReporter implements ErrorReporter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report the error to the logs
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param error
|
|
||||||
*/
|
|
||||||
public void handleError(String name, Throwable error) {
|
|
||||||
String tag = null;
|
|
||||||
if(ContextManager.getContext() != null) {
|
|
||||||
PackageManager pm = ContextManager.getContext().getPackageManager();
|
|
||||||
try {
|
|
||||||
String appName = pm.getApplicationInfo(ContextManager.getContext().
|
|
||||||
getPackageName(), 0).name;
|
|
||||||
tag = appName + "-" + name; //$NON-NLS-1$
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
// give up
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(tag == null)
|
|
||||||
tag = "unknown-" + name; //$NON-NLS-1$
|
|
||||||
|
|
||||||
if(error == null)
|
|
||||||
Log.e(tag, "Exception: " + name); //$NON-NLS-1$
|
|
||||||
else
|
|
||||||
Log.e(tag, error.toString(), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uncaught exception handler uses the exception utilities class to
|
|
||||||
* report errors
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class TodorooUncaughtExceptionHandler implements UncaughtExceptionHandler {
|
|
||||||
private final UncaughtExceptionHandler defaultUEH;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected ExceptionService exceptionService;
|
|
||||||
|
|
||||||
public TodorooUncaughtExceptionHandler() {
|
|
||||||
defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
|
|
||||||
DependencyInjectionService.getInstance().inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void uncaughtException(Thread thread, Throwable ex) {
|
|
||||||
if(exceptionService != null)
|
|
||||||
exceptionService.reportError("uncaught", ex); //$NON-NLS-1$
|
|
||||||
defaultUEH.uncaughtException(thread, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class HttpErrorException extends IOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 5373340422464657279L;
|
|
||||||
|
|
||||||
public HttpErrorException(int code, String message) {
|
|
||||||
super(String.format("%d %s", code, message)); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.entity.StringEntity;
|
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
|
||||||
import org.apache.http.params.BasicHttpParams;
|
|
||||||
import org.apache.http.params.HttpConnectionParams;
|
|
||||||
import org.apache.http.params.HttpParams;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RestClient allows Android to consume web requests.
|
|
||||||
* <p>
|
|
||||||
* Portions by Praeda:
|
|
||||||
* http://senior.ceng.metu.edu.tr/2009/praeda/2009/01/11/a-simple
|
|
||||||
* -restful-client-at-android/
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class HttpRestClient implements RestClient {
|
|
||||||
|
|
||||||
private static final int HTTP_UNAVAILABLE_END = 599;
|
|
||||||
private static final int HTTP_UNAVAILABLE_START = 500;
|
|
||||||
private static final int HTTP_OK = 200;
|
|
||||||
|
|
||||||
private static final int TIMEOUT_MILLIS = 30000;
|
|
||||||
|
|
||||||
private static WeakReference<HttpClient> httpClient = null;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private Boolean debug;
|
|
||||||
|
|
||||||
public HttpRestClient() {
|
|
||||||
DependencyInjectionService.getInstance().inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String convertStreamToString(InputStream is) {
|
|
||||||
/*
|
|
||||||
* To convert the InputStream to String we use the
|
|
||||||
* BufferedReader.readLine() method. We iterate until the BufferedReader
|
|
||||||
* return null which means there's no more data to read. Each line will
|
|
||||||
* appended to a StringBuilder and returned as String.
|
|
||||||
*/
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 16384);
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
String line = null;
|
|
||||||
try {
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
sb.append(line + "\n"); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized static HttpClient getClient() {
|
|
||||||
if (httpClient == null || httpClient.get() == null) {
|
|
||||||
HttpParams params = new BasicHttpParams();
|
|
||||||
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_MILLIS);
|
|
||||||
HttpConnectionParams.setSoTimeout(params, TIMEOUT_MILLIS);
|
|
||||||
HttpClient client = new DefaultHttpClient(params);
|
|
||||||
httpClient = new WeakReference<HttpClient>(client);
|
|
||||||
return client;
|
|
||||||
} else {
|
|
||||||
return httpClient.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String processHttpResponse(HttpResponse response) throws IOException {
|
|
||||||
int statusCode = response.getStatusLine().getStatusCode();
|
|
||||||
if(statusCode >= HTTP_UNAVAILABLE_START && statusCode <= HTTP_UNAVAILABLE_END) {
|
|
||||||
throw new HttpUnavailableException();
|
|
||||||
} else if(statusCode != HTTP_OK) {
|
|
||||||
throw new HttpErrorException(response.getStatusLine().getStatusCode(),
|
|
||||||
response.getStatusLine().getReasonPhrase());
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpEntity entity = response.getEntity();
|
|
||||||
|
|
||||||
if (entity != null) {
|
|
||||||
InputStream contentStream = entity.getContent();
|
|
||||||
try {
|
|
||||||
return convertStreamToString(contentStream);
|
|
||||||
} finally {
|
|
||||||
contentStream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Issue an HTTP GET for the given URL, return the response
|
|
||||||
*
|
|
||||||
* @param url url with url-encoded params
|
|
||||||
* @return response, or null if there was no response
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public synchronized String get(String url) throws IOException {
|
|
||||||
if(debug)
|
|
||||||
Log.d("http-rest-client-get", url); //$NON-NLS-1$
|
|
||||||
|
|
||||||
try {
|
|
||||||
HttpGet httpGet = new HttpGet(url);
|
|
||||||
HttpResponse response = getClient().execute(httpGet);
|
|
||||||
|
|
||||||
return processHttpResponse(response);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
IOException ioException = new IOException(e.getMessage());
|
|
||||||
ioException.initCause(e);
|
|
||||||
throw ioException;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Issue an HTTP POST for the given URL, return the response
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @param data
|
|
||||||
* url-encoded data
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public synchronized String post(String url, String data) throws IOException {
|
|
||||||
if(debug)
|
|
||||||
Log.d("http-rest-client-post", url + " | " + data); //$NON-NLS-1$ //$NON-NLS-2$
|
|
||||||
|
|
||||||
try {
|
|
||||||
HttpPost httpPost = new HttpPost(url);
|
|
||||||
httpPost.setEntity(new StringEntity(data));
|
|
||||||
HttpResponse response = getClient().execute(httpPost);
|
|
||||||
|
|
||||||
return processHttpResponse(response);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
IOException ioException = new IOException(e.getMessage());
|
|
||||||
ioException.initCause(e);
|
|
||||||
throw ioException;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception displayed when a 500 error is received on an HTTP invocation
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class HttpUnavailableException extends IOException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 5373340422464657279L;
|
|
||||||
|
|
||||||
public HttpUnavailableException() {
|
|
||||||
super();
|
|
||||||
DependencyInjectionService.getInstance().inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return "Sorry, our servers are experiencing some issues. Please try again later!"; //$NON-NLS-1$ // FIXME
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notification Manager stub
|
|
||||||
*
|
|
||||||
* @author timsu
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface NotificationManager {
|
|
||||||
|
|
||||||
public void cancel(int id);
|
|
||||||
|
|
||||||
public void cancelAll();
|
|
||||||
|
|
||||||
public void notify(int id, Notification notification);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiation of notification manager that passes through to
|
|
||||||
* Android's notification manager
|
|
||||||
*
|
|
||||||
* @author timsu
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class AndroidNotificationManager implements NotificationManager {
|
|
||||||
private final android.app.NotificationManager nm;
|
|
||||||
public AndroidNotificationManager(Context context) {
|
|
||||||
nm = (android.app.NotificationManager)
|
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel(int id) {
|
|
||||||
nm.cancel(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancelAll() {
|
|
||||||
nm.cancelAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void notify(int id, Notification notification) {
|
|
||||||
nm.notify(id, notification);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package com.todoroo.andlib.service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RestClient stub invokes the HTML requests as desired
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface RestClient {
|
|
||||||
public String get(String url) throws IOException;
|
|
||||||
public String post(String url, String data) throws IOException;
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.AND;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.EXISTS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.LEFT_PARENTHESIS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.NOT;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.OR;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.RIGHT_PARENTHESIS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
|
|
||||||
public abstract class Criterion {
|
|
||||||
protected final Operator operator;
|
|
||||||
|
|
||||||
Criterion(Operator operator) {
|
|
||||||
this.operator = operator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion all = new Criterion(Operator.exists) {
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Criterion none = new Criterion(Operator.exists) {
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Criterion and(final Criterion criterion, final Criterion... criterions) {
|
|
||||||
return new Criterion(Operator.and) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(criterion);
|
|
||||||
for (Criterion c : criterions) {
|
|
||||||
sb.append(SPACE).append(AND).append(SPACE).append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion or(final Criterion criterion, final Criterion... criterions) {
|
|
||||||
return new Criterion(Operator.or) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(criterion);
|
|
||||||
for (Criterion c : criterions) {
|
|
||||||
sb.append(SPACE).append(OR).append(SPACE).append(c.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion exists(final Query query) {
|
|
||||||
return new Criterion(Operator.exists) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(EXISTS).append(SPACE).append(LEFT_PARENTHESIS).append(query).append(RIGHT_PARENTHESIS);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion not(final Criterion criterion) {
|
|
||||||
return new Criterion(Operator.not) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(NOT).append(SPACE);
|
|
||||||
criterion.populate(sb);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void populate(StringBuilder sb);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder(LEFT_PARENTHESIS);
|
|
||||||
populate(builder);
|
|
||||||
builder.append(RIGHT_PARENTHESIS);
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.AS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
|
|
||||||
public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
|
|
||||||
protected String alias;
|
|
||||||
protected final String expression;
|
|
||||||
|
|
||||||
protected DBObject(String expression){
|
|
||||||
this.expression = expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T as(String newAlias) {
|
|
||||||
try {
|
|
||||||
T clone = (T) clone();
|
|
||||||
clone.alias = newAlias;
|
|
||||||
return clone;
|
|
||||||
} catch (CloneNotSupportedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasAlias() {
|
|
||||||
return alias != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
DBObject<?> dbObject = (DBObject<?>) o;
|
|
||||||
|
|
||||||
if (alias != null ? !alias.equals(dbObject.alias) : dbObject.alias != null) return false;
|
|
||||||
if (expression != null ? !expression.equals(dbObject.expression) : dbObject.expression != null) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = alias != null ? alias.hashCode() : 0;
|
|
||||||
result = 31 * result + (expression != null ? expression.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() {
|
|
||||||
if (hasAlias()) {
|
|
||||||
return alias;
|
|
||||||
}
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String toStringInSelect() {
|
|
||||||
StringBuilder sb = new StringBuilder(expression);
|
|
||||||
if (hasAlias()) {
|
|
||||||
sb.append(SPACE).append(AS).append(SPACE).append(alias);
|
|
||||||
} else {
|
|
||||||
int pos = expression.indexOf('.');
|
|
||||||
if(pos > 0)
|
|
||||||
sb.append(SPACE).append(AS).append(SPACE).append(expression.substring(pos + 1));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
public class EqCriterion extends UnaryCriterion {
|
|
||||||
EqCriterion(Field field, Object value) {
|
|
||||||
super(field, Operator.eq, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.AND;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.BETWEEN;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.COMMA;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.LEFT_PARENTHESIS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.RIGHT_PARENTHESIS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
|
|
||||||
public class Field extends DBObject<Field> {
|
|
||||||
|
|
||||||
protected Field(String expression) {
|
|
||||||
super(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Field field(String expression) {
|
|
||||||
return new Field(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion eq(Object value) {
|
|
||||||
if(value == null)
|
|
||||||
return UnaryCriterion.isNull(this);
|
|
||||||
return UnaryCriterion.eq(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion neq(Object value) {
|
|
||||||
if(value == null)
|
|
||||||
return UnaryCriterion.isNotNull(this);
|
|
||||||
return UnaryCriterion.neq(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion gt(Object value) {
|
|
||||||
return UnaryCriterion.gt(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion lt(final Object value) {
|
|
||||||
return UnaryCriterion.lt(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion lte(final Object value) {
|
|
||||||
return UnaryCriterion.lte(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion isNull() {
|
|
||||||
return UnaryCriterion.isNull(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion isNotNull() {
|
|
||||||
return UnaryCriterion.isNotNull(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion between(final Object lower, final Object upper) {
|
|
||||||
final Field field = this;
|
|
||||||
return new Criterion(null) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(field).append(SPACE).append(BETWEEN).append(SPACE).append(lower).append(SPACE).append(AND)
|
|
||||||
.append(SPACE).append(upper);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion like(final String value) {
|
|
||||||
return UnaryCriterion.like(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion like(String value, String escape) {
|
|
||||||
return UnaryCriterion.like(this, value, escape);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public <T> Criterion in(final T... value) {
|
|
||||||
final Field field = this;
|
|
||||||
return new Criterion(Operator.in) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS).append(SPACE);
|
|
||||||
for (T t : value) {
|
|
||||||
sb.append(t.toString()).append(COMMA);
|
|
||||||
}
|
|
||||||
sb.deleteCharAt(sb.length() - 1).append(RIGHT_PARENTHESIS);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public Criterion in(final Query query) {
|
|
||||||
final Field field = this;
|
|
||||||
return new Criterion(Operator.in) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS).append(query)
|
|
||||||
.append(RIGHT_PARENTHESIS);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Field upper(Field title) {
|
|
||||||
return new Field("UPPER(" + title.toString() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return SQL now (in milliseconds)
|
|
||||||
*/
|
|
||||||
public static Field now() {
|
|
||||||
return new Field("(strftime('%s','now')*1000)");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GroupBy {
|
|
||||||
private List<Field> fields = new ArrayList<Field>();
|
|
||||||
|
|
||||||
public static GroupBy groupBy(Field field) {
|
|
||||||
GroupBy groupBy = new GroupBy();
|
|
||||||
groupBy.fields.add(field);
|
|
||||||
return groupBy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.JOIN;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.ON;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
|
|
||||||
public class Join {
|
|
||||||
private final SqlTable joinTable;
|
|
||||||
private final JoinType joinType;
|
|
||||||
private final Criterion[] criterions;
|
|
||||||
|
|
||||||
private Join(SqlTable table, JoinType joinType, Criterion... criterions) {
|
|
||||||
joinTable = table;
|
|
||||||
this.joinType = joinType;
|
|
||||||
this.criterions = criterions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Join inner(SqlTable expression, Criterion... criterions) {
|
|
||||||
return new Join(expression, JoinType.INNER, criterions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Join left(SqlTable table, Criterion... criterions) {
|
|
||||||
return new Join(table, JoinType.LEFT, criterions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Join right(SqlTable table, Criterion... criterions) {
|
|
||||||
return new Join(table, JoinType.RIGHT, criterions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Join out(SqlTable table, Criterion... criterions) {
|
|
||||||
return new Join(table, JoinType.OUT, criterions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(joinType).append(SPACE).append(JOIN).append(SPACE).append(joinTable).append(SPACE).append(ON);
|
|
||||||
for (Criterion criterion : criterions) {
|
|
||||||
sb.append(SPACE).append(criterion);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
public enum JoinType {
|
|
||||||
INNER, LEFT, RIGHT, OUT
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public final class Operator {
|
|
||||||
|
|
||||||
private final String operator;
|
|
||||||
public static final Operator eq = new Operator("=");
|
|
||||||
public static final Operator neq = new Operator("<>");
|
|
||||||
public static final Operator isNull = new Operator("IS NULL");
|
|
||||||
public static final Operator isNotNull = new Operator("IS NOT NULL");
|
|
||||||
public static final Operator gt = new Operator(">");
|
|
||||||
public static final Operator lt = new Operator("<");
|
|
||||||
public static final Operator gte = new Operator(">=");
|
|
||||||
public static final Operator lte = new Operator("<=");
|
|
||||||
public static final Operator and = new Operator("AND");
|
|
||||||
public static final Operator or = new Operator("OR");
|
|
||||||
public static final Operator not = new Operator("NOT");
|
|
||||||
public static final Operator exists = new Operator("EXISTS");
|
|
||||||
public static final Operator like = new Operator("LIKE");
|
|
||||||
public static final Operator in = new Operator("IN");
|
|
||||||
|
|
||||||
private static final Map<Operator, Operator> contraryRegistry = new HashMap<Operator, Operator>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
contraryRegistry.put(eq, neq);
|
|
||||||
contraryRegistry.put(neq, eq);
|
|
||||||
contraryRegistry.put(isNull, isNotNull);
|
|
||||||
contraryRegistry.put(isNotNull, isNull);
|
|
||||||
contraryRegistry.put(gt, lte);
|
|
||||||
contraryRegistry.put(lte, gt);
|
|
||||||
contraryRegistry.put(lt, gte);
|
|
||||||
contraryRegistry.put(gte, lt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Operator(String operator) {
|
|
||||||
this.operator = operator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Operator getContrary() {
|
|
||||||
if(!contraryRegistry.containsKey(this)){
|
|
||||||
Operator opposite = new Operator(not.toString() + SPACE + this.toString());
|
|
||||||
contraryRegistry.put(this, opposite);
|
|
||||||
contraryRegistry.put(opposite, this);
|
|
||||||
}
|
|
||||||
return contraryRegistry.get(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return this.operator.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
|
|
||||||
public class Order {
|
|
||||||
private final Object expression;
|
|
||||||
private final OrderType orderType;
|
|
||||||
|
|
||||||
private Order(Object expression) {
|
|
||||||
this(expression, OrderType.ASC);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Order(Object expression, OrderType orderType) {
|
|
||||||
this.expression = expression;
|
|
||||||
this.orderType = orderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Order asc(Object expression) {
|
|
||||||
return new Order(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Order desc(Object expression) {
|
|
||||||
return new Order(expression, OrderType.DESC);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return expression + SPACE + orderType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order reverse() {
|
|
||||||
if(orderType == OrderType.ASC)
|
|
||||||
return new Order(expression, OrderType.DESC);
|
|
||||||
else
|
|
||||||
return new Order(expression, OrderType.ASC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
public enum OrderType {
|
|
||||||
DESC, ASC
|
|
||||||
}
|
|
||||||
@ -1,205 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.ALL;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.COMMA;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.FROM;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.GROUP_BY;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.LEFT_PARENTHESIS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.LIMIT;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.ORDER_BY;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.RIGHT_PARENTHESIS;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SELECT;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.WHERE;
|
|
||||||
import static com.todoroo.andlib.sql.SqlTable.table;
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.data.Property;
|
|
||||||
|
|
||||||
public final class Query {
|
|
||||||
|
|
||||||
private SqlTable table;
|
|
||||||
private String queryTemplate = null;
|
|
||||||
private final ArrayList<Criterion> criterions = new ArrayList<Criterion>();
|
|
||||||
private final ArrayList<Field> fields = new ArrayList<Field>();
|
|
||||||
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 int limits = -1;
|
|
||||||
|
|
||||||
private Query(Field... fields) {
|
|
||||||
this.fields.addAll(asList(fields));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Query select(Field... fields) {
|
|
||||||
return new Query(fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query from(SqlTable fromTable) {
|
|
||||||
this.table = fromTable;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query join(Join... join) {
|
|
||||||
joins.addAll(asList(join));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query where(Criterion criterion) {
|
|
||||||
criterions.add(criterion);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query groupBy(Field... groupBy) {
|
|
||||||
groupBies.addAll(asList(groupBy));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query orderBy(Order... order) {
|
|
||||||
orders.addAll(asList(order));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query limit(int limit) {
|
|
||||||
limits = limit;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query appendSelectFields(Property<?>... selectFields) {
|
|
||||||
this.fields.addAll(asList(selectFields));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return this == o || !(o == null || getClass() != o.getClass()) && this.toString().equals(o.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return toString().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sql = new StringBuilder();
|
|
||||||
visitSelectClause(sql);
|
|
||||||
visitFromClause(sql);
|
|
||||||
|
|
||||||
visitJoinClause(sql);
|
|
||||||
if(queryTemplate == null) {
|
|
||||||
visitWhereClause(sql);
|
|
||||||
visitGroupByClause(sql);
|
|
||||||
visitOrderByClause(sql);
|
|
||||||
visitLimitClause(sql);
|
|
||||||
} else {
|
|
||||||
if(groupBies.size() > 0 || orders.size() > 0 ||
|
|
||||||
havings.size() > 0)
|
|
||||||
throw new IllegalStateException("Can't have extras AND query template"); //$NON-NLS-1$
|
|
||||||
sql.append(queryTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void visitFromClause(StringBuilder sql) {
|
|
||||||
if (table == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sql.append(FROM).append(SPACE).append(table).append(SPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void visitSelectClause(StringBuilder sql) {
|
|
||||||
sql.append(SELECT).append(SPACE);
|
|
||||||
if (fields.isEmpty()) {
|
|
||||||
sql.append(ALL).append(SPACE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (Field field : fields) {
|
|
||||||
sql.append(field.toStringInSelect()).append(COMMA);
|
|
||||||
}
|
|
||||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void visitLimitClause(StringBuilder sql) {
|
|
||||||
if(limits > -1)
|
|
||||||
sql.append(LIMIT).append(SPACE).append(limits).append(SPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SqlTable as(String alias) {
|
|
||||||
return table(LEFT_PARENTHESIS + this.toString() + RIGHT_PARENTHESIS).as(alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Query having(Criterion criterion) {
|
|
||||||
this.havings.add(criterion);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of fields returned by this query
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Property<?>[] getFields() {
|
|
||||||
return fields.toArray(new Property<?>[fields.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the SQL query template (comes after the "from")
|
|
||||||
* @param sqlQuery
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Query withQueryTemplate(String template) {
|
|
||||||
queryTemplate = template;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.COMMA;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.GROUP_BY;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.LIMIT;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.ORDER_BY;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.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,25 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public final class SqlConstants {
|
|
||||||
static final String SELECT = "SELECT";
|
|
||||||
static final String SPACE = " ";
|
|
||||||
static final String AS = "AS";
|
|
||||||
static final String COMMA = ",";
|
|
||||||
static final String FROM = "FROM";
|
|
||||||
static final String ON = "ON";
|
|
||||||
static final String JOIN = "JOIN";
|
|
||||||
static final String ALL = "*";
|
|
||||||
static final String LEFT_PARENTHESIS = "(";
|
|
||||||
static final String RIGHT_PARENTHESIS = ")";
|
|
||||||
static final String AND = "AND";
|
|
||||||
static final String BETWEEN = "BETWEEN";
|
|
||||||
static final String LIKE = "LIKE";
|
|
||||||
static final String OR = "OR";
|
|
||||||
static final String ORDER_BY = "ORDER BY";
|
|
||||||
static final String GROUP_BY = "GROUP BY";
|
|
||||||
static final String WHERE = "WHERE";
|
|
||||||
public static final String EXISTS = "EXISTS";
|
|
||||||
public static final String NOT = "NOT";
|
|
||||||
public static final String LIMIT = "LIMIT";
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
public class SqlTable extends DBObject<SqlTable> {
|
|
||||||
|
|
||||||
protected SqlTable(String expression) {
|
|
||||||
super(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SqlTable table(String table) {
|
|
||||||
return new SqlTable(table);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
protected String fieldExpression(String fieldName) {
|
|
||||||
if (hasAlias()) {
|
|
||||||
return alias + "." + fieldName;
|
|
||||||
}
|
|
||||||
return expression+"."+fieldName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
package com.todoroo.andlib.sql;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
|
||||||
|
|
||||||
public class UnaryCriterion extends Criterion {
|
|
||||||
protected final Field expression;
|
|
||||||
protected final Object value;
|
|
||||||
|
|
||||||
UnaryCriterion(Field expression, Operator operator, Object value) {
|
|
||||||
super(operator);
|
|
||||||
this.expression = expression;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(StringBuilder sb) {
|
|
||||||
beforePopulateOperator(sb);
|
|
||||||
populateOperator(sb);
|
|
||||||
afterPopulateOperator(sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion eq(Field expression, Object value) {
|
|
||||||
return new UnaryCriterion(expression, Operator.eq, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void beforePopulateOperator(StringBuilder sb) {
|
|
||||||
sb.append(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void populateOperator(StringBuilder sb) {
|
|
||||||
sb.append(operator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
protected void afterPopulateOperator(StringBuilder sb) {
|
|
||||||
if(value == null)
|
|
||||||
return;
|
|
||||||
else if(value instanceof String)
|
|
||||||
sb.append("'").append(sanitize((String) value)).append("'");
|
|
||||||
else
|
|
||||||
sb.append(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitize the given input for SQL
|
|
||||||
* @param input
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static String sanitize(String input) {
|
|
||||||
return input.replace("'", "''");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion neq(Field field, Object value) {
|
|
||||||
return new UnaryCriterion(field, Operator.neq, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion gt(Field field, Object value) {
|
|
||||||
return new UnaryCriterion(field, Operator.gt, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion lt(Field field, Object value) {
|
|
||||||
return new UnaryCriterion(field, Operator.lt, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion lte(Field field, Object value) {
|
|
||||||
return new UnaryCriterion(field, Operator.lte, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion isNull(Field field) {
|
|
||||||
return new UnaryCriterion(field, Operator.isNull, null) {
|
|
||||||
@Override
|
|
||||||
protected void populateOperator(StringBuilder sb) {
|
|
||||||
sb.append(SPACE).append(operator);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion isNotNull(Field field) {
|
|
||||||
return new UnaryCriterion(field, Operator.isNotNull, null) {
|
|
||||||
@Override
|
|
||||||
protected void populateOperator(StringBuilder sb) {
|
|
||||||
sb.append(SPACE).append(operator);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion like(Field field, String value) {
|
|
||||||
return new UnaryCriterion(field, Operator.like, value) {
|
|
||||||
@Override
|
|
||||||
protected void populateOperator(StringBuilder sb) {
|
|
||||||
sb.append(SPACE).append(operator).append(SPACE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Criterion like(Field field, String value, final String escape) {
|
|
||||||
return new UnaryCriterion(field, Operator.like, value) {
|
|
||||||
@Override
|
|
||||||
protected void populateOperator(StringBuilder sb) {
|
|
||||||
sb.append(SPACE).append(operator).append(SPACE);
|
|
||||||
}
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
@Override
|
|
||||||
protected void afterPopulateOperator(StringBuilder sb) {
|
|
||||||
super.afterPopulateOperator(sb);
|
|
||||||
sb.append(SPACE).append("ESCAPE").append(" '").append(sanitize(escape)).append("'");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,385 +0,0 @@
|
|||||||
package com.todoroo.andlib.utility;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.net.NetworkInfo.State;
|
|
||||||
import android.text.InputType;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnTouchListener;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.service.Autowired;
|
|
||||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
|
||||||
import com.todoroo.andlib.service.ExceptionService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Android Utility Classes
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class AndroidUtilities {
|
|
||||||
|
|
||||||
public static final String SEPARATOR_ESCAPE = "!PIPE!"; //$NON-NLS-1$
|
|
||||||
public static final String SERIALIZATION_SEPARATOR = "|"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
// --- utility methods
|
|
||||||
|
|
||||||
private static class ExceptionHelper {
|
|
||||||
@Autowired
|
|
||||||
public ExceptionService exceptionService;
|
|
||||||
|
|
||||||
public ExceptionHelper() {
|
|
||||||
DependencyInjectionService.getInstance().inject(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Suppress virtual keyboard until user's first tap */
|
|
||||||
public static void suppressVirtualKeyboard(final TextView editor) {
|
|
||||||
final int inputType = editor.getInputType();
|
|
||||||
editor.setInputType(InputType.TYPE_NULL);
|
|
||||||
editor.setOnTouchListener(new OnTouchListener() {
|
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
|
||||||
editor.setInputType(inputType);
|
|
||||||
editor.setOnTouchListener(null);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if we're connected to the internet
|
|
||||||
*/
|
|
||||||
public static boolean isConnected(Context context) {
|
|
||||||
ConnectivityManager manager = (ConnectivityManager)
|
|
||||||
context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
NetworkInfo info = manager.getActiveNetworkInfo();
|
|
||||||
if (info == null)
|
|
||||||
return false;
|
|
||||||
if (info.getState() != State.CONNECTED)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fetch the image specified by the given url */
|
|
||||||
public static Bitmap fetchImage(URL url) throws IOException {
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
|
||||||
URLConnection conn = url.openConnection();
|
|
||||||
conn.connect();
|
|
||||||
is = conn.getInputStream();
|
|
||||||
BufferedInputStream bis = new BufferedInputStream(is, 16384);
|
|
||||||
try {
|
|
||||||
Bitmap bitmap = BitmapFactory.decodeStream(bis);
|
|
||||||
return bitmap;
|
|
||||||
} finally {
|
|
||||||
bis.close();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if(is != null)
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the given intent, handling security exceptions if they arise
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @param intent
|
|
||||||
* @param request request code. if negative, no request.
|
|
||||||
*/
|
|
||||||
public static void startExternalIntent(Context context, Intent intent, int request) {
|
|
||||||
try {
|
|
||||||
if(request > -1 && context instanceof Activity)
|
|
||||||
((Activity)context).startActivityForResult(intent, request);
|
|
||||||
else
|
|
||||||
context.startActivity(intent);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ExceptionHelper helper = new ExceptionHelper();
|
|
||||||
helper.exceptionService.displayAndReportError(context,
|
|
||||||
"start-external-intent-" + intent.toString(), //$NON-NLS-1$
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the given intent, handling security exceptions if they arise
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param intent
|
|
||||||
* @param requestCode
|
|
||||||
*/
|
|
||||||
public static void startExternalIntentForResult(
|
|
||||||
Activity activity, Intent intent, int requestCode) {
|
|
||||||
try {
|
|
||||||
activity.startActivityForResult(intent, requestCode);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
ExceptionHelper helper = new ExceptionHelper();
|
|
||||||
helper.exceptionService.displayAndReportError(activity,
|
|
||||||
"start-external-intent-" + intent.toString(), //$NON-NLS-1$
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put an arbitrary object into a {@link ContentValues}
|
|
||||||
* @param target
|
|
||||||
* @param key
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
public static void putInto(ContentValues target, String key, Object value) {
|
|
||||||
if(value instanceof String)
|
|
||||||
target.put(key, (String) value);
|
|
||||||
else if(value instanceof Long)
|
|
||||||
target.put(key, (Long) value);
|
|
||||||
else if(value instanceof Integer)
|
|
||||||
target.put(key, (Integer) value);
|
|
||||||
else if(value instanceof Double)
|
|
||||||
target.put(key, (Double) value);
|
|
||||||
else
|
|
||||||
throw new UnsupportedOperationException("Could not handle type " + //$NON-NLS-1$
|
|
||||||
value.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rips apart a content value into two string arrays, keys and value
|
|
||||||
*/
|
|
||||||
public static String[][] contentValuesToStringArrays(ContentValues source) {
|
|
||||||
String[][] result = new String[2][source.size()];
|
|
||||||
int i = 0;
|
|
||||||
for(Entry<String, Object> entry : source.valueSet()) {
|
|
||||||
result[0][i] = entry.getKey();
|
|
||||||
result[1][i++] = entry.getValue().toString();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return index of value in array
|
|
||||||
* @param array array to search
|
|
||||||
* @param value value to look for
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static <TYPE> int indexOf(TYPE[] array, TYPE value) {
|
|
||||||
for(int i = 0; i < array.length; i++)
|
|
||||||
if(array[i].equals(value))
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes a content value into a string
|
|
||||||
*/
|
|
||||||
public static String contentValuesToSerializedString(ContentValues source) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
for(Entry<String, Object> entry : source.valueSet()) {
|
|
||||||
result.append(entry.getKey().replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE)).append(
|
|
||||||
SERIALIZATION_SEPARATOR);
|
|
||||||
Object value = entry.getValue();
|
|
||||||
if(value instanceof Integer)
|
|
||||||
result.append('i').append(value);
|
|
||||||
else if(value instanceof Double)
|
|
||||||
result.append('d').append(value);
|
|
||||||
else if(value instanceof Long)
|
|
||||||
result.append('l').append(value);
|
|
||||||
else if(value instanceof String)
|
|
||||||
result.append('s').append(value.toString());
|
|
||||||
else
|
|
||||||
throw new UnsupportedOperationException(value.getClass().toString());
|
|
||||||
result.append(SERIALIZATION_SEPARATOR);
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn ContentValues into a string
|
|
||||||
* @param string
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static ContentValues contentValuesFromSerializedString(String string) {
|
|
||||||
if(string == null)
|
|
||||||
return new ContentValues();
|
|
||||||
|
|
||||||
String[] pairs = string.split("\\" + SERIALIZATION_SEPARATOR); //$NON-NLS-1$
|
|
||||||
ContentValues result = new ContentValues();
|
|
||||||
for(int i = 0; i < pairs.length; i += 2) {
|
|
||||||
String key = pairs[i].replaceAll(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR);
|
|
||||||
String value = pairs[i+1].substring(1);
|
|
||||||
switch(pairs[i+1].charAt(0)) {
|
|
||||||
case 'i':
|
|
||||||
result.put(key, Integer.parseInt(value));
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
result.put(key, Double.parseDouble(value));
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
result.put(key, Long.parseLong(value));
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
result.put(key, value.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn ContentValues into a string
|
|
||||||
* @param string
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static ContentValues contentValuesFromString(String string) {
|
|
||||||
if(string == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
String[] pairs = string.split("=");
|
|
||||||
ContentValues result = new ContentValues();
|
|
||||||
String key = null;
|
|
||||||
for(int i = 0; i < pairs.length; i++) {
|
|
||||||
String newKey = null;
|
|
||||||
int lastSpace = pairs[i].lastIndexOf(' ');
|
|
||||||
if(lastSpace != -1) {
|
|
||||||
newKey = pairs[i].substring(lastSpace + 1);
|
|
||||||
pairs[i] = pairs[i].substring(0, lastSpace);
|
|
||||||
} else {
|
|
||||||
newKey = pairs[i];
|
|
||||||
}
|
|
||||||
if(key != null)
|
|
||||||
result.put(key.trim(), pairs[i].trim());
|
|
||||||
key = newKey;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if a and b or null or a.equals(b)
|
|
||||||
* @param a
|
|
||||||
* @param b
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean equals(Object a, Object b) {
|
|
||||||
if(a == null && b == null)
|
|
||||||
return true;
|
|
||||||
if(a == null)
|
|
||||||
return false;
|
|
||||||
return a.equals(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy a file from one place to another
|
|
||||||
*
|
|
||||||
* @param in
|
|
||||||
* @param out
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static void copyFile(File in, File out) throws Exception {
|
|
||||||
FileInputStream fis = new FileInputStream(in);
|
|
||||||
FileOutputStream fos = new FileOutputStream(out);
|
|
||||||
try {
|
|
||||||
byte[] buf = new byte[1024];
|
|
||||||
int i = 0;
|
|
||||||
while ((i = fis.read(buf)) != -1) {
|
|
||||||
fos.write(buf, 0, i);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
fis.close();
|
|
||||||
fos.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a child view of a certain type
|
|
||||||
* @param view
|
|
||||||
* @param type
|
|
||||||
* @return first view (by DFS) if found, or null if none
|
|
||||||
*/
|
|
||||||
public static <TYPE> TYPE findViewByType(View view, Class<TYPE> type) {
|
|
||||||
if(view == null)
|
|
||||||
return null;
|
|
||||||
if(type.isInstance(view))
|
|
||||||
return (TYPE) view;
|
|
||||||
if(view instanceof ViewGroup) {
|
|
||||||
ViewGroup group = (ViewGroup) view;
|
|
||||||
for(int i = 0; i < group.getChildCount(); i++) {
|
|
||||||
TYPE v = findViewByType(group.getChildAt(i), type);
|
|
||||||
if(v != null)
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Android SDK version as an integer. Works on all versions
|
|
||||||
*/
|
|
||||||
public static int getSdkVersion() {
|
|
||||||
return Integer.parseInt(android.os.Build.VERSION.SDK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy databases to a given folder. Useful for debugging
|
|
||||||
* @param folder
|
|
||||||
*/
|
|
||||||
public static void copyDatabases(Context context, String folder) {
|
|
||||||
File folderFile = new File(folder);
|
|
||||||
if(!folderFile.exists())
|
|
||||||
folderFile.mkdir();
|
|
||||||
for(String db : context.databaseList()) {
|
|
||||||
File dbFile = context.getDatabasePath(db);
|
|
||||||
try {
|
|
||||||
copyFile(dbFile, new File(folderFile.getAbsolutePath() +
|
|
||||||
File.separator + db));
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("ERROR", "ERROR COPYING DB " + db, e); //$NON-NLS-1$ //$NON-NLS-2$
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort files by date so the newest file is on top
|
|
||||||
* @param files
|
|
||||||
*/
|
|
||||||
public static void sortFilesByDateDesc(File[] files) {
|
|
||||||
Arrays.sort(files, new Comparator<File>() {
|
|
||||||
public int compare(File o1, File o2) {
|
|
||||||
return Long.valueOf(o2.lastModified()).compareTo(Long.valueOf(o1.lastModified()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sleep, ignoring interruption
|
|
||||||
* @param l
|
|
||||||
*/
|
|
||||||
public static void sleepDeep(long l) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(l);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,335 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009, Todoroo Inc
|
|
||||||
* All Rights Reserved
|
|
||||||
* http://www.todoroo.com
|
|
||||||
*/
|
|
||||||
package com.todoroo.andlib.utility;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.service.Autowired;
|
|
||||||
import com.todoroo.andlib.service.ContextManager;
|
|
||||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
|
||||||
|
|
||||||
|
|
||||||
public class DateUtilities {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer yearsResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer monthsResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer weeksResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer daysResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer hoursResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer minutesResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer secondsResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer daysAbbrevResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer hoursAbbrevResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer minutesAbbrevResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer secondsAbbrevResource;
|
|
||||||
|
|
||||||
public DateUtilities() {
|
|
||||||
DependencyInjectionService.getInstance().inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ======================================================================
|
|
||||||
* ============================================================ long time
|
|
||||||
* ====================================================================== */
|
|
||||||
|
|
||||||
/** Convert unixtime into date */
|
|
||||||
public static final Date unixtimeToDate(long millis) {
|
|
||||||
if(millis == 0)
|
|
||||||
return null;
|
|
||||||
return new Date(millis);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert date into unixtime */
|
|
||||||
public static final long dateToUnixtime(Date date) {
|
|
||||||
if(date == null)
|
|
||||||
return 0;
|
|
||||||
return date.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns unixtime for current time */
|
|
||||||
public static final long now() {
|
|
||||||
return System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns unixtime one month from now */
|
|
||||||
public static final long oneMonthFromNow() {
|
|
||||||
Date date = new Date();
|
|
||||||
date.setMonth(date.getMonth() + 1);
|
|
||||||
return date.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Represents a single hour */
|
|
||||||
public static long ONE_HOUR = 3600000L;
|
|
||||||
|
|
||||||
/** Represents a single day */
|
|
||||||
public static long ONE_DAY = 24 * ONE_HOUR;
|
|
||||||
|
|
||||||
/** Represents a single week */
|
|
||||||
public static long ONE_WEEK = 7 * ONE_DAY;
|
|
||||||
|
|
||||||
/* ======================================================================
|
|
||||||
* =========================================================== formatters
|
|
||||||
* ====================================================================== */
|
|
||||||
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static boolean is24HourFormat(Context context) {
|
|
||||||
String value = android.provider.Settings.System.getString(context.getContentResolver(),
|
|
||||||
android.provider.Settings.System.TIME_12_24);
|
|
||||||
boolean b24 = !(value == null || value.equals("12"));
|
|
||||||
return b24;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context android context
|
|
||||||
* @param date time to format
|
|
||||||
* @return time, with hours and minutes
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static String getTimeString(Context context, Date date) {
|
|
||||||
String value;
|
|
||||||
if (is24HourFormat(context)) {
|
|
||||||
value = "H:mm";
|
|
||||||
} else {
|
|
||||||
value = "h:mm a";
|
|
||||||
}
|
|
||||||
return new SimpleDateFormat(value).format(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context android context
|
|
||||||
* @param date date to format
|
|
||||||
* @return date, with month, day, and year
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static String getDateString(Context context, Date date) {
|
|
||||||
String month = "'" + DateUtils.getMonthString(date.getMonth() +
|
|
||||||
Calendar.JANUARY, DateUtils.LENGTH_MEDIUM) + "'";
|
|
||||||
String value;
|
|
||||||
// united states, you are special
|
|
||||||
if (Locale.US.equals(Locale.getDefault())
|
|
||||||
|| Locale.CANADA.equals(Locale.getDefault()))
|
|
||||||
value = month + " d yyyy";
|
|
||||||
else
|
|
||||||
value = "d " + month + " yyyy";
|
|
||||||
return new SimpleDateFormat(value).format(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return date format as getDateFormat with weekday
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static String getDateStringWithWeekday(Context context, Date date) {
|
|
||||||
String weekday = DateUtils.getDayOfWeekString(date.getDay() + Calendar.SUNDAY,
|
|
||||||
DateUtils.LENGTH_LONG);
|
|
||||||
return weekday + ", " + getDateString(context, date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return date format as getDateFormat with weekday
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static String getDateStringWithTimeAndWeekday(Context context, Date date) {
|
|
||||||
return getDateStringWithWeekday(context, date) + " " + getTimeString(context, date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return date with time at the end
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public static String getDateStringWithTime(Context context, Date date) {
|
|
||||||
return getDateString(context, date) + " " + getTimeString(context, date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ======================================================================
|
|
||||||
* ============================================================= duration
|
|
||||||
* ====================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method for dropping the preposition argument.
|
|
||||||
* @param duration in millis
|
|
||||||
* @param unitsToShow number of units to show (i.e. if 2, then 5 hours
|
|
||||||
* 3 minutes 2 seconds is truncated to 5 hours 3 minutes)
|
|
||||||
*/
|
|
||||||
public String getDurationString(long duration, int unitsToShow) {
|
|
||||||
return getDurationString(duration, unitsToShow, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a time into the format: 5 days, 3 hours, 2 minutes
|
|
||||||
*
|
|
||||||
* @param duration in millis
|
|
||||||
* @param unitsToShow number of units to show (i.e. if 2, then 5 hours
|
|
||||||
* 3 minutes 2 seconds is truncated to 5 hours 3 minutes)
|
|
||||||
* @param withPreposition whether there is a preceding preposition
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getDurationString(long duration, int unitsToShow, boolean withPreposition) {
|
|
||||||
Resources r = ContextManager.getContext().getResources();
|
|
||||||
short unitsDisplayed = 0;
|
|
||||||
duration = Math.abs(duration);
|
|
||||||
|
|
||||||
if(duration == 0)
|
|
||||||
return r.getQuantityString(secondsResource, 0, 0);
|
|
||||||
|
|
||||||
Date now = new Date(80, 1, 1);
|
|
||||||
Date then = new Date(now.getTime() + duration);
|
|
||||||
|
|
||||||
int[] values = new int[] {
|
|
||||||
then.getYear() - now.getYear(),
|
|
||||||
then.getMonth() - now.getMonth(),
|
|
||||||
(then.getDate() - now.getDate())/7,
|
|
||||||
(then.getDate() - now.getDate()) - (then.getDate() - now.getDate())/7*7,
|
|
||||||
then.getHours() - now.getHours(),
|
|
||||||
then.getMinutes() - now.getMinutes(),
|
|
||||||
then.getSeconds() - now.getSeconds(),
|
|
||||||
};
|
|
||||||
int[] maxValues = new int[] {
|
|
||||||
Integer.MAX_VALUE,
|
|
||||||
12,
|
|
||||||
5,
|
|
||||||
7,
|
|
||||||
24,
|
|
||||||
60,
|
|
||||||
60
|
|
||||||
};
|
|
||||||
|
|
||||||
// perform rounding (this is definitely magic... trust the unit tests)
|
|
||||||
int cursor = 0;
|
|
||||||
while(values[cursor] == 0 && ++cursor < values.length)
|
|
||||||
;
|
|
||||||
int postCursor = cursor + unitsToShow;
|
|
||||||
for(int i = values.length - 1; i >= postCursor; i--) {
|
|
||||||
if(values[i] >= maxValues[i]/2) {
|
|
||||||
values[i-1]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(int i = Math.min(values.length, postCursor) - 1; i >= 1; i--) {
|
|
||||||
if(values[i] == maxValues[i]) {
|
|
||||||
values[i-1]++;
|
|
||||||
for(int j = i; j < values.length; j++)
|
|
||||||
values[j] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
unitsDisplayed = displayUnits(r, yearsResource, unitsToShow, values[0],
|
|
||||||
unitsDisplayed, result);
|
|
||||||
unitsDisplayed = displayUnits(r, monthsResource, unitsToShow, values[1],
|
|
||||||
unitsDisplayed, result);
|
|
||||||
unitsDisplayed = displayUnits(r, weeksResource, unitsToShow, values[2],
|
|
||||||
unitsDisplayed, result);
|
|
||||||
unitsDisplayed = displayUnits(r, daysResource, unitsToShow, values[3],
|
|
||||||
unitsDisplayed, result);
|
|
||||||
unitsDisplayed = displayUnits(r, hoursResource, unitsToShow, values[4],
|
|
||||||
unitsDisplayed, result);
|
|
||||||
unitsDisplayed = displayUnits(r, minutesResource, unitsToShow, values[5],
|
|
||||||
unitsDisplayed, result);
|
|
||||||
unitsDisplayed = displayUnits(r, secondsResource, unitsToShow, values[6],
|
|
||||||
unitsDisplayed, result);
|
|
||||||
|
|
||||||
return result.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Display units, rounding up if necessary. Returns units to show */
|
|
||||||
private short displayUnits(Resources r, int resource, int unitsToShow, int value,
|
|
||||||
short unitsDisplayed, StringBuilder result) {
|
|
||||||
if(unitsDisplayed < unitsToShow && value > 0) {
|
|
||||||
result.append(r.getQuantityString(resource, value, value)).
|
|
||||||
append(' ');
|
|
||||||
unitsDisplayed++;
|
|
||||||
}
|
|
||||||
return unitsDisplayed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a time into the format: 5 days, 3 hrs, 2 min
|
|
||||||
*
|
|
||||||
* @param r Resources to get strings from
|
|
||||||
* @param timeInSeconds
|
|
||||||
* @param unitsToShow number of units to show (i.e. if 2, then 5 hours
|
|
||||||
* 3 minutes 2 seconds is truncated to 5 hours 3 minutes)
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getAbbreviatedDurationString(Resources r, int timeInSeconds,
|
|
||||||
int unitsToShow) {
|
|
||||||
short days, hours, minutes, seconds;
|
|
||||||
short unitsDisplayed = 0;
|
|
||||||
timeInSeconds = Math.abs(timeInSeconds);
|
|
||||||
|
|
||||||
if(timeInSeconds == 0)
|
|
||||||
return r.getQuantityString(secondsAbbrevResource, 0, 0);
|
|
||||||
|
|
||||||
days = (short)(timeInSeconds / 24 / 3600);
|
|
||||||
timeInSeconds -= days*24*3600;
|
|
||||||
hours = (short)(timeInSeconds / 3600);
|
|
||||||
timeInSeconds -= hours * 3600;
|
|
||||||
minutes = (short)(timeInSeconds / 60);
|
|
||||||
timeInSeconds -= minutes * 60;
|
|
||||||
seconds = (short)timeInSeconds;
|
|
||||||
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
if(days > 0) {
|
|
||||||
// round up if needed
|
|
||||||
if(unitsDisplayed == unitsToShow && hours >= 12)
|
|
||||||
days++;
|
|
||||||
result.append(r.getQuantityString(daysAbbrevResource, days, days)).
|
|
||||||
append(' ');
|
|
||||||
unitsDisplayed++;
|
|
||||||
}
|
|
||||||
if(unitsDisplayed < unitsToShow && hours > 0) {
|
|
||||||
// round up if needed
|
|
||||||
if(unitsDisplayed == unitsToShow && minutes >= 30)
|
|
||||||
days++;
|
|
||||||
result.append(r.getQuantityString(hoursAbbrevResource, hours,
|
|
||||||
hours)).
|
|
||||||
append(' ');
|
|
||||||
unitsDisplayed++;
|
|
||||||
}
|
|
||||||
if(unitsDisplayed < unitsToShow && minutes > 0) {
|
|
||||||
// round up if needed
|
|
||||||
if(unitsDisplayed == unitsToShow && seconds >= 30)
|
|
||||||
days++;
|
|
||||||
result.append(r.getQuantityString(minutesAbbrevResource, minutes,
|
|
||||||
minutes)).append(' ');
|
|
||||||
unitsDisplayed++;
|
|
||||||
}
|
|
||||||
if(unitsDisplayed < unitsToShow && seconds > 0) {
|
|
||||||
result.append(r.getQuantityString(secondsAbbrevResource, seconds,
|
|
||||||
seconds)).append(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,188 +0,0 @@
|
|||||||
package com.todoroo.andlib.utility;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.service.Autowired;
|
|
||||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
|
||||||
|
|
||||||
public class DialogUtilities {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer informationDialogTitleResource;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public Integer confirmDialogTitleResource;
|
|
||||||
|
|
||||||
public DialogUtilities() {
|
|
||||||
DependencyInjectionService.getInstance().inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a dialog box with a EditText and an ok / cancel
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param text
|
|
||||||
* @param okListener
|
|
||||||
*/
|
|
||||||
public void viewDialog(final Activity activity, final String text,
|
|
||||||
final View view, final DialogInterface.OnClickListener okListener,
|
|
||||||
final DialogInterface.OnClickListener cancelListener) {
|
|
||||||
if(activity.isFinishing())
|
|
||||||
return;
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
new AlertDialog.Builder(activity)
|
|
||||||
.setTitle(confirmDialogTitleResource)
|
|
||||||
.setMessage(text)
|
|
||||||
.setView(view)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setPositiveButton(android.R.string.ok, okListener)
|
|
||||||
.setNegativeButton(android.R.string.cancel, cancelListener)
|
|
||||||
.show().setOwnerActivity(activity);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a dialog box with an OK button
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param text
|
|
||||||
* @param okListener
|
|
||||||
*/
|
|
||||||
public void okDialog(final Activity activity, final String text,
|
|
||||||
final DialogInterface.OnClickListener okListener) {
|
|
||||||
if(activity.isFinishing())
|
|
||||||
return;
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
new AlertDialog.Builder(activity)
|
|
||||||
.setTitle(informationDialogTitleResource)
|
|
||||||
.setMessage(text)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setPositiveButton(android.R.string.ok, okListener)
|
|
||||||
.show().setOwnerActivity(activity);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a dialog box with an OK button
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param text
|
|
||||||
* @param okListener
|
|
||||||
*/
|
|
||||||
public void okDialog(final Activity activity, final int icon, final CharSequence text,
|
|
||||||
final DialogInterface.OnClickListener okListener) {
|
|
||||||
if(activity.isFinishing())
|
|
||||||
return;
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
new AlertDialog.Builder(activity)
|
|
||||||
.setTitle(informationDialogTitleResource)
|
|
||||||
.setMessage(text)
|
|
||||||
.setIcon(icon)
|
|
||||||
.setPositiveButton(android.R.string.ok, okListener)
|
|
||||||
.show().setOwnerActivity(activity);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a dialog box with OK and Cancel buttons and custom title
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param title
|
|
||||||
* @param text
|
|
||||||
* @param okListener
|
|
||||||
* @param cancelListener
|
|
||||||
*/
|
|
||||||
public void okCancelDialog(final Activity activity, final String title,
|
|
||||||
final String text, final DialogInterface.OnClickListener okListener,
|
|
||||||
final DialogInterface.OnClickListener cancelListener) {
|
|
||||||
if(activity.isFinishing())
|
|
||||||
return;
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
new AlertDialog.Builder(activity)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(text)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setPositiveButton(android.R.string.ok, okListener)
|
|
||||||
.setNegativeButton(android.R.string.cancel, cancelListener)
|
|
||||||
.show().setOwnerActivity(activity);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a dialog box with OK and Cancel buttons
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param text
|
|
||||||
* @param okListener
|
|
||||||
* @param cancelListener
|
|
||||||
*/
|
|
||||||
public void okCancelDialog(final Activity activity, final String text,
|
|
||||||
final DialogInterface.OnClickListener okListener,
|
|
||||||
final DialogInterface.OnClickListener cancelListener) {
|
|
||||||
if(activity.isFinishing())
|
|
||||||
return;
|
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
new AlertDialog.Builder(activity)
|
|
||||||
.setTitle(confirmDialogTitleResource)
|
|
||||||
.setMessage(text)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setPositiveButton(android.R.string.ok, okListener)
|
|
||||||
.setNegativeButton(android.R.string.cancel, cancelListener)
|
|
||||||
.show().setOwnerActivity(activity);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a progress dialog. Must be run on the UI thread
|
|
||||||
* @param context
|
|
||||||
* @param text
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public ProgressDialog progressDialog(Context context, String text) {
|
|
||||||
ProgressDialog dialog = new ProgressDialog(context);
|
|
||||||
dialog.setIndeterminate(true);
|
|
||||||
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
|
||||||
dialog.setMessage(text);
|
|
||||||
dialog.show();
|
|
||||||
dialog.setOwnerActivity((Activity)context);
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismiss a dialog off the UI thread
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* @param dialog
|
|
||||||
*/
|
|
||||||
public void dismissDialog(Activity activity, final ProgressDialog dialog) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
dialog.dismiss();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// could have killed activity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
package com.todoroo.andlib.utility;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* E-mail Validator Copyright 2008 Les Hazlewood
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public final class EmailValidator {
|
|
||||||
|
|
||||||
// RFC 2822 2.2.2 Structured Header Field Bodies
|
|
||||||
private static final String wsp = "[ \\t]"; // space or tab
|
|
||||||
private static final String fwsp = wsp + "*";
|
|
||||||
|
|
||||||
// RFC 2822 3.2.1 Primitive tokens
|
|
||||||
private static final String dquote = "\\\"";
|
|
||||||
// ASCII Control characters excluding white space:
|
|
||||||
private static final String noWsCtl = "\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F";
|
|
||||||
// all ASCII characters except CR and LF:
|
|
||||||
private static final String asciiText = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]";
|
|
||||||
|
|
||||||
// RFC 2822 3.2.2 Quoted characters:
|
|
||||||
// single backslash followed by a text char
|
|
||||||
private static final String quotedPair = "(\\\\" + asciiText + ")";
|
|
||||||
|
|
||||||
// RFC 2822 3.2.4 Atom:
|
|
||||||
private static final String atext = "[a-zA-Z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]";
|
|
||||||
private static final String dotAtomText = atext + "+" + "(" + "\\." + atext
|
|
||||||
+ "+)*";
|
|
||||||
private static final String dotAtom = fwsp + "(" + dotAtomText + ")" + fwsp;
|
|
||||||
|
|
||||||
// RFC 2822 3.2.5 Quoted strings:
|
|
||||||
// noWsCtl and the rest of ASCII except the doublequote and backslash
|
|
||||||
// characters:
|
|
||||||
private static final String qtext = "[" + noWsCtl
|
|
||||||
+ "\\x21\\x23-\\x5B\\x5D-\\x7E]";
|
|
||||||
private static final String qcontent = "(" + qtext + "|" + quotedPair + ")";
|
|
||||||
private static final String quotedString = dquote + "(" + fwsp + qcontent
|
|
||||||
+ ")*" + fwsp + dquote;
|
|
||||||
|
|
||||||
// RFC 1035 tokens for domain names:
|
|
||||||
private static final String letter = "[a-zA-Z]";
|
|
||||||
private static final String letDig = "[a-zA-Z0-9]";
|
|
||||||
private static final String letDigHyp = "[a-zA-Z0-9-]";
|
|
||||||
private static final String rfcLabel = letDig + "(" + letDigHyp + "{0,61}"
|
|
||||||
+ letDig + ")?";
|
|
||||||
private static final String rfc1035DomainName = rfcLabel + "(\\."
|
|
||||||
+ rfcLabel + ")*\\." + letter + "{2,6}";
|
|
||||||
|
|
||||||
private static final String domain = rfc1035DomainName;
|
|
||||||
|
|
||||||
private static final String localPart = "((" + dotAtom + ")|("
|
|
||||||
+ quotedString + "))";
|
|
||||||
private static final String addrSpec = localPart + "@" + domain;
|
|
||||||
|
|
||||||
// now compile a pattern for efficient re-use:
|
|
||||||
// if we're allowing quoted identifiers or not:
|
|
||||||
private static final String patternString = addrSpec;
|
|
||||||
public static final Pattern VALID_PATTERN = Pattern.compile(patternString);
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean validateEmail(String value) {
|
|
||||||
return VALID_PATTERN.matcher(value).matches();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
package com.todoroo.andlib.utility;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pair utility class
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
* @param <L>
|
|
||||||
* @param <R>
|
|
||||||
*/
|
|
||||||
public class Pair<L, R> {
|
|
||||||
|
|
||||||
private final L left;
|
|
||||||
private final R right;
|
|
||||||
|
|
||||||
public R getRight() {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
|
|
||||||
public L getLeft() {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pair(final L left, final R right) {
|
|
||||||
this.left = left;
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <A, B> Pair<A, B> create(A left, B right) {
|
|
||||||
return new Pair<A, B>(left, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object o) {
|
|
||||||
if (!(o instanceof Pair<?, ?>))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
final Pair<?, ?> other = (Pair<?, ?>) o;
|
|
||||||
return equal(getLeft(), other.getLeft()) && equal(getRight(), other.getRight());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final boolean equal(Object o1, Object o2) {
|
|
||||||
if (o1 == null) {
|
|
||||||
return o2 == null;
|
|
||||||
}
|
|
||||||
return o1.equals(o2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int hLeft = getLeft() == null ? 0 : getLeft().hashCode();
|
|
||||||
int hRight = getRight() == null ? 0 : getRight().hashCode();
|
|
||||||
|
|
||||||
return hLeft + (57 * hRight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
package com.todoroo.andlib.utility;
|
|
||||||
|
|
||||||
import java.lang.ref.Reference;
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
|
||||||
import java.lang.ref.SoftReference;
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SoftHashMap from javaspecialists.eu Issue 98
|
|
||||||
* <http://www.javaspecialists.eu/archive/Issue098.html>
|
|
||||||
*
|
|
||||||
* @param <K>
|
|
||||||
* @param <V>
|
|
||||||
*/
|
|
||||||
public class SoftHashMap<K, V> extends AbstractMap<K, V> {
|
|
||||||
private static final long serialVersionUID = -3796460667941300642L;
|
|
||||||
|
|
||||||
/** The internal HashMap that will hold the SoftReference. */
|
|
||||||
private final Map<K, SoftReference<V>> hash = new HashMap<K, SoftReference<V>>();
|
|
||||||
|
|
||||||
private final Map<SoftReference<V>, K> reverseLookup = new HashMap<SoftReference<V>, K>();
|
|
||||||
|
|
||||||
/** Reference queue for cleared SoftReference objects. */
|
|
||||||
protected final ReferenceQueue<V> queue = new ReferenceQueue<V>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V get(Object key) {
|
|
||||||
expungeStaleEntries();
|
|
||||||
V result = null;
|
|
||||||
// We get the SoftReference represented by that key
|
|
||||||
SoftReference<V> soft_ref = hash.get(key);
|
|
||||||
if (soft_ref != null) {
|
|
||||||
// From the SoftReference we get the value, which can be
|
|
||||||
// null if it has been garbage collected
|
|
||||||
result = soft_ref.get();
|
|
||||||
if (result == null) {
|
|
||||||
// If the value has been garbage collected, remove the
|
|
||||||
// entry from the HashMap.
|
|
||||||
hash.remove(key);
|
|
||||||
reverseLookup.remove(soft_ref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expungeStaleEntries() {
|
|
||||||
Reference<? extends V> sv;
|
|
||||||
while ((sv = queue.poll()) != null) {
|
|
||||||
hash.remove(reverseLookup.remove(sv));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V put(K key, V value) {
|
|
||||||
expungeStaleEntries();
|
|
||||||
SoftReference<V> soft_ref = new SoftReference<V>(value, queue);
|
|
||||||
reverseLookup.put(soft_ref, key);
|
|
||||||
SoftReference<V> result = hash.put(key, soft_ref);
|
|
||||||
if (result == null)
|
|
||||||
return null;
|
|
||||||
reverseLookup.remove(result);
|
|
||||||
return result.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V remove(Object key) {
|
|
||||||
expungeStaleEntries();
|
|
||||||
SoftReference<V> result = hash.remove(key);
|
|
||||||
if (result == null)
|
|
||||||
return null;
|
|
||||||
return result.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
hash.clear();
|
|
||||||
reverseLookup.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
expungeStaleEntries();
|
|
||||||
return hash.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a copy of the key/values in the map at the point of calling.
|
|
||||||
* However, setValue still sets the value in the actual SoftHashMap.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Set<Entry<K, V>> entrySet() {
|
|
||||||
expungeStaleEntries();
|
|
||||||
Set<Entry<K, V>> result = new LinkedHashSet<Entry<K, V>>();
|
|
||||||
for (final Entry<K, SoftReference<V>> entry : hash.entrySet()) {
|
|
||||||
final V value = entry.getValue().get();
|
|
||||||
if (value != null) {
|
|
||||||
result.add(new Entry<K, V>() {
|
|
||||||
public K getKey() {
|
|
||||||
return entry.getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
public V getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V setValue(V v) {
|
|
||||||
entry.setValue(new SoftReference<V>(v, queue));
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,450 +0,0 @@
|
|||||||
package com.todoroo.andlib.utility;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2008 Google Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Process;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>UserTask enables proper and easy use of the UI thread. This class allows to
|
|
||||||
* perform background operations and publish results on the UI thread without
|
|
||||||
* having to manipulate threads and/or handlers.</p>
|
|
||||||
*
|
|
||||||
* <p>A user task is defined by a computation that runs on a background thread and
|
|
||||||
* whose result is published on the UI thread. A user task is defined by 3 generic
|
|
||||||
* types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
|
|
||||||
* and 4 steps, called <code>begin</code>, <code>doInBackground</code>,
|
|
||||||
* <code>processProgress<code> and <code>end</code>.</p>
|
|
||||||
*
|
|
||||||
* <h2>Usage</h2>
|
|
||||||
* <p>UserTask must be subclassed to be used. The subclass will override at least
|
|
||||||
* one method ({@link #doInBackground(Object[])}), and most often will override a
|
|
||||||
* second one ({@link #end(Object)}.)</p>
|
|
||||||
*
|
|
||||||
* <p>Here is an example of subclassing:</p>
|
|
||||||
* <pre>
|
|
||||||
* private class DownloadFilesTask extends UserTask<URL, Integer, Long> {
|
|
||||||
* public File doInBackground(URL... urls) {
|
|
||||||
* int count = urls.length;
|
|
||||||
* long totalSize = 0;
|
|
||||||
* for (int i = 0; i < count; i++) {
|
|
||||||
* totalSize += Downloader.downloadFile(urls[i]);
|
|
||||||
* publishProgress((int) ((i / (float) count) * 100));
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public void processProgress(Integer... progress) {
|
|
||||||
* setProgressPercent(progress[0]);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public void end(Long result) {
|
|
||||||
* showDialog("Downloaded " + result + " bytes");
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>Once created, a task is executed very simply:</p>
|
|
||||||
* <pre>
|
|
||||||
* new DownloadFilesTask().execute(new URL[] { ... });
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <h2>User task's generic types</h2>
|
|
||||||
* <p>The three types used by a user task are the following:</p>
|
|
||||||
* <ol>
|
|
||||||
* <li><code>Params</code>, the type of the parameters sent to the task upon
|
|
||||||
* execution.</li>
|
|
||||||
* <li><code>Progress</code>, the type of the progress units published during
|
|
||||||
* the background computation.</li>
|
|
||||||
* <li><code>Result</code>, the type of the result of the background
|
|
||||||
* computation.</li>
|
|
||||||
* </ol>
|
|
||||||
* <p>Not all types are always used by a user task. To mark a type as unused,
|
|
||||||
* simply use the type {@link Void}:</p>
|
|
||||||
* <pre>
|
|
||||||
* private class MyTask extends UserTask<Void, Void, Void) { ... }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <h2>The 4 steps</h2>
|
|
||||||
* <p>When a user task is executed, the task goes through 4 steps:</p>
|
|
||||||
* <ol>
|
|
||||||
* <li>{@link #begin()}, invoked on the UI thread immediately after the task
|
|
||||||
* is executed. This step is normally used to setup the task, for instance by
|
|
||||||
* showing a progress bar in the user interface.</li>
|
|
||||||
* <li>{@link #doInBackground(Object[])}, invoked on the background thread
|
|
||||||
* immediately after {@link #begin()} finishes executing. This step is used
|
|
||||||
* to perform background computation that can take a long time. The parameters
|
|
||||||
* of the user task are passed to this step. The result of the computation must
|
|
||||||
* be returned by this step and will be passed back to the last step. This step
|
|
||||||
* can also use {@link #publishProgress(Object[])} to publish one or more units
|
|
||||||
* of progress. These values are published on the UI thread, in the
|
|
||||||
* {@link #processProgress(Object[])} step.</li>
|
|
||||||
* <li>{@link #processProgress(Object[])}, invoked on the UI thread after a
|
|
||||||
* call to {@link #publishProgress(Object[])}. The timing of the execution is
|
|
||||||
* undefined. This method is used to display any form of progress in the user
|
|
||||||
* interface while the background computation is still executing. For instance,
|
|
||||||
* it can be used to animate a progress bar or show logs in a text field.</li>
|
|
||||||
* <li>{@link #end(Object)}, invoked on the UI thread after the background
|
|
||||||
* computation finishes. The result of the background computation is passed to
|
|
||||||
* this step as a parameter.</li>
|
|
||||||
* </ol>
|
|
||||||
*
|
|
||||||
* <h2>Threading rules</h2>
|
|
||||||
* <p>There are a few threading rules that must be followed for this class to
|
|
||||||
* work properly:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>The task instance must be created on the UI thread.</li>
|
|
||||||
* <li>{@link #execute(Object[])} must be invoked on the UI thread.</li>
|
|
||||||
* <li>Do not call {@link #begin()}, {@link #end(Object)},
|
|
||||||
* {@link #doInBackground(Object[])}, {@link #processProgress(Object[])}
|
|
||||||
* manually.</li>
|
|
||||||
* <li>The task can be executed only once (an exception will be thrown if
|
|
||||||
* a second execution is attempted.)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("nls")
|
|
||||||
public abstract class UserTask<Params, Progress, Result> {
|
|
||||||
private static final String LOG_TAG = "UserTask";
|
|
||||||
|
|
||||||
private static final int CORE_POOL_SIZE = 4;
|
|
||||||
private static final int MAXIMUM_POOL_SIZE = 10;
|
|
||||||
private static final int KEEP_ALIVE = 10;
|
|
||||||
|
|
||||||
private static final BlockingQueue<Runnable> sWorkQueue =
|
|
||||||
new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE);
|
|
||||||
|
|
||||||
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
|
|
||||||
private final AtomicInteger mCount = new AtomicInteger(1);
|
|
||||||
|
|
||||||
public Thread newThread(Runnable r) {
|
|
||||||
final Thread thread = new Thread(r, "UserTask #" + mCount.getAndIncrement());
|
|
||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
|
|
||||||
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
|
|
||||||
|
|
||||||
private static final int MESSAGE_POST_RESULT = 0x1;
|
|
||||||
private static final int MESSAGE_POST_PROGRESS = 0x2;
|
|
||||||
|
|
||||||
protected static InternalHandler sHandler;
|
|
||||||
|
|
||||||
private final WorkerRunnable<Params, Result> mWorker;
|
|
||||||
private final FutureTask<Result> mFuture;
|
|
||||||
|
|
||||||
private volatile Status mStatus = Status.PENDING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates the current status of the task. Each status will be set only once
|
|
||||||
* during the lifetime of a task.
|
|
||||||
*/
|
|
||||||
public enum Status {
|
|
||||||
/**
|
|
||||||
* Indicates that the task has not been executed yet.
|
|
||||||
*/
|
|
||||||
PENDING,
|
|
||||||
/**
|
|
||||||
* Indicates that the task is running.
|
|
||||||
*/
|
|
||||||
RUNNING,
|
|
||||||
/**
|
|
||||||
* Indicates that {@link UserTask#end(Object)} has finished.
|
|
||||||
*/
|
|
||||||
FINISHED,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new user task. This constructor must be invoked on the UI thread.
|
|
||||||
*/
|
|
||||||
public UserTask() {
|
|
||||||
synchronized(UserTask.class) {
|
|
||||||
if (sHandler == null) {
|
|
||||||
sHandler = new InternalHandler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mWorker = new WorkerRunnable<Params, Result>() {
|
|
||||||
public Result call() throws Exception {
|
|
||||||
return doInBackground(mParams);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mFuture = new FutureTask<Result>(mWorker) {
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
Result result = null;
|
|
||||||
try {
|
|
||||||
result = mFuture.get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
android.util.Log.w(LOG_TAG, e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
throw new RuntimeException("An error occured while executing doInBackground()",
|
|
||||||
e.getCause());
|
|
||||||
} catch (CancellationException e) {
|
|
||||||
return;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
throw new RuntimeException("An error occured while executing "
|
|
||||||
+ "doInBackground()", t);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
|
|
||||||
new UserTaskResult<Result>(UserTask.this, result));
|
|
||||||
message.sendToTarget();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current status of this task.
|
|
||||||
*
|
|
||||||
* @return The current status.
|
|
||||||
*/
|
|
||||||
public final Status getStatus() {
|
|
||||||
return mStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this method to perform a computation on a background thread. The
|
|
||||||
* specified parameters are the parameters passed to {@link #execute(Object[])}
|
|
||||||
* by the caller of this task.
|
|
||||||
*
|
|
||||||
* This method can call {@link #publishProgress(Object[])} to publish updates
|
|
||||||
* on the UI thread.
|
|
||||||
*
|
|
||||||
* @params params The parameters of the task.
|
|
||||||
*
|
|
||||||
* @return A result, defined by the subclass of this task.
|
|
||||||
*
|
|
||||||
* @see #begin()
|
|
||||||
* @see #end(Object)
|
|
||||||
* @see #publishProgress(Object[])
|
|
||||||
*/
|
|
||||||
public Result doInBackground(@SuppressWarnings("unused") Params... params) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs on the UI thread before {@link #doInBackground(Object[])}.
|
|
||||||
*
|
|
||||||
* @see #end(Object)
|
|
||||||
* @see #doInBackground(Object[])
|
|
||||||
*/
|
|
||||||
public void begin() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs on the UI thread after {@link #doInBackground(Object[])}. The
|
|
||||||
* specified result is the value returned by {@link #doInBackground(Object[])}
|
|
||||||
* or null if the task was cancelled or an exception occured.
|
|
||||||
*
|
|
||||||
* @see #begin()
|
|
||||||
* @see #doInBackground(Object[])
|
|
||||||
*/
|
|
||||||
public void end(@SuppressWarnings("unused") Result result) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs on the UI thread after {@link #publishProgress(Object[])} is invoked.
|
|
||||||
* The specified values are the values passed to {@link #publishProgress(Object[])}.
|
|
||||||
*
|
|
||||||
* @see #publishProgress(Object[])
|
|
||||||
* @see #doInBackground(Object[])
|
|
||||||
*/
|
|
||||||
public void processProgress(@SuppressWarnings("unused") Progress... values) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns <tt>true</tt> if this task was cancelled before it completed
|
|
||||||
* normally.
|
|
||||||
*
|
|
||||||
* @return <tt>true</tt> if task was cancelled before it completed
|
|
||||||
*
|
|
||||||
* @see #cancel(boolean)
|
|
||||||
*/
|
|
||||||
public final boolean isCancelled() {
|
|
||||||
return mFuture.isCancelled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to cancel execution of this task. This attempt will
|
|
||||||
* fail if the task has already completed, already been cancelled,
|
|
||||||
* or could not be cancelled for some other reason. If successful,
|
|
||||||
* and this task has not started when <tt>cancel</tt> is called,
|
|
||||||
* this task should never run. If the task has already started,
|
|
||||||
* then the <tt>mayInterruptIfRunning</tt> parameter determines
|
|
||||||
* whether the thread executing this task should be interrupted in
|
|
||||||
* an attempt to stop the task.
|
|
||||||
*
|
|
||||||
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
|
|
||||||
* task should be interrupted; otherwise, in-progress tasks are allowed
|
|
||||||
* to complete.
|
|
||||||
*
|
|
||||||
* @return <tt>false</tt> if the task could not be cancelled,
|
|
||||||
* typically because it has already completed normally;
|
|
||||||
* <tt>true</tt> otherwise
|
|
||||||
*
|
|
||||||
* @see #isCancelled()
|
|
||||||
*/
|
|
||||||
public final boolean cancel(boolean mayInterruptIfRunning) {
|
|
||||||
return mFuture.cancel(mayInterruptIfRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits if necessary for the computation to complete, and then
|
|
||||||
* retrieves its result.
|
|
||||||
*
|
|
||||||
* @return The computed result.
|
|
||||||
*
|
|
||||||
* @throws CancellationException If the computation was cancelled.
|
|
||||||
* @throws ExecutionException If the computation threw an exception.
|
|
||||||
* @throws InterruptedException If the current thread was interrupted
|
|
||||||
* while waiting.
|
|
||||||
*/
|
|
||||||
public final Result get() throws InterruptedException, ExecutionException {
|
|
||||||
return mFuture.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits if necessary for at most the given time for the computation
|
|
||||||
* to complete, and then retrieves its result.
|
|
||||||
*
|
|
||||||
* @return The computed result.
|
|
||||||
*
|
|
||||||
* @throws CancellationException If the computation was cancelled.
|
|
||||||
* @throws ExecutionException If the computation threw an exception.
|
|
||||||
* @throws InterruptedException If the current thread was interrupted
|
|
||||||
* while waiting.
|
|
||||||
* @throws TimeoutException If the wait timed out.
|
|
||||||
*/
|
|
||||||
public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
|
|
||||||
ExecutionException, TimeoutException {
|
|
||||||
return mFuture.get(timeout, unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the task with the specified parameters. The task returns
|
|
||||||
* itself (this) so that the caller can keep a reference to it.
|
|
||||||
*
|
|
||||||
* This method must be invoked on the UI thread.
|
|
||||||
*
|
|
||||||
* @params params The parameters of the task.
|
|
||||||
*
|
|
||||||
* @return This instance of UserTask.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If {@link #getStatus()} returns either
|
|
||||||
* {@link com.google.android.photostream.UserTask.Status#RUNNING} or
|
|
||||||
* {@link com.google.android.photostream.UserTask.Status#FINISHED}.
|
|
||||||
*/
|
|
||||||
public final UserTask<Params, Progress, Result> execute(Params... params) {
|
|
||||||
if (mStatus != Status.PENDING) {
|
|
||||||
switch (mStatus) {
|
|
||||||
case RUNNING:
|
|
||||||
throw new IllegalStateException("Cannot execute task:"
|
|
||||||
+ " the task is already running.");
|
|
||||||
case FINISHED:
|
|
||||||
throw new IllegalStateException("Cannot execute task:"
|
|
||||||
+ " the task has already been executed "
|
|
||||||
+ "(a task can be executed only once)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mStatus = Status.RUNNING;
|
|
||||||
|
|
||||||
begin();
|
|
||||||
|
|
||||||
mWorker.mParams = params;
|
|
||||||
|
|
||||||
try {
|
|
||||||
sExecutor.execute(mFuture);
|
|
||||||
} catch (RejectedExecutionException e) {
|
|
||||||
// cannot schedule because of some other error. just die quietly
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be invoked from {@link #doInBackground(Object[])} to
|
|
||||||
* publish updates on the UI thread while the background computation is
|
|
||||||
* still running. Each call to this method will trigger the execution of
|
|
||||||
* {@link #processProgress(Object[])} on the UI thread.
|
|
||||||
*
|
|
||||||
* @params values The progress values to update the UI with.
|
|
||||||
*
|
|
||||||
* @see #processProgress(Object[])
|
|
||||||
* @see #doInBackground(Object[])
|
|
||||||
*/
|
|
||||||
protected final void publishProgress(Progress... values) {
|
|
||||||
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
|
|
||||||
new UserTaskResult<Progress>(this, values)).sendToTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void finish(Result result) {
|
|
||||||
end(result);
|
|
||||||
mStatus = Status.FINISHED;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class InternalHandler extends Handler {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
UserTaskResult result = (UserTaskResult) msg.obj;
|
|
||||||
switch (msg.what) {
|
|
||||||
case MESSAGE_POST_RESULT:
|
|
||||||
// There is only one result
|
|
||||||
result.mTask.finish(result.mData[0]);
|
|
||||||
break;
|
|
||||||
case MESSAGE_POST_PROGRESS:
|
|
||||||
result.mTask.processProgress(result.mData);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
|
|
||||||
Params[] mParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class UserTaskResult<Data> {
|
|
||||||
final UserTask<?, ?, ?> mTask;
|
|
||||||
final Data[] mData;
|
|
||||||
|
|
||||||
UserTaskResult(UserTask<?, ?, ?> task, Data... data) {
|
|
||||||
mTask = task;
|
|
||||||
mData = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,331 +0,0 @@
|
|||||||
/**
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.common;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.timsu.astrid.R;
|
|
||||||
import com.todoroo.andlib.data.Property.LongProperty;
|
|
||||||
import com.todoroo.andlib.data.TodorooCursor;
|
|
||||||
import com.todoroo.andlib.service.NotificationManager;
|
|
||||||
import com.todoroo.astrid.model.Task;
|
|
||||||
import com.todoroo.astrid.utility.Constants;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper class for writing synchronization services for Astrid. This class
|
|
||||||
* contains logic for merging incoming changes and writing outgoing changes.
|
|
||||||
* <p>
|
|
||||||
* Use {@link initiate} as the entry point for your synchronization service,
|
|
||||||
* which should handle authentication and then call {@link synchronizeTasks} to
|
|
||||||
* initiate synchronization.
|
|
||||||
*
|
|
||||||
* @author timsu
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class SyncProvider<TYPE extends TaskContainer> {
|
|
||||||
|
|
||||||
// --- abstract methods - your services should implement these
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform log in (launching activity if necessary) and sync. This is
|
|
||||||
* invoked when users manually request synchronization
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
* context
|
|
||||||
*/
|
|
||||||
abstract protected void initiateManual(Activity activity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform synchronize. Since this can be called from background services,
|
|
||||||
* you should not open up new activities. Instead, if the user is not signed
|
|
||||||
* in, your service should do nothing.
|
|
||||||
*
|
|
||||||
* @param service
|
|
||||||
* context
|
|
||||||
*/
|
|
||||||
abstract protected void initiateBackground(Service service);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the text of a notification and the intent to open when tapped
|
|
||||||
* @param context
|
|
||||||
* @param notification
|
|
||||||
*/
|
|
||||||
abstract protected void updateNotification(Context context, Notification n);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deal with an exception that occurs during synchronization
|
|
||||||
*
|
|
||||||
* @param tag
|
|
||||||
* short string description of where error occurred
|
|
||||||
* @param e
|
|
||||||
* exception
|
|
||||||
* @param displayError
|
|
||||||
* whether to display error to the user
|
|
||||||
*/
|
|
||||||
abstract protected void handleException(String tag, Exception e, boolean displayError);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a task on the remote server.
|
|
||||||
*
|
|
||||||
* @param task
|
|
||||||
* task to create
|
|
||||||
* @return task created on remote server
|
|
||||||
*/
|
|
||||||
abstract protected TYPE create(TYPE task) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push variables from given task to the remote server.
|
|
||||||
*
|
|
||||||
* @param task
|
|
||||||
* task proxy to push
|
|
||||||
* @param remoteTask
|
|
||||||
* remote task that we merged with. may be null
|
|
||||||
*/
|
|
||||||
abstract protected void push(TYPE task, TYPE remote) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch remote task. Used to re-read merged tasks
|
|
||||||
*
|
|
||||||
* @param task
|
|
||||||
* task with id's to re-read
|
|
||||||
* @return new Task
|
|
||||||
*/
|
|
||||||
abstract protected TYPE pull(TYPE task) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a task container from a task in the database
|
|
||||||
*
|
|
||||||
* @param task
|
|
||||||
*/
|
|
||||||
abstract protected TYPE read(TodorooCursor<Task> task) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save task. Used to save local tasks that have been updated and remote
|
|
||||||
* tasks that need to be created locally
|
|
||||||
*
|
|
||||||
* @param task
|
|
||||||
*/
|
|
||||||
abstract protected void write(TYPE task) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a task in the list with the same remote identifier(s) as
|
|
||||||
* the task passed in
|
|
||||||
*
|
|
||||||
* @return task from list if matches, null otherwise
|
|
||||||
*/
|
|
||||||
abstract protected int matchTask(ArrayList<TYPE> tasks, TYPE target);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transfer remote identifier(s) from one task to another
|
|
||||||
*/
|
|
||||||
abstract protected void transferIdentifiers(TYPE source,
|
|
||||||
TYPE destination);
|
|
||||||
|
|
||||||
// --- implementation
|
|
||||||
|
|
||||||
private final Notification notification;
|
|
||||||
|
|
||||||
public SyncProvider() {
|
|
||||||
// initialize notification
|
|
||||||
int icon = android.R.drawable.stat_notify_sync;
|
|
||||||
long when = System.currentTimeMillis();
|
|
||||||
notification = new Notification(icon, null, when);
|
|
||||||
notification.flags |= Notification.FLAG_ONGOING_EVENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void synchronize(final Context context) {
|
|
||||||
// display toast
|
|
||||||
if(context instanceof Activity) {
|
|
||||||
((Activity) context).runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(context, R.string.SyP_progress_toast,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
initiateManual((Activity)context);
|
|
||||||
} else if(context instanceof Service) {
|
|
||||||
// display notification
|
|
||||||
updateNotification(context, notification);
|
|
||||||
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
|
|
||||||
nm.notify(Constants.NOTIFICATION_SYNC, notification);
|
|
||||||
|
|
||||||
// start next step in background thread
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
initiateBackground((Service)context);
|
|
||||||
} finally {
|
|
||||||
nm.cancel(Constants.NOTIFICATION_SYNC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- synchronization logic
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to synchronize remote tasks with our local database.
|
|
||||||
*
|
|
||||||
* This initiates the following process: 1. local changes are read 2. remote
|
|
||||||
* changes are read 3. local tasks are merged with remote changes and pushed
|
|
||||||
* across 4. remote changes are then read in
|
|
||||||
*
|
|
||||||
* @param data synchronization data structure
|
|
||||||
*/
|
|
||||||
protected void synchronizeTasks(SyncData<TYPE> data) throws IOException {
|
|
||||||
int length;
|
|
||||||
|
|
||||||
// create internal data structures
|
|
||||||
HashMap<String, Integer> remoteNewTaskNameMap = new HashMap<String, Integer>();
|
|
||||||
length = data.remoteUpdated.size();
|
|
||||||
for(int i = 0; i < length; i++) {
|
|
||||||
TYPE remote = data.remoteUpdated.get(i);
|
|
||||||
if(remote.task.getId() != Task.NO_ID)
|
|
||||||
continue;
|
|
||||||
remoteNewTaskNameMap.put(remote.task.getValue(Task.TITLE), i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. CREATE: grab newly created tasks and create them remotely
|
|
||||||
length = data.localCreated.getCount();
|
|
||||||
for(int i = 0; i < length; i++) {
|
|
||||||
data.localCreated.moveToNext();
|
|
||||||
TYPE local = read(data.localCreated);
|
|
||||||
try {
|
|
||||||
|
|
||||||
String taskTitle = local.task.getValue(Task.TITLE);
|
|
||||||
|
|
||||||
/* If there exists an incoming remote task with the same name and no
|
|
||||||
* mapping, we don't want to create this on the remote server,
|
|
||||||
* because user could have synchronized like this before. Instead,
|
|
||||||
* we create a mapping and do an update.
|
|
||||||
*/
|
|
||||||
if (remoteNewTaskNameMap.containsKey(taskTitle)) {
|
|
||||||
int remoteIndex = remoteNewTaskNameMap.remove(taskTitle);
|
|
||||||
TYPE remote = data.remoteUpdated.get(remoteIndex);
|
|
||||||
|
|
||||||
transferIdentifiers(remote, local);
|
|
||||||
push(local, remote);
|
|
||||||
|
|
||||||
// re-read remote task after merge, update remote task list
|
|
||||||
remote = pull(remote);
|
|
||||||
remote.task.setId(local.task.getId());
|
|
||||||
data.remoteUpdated.set(remoteIndex, remote);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
create(local);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleException("sync-local-created", e, false); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
write(local);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. UPDATE: for each updated local task
|
|
||||||
length = data.localUpdated.getCount();
|
|
||||||
for(int i = 0; i < length; i++) {
|
|
||||||
data.localUpdated.moveToNext();
|
|
||||||
TYPE local = read(data.localUpdated);
|
|
||||||
try {
|
|
||||||
if(local.task == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// if there is a conflict, merge
|
|
||||||
int remoteIndex = matchTask((ArrayList<TYPE>)data.remoteUpdated, local);
|
|
||||||
if(remoteIndex != -1) {
|
|
||||||
TYPE remote = data.remoteUpdated.get(remoteIndex);
|
|
||||||
push(local, remote);
|
|
||||||
|
|
||||||
// re-read remote task after merge
|
|
||||||
remote = pull(remote);
|
|
||||||
remote.task.setId(local.task.getId());
|
|
||||||
data.remoteUpdated.set(remoteIndex, remote);
|
|
||||||
} else {
|
|
||||||
push(local, null);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleException("sync-local-updated", e, false); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
write(local);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. REMOTE: load remote information
|
|
||||||
|
|
||||||
// Rearrange remoteTasks so completed tasks get synchronized first.
|
|
||||||
// This prevents bugs where a repeated task has two copies come down
|
|
||||||
// the wire, the new version and the completed old version. The new
|
|
||||||
// version would get merged, then completed, if done in the wrong order.
|
|
||||||
|
|
||||||
Collections.sort(data.remoteUpdated, new Comparator<TYPE>() {
|
|
||||||
private static final int SENTINEL = -2;
|
|
||||||
private final int check(TYPE o1, TYPE o2, LongProperty property) {
|
|
||||||
long o1Property = o1.task.getValue(property);
|
|
||||||
long o2Property = o2.task.getValue(property);
|
|
||||||
if(o1Property != 0 && o2Property != 0)
|
|
||||||
return 0;
|
|
||||||
else if(o1Property != 0)
|
|
||||||
return -1;
|
|
||||||
else if(o2Property != 0)
|
|
||||||
return 1;
|
|
||||||
return SENTINEL;
|
|
||||||
}
|
|
||||||
public int compare(TYPE o1, TYPE o2) {
|
|
||||||
int comparison = check(o1, o2, Task.DELETION_DATE);
|
|
||||||
if(comparison != SENTINEL)
|
|
||||||
return comparison;
|
|
||||||
comparison = check(o1, o2, Task.COMPLETION_DATE);
|
|
||||||
if(comparison != SENTINEL)
|
|
||||||
return comparison;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
length = data.remoteUpdated.size();
|
|
||||||
for(int i = 0; i < length; i++) {
|
|
||||||
TYPE remote = data.remoteUpdated.get(i);
|
|
||||||
|
|
||||||
// don't synchronize new & deleted / completed tasks
|
|
||||||
if(!remote.task.isSaved() && (remote.task.isDeleted() ||
|
|
||||||
remote.task.isCompleted()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
write(remote);
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleException("sync-remote-updated", e, false); //$NON-NLS-1$
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helper classes
|
|
||||||
|
|
||||||
/** data structure builder */
|
|
||||||
protected static class SyncData<TYPE extends TaskContainer> {
|
|
||||||
public final ArrayList<TYPE> remoteUpdated;
|
|
||||||
|
|
||||||
public final TodorooCursor<Task> localCreated;
|
|
||||||
public final TodorooCursor<Task> localUpdated;
|
|
||||||
|
|
||||||
public SyncData(ArrayList<TYPE> remoteUpdated,
|
|
||||||
TodorooCursor<Task> localCreated,
|
|
||||||
TodorooCursor<Task> localUpdated) {
|
|
||||||
super();
|
|
||||||
this.remoteUpdated = remoteUpdated;
|
|
||||||
this.localCreated = localCreated;
|
|
||||||
this.localUpdated = localUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
package com.todoroo.astrid.common;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.DialogInterface.OnClickListener;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.Preference;
|
|
||||||
import android.preference.Preference.OnPreferenceClickListener;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup.OnHierarchyChangeListener;
|
|
||||||
|
|
||||||
import com.timsu.astrid.R;
|
|
||||||
import com.todoroo.andlib.service.Autowired;
|
|
||||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.andlib.utility.DialogUtilities;
|
|
||||||
import com.todoroo.andlib.widget.TodorooPreferences;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for common synchronization action: displaying synchronization
|
|
||||||
* preferences and an action panel so users can initiate actions from the menu.
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
abstract public class SyncProviderPreferences extends TodorooPreferences {
|
|
||||||
|
|
||||||
// --- interface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return your preference resource
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
abstract public int getPreferenceResource();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* kick off synchronization
|
|
||||||
*/
|
|
||||||
abstract public void startSync();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* log user out
|
|
||||||
*/
|
|
||||||
abstract public void logOut();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return get preference utilities
|
|
||||||
*/
|
|
||||||
abstract public SyncProviderUtilities getUtilities();
|
|
||||||
|
|
||||||
|
|
||||||
// --- implementation
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private DialogUtilities dialogUtilities;
|
|
||||||
|
|
||||||
private int statusColor = Color.BLACK;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChildViewRemoved(View parent, View child) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChildViewAdded(View parent, View child) {
|
|
||||||
View view = findViewById(R.id.status);
|
|
||||||
if(view != null)
|
|
||||||
view.setBackgroundColor(statusColor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param resource
|
|
||||||
* if null, updates all resources
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void updatePreferences(Preference preference, Object value) {
|
|
||||||
final Resources r = getResources();
|
|
||||||
|
|
||||||
// interval
|
|
||||||
if (r.getString(getUtilities().getSyncIntervalKey()).equals(
|
|
||||||
preference.getKey())) {
|
|
||||||
int index = AndroidUtilities.indexOf(
|
|
||||||
r.getStringArray(R.array.sync_SPr_interval_values),
|
|
||||||
(String) value);
|
|
||||||
if (index <= 0)
|
|
||||||
preference.setSummary(R.string.sync_SPr_interval_desc_disabled);
|
|
||||||
else
|
|
||||||
preference.setSummary(r.getString(
|
|
||||||
R.string.sync_SPr_interval_desc,
|
|
||||||
r.getStringArray(R.array.sync_SPr_interval_entries)[index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// status
|
|
||||||
else if (r.getString(R.string.sync_SPr_status_key).equals(preference.getKey())) {
|
|
||||||
boolean loggedIn = getUtilities().isLoggedIn();
|
|
||||||
String status;
|
|
||||||
String subtitle = ""; //$NON-NLS-1$
|
|
||||||
|
|
||||||
// ! logged in - display message, click -> sync
|
|
||||||
if(!loggedIn) {
|
|
||||||
status = r.getString(R.string.sync_status_loggedout);
|
|
||||||
statusColor = Color.RED;
|
|
||||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference p) {
|
|
||||||
startSync();
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// sync is occurring
|
|
||||||
else if(getUtilities().isOngoing()) {
|
|
||||||
status = r.getString(R.string.sync_status_ongoing);
|
|
||||||
statusColor = Color.rgb(0, 0, 100);
|
|
||||||
}
|
|
||||||
// last sync was error
|
|
||||||
else if(getUtilities().getLastAttemptedSyncDate() != 0) {
|
|
||||||
status = r.getString(R.string.sync_status_failed,
|
|
||||||
DateUtilities.getDateStringWithTime(SyncProviderPreferences.this,
|
|
||||||
new Date(getUtilities().getLastAttemptedSyncDate())));
|
|
||||||
if(getUtilities().getLastSyncDate() > 0) {
|
|
||||||
subtitle = r.getString(R.string.sync_status_failed_subtitle,
|
|
||||||
DateUtilities.getDateStringWithTime(SyncProviderPreferences.this,
|
|
||||||
new Date(getUtilities().getLastSyncDate())));
|
|
||||||
}
|
|
||||||
statusColor = Color.rgb(100, 0, 0);
|
|
||||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference p) {
|
|
||||||
String error = getUtilities().getLastError();
|
|
||||||
if(error != null)
|
|
||||||
dialogUtilities.okDialog(SyncProviderPreferences.this, error, null);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if(getUtilities().getLastSyncDate() > 0) {
|
|
||||||
status = r.getString(R.string.sync_status_success,
|
|
||||||
DateUtilities.getDateStringWithTime(SyncProviderPreferences.this,
|
|
||||||
new Date(getUtilities().getLastSyncDate())));
|
|
||||||
statusColor = Color.rgb(0, 100, 0);
|
|
||||||
} else {
|
|
||||||
status = r.getString(R.string.sync_status_never);
|
|
||||||
statusColor = Color.rgb(0, 0, 100);
|
|
||||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference p) {
|
|
||||||
startSync();
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
preference.setTitle(status);
|
|
||||||
preference.setSummary(subtitle);
|
|
||||||
|
|
||||||
View view = findViewById(R.id.status);
|
|
||||||
if(view != null)
|
|
||||||
view.setBackgroundColor(statusColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync button
|
|
||||||
else if (r.getString(R.string.sync_SPr_sync_key).equals(preference.getKey())) {
|
|
||||||
boolean loggedIn = getUtilities().isLoggedIn();
|
|
||||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference p) {
|
|
||||||
startSync();
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(!loggedIn)
|
|
||||||
preference.setTitle(R.string.sync_SPr_sync_log_in);
|
|
||||||
}
|
|
||||||
|
|
||||||
// log out button
|
|
||||||
else if (r.getString(R.string.sync_SPr_forget_key).equals(preference.getKey())) {
|
|
||||||
boolean loggedIn = getUtilities().isLoggedIn();
|
|
||||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
|
||||||
public boolean onPreferenceClick(Preference p) {
|
|
||||||
dialogUtilities.okCancelDialog(SyncProviderPreferences.this,
|
|
||||||
r.getString(R.string.sync_forget_confirm), new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog,
|
|
||||||
int which) {
|
|
||||||
logOut();
|
|
||||||
initializePreference(getPreferenceScreen());
|
|
||||||
}
|
|
||||||
}, null);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(!loggedIn)
|
|
||||||
preference.setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,137 +0,0 @@
|
|||||||
package com.todoroo.astrid.common;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.Editor;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.service.ContextManager;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync Provider Utility class for accessing preferences
|
|
||||||
*/
|
|
||||||
abstract public class SyncProviderUtilities {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return your plugin identifier
|
|
||||||
*/
|
|
||||||
abstract public String getIdentifier();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return key for sync interval
|
|
||||||
*/
|
|
||||||
abstract public int getSyncIntervalKey();
|
|
||||||
|
|
||||||
// --- implementation
|
|
||||||
|
|
||||||
private static final String PREF_TOKEN = "_token"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
private static final String PREF_LAST_SYNC = "_last_sync"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
private static final String PREF_LAST_ATTEMPTED_SYNC = "_last_attempted"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
private static final String PREF_LAST_ERROR = "_last_error"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
private static final String PREF_ONGOING = "_ongoing"; //$NON-NLS-1$
|
|
||||||
|
|
||||||
/** Get preferences object from the context */
|
|
||||||
protected static SharedPreferences getPrefs() {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(ContextManager.getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if we have a token for this user, false otherwise
|
|
||||||
*/
|
|
||||||
public boolean isLoggedIn() {
|
|
||||||
return getPrefs().getString(getIdentifier() + PREF_TOKEN, null) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** authentication token, or null if doesn't exist */
|
|
||||||
public String getToken() {
|
|
||||||
return getPrefs().getString(getIdentifier() + PREF_TOKEN, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the authentication token. Set to null to clear. */
|
|
||||||
public void setToken(String setting) {
|
|
||||||
Editor editor = getPrefs().edit();
|
|
||||||
editor.putString(getIdentifier() + PREF_TOKEN, setting);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return Last Successful Sync Date, or 0 */
|
|
||||||
public long getLastSyncDate() {
|
|
||||||
return getPrefs().getLong(getIdentifier() + PREF_LAST_SYNC, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return Last Attempted Sync Date, or 0 if it was successful */
|
|
||||||
public long getLastAttemptedSyncDate() {
|
|
||||||
return getPrefs().getLong(getIdentifier() + PREF_LAST_ATTEMPTED_SYNC, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return Last Error, or null if no last error */
|
|
||||||
public String getLastError() {
|
|
||||||
return getPrefs().getString(PREF_LAST_ERROR, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return Last Error, or null if no last error */
|
|
||||||
public boolean isOngoing() {
|
|
||||||
return getPrefs().getBoolean(getIdentifier() + PREF_ONGOING, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Deletes Last Successful Sync Date */
|
|
||||||
public void clearLastSyncDate() {
|
|
||||||
Editor editor = getPrefs().edit();
|
|
||||||
editor.remove(getIdentifier() + PREF_LAST_SYNC);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set Last Successful Sync Date */
|
|
||||||
public void setLastError(String error) {
|
|
||||||
Editor editor = getPrefs().edit();
|
|
||||||
editor.putString(getIdentifier() + PREF_LAST_ERROR, error);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set Ongoing */
|
|
||||||
public void stopOngoing() {
|
|
||||||
Editor editor = getPrefs().edit();
|
|
||||||
editor.putBoolean(getIdentifier() + PREF_ONGOING, false);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set Last Successful Sync Date */
|
|
||||||
public void recordSuccessfulSync() {
|
|
||||||
Editor editor = getPrefs().edit();
|
|
||||||
editor.putLong(getIdentifier() + PREF_LAST_SYNC, DateUtilities.now());
|
|
||||||
editor.putLong(getIdentifier() + PREF_LAST_ATTEMPTED_SYNC, 0);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set Last Attempted Sync Date */
|
|
||||||
public void recordSyncStart() {
|
|
||||||
Editor editor = getPrefs().edit();
|
|
||||||
editor.putLong(getIdentifier() + PREF_LAST_ATTEMPTED_SYNC,
|
|
||||||
DateUtilities.now());
|
|
||||||
editor.putString(getIdentifier() + PREF_LAST_ERROR, null);
|
|
||||||
editor.putBoolean(getIdentifier() + PREF_ONGOING, true);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the frequency, in seconds, auto-sync should occur.
|
|
||||||
*
|
|
||||||
* @return seconds duration, or 0 if not desired
|
|
||||||
*/
|
|
||||||
public int getSyncAutoSyncFrequency() {
|
|
||||||
String value = getPrefs().getString(
|
|
||||||
ContextManager.getContext().getString(
|
|
||||||
getSyncIntervalKey()), null);
|
|
||||||
if (value == null)
|
|
||||||
return 0;
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(value);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
package com.todoroo.astrid.common;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
|
||||||
import com.todoroo.astrid.model.Metadata;
|
|
||||||
import com.todoroo.astrid.model.Task;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Container class for transmitting tasks and including local and remote
|
|
||||||
* metadata. Synchronization Providers can subclass this class if desired.
|
|
||||||
*
|
|
||||||
* @see {@link SyncProvider}
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TaskContainer {
|
|
||||||
public Task task;
|
|
||||||
public ArrayList<Metadata> metadata;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the metadata contains anything with the given key
|
|
||||||
* @param key
|
|
||||||
* @return first match. or null
|
|
||||||
*/
|
|
||||||
public Metadata findMetadata(String key) {
|
|
||||||
for(Metadata item : metadata) {
|
|
||||||
if(AndroidUtilities.equals(key, item.getValue(Metadata.KEY)))
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue