true
to add a space in front of each word, false
otherwise.
- * @return This instance.
- */
- public NGramGenerator setAddSpaceInFront(boolean addSpace)
- {
- mAddSpaceInFront = addSpace;
- return this;
- }
-
-
- /**
- * Sets the {@link Locale} to use when converting the input string to lower case. This has no effect when {@link #setAllLowercase(boolean)} is called with
- * false
.
- *
- * @param locale
- * The {@link Locale} to user for the conversion to lower case.
- * @return This instance.
- */
- public NGramGenerator setLocale(Locale locale)
- {
- mLocale = locale;
- return this;
- }
-
-
- /**
- * Get all N-grams contained in the given String.
- *
- * @param data
- * The String to analyze.
- * @return A {@link Set} containing all N-grams.
- */
- public Setnull
to create a new set.
- * @param data
- * The String to analyze.
- *
- * @return The {@link Set} containing the N-grams.
- */
- public Settrue
to add a space in front of each word, false
otherwise.
+ *
+ * @return This instance.
+ */
+ public NGramGenerator setAddSpaceInFront(boolean addSpace)
+ {
+ mAddSpaceInFront = addSpace;
+ return this;
+ }
+
+
+ /**
+ * Sets the {@link Locale} to use when converting the input string to lower case. This has no effect when {@link #setAllLowercase(boolean)} is called with
+ * false
.
+ *
+ * @param locale
+ * The {@link Locale} to user for the conversion to lower case.
+ *
+ * @return This instance.
+ */
+ public NGramGenerator setLocale(Locale locale)
+ {
+ mLocale = locale;
+ return this;
+ }
+
+
+ /**
+ * Get all N-grams contained in the given String.
+ *
+ * @param data
+ * The String to analyze.
+ *
+ * @return A {@link Set} containing all N-grams.
+ */
+ public Setnull
to create a new set.
+ * @param data
+ * The String to analyze.
+ *
+ * @return The {@link Set} containing the N-grams.
+ */
+ public Setnull
.
- */
- public void fire(Context context, ContentValues values)
- {
- context.getContentResolver().update(uri(TaskContract.taskAuthority(context)), values == null ? new ContentValues() : values, null, null);
- }
-
-
- /**
- * Run the operation on the given handler.
- *
- * @param context
- * A {@link Context}.
- * @param handler
- * A {@link Handler} to run the operation on.
- * @param uri
- * The {@link Uri} that triggered this operation.
- * @param db
- * The database.
- * @param values
- * The {@link ContentValues} that were supplied.
- */
- void run(final Context context, Handler handler, final Uri uri, final SQLiteDatabase db, final ContentValues values)
- {
- handler.post(new Runnable()
- {
- @Override
- public void run()
- {
- synchronized (mLock)
- {
- mHandler.handleOperation(context, uri, db, values);
- }
- }
- });
- }
-
-
- /**
- * Returns the {@link Uri} that triggers this {@link ContentOperation}.
- *
- * @param authority
- * The authority of this provide.
- * @return A {@link Uri}.
- */
- private Uri uri(String authority)
- {
- return new Uri.Builder().scheme("content").authority(authority).path(BASE_PATH).appendPath(this.toString()).build();
- }
-
-
- /**
- * Register the operations with the given {@link UriMatcher}.
- *
- * @param uriMatcher
- * The {@link UriMatcher}.
- * @param authority
- * The authority of this TaskProvider.
- * @param firstID
- * Teh first Id to use for our Uris.
- */
- public static void register(UriMatcher uriMatcher, String authority, int firstID)
- {
- for (ContentOperation op : values())
- {
- Uri uri = op.uri(authority);
- uriMatcher.addURI(authority, uri.getPath().substring(1) /* remove leading slash */, firstID + op.ordinal());
- }
- }
-
-
- /**
- * Return a {@link ContentOperation} that belongs to the given id.
- *
- * @param id
- * The id or the {@link ContentOperation}.
- * @param firstId
- * The first ID to use for Uris.
- * @return The respective {@link ContentOperation} or null
if none was found.
- */
- public static ContentOperation get(int id, int firstId)
- {
- if (id < firstId)
- {
- return null;
- }
-
- if (id - firstId >= values().length)
- {
- return null;
- }
-
- return values()[id - firstId];
- }
-
- public interface OperationHandler
- {
- public void handleOperation(Context context, Uri uri, SQLiteDatabase db, ContentValues values);
- }
+ /**
+ * When the local timezone has been changed we need to update the due and start sorting values. This handler will take care of running the appropriate
+ * update. In addition it fires an operation to update all notifications.
+ */
+ UPDATE_TIMEZONE(new OperationHandler()
+ {
+ @Override
+ public void handleOperation(Context context, Uri uri, SQLiteDatabase db, ContentValues values)
+ {
+ long start = System.currentTimeMillis();
+
+ // request an update of all instance values
+ ContentValues vals = new ContentValues(1);
+ TaskInstancesProcessor.addUpdateRequest(vals);
+
+ // execute update that triggers a recalculation of all due and start sorting values
+ int count = context.getContentResolver().update(
+ TaskContract.Tasks.getContentUri(uri.getAuthority()).buildUpon().appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true").build(),
+ vals, null, null);
+
+ Log.i("TaskProvider", "time to update " + count + " tasks: " + (System.currentTimeMillis() - start) + " ms");
+
+ // now update alarms as well
+ UPDATE_NOTIFICATION_ALARM.fire(context, null);
+ }
+ }),
+
+ /**
+ * Takes care of everything we need to send task start and task due broadcasts.
+ */
+ POST_NOTIFICATIONS(new OperationHandler()
+ {
+
+ @Override
+ public void handleOperation(Context context, Uri uri, SQLiteDatabase db, ContentValues values)
+ {
+ TimeZone localTimeZone = TimeZone.getDefault();
+
+ // the date-time of when the last notification was shown
+ DateTime lastAlarm = getLastAlarmTimestamp(context);
+ // the current time, we show all notifications between null
.
+ */
+ public void fire(Context context, ContentValues values)
+ {
+ context.getContentResolver().update(uri(TaskContract.taskAuthority(context)), values == null ? new ContentValues() : values, null, null);
+ }
+
+
+ /**
+ * Run the operation on the given handler.
+ *
+ * @param context
+ * A {@link Context}.
+ * @param handler
+ * A {@link Handler} to run the operation on.
+ * @param uri
+ * The {@link Uri} that triggered this operation.
+ * @param db
+ * The database.
+ * @param values
+ * The {@link ContentValues} that were supplied.
+ */
+ void run(final Context context, Handler handler, final Uri uri, final SQLiteDatabase db, final ContentValues values)
+ {
+ handler.post(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ synchronized (mLock)
+ {
+ mHandler.handleOperation(context, uri, db, values);
+ }
+ }
+ });
+ }
+
+
+ /**
+ * Returns the {@link Uri} that triggers this {@link ContentOperation}.
+ *
+ * @param authority
+ * The authority of this provide.
+ *
+ * @return A {@link Uri}.
+ */
+ private Uri uri(String authority)
+ {
+ return new Uri.Builder().scheme("content").authority(authority).path(BASE_PATH).appendPath(this.toString()).build();
+ }
+
+
+ /**
+ * Register the operations with the given {@link UriMatcher}.
+ *
+ * @param uriMatcher
+ * The {@link UriMatcher}.
+ * @param authority
+ * The authority of this TaskProvider.
+ * @param firstID
+ * Teh first Id to use for our Uris.
+ */
+ public static void register(UriMatcher uriMatcher, String authority, int firstID)
+ {
+ for (ContentOperation op : values())
+ {
+ Uri uri = op.uri(authority);
+ uriMatcher.addURI(authority, uri.getPath().substring(1) /* remove leading slash */, firstID + op.ordinal());
+ }
+ }
+
+
+ /**
+ * Return a {@link ContentOperation} that belongs to the given id.
+ *
+ * @param id
+ * The id or the {@link ContentOperation}.
+ * @param firstId
+ * The first ID to use for Uris.
+ *
+ * @return The respective {@link ContentOperation} or null
if none was found.
+ */
+ public static ContentOperation get(int id, int firstId)
+ {
+ if (id < firstId)
+ {
+ return null;
+ }
+
+ if (id - firstId >= values().length)
+ {
+ return null;
+ }
+
+ return values()[id - firstId];
+ }
+
+
+ public interface OperationHandler
+ {
+ public void handleOperation(Context context, Uri uri, SQLiteDatabase db, ContentValues values);
+ }
}
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/FTSDatabaseHelper.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/FTSDatabaseHelper.java
index 60a098f042186eafa9fdf424518aafbc8f790016..957a925f64841dc505ed82a0c29c98edf356fa6d 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/FTSDatabaseHelper.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/FTSDatabaseHelper.java
@@ -17,8 +17,10 @@
package org.dmfs.provider.tasks;
-import java.util.HashSet;
-import java.util.Set;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.text.TextUtils;
import org.dmfs.ngrams.NGramGenerator;
import org.dmfs.provider.tasks.TaskContract.Properties;
@@ -27,10 +29,8 @@ import org.dmfs.provider.tasks.TaskContract.Tasks;
import org.dmfs.provider.tasks.TaskDatabaseHelper.Tables;
import org.dmfs.provider.tasks.model.TaskAdapter;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.text.TextUtils;
+import java.util.HashSet;
+import java.util.Set;
/**
@@ -42,518 +42,526 @@ import android.text.TextUtils;
public class FTSDatabaseHelper
{
- private final static float SEARCH_RESULTS_MIN_SCORE = 0.4f;
-
- /**
- * A Generator for 3-grams.
- */
- private final static NGramGenerator TRIGRAM_GENERATOR = new NGramGenerator(3, 1).setAddSpaceInFront(true);
-
- /**
- * A Generator for 4-grams.
- */
- private final static NGramGenerator TETRAGRAM_GENERATOR = new NGramGenerator(4, 3 /* shorter words are fully covered by trigrams */).setAddSpaceInFront(true);
-
- /**
- * Search content columns. Defines all the columns for the full text search
- *
- * @author Tobias Reinsch null
if the entry is not related to a property.
- */
- public static final String PROPERTY_ID = "fts_property_id";
-
- /**
- * The the type of the searchable entry
- */
- public static final String TYPE = "fts_type";
-
- /**
- * An n-gram for a task.
- */
- public static final String NGRAM_ID = "fts_ngram_id";
-
- }
-
- /**
- * The columns of the N-gram table for the FTS search
- *
- * @author Tobias Reinsch TYPE
column.
- *
- * @author Tobias Reinsch null
as searchable text will remove the entry.
- *
- * @param db
- * The writable {@link SQLiteDatabase}.
- * @param taskId
- * the row id of the task this property belongs to.
- * @param propertyId
- * the id of the property
- * @param searchableText
- * the searchable text value of the property
- */
- public static void updatePropertyFTSEntry(SQLiteDatabase db, long taskId, long propertyId, String searchableText)
- {
- updateEntry(db, taskId, propertyId, SearchableTypes.PROPERTY, searchableText);
- }
-
-
- /**
- * Inserts NGrams into the NGram database.
- *
- * @param db
- * A writable {@link SQLiteDatabase}.
- * @param ngrams
- * The set of NGrams.
- * @return The ids of the ngrams in the given set.
- */
- private static SetcontentType
is not {@link SearchableTypes#PROPERTY}.
- * @param contentType
- * The {@link SearchableTypes} type.
- * @return The number of deleted relations.
- */
- private static int deleteNGramRelations(SQLiteDatabase db, long taskId, long propertyId, int contentType)
- {
- StringBuilder whereClause = new StringBuilder(FTSContentColumns.TASK_ID).append(" = ").append(taskId);
- whereClause.append(" AND ").append(FTSContentColumns.TYPE).append(" = ").append(contentType);
- if (contentType == SearchableTypes.PROPERTY)
- {
- whereClause.append(" AND ").append(FTSContentColumns.PROPERTY_ID).append(" = ").append(propertyId);
- }
- return db.delete(FTS_CONTENT_TABLE, whereClause.toString(), null);
- }
-
-
- /**
- * Queries the task database to get a cursor with the search results.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param searchString
- * The search query string.
- * @param projection
- * The database projection for the query.
- * @param selection
- * The selection for the query.
- * @param selectionArgs
- * The arguments for the query.
- * @param sortOrder
- * The sorting order of the query.
- * @return A cursor of the task database with the search result.
- */
- public static Cursor getTaskSearchCursor(SQLiteDatabase db, String searchString, String[] projection, String selection, String[] selectionArgs,
- String sortOrder)
- {
-
- StringBuilder selectionBuilder = new StringBuilder(1024);
-
- if (!TextUtils.isEmpty(selection))
- {
- selectionBuilder.append(" (");
- selectionBuilder.append(selection);
- selectionBuilder.append(") AND (");
- }
- else
- {
- selectionBuilder.append(" (");
- }
-
- Setnull
if the entry is not related to a property.
+ */
+ public static final String PROPERTY_ID = "fts_property_id";
+
+ /**
+ * The the type of the searchable entry
+ */
+ public static final String TYPE = "fts_type";
+
+ /**
+ * An n-gram for a task.
+ */
+ public static final String NGRAM_ID = "fts_ngram_id";
+
+ }
+
+
+ /**
+ * The columns of the N-gram table for the FTS search
+ *
+ * @author Tobias Reinsch TYPE
column.
+ *
+ * @author Tobias Reinsch null
as searchable text will remove the entry.
+ *
+ * @param db
+ * The writable {@link SQLiteDatabase}.
+ * @param taskId
+ * the row id of the task this property belongs to.
+ * @param propertyId
+ * the id of the property
+ * @param searchableText
+ * the searchable text value of the property
+ */
+ public static void updatePropertyFTSEntry(SQLiteDatabase db, long taskId, long propertyId, String searchableText)
+ {
+ updateEntry(db, taskId, propertyId, SearchableTypes.PROPERTY, searchableText);
+ }
+
+
+ /**
+ * Inserts NGrams into the NGram database.
+ *
+ * @param db
+ * A writable {@link SQLiteDatabase}.
+ * @param ngrams
+ * The set of NGrams.
+ *
+ * @return The ids of the ngrams in the given set.
+ */
+ private static SetcontentType
is not {@link SearchableTypes#PROPERTY}.
+ * @param contentType
+ * The {@link SearchableTypes} type.
+ *
+ * @return The number of deleted relations.
+ */
+ private static int deleteNGramRelations(SQLiteDatabase db, long taskId, long propertyId, int contentType)
+ {
+ StringBuilder whereClause = new StringBuilder(FTSContentColumns.TASK_ID).append(" = ").append(taskId);
+ whereClause.append(" AND ").append(FTSContentColumns.TYPE).append(" = ").append(contentType);
+ if (contentType == SearchableTypes.PROPERTY)
+ {
+ whereClause.append(" AND ").append(FTSContentColumns.PROPERTY_ID).append(" = ").append(propertyId);
+ }
+ return db.delete(FTS_CONTENT_TABLE, whereClause.toString(), null);
+ }
+
+
+ /**
+ * Queries the task database to get a cursor with the search results.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param searchString
+ * The search query string.
+ * @param projection
+ * The database projection for the query.
+ * @param selection
+ * The selection for the query.
+ * @param selectionArgs
+ * The arguments for the query.
+ * @param sortOrder
+ * The sorting order of the query.
+ *
+ * @return A cursor of the task database with the search result.
+ */
+ public static Cursor getTaskSearchCursor(SQLiteDatabase db, String searchString, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder)
+ {
+
+ StringBuilder selectionBuilder = new StringBuilder(1024);
+
+ if (!TextUtils.isEmpty(selection))
+ {
+ selectionBuilder.append(" (");
+ selectionBuilder.append(selection);
+ selectionBuilder.append(") AND (");
+ }
+ else
+ {
+ selectionBuilder.append(" (");
+ }
+
+ Settrue
if this operation is triggered by a sync adapter, false otherwise.
- * @param log
- * An {@link ProviderOperationsLog} to log this operation.
- * @param authority
- * The authority of this provider.
- */
- public true
if this operation is triggered by a sync adapter, false otherwise.
+ * @param log
+ * An {@link ProviderOperationsLog} to log this operation.
+ * @param authority
+ * The authority of this provider.
+ */
+ public null
.
- *
- * @param bundle
- * A {@link Bundle} or null
.
- * @param clearLog
- * true
to clear the log afterwards, false
to keep it.
- * @return The {@link Bundle} that was passed or created.
- */
- public Bundle toBundle(Bundle bundle, boolean clearLog)
- {
- if (bundle == null)
- {
- bundle = new Bundle(2);
- }
+ /**
+ * Adds the operations log to the given {@link Bundle}, creating one if the given bundle is null
.
+ *
+ * @param bundle
+ * A {@link Bundle} or null
.
+ * @param clearLog
+ * true
to clear the log afterwards, false
to keep it.
+ *
+ * @return The {@link Bundle} that was passed or created.
+ */
+ public Bundle toBundle(Bundle bundle, boolean clearLog)
+ {
+ if (bundle == null)
+ {
+ bundle = new Bundle(2);
+ }
- synchronized (this)
- {
- bundle.putParcelableArrayList(TaskContract.EXTRA_OPERATIONS_URIS, mUris);
- bundle.putIntegerArrayList(TaskContract.EXTRA_OPERATIONS, mOperations);
- if (clearLog)
- {
- // we can't just clear the ArrayLists, because the Bundle keeps a reference to them
- mUris = new ArrayListtrue
to clear the log afterwards, false
to keep it.
- * @return The {@link Bundle} that was created.
- */
- public Bundle toBundle(boolean clearLog)
- {
- return toBundle(null, clearLog);
- }
+ /**
+ * Returns a new {@link Bundle} containing the log.
+ *
+ * @param clearLog
+ * true
to clear the log afterwards, false
to keep it.
+ *
+ * @return The {@link Bundle} that was created.
+ */
+ public Bundle toBundle(boolean clearLog)
+ {
+ return toBundle(null, clearLog);
+ }
- /**
- * Returns whether any operations have been logged or not.
- *
- * @return true
if this log is empty, false
if it contains any logs of operations.
- */
- public boolean isEmpty()
- {
- return mUris.size() == 0;
- }
+ /**
+ * Returns whether any operations have been logged or not.
+ *
+ * @return true
if this log is empty, false
if it contains any logs of operations.
+ */
+ public boolean isEmpty()
+ {
+ return mUris.size() == 0;
+ }
}
\ No newline at end of file
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/SQLiteContentProvider.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/SQLiteContentProvider.java
index eade9a4daefc780fcfed5fbef190f8227eaf6f71..cc421204f55762a7a7af7f1c94f185ad357180e2 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/SQLiteContentProvider.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/SQLiteContentProvider.java
@@ -16,10 +16,6 @@
package org.dmfs.provider.tasks;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -31,6 +27,10 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
@@ -46,270 +46,267 @@ import android.net.Uri;
abstract class SQLiteContentProvider extends ContentProvider
{
- @SuppressWarnings("unused")
- private static final String TAG = "SQLiteContentProvider";
-
- private SQLiteOpenHelper mOpenHelper;
- private Set* TODO: Also, we could use some refactoring... *
- * + * * @author Marten Gajda- * Value: String - *
- */ - public static final String _SYNC_ID = "_sync_id"; - - /** - * Sync version as set by the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC_VERSION = "sync_version"; - - /** - * Indicates that a task or a task list has been changed. - *- * Value: Integer - *
- */ - public static final String _DIRTY = "_dirty"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC1 = "sync1"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC2 = "sync2"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC3 = "sync3"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC4 = "sync4"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC5 = "sync5"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC6 = "sync6"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC7 = "sync7"; - - /** - * A general purpose column for the sync adapter. - *- * Value: String - *
- */ - public static final String SYNC8 = "sync8"; - - } - - /** - * Additional sync columns for task lists. - * - * @author Marten Gajda- * Value: String - *
- */ - public static final String ACCOUNT_NAME = "account_name"; - - /** - * The type of the account this list belongs to. This field is write-once. - *- * Value: String - *
- */ - public static final String ACCOUNT_TYPE = "account_type"; - } - - /** - * Additional sync columns for tasks. - * - * @author Marten Gajda- * Value: String - *
- */ - public static final String _UID = "_uid"; - - /** - * Deleted flag of a task. This is set to1
by the content provider when a task app deletes a task. The sync adapter has to remove the task
- * again to finish the removal. This value is read-only.
- * - * Value: Integer - *
- *- * read-only - *
- */ - public static final String _DELETED = "_deleted"; - } - - /** - * Data columns of task lists. - * - * @author Marten Gajda- * Value: Long - *
- *- * read-only - *
- */ - public static final String _ID = "_id"; - - /** - * The name of the task list. - *- * Value: String - *
- */ - public static final String LIST_NAME = "list_name"; - - /** - * The color of this list as integer (0xaarrggbb). Only the sync adapter can change this. - *- * Value: Integer - *
- */ - public static final String LIST_COLOR = "list_color"; - - /** - * The access level a user has on this list. This value is not used yet, sync adapters should set it to0
.
- * - * Value: Integer - *
- */ - public static final String ACCESS_LEVEL = "list_access_level"; - - /** - * Indicates that a task list is set to be visible. - *- * Value: Integer (0 or 1) - *
- */ - public static final String VISIBLE = "visible"; - - /** - * Indicates that a task list is set to be synced. - *- * Value: Integer (0 or 1) - *
- */ - public static final String SYNC_ENABLED = "sync_enabled"; - - /** - * The email address of the list owner. - *- * Value: String - *
- */ - public static final String OWNER = "list_owner"; - - } - - /** - * The task list table holds one entry for each task list. - * - * @author Marten Gajda- * Value: Integer - *
- */ - public static final String _ID = "_id"; - - /** - * The id of the list this task belongs to. This value is write-once and must not benull
.
- * - * Value: Integer - *
- */ - public static final String LIST_ID = "list_id"; - - /** - * The title of the task. - *- * Value: String - *
- */ - public static final String TITLE = "title"; - - /** - * The location of the task. - *- * Value: String - *
- */ - public static final String LOCATION = "location"; - - /** - * A geographic location related to the task. The should be a string in the format "longitude,latitude". - *- * Value: String - *
- */ - public static final String GEO = "geo"; - - /** - * The description of a task. - *- * Value: String - *
- */ - public static final String DESCRIPTION = "description"; - - /** - * An URL for this task. Must be a valid URL if notnull
-
- * - * Value: String - *
- */ - public static final String URL = "url"; - - /** - * The email address of the organizer if any, {@code null} otherwise. - *- * Value: String - *
- */ - public static final String ORGANIZER = "organizer"; - - /** - * The priority of a task. This is an Integer between zero and 9. Zero means there is no priority set. 1 is the highest priority and 9 the lowest. - *- * Value: Integer - *
- */ - public static final String PRIORITY = "priority"; - - /** - * The default value of {@link #PRIORITY}. - */ - public static final int PRIORITY_DEFAULT = 0; - - /** - * The classification of a task. This value must be eithernull
or one of {@link #CLASSIFICATION_PUBLIC}, {@link #CLASSIFICATION_PRIVATE},
- * {@link #CLASSIFICATION_CONFIDENTIAL}.
- * - * Value: Integer - *
- */ - public static final String CLASSIFICATION = "class"; - - /** - * Classification value for public tasks. - */ - public static final int CLASSIFICATION_PUBLIC = 0; - - /** - * Classification value for private tasks. - */ - public static final int CLASSIFICATION_PRIVATE = 1; - - /** - * Classification value for confidential tasks. - */ - public static final int CLASSIFICATION_CONFIDENTIAL = 2; - - /** - * Default value of {@link #CLASSIFICATION}. - */ - public static final Integer CLASSIFICATION_DEFAULT = null; - - /** - * Date of completion of this task in milliseconds since the epoch or {@code null} if this task has not been completed yet. - *- * Value: Long - *
- */ - public static final String COMPLETED = "completed"; - - /** - * Indicates that the date of completion is an all-day date. - *- * Value: Integer - *
- */ - public static final String COMPLETED_IS_ALLDAY = "completed_is_allday"; - - /** - * A number between 0 and 100 that indicates the progress of the task ornull
.
- * - * Value: Integer (0-100) - *
- */ - public static final String PERCENT_COMPLETE = "percent_complete"; - - /** - * The status of this task. One of {@link #STATUS_NEEDS_ACTION},{@link #STATUS_IN_PROCESS}, {@link #STATUS_COMPLETED}, {@link #STATUS_CANCELLED}. - *- * Value: Integer - *
- */ - public static final String STATUS = "status"; - - /** - * A specific status indicating that nothing has been done yet. - */ - public static final int STATUS_NEEDS_ACTION = 0; - - /** - * A specific status indicating that some work has been done. - */ - public static final int STATUS_IN_PROCESS = 1; - - /** - * A specific status indicating that the task is completed. - */ - public static final int STATUS_COMPLETED = 2; - - /** - * A specific status indicating that the task has been cancelled. - */ - public static final int STATUS_CANCELLED = 3; - - /** - * The default status is "needs action". - */ - public static final int STATUS_DEFAULT = STATUS_NEEDS_ACTION; - - /** - * A flag that indicates a task is new (i.e. not work has been done yet). This flag is read-only. Its value is1
when
- * {@link #STATUS} equals {@link #STATUS_NEEDS_ACTION} and 0
otherwise.
- * - * Value: Integer - *
- *- * read-only - *
- */ - public static final String IS_NEW = "is_new"; - - /** - * A flag that indicates a task is closed (no more work has to be done). This flag is read-only. Its value is1
when
- * {@link #STATUS} equals {@link #STATUS_COMPLETED} or {@link #STATUS_CANCELLED} and 0
otherwise.
- * - * Value: Integer - *
- *- * read-only - *
- */ - public static final String IS_CLOSED = "is_closed"; - - /** - * An individual color for this task in the format 0xaarrggbb or {@code null} to use {@link TaskListColumns#LIST_COLOR} instead. - *- * Value: Integer - *
- */ - public static final String TASK_COLOR = "task_color"; - - /** - * When this task starts in milliseconds since the epoch. - *- * Value: Long - *
- */ - public static final String DTSTART = "dtstart"; - - /** - * Boolean: flag that indicates that this is an all-day task. - */ - public static final String IS_ALLDAY = "is_allday"; - - /** - * When this task has been created in milliseconds since the epoch. - *- * Value: Long - *
- */ - public static final String CREATED = "created"; - - /** - * When this task had been modified the last time in milliseconds since the epoch. - *- * Value: Long - *
- */ - public static final String LAST_MODIFIED = "last_modified"; - - /** - * String: An Olson Id of the time zone of this task. If this value isnull
, it's automatically replaced by the local time zone.
- */
- public static final String TZ = "tz";
-
- /**
- * When this task is due in milliseconds since the epoch. Only one of {@link #DUE} or {@link #DURATION} must be supplied (or none of both if the task
- * has no due date).
- * - * Value: Long - *
- */ - public static final String DUE = "due"; - - /** - * The duration of this task. Only one of {@link #DUE} or {@link #DURATION} must be supplied (or none of both if the task has no due date). Setting a - * {@link #DURATION} is not allowed when {@link #DTSTART} isnull
. The Value must be a duration string as in RFC 5545 Section 3.3.6.
- * - * Value: String - *
- */ - public static final String DURATION = "duration"; - - /** - * A comma separated list of time Strings in RFC 5545 format (see RFC 5545 Section 3.3.4 - * and RFC 5545 Section 3.3.5) that contains dates of instances of e recurring task. - * All-day tasks must use the DATE format specified in section 3.3.4 of RFC 5545. - * - * This value must be {@code null} for exception instances. - *- * Value: String - *
- */ - public static final String RDATE = "rdate"; - - /** - * A comma separated list of time Strings in RFC 5545 format (see RFC 5545 Section 3.3.4 - * and RFC 5545 Section 3.3.5) that contains dates of exceptions of a recurring task. - * All-day tasks must use the DATE format specified in section 3.3.4 of RFC 5545. - * - * This value must be {@code null} for exception instances. - *- * Value: String - *
- */ - public static final String EXDATE = "exdate"; - - /** - * A recurrence rule as specified in RFC 5545 Section 3.3.10. - * - * This value must be {@code null} for exception instances. - *- * Value: String - *
- */ - public static final String RRULE = "rrule"; - - /** - * The _sync_id of the original event if this is an exception,null
otherwise. Only one of {@link #ORIGINAL_INSTANCE_SYNC_ID} or
- * {@link #ORIGINAL_INSTANCE_ID} must be set if this task is an exception. The other one will be updated by the content provider.
- * - * Value: String - *
- */ - public static final String ORIGINAL_INSTANCE_SYNC_ID = "original_instance_sync_id"; - - /** - * The row id of the original event if this is an exception,null
otherwise. Only one of {@link #ORIGINAL_INSTANCE_SYNC_ID} or
- * {@link #ORIGINAL_INSTANCE_ID} must be set if this task is an exception. The other one will be updated by the content provider.
- * - * Value: Long - *
- */ - public static final String ORIGINAL_INSTANCE_ID = "original_instance_id"; - - /** - * The time in milliseconds since the Epoch of the original instance that is overridden by this instance ornull
if this task is not an
- * exception.
- * - * Value: Long - *
- */ - public static final String ORIGINAL_INSTANCE_TIME = "original_instance_time"; - - /** - * A flag indicating that the original instance was an all-day task. - *- * Value: Integer - *
- */ - public static final String ORIGINAL_INSTANCE_ALLDAY = "original_instance_allday"; - - /** - * The row id of the parent task.null
if the task has no parent task.
- * - * Value: Long - *
- */ - public static final String PARENT_ID = "parent_id"; - - /** - * The sorting of this task under it's parent task. - *- * Value: String - *
- */ - public static final String SORTING = "sorting"; - - /** - * Indicates how many alarms a task has.0
means the task has no alarms. This field is read only as it's set automatically.
- * - * Value: Integer - *
- * Read-only - */ - public static final String HAS_ALARMS = "has_alarms"; - - /** - * Indicates that this task has extended properties like attachments, alarms or relations. This field is read only as it's set automatically. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String HAS_PROPERTIES = "has_properties"; - - /** - * Indicates that this task has been pinned to the notification area. This flag is moved to the exception when an exception for the first instance of a - * recurring task is created. That means, if you edit a pinned recurring task, the pinned flag is moved to the exception and cleared from the master - * task. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String PINNED = "pinned"; - } - - /** - * Columns that are valid in a search query. - * - * @author Marten Gajda- * Value: Float - *
- */ - public final static String SCORE = "score"; - } - - /** - * The task table stores the data of all tasks. - * - * @author Marten Gajda- * Value: String - *
- *- * read-only - *
- */ - public static final String ACCOUNT_NAME = TaskLists.ACCOUNT_NAME; - - /** - * The type of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: String - *
- *- * read-only - *
- */ - public static final String ACCOUNT_TYPE = TaskLists.ACCOUNT_TYPE; - - /** - * The name of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. - *- * Value: String - *
- *- * read-only - *
- */ - public static final String LIST_NAME = TaskLists.LIST_NAME; - /** - * The color of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. To change the color of an individual task use {@code TASK_COLOR} instead. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String LIST_COLOR = TaskLists.LIST_COLOR; - - /** - * The owner of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: String - *
- *- * read-only - *
- */ - public static final String LIST_OWNER = TaskLists.OWNER; - - /** - * The access level of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String LIST_ACCESS_LEVEL = TaskLists.ACCESS_LEVEL; - - /** - * The visibility of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String VISIBLE = "visible"; - - static final String CONTENT_URI_PATH = "tasks"; - - static final String SEARCH_URI_PATH = "tasks_search"; - - static final String SEARCH_QUERY_PARAMETER = "q"; - - public static final String DEFAULT_SORT_ORDER = DUE; - - public static final String[] SYNC_ADAPTER_COLUMNS = new String[] { _DIRTY, SYNC1, SYNC2, SYNC3, SYNC4, SYNC5, SYNC6, SYNC7, SYNC8, _SYNC_ID, - SYNC_VERSION, }; - - - /** - * Get the tasks content {@link Uri} using the given authority. - * - * @param authority - * The authority. - * @return A {@link Uri}. - */ - public final static Uri getContentUri(String authority) - { - return getUriFactory(authority).getUri(CONTENT_URI_PATH); - } - - - public final static Uri getSearchUri(String authority, String query) - { - Uri.Builder builder = getUriFactory(authority).getUri(SEARCH_URI_PATH).buildUpon(); - builder.appendQueryParameter(SEARCH_QUERY_PARAMETER, Uri.encode(query)); - return builder.build(); - } - } - - /** - * Columns of a task instance. - * - * @author Yannic Ahrens- * Value: Long - *
- */ - public static final String TASK_ID = "task_id"; - - /** - * The start date of an instance in milliseconds since the epoch ornull
if the instance has no start date. At present this is read only.
- * - * Value: Long - *
- */ - public static final String INSTANCE_START = "instance_start"; - - /** - * The due date of an instance in milliseconds since the epoch ornull
if the instance has no due date. At present this is read only.
- * - * Value: Long - *
- */ - public static final String INSTANCE_DUE = "instance_due"; - - /** - * This column should be used in an order clause to sort instances by start date. The only guarantee about the values in this column is the sort order. - * Don't make any other assumptions about the value. - *- * Value: Long - *
- *- * read-only - *
- */ - public static final String INSTANCE_START_SORTING = "instance_start_sorting"; - - /** - * This column should be used in an order clause to sort instances by due date. The only guarantee about the values in this column is the sort order. - * Don't make any other assumptions about the value. - *- * Value: Long - *
- *- * read-only - *
- */ - public static final String INSTANCE_DUE_SORTING = "instance_due_sorting"; - - /** - * The duration of an instance in milliseconds ornull
if the instance has only one of start or due date or none of both. At present this
- * is read only.
- * - * Value: Long - *
- */ - public static final String INSTANCE_DURATION = "instance_duration"; - - } - - /** - * Instances of a task. At present this table is read only. Currently it contains exactly one entry per task (and task exception), so it's merely a copy of - * {@link Tasks}. - *- * TODO: Insert all instances of recurring the tasks. - *
- *- * TODO: In later releases it's planned to provide a convenient interface to add, change or delete task instances via this URI. - *
- * - * @author Yannic Ahrens- * Value: String - *
- *- * read-only - *
- */ - public static final String ACCOUNT_NAME = TaskLists.ACCOUNT_NAME; - - /** - * The type of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: String - *
- *- * read-only - *
- */ - public static final String ACCOUNT_TYPE = TaskLists.ACCOUNT_TYPE; - - /** - * The name of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. - *- * Value: String - *
- *- * read-only - *
- */ - public static final String LIST_NAME = TaskLists.LIST_NAME; - /** - * The color of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. To change the color of an individual task use {@code TASK_COLOR} instead. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String LIST_COLOR = TaskLists.LIST_COLOR; - - /** - * The owner of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: String - *
- *- * read-only - *
- */ - public static final String LIST_OWNER = TaskLists.OWNER; - - /** - * The access level of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String LIST_ACCESS_LEVEL = TaskLists.ACCESS_LEVEL; - - /** - * The visibility of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *- * Value: Integer - *
- *- * read-only - *
- */ - public static final String VISIBLE = "visible"; - - static final String CONTENT_URI_PATH = "instances"; - - public static final String DEFAULT_SORT_ORDER = INSTANCE_DUE_SORTING; - - - /** - * Get the instances content {@link Uri} using the given authority. - * - * @param authority - * The authority. - * @return A {@link Uri}. - */ - public final static Uri getContentUri(String authority) - { - return getUriFactory(authority).getUri(CONTENT_URI_PATH); - } - - } - - /** - * Available values in Categories. - * - * Categories are per account. It's up to the front-end to ensure consistency of category colors across accounts. - * - * @author Marten Gajda+ * Value: String + *
+ */ + public static final String _SYNC_ID = "_sync_id"; + + /** + * Sync version as set by the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC_VERSION = "sync_version"; + + /** + * Indicates that a task or a task list has been changed. + *+ * Value: Integer + *
+ */ + public static final String _DIRTY = "_dirty"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC1 = "sync1"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC2 = "sync2"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC3 = "sync3"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC4 = "sync4"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC5 = "sync5"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC6 = "sync6"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC7 = "sync7"; + + /** + * A general purpose column for the sync adapter. + *+ * Value: String + *
+ */ + public static final String SYNC8 = "sync8"; + + } + + + /** + * Additional sync columns for task lists. + * + * @author Marten Gajda+ * Value: String + *
+ */ + public static final String ACCOUNT_NAME = "account_name"; + + /** + * The type of the account this list belongs to. This field is write-once. + *+ * Value: String + *
+ */ + public static final String ACCOUNT_TYPE = "account_type"; + } + + + /** + * Additional sync columns for tasks. + * + * @author Marten Gajda+ * Value: String + *
+ */ + public static final String _UID = "_uid"; + + /** + * Deleted flag of a task. This is set to1
by the content provider when a task app deletes a task. The sync adapter has to remove the task
+ * again to finish the removal. This value is read-only.
+ * + * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String _DELETED = "_deleted"; + } + + + /** + * Data columns of task lists. + * + * @author Marten Gajda+ * Value: Long + *
+ *+ * read-only + *
+ */ + public static final String _ID = "_id"; + + /** + * The name of the task list. + *+ * Value: String + *
+ */ + public static final String LIST_NAME = "list_name"; + + /** + * The color of this list as integer (0xaarrggbb). Only the sync adapter can change this. + *+ * Value: Integer + *
+ */ + public static final String LIST_COLOR = "list_color"; + + /** + * The access level a user has on this list. This value is not used yet, sync adapters should set it to0
.
+ * + * Value: Integer + *
+ */ + public static final String ACCESS_LEVEL = "list_access_level"; + + /** + * Indicates that a task list is set to be visible. + *+ * Value: Integer (0 or 1) + *
+ */ + public static final String VISIBLE = "visible"; + + /** + * Indicates that a task list is set to be synced. + *+ * Value: Integer (0 or 1) + *
+ */ + public static final String SYNC_ENABLED = "sync_enabled"; + + /** + * The email address of the list owner. + *+ * Value: String + *
+ */ + public static final String OWNER = "list_owner"; + + } + + + /** + * The task list table holds one entry for each task list. + * + * @author Marten Gajda+ * Value: Integer + *
+ */ + public static final String _ID = "_id"; + + /** + * The id of the list this task belongs to. This value is write-once and must not benull
.
+ * + * Value: Integer + *
+ */ + public static final String LIST_ID = "list_id"; + + /** + * The title of the task. + *+ * Value: String + *
+ */ + public static final String TITLE = "title"; + + /** + * The location of the task. + *+ * Value: String + *
+ */ + public static final String LOCATION = "location"; + + /** + * A geographic location related to the task. The should be a string in the format "longitude,latitude". + *+ * Value: String + *
+ */ + public static final String GEO = "geo"; + + /** + * The description of a task. + *+ * Value: String + *
+ */ + public static final String DESCRIPTION = "description"; + + /** + * An URL for this task. Must be a valid URL if notnull
-
+ * + * Value: String + *
+ */ + public static final String URL = "url"; + + /** + * The email address of the organizer if any, {@code null} otherwise. + *+ * Value: String + *
+ */ + public static final String ORGANIZER = "organizer"; + + /** + * The priority of a task. This is an Integer between zero and 9. Zero means there is no priority set. 1 is the highest priority and 9 the lowest. + *+ * Value: Integer + *
+ */ + public static final String PRIORITY = "priority"; + + /** + * The default value of {@link #PRIORITY}. + */ + public static final int PRIORITY_DEFAULT = 0; + + /** + * The classification of a task. This value must be eithernull
or one of {@link #CLASSIFICATION_PUBLIC}, {@link #CLASSIFICATION_PRIVATE},
+ * {@link #CLASSIFICATION_CONFIDENTIAL}.
+ * + * Value: Integer + *
+ */ + public static final String CLASSIFICATION = "class"; + + /** + * Classification value for public tasks. + */ + public static final int CLASSIFICATION_PUBLIC = 0; + + /** + * Classification value for private tasks. + */ + public static final int CLASSIFICATION_PRIVATE = 1; + + /** + * Classification value for confidential tasks. + */ + public static final int CLASSIFICATION_CONFIDENTIAL = 2; + + /** + * Default value of {@link #CLASSIFICATION}. + */ + public static final Integer CLASSIFICATION_DEFAULT = null; + + /** + * Date of completion of this task in milliseconds since the epoch or {@code null} if this task has not been completed yet. + *+ * Value: Long + *
+ */ + public static final String COMPLETED = "completed"; + + /** + * Indicates that the date of completion is an all-day date. + *+ * Value: Integer + *
+ */ + public static final String COMPLETED_IS_ALLDAY = "completed_is_allday"; + + /** + * A number between 0 and 100 that indicates the progress of the task ornull
.
+ * + * Value: Integer (0-100) + *
+ */ + public static final String PERCENT_COMPLETE = "percent_complete"; + + /** + * The status of this task. One of {@link #STATUS_NEEDS_ACTION},{@link #STATUS_IN_PROCESS}, {@link #STATUS_COMPLETED}, {@link #STATUS_CANCELLED}. + *+ * Value: Integer + *
+ */ + public static final String STATUS = "status"; + + /** + * A specific status indicating that nothing has been done yet. + */ + public static final int STATUS_NEEDS_ACTION = 0; + + /** + * A specific status indicating that some work has been done. + */ + public static final int STATUS_IN_PROCESS = 1; + + /** + * A specific status indicating that the task is completed. + */ + public static final int STATUS_COMPLETED = 2; + + /** + * A specific status indicating that the task has been cancelled. + */ + public static final int STATUS_CANCELLED = 3; + + /** + * The default status is "needs action". + */ + public static final int STATUS_DEFAULT = STATUS_NEEDS_ACTION; + + /** + * A flag that indicates a task is new (i.e. not work has been done yet). This flag is read-only. Its value is1
when
+ * {@link #STATUS} equals {@link #STATUS_NEEDS_ACTION} and 0
otherwise.
+ * + * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String IS_NEW = "is_new"; + + /** + * A flag that indicates a task is closed (no more work has to be done). This flag is read-only. Its value is1
when
+ * {@link #STATUS} equals {@link #STATUS_COMPLETED} or {@link #STATUS_CANCELLED} and 0
otherwise.
+ * + * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String IS_CLOSED = "is_closed"; + + /** + * An individual color for this task in the format 0xaarrggbb or {@code null} to use {@link TaskListColumns#LIST_COLOR} instead. + *+ * Value: Integer + *
+ */ + public static final String TASK_COLOR = "task_color"; + + /** + * When this task starts in milliseconds since the epoch. + *+ * Value: Long + *
+ */ + public static final String DTSTART = "dtstart"; + + /** + * Boolean: flag that indicates that this is an all-day task. + */ + public static final String IS_ALLDAY = "is_allday"; + + /** + * When this task has been created in milliseconds since the epoch. + *+ * Value: Long + *
+ */ + public static final String CREATED = "created"; + + /** + * When this task had been modified the last time in milliseconds since the epoch. + *+ * Value: Long + *
+ */ + public static final String LAST_MODIFIED = "last_modified"; + + /** + * String: An Olson Id of the time zone of this task. If this value isnull
, it's automatically replaced by the local time zone.
+ */
+ public static final String TZ = "tz";
+
+ /**
+ * When this task is due in milliseconds since the epoch. Only one of {@link #DUE} or {@link #DURATION} must be supplied (or none of both if the task
+ * has no due date).
+ * + * Value: Long + *
+ */ + public static final String DUE = "due"; + + /** + * The duration of this task. Only one of {@link #DUE} or {@link #DURATION} must be supplied (or none of both if the task has no due date). Setting a + * {@link #DURATION} is not allowed when {@link #DTSTART} isnull
. The Value must be a duration string as in RFC 5545 Section 3.3.6.
+ * + * Value: String + *
+ */ + public static final String DURATION = "duration"; + + /** + * A comma separated list of time Strings in RFC 5545 format (see RFC 5545 Section 3.3.4 + * and RFC 5545 Section 3.3.5) that contains dates of instances of e recurring task. + * All-day tasks must use the DATE format specified in section 3.3.4 of RFC 5545. + *+ * This value must be {@code null} for exception instances. + *
+ * Value: String + *
+ */ + public static final String RDATE = "rdate"; + + /** + * A comma separated list of time Strings in RFC 5545 format (see RFC 5545 Section 3.3.4 + * and RFC 5545 Section 3.3.5) that contains dates of exceptions of a recurring task. + * All-day tasks must use the DATE format specified in section 3.3.4 of RFC 5545. + *+ * This value must be {@code null} for exception instances. + *
+ * Value: String + *
+ */ + public static final String EXDATE = "exdate"; + + /** + * A recurrence rule as specified in RFC 5545 Section 3.3.10. + *+ * This value must be {@code null} for exception instances. + *
+ * Value: String + *
+ */ + public static final String RRULE = "rrule"; + + /** + * The _sync_id of the original event if this is an exception,null
otherwise. Only one of {@link #ORIGINAL_INSTANCE_SYNC_ID} or
+ * {@link #ORIGINAL_INSTANCE_ID} must be set if this task is an exception. The other one will be updated by the content provider.
+ * + * Value: String + *
+ */ + public static final String ORIGINAL_INSTANCE_SYNC_ID = "original_instance_sync_id"; + + /** + * The row id of the original event if this is an exception,null
otherwise. Only one of {@link #ORIGINAL_INSTANCE_SYNC_ID} or
+ * {@link #ORIGINAL_INSTANCE_ID} must be set if this task is an exception. The other one will be updated by the content provider.
+ * + * Value: Long + *
+ */ + public static final String ORIGINAL_INSTANCE_ID = "original_instance_id"; + + /** + * The time in milliseconds since the Epoch of the original instance that is overridden by this instance ornull
if this task is not an
+ * exception.
+ * + * Value: Long + *
+ */ + public static final String ORIGINAL_INSTANCE_TIME = "original_instance_time"; + + /** + * A flag indicating that the original instance was an all-day task. + *+ * Value: Integer + *
+ */ + public static final String ORIGINAL_INSTANCE_ALLDAY = "original_instance_allday"; + + /** + * The row id of the parent task.null
if the task has no parent task.
+ * + * Value: Long + *
+ */ + public static final String PARENT_ID = "parent_id"; + + /** + * The sorting of this task under it's parent task. + *+ * Value: String + *
+ */ + public static final String SORTING = "sorting"; + + /** + * Indicates how many alarms a task has.0
means the task has no alarms. This field is read only as it's set automatically.
+ * + * Value: Integer + *
+ * Read-only + */ + public static final String HAS_ALARMS = "has_alarms"; + + /** + * Indicates that this task has extended properties like attachments, alarms or relations. This field is read only as it's set automatically. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String HAS_PROPERTIES = "has_properties"; + + /** + * Indicates that this task has been pinned to the notification area. This flag is moved to the exception when an exception for the first instance of a + * recurring task is created. That means, if you edit a pinned recurring task, the pinned flag is moved to the exception and cleared from the master + * task. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String PINNED = "pinned"; + } + + + /** + * Columns that are valid in a search query. + * + * @author Marten Gajda+ * Value: Float + *
+ */ + public final static String SCORE = "score"; + } + + + /** + * The task table stores the data of all tasks. + * + * @author Marten Gajda+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String ACCOUNT_NAME = TaskLists.ACCOUNT_NAME; + + /** + * The type of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String ACCOUNT_TYPE = TaskLists.ACCOUNT_TYPE; + + /** + * The name of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value + * here. + *+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String LIST_NAME = TaskLists.LIST_NAME; + /** + * The color of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value + * here. To change the color of an individual task use {@code TASK_COLOR} instead. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String LIST_COLOR = TaskLists.LIST_COLOR; + + /** + * The owner of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String LIST_OWNER = TaskLists.OWNER; + + /** + * The access level of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String LIST_ACCESS_LEVEL = TaskLists.ACCESS_LEVEL; + + /** + * The visibility of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String VISIBLE = "visible"; + + static final String CONTENT_URI_PATH = "tasks"; + + static final String SEARCH_URI_PATH = "tasks_search"; + + static final String SEARCH_QUERY_PARAMETER = "q"; + + public static final String DEFAULT_SORT_ORDER = DUE; + + public static final String[] SYNC_ADAPTER_COLUMNS = new String[] { + _DIRTY, SYNC1, SYNC2, SYNC3, SYNC4, SYNC5, SYNC6, SYNC7, SYNC8, _SYNC_ID, + SYNC_VERSION, }; + + + /** + * Get the tasks content {@link Uri} using the given authority. + * + * @param authority + * The authority. + * + * @return A {@link Uri}. + */ + public final static Uri getContentUri(String authority) + { + return getUriFactory(authority).getUri(CONTENT_URI_PATH); + } + + + public final static Uri getSearchUri(String authority, String query) + { + Uri.Builder builder = getUriFactory(authority).getUri(SEARCH_URI_PATH).buildUpon(); + builder.appendQueryParameter(SEARCH_QUERY_PARAMETER, Uri.encode(query)); + return builder.build(); + } + } + + + /** + * Columns of a task instance. + * + * @author Yannic Ahrens+ * Value: Long + *
+ */ + public static final String TASK_ID = "task_id"; + + /** + * The start date of an instance in milliseconds since the epoch ornull
if the instance has no start date. At present this is read only.
+ * + * Value: Long + *
+ */ + public static final String INSTANCE_START = "instance_start"; + + /** + * The due date of an instance in milliseconds since the epoch ornull
if the instance has no due date. At present this is read only.
+ * + * Value: Long + *
+ */ + public static final String INSTANCE_DUE = "instance_due"; + + /** + * This column should be used in an order clause to sort instances by start date. The only guarantee about the values in this column is the sort order. + * Don't make any other assumptions about the value. + *+ * Value: Long + *
+ *+ * read-only + *
+ */ + public static final String INSTANCE_START_SORTING = "instance_start_sorting"; + + /** + * This column should be used in an order clause to sort instances by due date. The only guarantee about the values in this column is the sort order. + * Don't make any other assumptions about the value. + *+ * Value: Long + *
+ *+ * read-only + *
+ */ + public static final String INSTANCE_DUE_SORTING = "instance_due_sorting"; + + /** + * The duration of an instance in milliseconds ornull
if the instance has only one of start or due date or none of both. At present this
+ * is read only.
+ * + * Value: Long + *
+ */ + public static final String INSTANCE_DURATION = "instance_duration"; + + } + + + /** + * Instances of a task. At present this table is read only. Currently it contains exactly one entry per task (and task exception), so it's merely a copy of + * {@link Tasks}. + *+ * TODO: Insert all instances of recurring the tasks. + *
+ *+ * TODO: In later releases it's planned to provide a convenient interface to add, change or delete task instances via this URI. + *
+ * + * @author Yannic Ahrens+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String ACCOUNT_NAME = TaskLists.ACCOUNT_NAME; + + /** + * The type of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String ACCOUNT_TYPE = TaskLists.ACCOUNT_TYPE; + + /** + * The name of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value + * here. + *+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String LIST_NAME = TaskLists.LIST_NAME; + /** + * The color of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value + * here. To change the color of an individual task use {@code TASK_COLOR} instead. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String LIST_COLOR = TaskLists.LIST_COLOR; + + /** + * The owner of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: String + *
+ *+ * read-only + *
+ */ + public static final String LIST_OWNER = TaskLists.OWNER; + + /** + * The access level of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String LIST_ACCESS_LEVEL = TaskLists.ACCESS_LEVEL; + + /** + * The visibility of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. + *+ * Value: Integer + *
+ *+ * read-only + *
+ */ + public static final String VISIBLE = "visible"; + + static final String CONTENT_URI_PATH = "instances"; + + public static final String DEFAULT_SORT_ORDER = INSTANCE_DUE_SORTING; + + + /** + * Get the instances content {@link Uri} using the given authority. + * + * @param authority + * The authority. + * + * @return A {@link Uri}. + */ + public final static Uri getContentUri(String authority) + { + return getUriFactory(authority).getUri(CONTENT_URI_PATH); + } + + } + + + /** + * Available values in Categories. + *
+ * Categories are per account. It's up to the front-end to ensure consistency of category colors across accounts.
+ *
+ * @author Marten Gajda
+ * Note: Attachments are write-once. To change an attachment you'll have to remove and re-add it.
+ *
- * Note: Attachments are write-once. To change an attachment you'll have to remove and re-add it.
- *
+ * Value: String
+ *
- * Value: String
- *
- * Value: String
- *
- * Value: String
- *
- * Value: Long
- *
- * Value: String
- *
- * Value: String
- *
- * Value: String
- *
- * Value: Long
- *
- * Value: String
- *
- * Value: Integer
- *
- * read-only
- *
- * Value: String
- *
- * Value: String
- *
- * When writing a relation, exactly one of {@link #RELATED_ID}, {@link #RELATED_UID} or {@link #RELATED_URI} must be given. {@link #RELATED_CONTENT_URI}
- * will be populated automatically if possible.
- *
- * Value: long
- *
- * Value: int
- *
- * Value: String
- *
- * Value: String (URI)
- *
- * Value: String (URI)
- *
- * This field is read-only.
- *
- * Value: duration string
- *
- * Value: Integer
- *
- * Value: Integer
- *
- * Value: String
- *
- * Value: Integer
- *
+ * Value: String
+ *
+ * Value: String
+ *
+ * Value: Long
+ *
+ * Value: String
+ *
+ * Value: String
+ *
+ * Value: String
+ *
+ * Value: Long
+ *
+ * Value: String
+ *
+ * Value: Integer
+ *
+ * read-only
+ *
+ * Value: String
+ *
+ * Value: String
+ *
+ * When writing a relation, exactly one of {@link #RELATED_ID}, {@link #RELATED_UID} or {@link #RELATED_URI} must be given. {@link #RELATED_CONTENT_URI}
+ * will be populated automatically if possible.
+ *
+ * Value: long
+ *
+ * Value: int
+ *
+ * Value: String
+ *
+ * Value: String (URI)
+ *
+ * Value: String (URI)
+ *
+ * This field is read-only.
+ *
+ * Value: duration string
+ *
+ * Value: Integer
+ *
+ * Value: Integer
+ *
+ * Value: String
+ *
+ * Value: Integer
+ *
+ * TODO: move all strings to separate final static variables.
+ */
+ @Override
+ public void onCreate(SQLiteDatabase db)
+ {
+
+ // create task list table
+ db.execSQL(SQL_CREATE_LISTS_TABLE);
+
+ // trigger that removes tasks of a list that has been removed
+ db.execSQL("CREATE TRIGGER task_list_cleanup_trigger AFTER DELETE ON " + Tables.LISTS + " BEGIN DELETE FROM " + Tables.TASKS + " WHERE "
+ + TaskContract.Tasks.LIST_ID + "= old." + TaskContract.TaskLists._ID + "; END");
+
+ // create task table
+ db.execSQL(SQL_CREATE_TASKS_TABLE);
+
+ // trigger that marks a list as dirty if a task in that list gets marked as dirty or deleted
+ db.execSQL("CREATE TRIGGER task_list_make_dirty_on_update AFTER UPDATE ON " + Tables.TASKS + " BEGIN UPDATE " + Tables.LISTS + " SET "
+ + TaskContract.TaskLists._DIRTY + "=" + TaskContract.TaskLists._DIRTY + " + " + "new." + TaskContract.Tasks._DIRTY + " + " + "new."
+ + TaskContract.Tasks._DELETED + " WHERE " + TaskContract.TaskLists._ID + "= new." + TaskContract.Tasks.LIST_ID + "; END");
+
+ // trigger that marks a list as dirty if a task in that list gets marked as dirty or deleted
+ db.execSQL("CREATE TRIGGER task_list_make_dirty_on_insert AFTER INSERT ON " + Tables.TASKS + " BEGIN UPDATE " + Tables.LISTS + " SET "
+ + TaskContract.TaskLists._DIRTY + "=" + TaskContract.TaskLists._DIRTY + " + " + "new." + TaskContract.Tasks._DIRTY + " + " + "new."
+ + TaskContract.Tasks._DELETED + " WHERE " + TaskContract.TaskLists._ID + "= new." + TaskContract.Tasks.LIST_ID + "; END");
+
+ // create instances table and view
+ db.execSQL(SQL_CREATE_INSTANCES_TABLE);
+
+ // create categories table
+ db.execSQL(SQL_CREATE_CATEGORIES_TABLE);
+
+ // create categories mapping table
+ db.execSQL(SQL_CREATE_CATEGORIES_MAPPING_TABLE);
+
+ // create alarms table
+ db.execSQL(SQL_CREATE_ALARMS_TABLE);
+
+ // create properties table
+ db.execSQL(SQL_CREATE_PROPERTIES_TABLE);
+
+ // create syncstate table
+ db.execSQL(SQL_CREATE_SYNCSTATE_TABLE);
+
+ // create views
+ db.execSQL(SQL_CREATE_TASK_VIEW);
+ db.execSQL(SQL_CREATE_TASK_PROPERTY_VIEW);
+ db.execSQL(SQL_CREATE_INSTANCE_VIEW);
+ db.execSQL(SQL_CREATE_INSTANCE_PROPERTY_VIEW);
+ db.execSQL(SQL_CREATE_INSTANCE_CATEGORY_VIEW);
+
+ // create indices
+ db.execSQL(createIndexString(Tables.INSTANCES, false, TaskContract.Instances.TASK_ID, TaskContract.Instances.INSTANCE_START,
+ TaskContract.Instances.INSTANCE_DUE));
+ db.execSQL(createIndexString(Tables.INSTANCES, false, TaskContract.Instances.INSTANCE_START_SORTING));
+ db.execSQL(createIndexString(Tables.INSTANCES, false, TaskContract.Instances.INSTANCE_DUE_SORTING));
+ db.execSQL(createIndexString(Tables.LISTS, false, TaskContract.TaskLists.ACCOUNT_NAME, // not sure if necessary
+ TaskContract.TaskLists.ACCOUNT_TYPE));
+ db.execSQL(createIndexString(Tables.TASKS, false, TaskContract.Tasks.STATUS, TaskContract.Tasks.LIST_ID, TaskContract.Tasks._SYNC_ID));
+ db.execSQL(createIndexString(Tables.PROPERTIES, false, TaskContract.Properties.MIMETYPE, TaskContract.Properties.TASK_ID));
+ db.execSQL(createIndexString(Tables.PROPERTIES, false, TaskContract.Properties.TASK_ID));
+ db.execSQL(createIndexString(Tables.CATEGORIES, false, TaskContract.Categories.ACCOUNT_NAME, TaskContract.Categories.ACCOUNT_TYPE,
+ TaskContract.Categories.NAME));
+ db.execSQL(createIndexString(Tables.CATEGORIES, false, TaskContract.Categories.NAME));
+ db.execSQL(createIndexString(Tables.SYNCSTATE, true, TaskContract.SyncState.ACCOUNT_NAME, TaskContract.SyncState.ACCOUNT_TYPE));
+
+ // trigger that removes properties of a task that has been removed
+ db.execSQL(SQL_CREATE_TASKS_CLEANUP_TRIGGER);
+
+ // trigger that removes alarms when an alarm property was deleted
+ db.execSQL(SQL_CREATE_ALARM_PROPERTY_CLEANUP_TRIGGER);
+
+ // trigger that removes tasks when a list was removed
+ db.execSQL(SQL_CREATE_LISTS_CLEANUP_TRIGGER);
+
+ // trigger that counts the alarms for tasks
+ db.execSQL(SQL_CREATE_ALARM_COUNT_CREATE_TRIGGER);
+ db.execSQL(SQL_CREATE_ALARM_COUNT_UPDATE_TRIGGER);
+ db.execSQL(SQL_CREATE_ALARM_COUNT_DELETE_TRIGGER);
+
+ // add cleanup trigger for orphaned properties
+ db.execSQL(SQL_CREATE_TASK_PROPERTY_CLEANUP_TRIGGER);
+
+ // initialize FTS
+ FTSDatabaseHelper.onCreate(db);
+
+ if (mListener != null)
+ {
+ mListener.onDatabaseCreated(db);
+ }
+ }
+
+
+ /**
+ * Manages the database schema migration.
+ */
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
+ {
+ Log.i(TAG, "updgrading db from " + oldVersion + " to " + newVersion);
+ if (oldVersion < 2)
+ {
+ // add IS_NEW and IS_CLOSED columns and update their values
+ db.execSQL("ALTER TABLE " + Tables.TASKS + " ADD COLUMN " + TaskContract.Tasks.IS_NEW + " INTEGER");
+ db.execSQL("ALTER TABLE " + Tables.TASKS + " ADD COLUMN " + TaskContract.Tasks.IS_CLOSED + " INTEGER");
+ db.execSQL("UPDATE " + Tables.TASKS + " SET " + TaskContract.Tasks.IS_NEW + " = 1 WHERE " + TaskContract.Tasks.STATUS + " = "
+ + TaskContract.Tasks.STATUS_NEEDS_ACTION);
+ db.execSQL("UPDATE " + Tables.TASKS + " SET " + TaskContract.Tasks.IS_NEW + " = 0 WHERE " + TaskContract.Tasks.STATUS + " != "
+ + TaskContract.Tasks.STATUS_NEEDS_ACTION);
+ db.execSQL("UPDATE " + Tables.TASKS + " SET " + TaskContract.Tasks.IS_CLOSED + " = 1 WHERE " + TaskContract.Tasks.STATUS + " > "
+ + TaskContract.Tasks.STATUS_IN_PROCESS);
+ db.execSQL("UPDATE " + Tables.TASKS + " SET " + TaskContract.Tasks.IS_CLOSED + " = 0 WHERE " + TaskContract.Tasks.STATUS + " <= "
+ + TaskContract.Tasks.STATUS_IN_PROCESS);
+ }
+
+ if (oldVersion < 3)
+ {
+ // add instance sortings
+ db.execSQL("ALTER TABLE " + Tables.INSTANCES + " ADD COLUMN " + TaskContract.Instances.INSTANCE_START_SORTING + " INTEGER");
+ db.execSQL("ALTER TABLE " + Tables.INSTANCES + " ADD COLUMN " + TaskContract.Instances.INSTANCE_DUE_SORTING + " INTEGER");
+ db.execSQL("UPDATE " + Tables.INSTANCES + " SET " + TaskContract.Instances.INSTANCE_START_SORTING + " = " + TaskContract.Instances.INSTANCE_START
+ + ", " + TaskContract.Instances.INSTANCE_DUE_SORTING + " = " + TaskContract.Instances.INSTANCE_DUE);
+ }
+ if (oldVersion < 4)
+ {
+ // drop old view before altering the schema
+ db.execSQL(SQL_DROP_TASK_VIEW);
+ db.execSQL(SQL_DROP_INSTANCE_VIEW);
+
+ // change property id column name to work with the left join in task view
+ db.execSQL(SQL_DROP_TASKS_CLEANUP_TRIGGER);
+ db.execSQL(SQL_DROP_PROPERTIES_TABLE);
+ db.execSQL(SQL_CREATE_PROPERTIES_TABLE);
+ db.execSQL(SQL_CREATE_TASKS_CLEANUP_TRIGGER);
+
+ // create categories mapping table
+ db.execSQL(SQL_CREATE_CATEGORIES_MAPPING_TABLE);
+
+ // create alarms table
+ db.execSQL(SQL_CREATE_ALARMS_TABLE);
+
+ // update views
+ db.execSQL(SQL_CREATE_TASK_VIEW);
+ db.execSQL(SQL_CREATE_TASK_PROPERTY_VIEW);
+ db.execSQL(SQL_CREATE_INSTANCE_VIEW);
+ db.execSQL(SQL_CREATE_INSTANCE_PROPERTY_VIEW);
+ db.execSQL(SQL_CREATE_INSTANCE_CATEGORY_VIEW);
+
+ // create Indices
+ db.execSQL(createIndexString(Tables.PROPERTIES, false, TaskContract.Properties.MIMETYPE, TaskContract.Properties.TASK_ID));
+ db.execSQL(createIndexString(Tables.PROPERTIES, false, TaskContract.Properties.TASK_ID));
+ db.execSQL(createIndexString(Tables.CATEGORIES, false, TaskContract.Categories.ACCOUNT_NAME, TaskContract.Categories.ACCOUNT_TYPE,
+ TaskContract.Categories.NAME));
+ db.execSQL(createIndexString(Tables.CATEGORIES, false, TaskContract.Categories.NAME));
+
+ // add new triggers
+ db.execSQL(SQL_CREATE_ALARM_PROPERTY_CLEANUP_TRIGGER);
+ db.execSQL(SQL_CREATE_ALARM_COUNT_CREATE_TRIGGER);
+ db.execSQL(SQL_CREATE_ALARM_COUNT_UPDATE_TRIGGER);
+ db.execSQL(SQL_CREATE_ALARM_COUNT_DELETE_TRIGGER);
+
+ }
+ if (oldVersion < 6)
+ {
+ db.execSQL("alter table " + Tables.TASKS + " add column " + Tasks.PARENT_ID + " integer;");
+ db.execSQL("alter table " + Tables.TASKS + " add column " + Tasks.HAS_ALARMS + " integer;");
+ db.execSQL("alter table " + Tables.TASKS + " add column " + Tasks.SORTING + " text;");
+ }
+ if (oldVersion < 7)
+ {
+ db.execSQL(SQL_CREATE_LISTS_CLEANUP_TRIGGER);
+ }
+ if (oldVersion < 8)
+ {
+ // replace priority 0 by null. We need this to sort the widget properly. Since 0 is the default this is no problem when syncing.
+ db.execSQL("update " + Tables.TASKS + " set " + Tasks.PRIORITY + "=null where " + Tasks.PRIORITY + "=0;");
+ }
+ if (oldVersion < 9)
+ {
+ // add missing column _UID
+ db.execSQL("alter table " + Tables.TASKS + " add column " + Tasks._UID + " integer;");
+ // add cleanup trigger for orphaned properties
+ db.execSQL(SQL_CREATE_TASK_PROPERTY_CLEANUP_TRIGGER);
+ }
+ if (oldVersion < 10)
+ {
+ // add property column to categories_mapping table. Since adding a constraint is not supported by SQLite we have to remove and recreate the entire
+ // table
+ db.execSQL("drop table " + Tables.CATEGORIES_MAPPING);
+ db.execSQL(SQL_CREATE_CATEGORIES_MAPPING_TABLE);
+ db.execSQL(SQL_CREATE_CATEGORY_PROPERTY_CLEANUP_TRIGGER);
+ }
+ if (oldVersion < 11)
+ {
+ db.execSQL("alter table " + Tables.TASKS + " add column " + Tasks.PINNED + " integer;");
+ db.execSQL("alter table " + Tables.TASKS + " add column " + Tasks.HAS_PROPERTIES + " integer;");
+ }
+
+ if (oldVersion < 12)
+ {
+ // rename the local account type
+ ContentValues values = new ContentValues(1);
+ values.put(TaskLists.ACCOUNT_TYPE, TaskContract.LOCAL_ACCOUNT_TYPE);
+ db.update(Tables.LISTS, values, TaskLists.ACCOUNT_TYPE + "=?", new String[] { "LOCAL" });
+ }
+
+ if (oldVersion < 13)
+ {
+ db.execSQL(SQL_CREATE_SYNCSTATE_TABLE);
+ }
+
+ if (oldVersion < 14)
+ {
+ // create a unique index for account name and account type on the sync state table
+ db.execSQL(createIndexString(Tables.SYNCSTATE, true, TaskContract.SyncState.ACCOUNT_NAME, TaskContract.SyncState.ACCOUNT_TYPE));
+ }
+
+ if (oldVersion < 16)
+ {
+ db.execSQL(createIndexString(Tables.INSTANCES, false, TaskContract.Instances.INSTANCE_START_SORTING));
+ db.execSQL(createIndexString(Tables.INSTANCES, false, TaskContract.Instances.INSTANCE_DUE_SORTING));
+ }
+
+ // upgrade FTS
+ FTSDatabaseHelper.onUpgrade(db, oldVersion, newVersion);
+
+ if (mListener != null)
+ {
+ mListener.onDatabaseUpdate(db, oldVersion, newVersion);
+ }
+ }
}
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/TaskProvider.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/TaskProvider.java
index 42adb411918ba97a2d5adf84f6a7680dc3381c88..c28751c72a183fa15ae126784c4a4c470414e57d 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/TaskProvider.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/TaskProvider.java
@@ -17,12 +17,33 @@
package org.dmfs.provider.tasks;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.OnAccountsUpdateListener;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.text.TextUtils;
import org.dmfs.provider.tasks.TaskContract.Alarms;
import org.dmfs.provider.tasks.TaskContract.Categories;
@@ -57,1295 +78,1287 @@ import org.dmfs.provider.tasks.processors.tasks.TaskExecutionProcessor;
import org.dmfs.provider.tasks.processors.tasks.TaskInstancesProcessor;
import org.dmfs.provider.tasks.processors.tasks.TaskValidatorProcessor;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.UriMatcher;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.text.TextUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
/**
* The provider for tasks.
- *
+ *
* TODO: add support for recurring tasks
- *
+ *
* TODO: add support for reminders
- *
+ *
* TODO: add support for attendees
- *
+ *
* TODO: refactor the selection stuff
- *
+ *
* @author Marten Gajda
- * TODO: store links into the calendar provider if we find an event that matches the UID.
- *
+ * TODO: store links into the calendar provider if we find an event that matches the UID.
+ * -1
if unknown.
- * null
.
- * -1
if the property doesn't refer to a task in this database or if it doesn't refer to a task
- * at all.
- * null
. If the
- * related object is an event or note this field may contain the content URI to the object.
- * -1
if unknown.
+ * null
.
+ * -1
if the property doesn't refer to a task in this database or if it doesn't refer to a task
+ * at all.
+ * null
. If the
+ * related object is an event or note this field may contain the content URI to the object.
+ * true
if the caller pretends to be a sync adapter, false
otherwise.
- */
- @Override
- public boolean isCallerSyncAdapter(Uri uri)
- {
- String param = uri.getQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER);
- return param != null && !"false".equals(param);
- }
-
-
- /**
- * Return true if the URI indicates to a load extended properties with {@link TaskContract#LOAD_PROPERTIES}.
- *
- * @param uri
- * The {@link Uri} to check.
- * @return true
if the URI requests to load extended properties, false
otherwise.
- */
- public boolean shouldLoadProperties(Uri uri)
- {
- String param = uri.getQueryParameter(TaskContract.LOAD_PROPERTIES);
- return param != null && !"false".equals(param);
- }
-
-
- /**
- * Get the account name from the given {@link Uri}.
- *
- * @param uri
- * The Uri to check.
- * @return The account name or null if no account name has been specified.
- */
- protected String getAccountName(Uri uri)
- {
- return uri.getQueryParameter(TaskContract.ACCOUNT_NAME);
- }
-
-
- /**
- * Get the account type from the given {@link Uri}.
- *
- * @param uri
- * The Uri to check.
- * @return The account type or null if no account type has been specified.
- */
- protected String getAccountType(Uri uri)
- {
- return uri.getQueryParameter(TaskContract.ACCOUNT_TYPE);
- }
-
-
- /**
- * Get any id from the given {@link Uri}.
- *
- * @param uri
- * The Uri.
- * @return The last path segment (which should contain the id).
- */
- private long getId(Uri uri)
- {
- return Long.parseLong(uri.getPathSegments().get(1));
- }
-
-
- /**
- * Build a selection string that selects the account specified in uri
.
- *
- * @param uri
- * A {@link Uri} that specifies an account.
- * @return A {@link StringBuilder} with a selection string for the account.
- */
- protected StringBuilder selectAccount(Uri uri)
- {
- StringBuilder sb = new StringBuilder(256);
- return selectAccount(sb, uri);
- }
-
-
- /**
- * Append the selection of the account specified in uri
to the {@link StringBuilder} sb
.
- *
- * @param sb
- * A {@link StringBuilder} that the selection is appended to.
- * @param uri
- * A {@link Uri} that specifies an account.
- * @return sb
.
- */
- protected StringBuilder selectAccount(StringBuilder sb, Uri uri)
- {
- String accountName = getAccountName(uri);
- String accountType = getAccountType(uri);
-
- if (accountName != null || accountType != null)
- {
-
- if (accountName != null)
- {
- if (sb.length() > 0)
- {
- sb.append(" AND ");
- }
-
- sb.append(TaskListSyncColumns.ACCOUNT_NAME);
- sb.append("=");
- DatabaseUtils.appendEscapedSQLString(sb, accountName);
- }
- if (accountType != null)
- {
-
- if (sb.length() > 0)
- {
- sb.append(" AND ");
- }
-
- sb.append(TaskListSyncColumns.ACCOUNT_TYPE);
- sb.append("=");
- DatabaseUtils.appendEscapedSQLString(sb, accountType);
- }
- }
- return sb;
- }
-
-
- /**
- * Append the selection of the account specified in uri
to the an {@link SQLiteQueryBuilder}.
- *
- * @param sqlBuilder
- * A {@link SQLiteQueryBuilder} that the selection is appended to.
- * @param uri
- * A {@link Uri} that specifies an account.
- */
- protected void selectAccount(SQLiteQueryBuilder sqlBuilder, Uri uri)
- {
- String accountName = getAccountName(uri);
- String accountType = getAccountType(uri);
-
- if (accountName != null)
- {
- sqlBuilder.appendWhere(" AND ");
- sqlBuilder.appendWhere(TaskListSyncColumns.ACCOUNT_NAME);
- sqlBuilder.appendWhere("=");
- sqlBuilder.appendWhereEscapeString(accountName);
- }
- if (accountType != null)
- {
- sqlBuilder.appendWhere(" AND ");
- sqlBuilder.appendWhere(TaskListSyncColumns.ACCOUNT_TYPE);
- sqlBuilder.appendWhere("=");
- sqlBuilder.appendWhereEscapeString(accountType);
- }
- }
-
-
- private StringBuilder _selectId(StringBuilder sb, long id, String key)
- {
- if (sb.length() > 0)
- {
- sb.append(" AND ");
- }
- sb.append(key);
- sb.append("=");
- sb.append(id);
- return sb;
- }
-
-
- protected StringBuilder selectId(Uri uri)
- {
- StringBuilder sb = new StringBuilder(128);
- return selectId(sb, uri);
- }
-
-
- protected StringBuilder selectId(StringBuilder sb, Uri uri)
- {
- return _selectId(sb, getId(uri), TaskListColumns._ID);
- }
-
-
- protected StringBuilder selectTaskId(Uri uri)
- {
- StringBuilder sb = new StringBuilder(128);
- return selectTaskId(sb, uri);
- }
-
-
- protected StringBuilder selectTaskId(long id)
- {
- StringBuilder sb = new StringBuilder(128);
- return selectTaskId(sb, id);
- }
-
-
- protected StringBuilder selectTaskId(StringBuilder sb, Uri uri)
- {
- return selectTaskId(sb, getId(uri));
- }
-
-
- protected StringBuilder selectTaskId(StringBuilder sb, long id)
- {
- return _selectId(sb, id, Instances.TASK_ID);
-
- }
-
-
- protected StringBuilder selectPropertyId(Uri uri)
- {
- StringBuilder sb = new StringBuilder(128);
- return selectPropertyId(sb, uri);
- }
-
-
- protected StringBuilder selectPropertyId(StringBuilder sb, Uri uri)
- {
- return selectPropertyId(sb, getId(uri));
- }
-
-
- protected StringBuilder selectPropertyId(long id)
- {
- StringBuilder sb = new StringBuilder(128);
- return selectPropertyId(sb, id);
- }
-
-
- protected StringBuilder selectPropertyId(StringBuilder sb, long id)
- {
- return _selectId(sb, id, PropertyColumns.PROPERTY_ID);
- }
-
-
- /**
- * Add a selection by ID to the given {@link SQLiteQueryBuilder}. The id is taken from the given Uri.
- *
- * @param sqlBuilder
- * The {@link SQLiteQueryBuilder} to append the selection to.
- * @param idColumn
- * The column that must match the id.
- * @param uri
- * An {@link Uri} that contains the id.
- */
- protected void selectId(SQLiteQueryBuilder sqlBuilder, String idColumn, Uri uri)
- {
- sqlBuilder.appendWhere(" AND ");
- sqlBuilder.appendWhere(idColumn);
- sqlBuilder.appendWhere("=");
- sqlBuilder.appendWhere(String.valueOf(getId(uri)));
- }
-
-
- /**
- * Append any arbitrary selection string to the selection in sb
- *
- * @param sb
- * A {@link StringBuilder} that already contains a selection string.
- * @param selection
- * A valid SQL selection string.
- * @return A string with the final selection.
- */
- protected String updateSelection(StringBuilder sb, String selection)
- {
- if (selection != null)
- {
- if (sb.length() > 0)
- {
- sb.append("AND ( ").append(selection).append(" ) ");
- }
- else
- {
- sb.append(" ( ").append(selection).append(" ) ");
- }
- }
- return sb.toString();
- }
-
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
- {
- final SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
- SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
- // initialize appendWhere, this allows us to append all other selections with a preceding "AND"
- sqlBuilder.appendWhere(" 1=1 ");
- boolean isSyncAdapter = isCallerSyncAdapter(uri);
-
- switch (mUriMatcher.match(uri))
- {
- case SYNCSTATE_ID:
- // the id is ignored, we only match by account type and name given in the Uri
- case SYNCSTATE:
- {
- if (TextUtils.isEmpty(getAccountName(uri)) || TextUtils.isEmpty(getAccountType(uri)))
- {
- throw new IllegalArgumentException("uri must contain an account when accessing syncstate");
- }
- selectAccount(sqlBuilder, uri);
- sqlBuilder.setTables(Tables.SYNCSTATE);
- break;
- }
- case LISTS:
- // add account to selection if any
- selectAccount(sqlBuilder, uri);
- sqlBuilder.setTables(Tables.LISTS);
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.TaskLists.DEFAULT_SORT_ORDER;
- }
- break;
-
- case LIST_ID:
- // add account to selection if any
- selectAccount(sqlBuilder, uri);
- sqlBuilder.setTables(Tables.LISTS);
- selectId(sqlBuilder, TaskListColumns._ID, uri);
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.TaskLists.DEFAULT_SORT_ORDER;
- }
- break;
-
- case TASKS:
- if (shouldLoadProperties(uri))
- {
- // extended properties were requested, therefore change to task view that includes these properties
- sqlBuilder.setTables(Tables.TASKS_PROPERTY_VIEW);
- }
- else
- {
- sqlBuilder.setTables(Tables.TASKS_VIEW);
- }
- if (!isSyncAdapter)
- {
- // do not return deleted rows if caller is not a sync adapter
- sqlBuilder.appendWhere(" AND ");
- sqlBuilder.appendWhere(Tasks._DELETED);
- sqlBuilder.appendWhere("=0");
- }
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.Tasks.DEFAULT_SORT_ORDER;
- }
- break;
-
- case TASK_ID:
- if (shouldLoadProperties(uri))
- {
- // extended properties were requested, therefore change to task view that includes these properties
- sqlBuilder.setTables(Tables.TASKS_PROPERTY_VIEW);
- }
- else
- {
- sqlBuilder.setTables(Tables.TASKS_VIEW);
- }
- selectId(sqlBuilder, TaskColumns._ID, uri);
- if (!isSyncAdapter)
- {
- // do not return deleted rows if caller is not a sync adapter
- sqlBuilder.appendWhere(" AND ");
- sqlBuilder.appendWhere(Tasks._DELETED);
- sqlBuilder.appendWhere("=0");
- }
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.Tasks.DEFAULT_SORT_ORDER;
- }
- break;
-
- case INSTANCES:
- if (shouldLoadProperties(uri))
- {
- // extended properties were requested, therefore change to instance view that includes these properties
- sqlBuilder.setTables(Tables.INSTANCE_PROPERTY_VIEW);
- }
- else
- {
- sqlBuilder.setTables(Tables.INSTANCE_VIEW);
- }
- if (!isSyncAdapter)
- {
- // do not return deleted rows if caller is not a sync adapter
- sqlBuilder.appendWhere(" AND ");
- sqlBuilder.appendWhere(Tasks._DELETED);
- sqlBuilder.appendWhere("=0");
- }
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.Instances.DEFAULT_SORT_ORDER;
- }
- break;
-
- case INSTANCE_ID:
- if (shouldLoadProperties(uri))
- {
- // extended properties were requested, therefore change to instance view that includes these properties
- sqlBuilder.setTables(Tables.INSTANCE_PROPERTY_VIEW);
- }
- else
- {
- sqlBuilder.setTables(Tables.INSTANCE_VIEW);
- }
- selectId(sqlBuilder, Instances._ID, uri);
- if (!isSyncAdapter)
- {
- // do not return deleted rows if caller is not a sync adapter
- sqlBuilder.appendWhere(" AND ");
- sqlBuilder.appendWhere(Tasks._DELETED);
- sqlBuilder.appendWhere("=0");
- }
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.Instances.DEFAULT_SORT_ORDER;
- }
- break;
-
- case CATEGORIES:
- selectAccount(sqlBuilder, uri);
- sqlBuilder.setTables(Tables.CATEGORIES);
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.Categories.DEFAULT_SORT_ORDER;
- }
- break;
-
- case CATEGORY_ID:
- selectAccount(sqlBuilder, uri);
- sqlBuilder.setTables(Tables.CATEGORIES);
- selectId(sqlBuilder, CategoriesColumns._ID, uri);
- if (sortOrder == null || sortOrder.length() == 0)
- {
- sortOrder = TaskContract.Categories.DEFAULT_SORT_ORDER;
- }
- break;
-
- case PROPERTIES:
- sqlBuilder.setTables(Tables.PROPERTIES);
- break;
-
- case PROPERTY_ID:
- sqlBuilder.setTables(Tables.PROPERTIES);
- selectId(sqlBuilder, PropertyColumns.PROPERTY_ID, uri);
- break;
-
- case SEARCH:
- String searchString = uri.getQueryParameter(Tasks.SEARCH_QUERY_PARAMETER);
- searchString = Uri.decode(searchString);
- Cursor searchCursor = FTSDatabaseHelper.getTaskSearchCursor(db, searchString, projection, selection, selectionArgs, sortOrder);
- if (searchCursor != null)
- {
- // attach tasks uri for notifications, that way the search results are updated when a task changes
- searchCursor.setNotificationUri(getContext().getContentResolver(), Tasks.getContentUri(mAuthority));
- }
- return searchCursor;
-
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- Cursor c = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
-
- if (c != null)
- {
- c.setNotificationUri(getContext().getContentResolver(), uri);
- }
- return c;
- }
-
-
- @Override
- public int deleteInTransaction(final SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs, final boolean isSyncAdapter)
- {
- int count = 0;
- String accountName = getAccountName(uri);
- String accountType = getAccountType(uri);
-
- switch (mUriMatcher.match(uri))
- {
- case SYNCSTATE_ID:
- // the id is ignored, we only match by account type and name given in the Uri
- case SYNCSTATE:
- {
- if (!isSyncAdapter)
- {
- throw new IllegalAccessError("only sync adapters may access syncstate");
- }
- if (TextUtils.isEmpty(getAccountName(uri)) || TextUtils.isEmpty(getAccountType(uri)))
- {
- throw new IllegalArgumentException("uri must contain an account when accessing syncstate");
- }
- selection = updateSelection(selectAccount(uri), selection);
- count = db.delete(Tables.SYNCSTATE, selection, selectionArgs);
- break;
- }
- /*
- * Deleting task lists is only allowed to sync adapters. They must provide ACCOUNT_NAME and ACCOUNT_TYPE.
+ /**
+ * Our authority.
+ */
+ String mAuthority;
+
+ /**
+ * The {@link UriMatcher} we use.
+ */
+ private UriMatcher mUriMatcher;
+
+ /**
+ * A handler to execute asynchronous jobs.
+ */
+ Handler mAsyncHandler;
+
+ /**
+ * An {@link ProviderOperationsLog} to track all changes within a transaction.
+ */
+ private ProviderOperationsLog mOperationsLog = new ProviderOperationsLog();
+
+
+ @Override
+ public boolean onCreate()
+ {
+ ProviderInfo providerInfo = getProviderInfo();
+
+ mAuthority = providerInfo.authority;
+
+ mTaskProcessors.add(new TaskValidatorProcessor());
+ mTaskProcessors.add(new AutoUpdateProcessor());
+ mTaskProcessors.add(new RelationProcessor());
+ mTaskProcessors.add(new TaskInstancesProcessor());
+ mTaskProcessors.add(new FtsProcessor());
+ mTaskProcessors.add(new ChangeListProcessor());
+ mTaskProcessors.add(new TaskExecutionProcessor());
+
+ mListProcessors.add(new ListValidatorProcessor());
+ mListProcessors.add(new ListExecutionProcessor());
+
+ mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mUriMatcher.addURI(mAuthority, TaskContract.TaskLists.CONTENT_URI_PATH, LISTS);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.TaskLists.CONTENT_URI_PATH + "/#", LIST_ID);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.Tasks.CONTENT_URI_PATH, TASKS);
+ mUriMatcher.addURI(mAuthority, TaskContract.Tasks.CONTENT_URI_PATH + "/#", TASK_ID);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.Instances.CONTENT_URI_PATH, INSTANCES);
+ mUriMatcher.addURI(mAuthority, TaskContract.Instances.CONTENT_URI_PATH + "/#", INSTANCE_ID);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.Properties.CONTENT_URI_PATH, PROPERTIES);
+ mUriMatcher.addURI(mAuthority, TaskContract.Properties.CONTENT_URI_PATH + "/#", PROPERTY_ID);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.Categories.CONTENT_URI_PATH, CATEGORIES);
+ mUriMatcher.addURI(mAuthority, TaskContract.Categories.CONTENT_URI_PATH + "/#", CATEGORY_ID);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.Alarms.CONTENT_URI_PATH, ALARMS);
+ mUriMatcher.addURI(mAuthority, TaskContract.Alarms.CONTENT_URI_PATH + "/#", ALARM_ID);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.Tasks.SEARCH_URI_PATH, SEARCH);
+
+ mUriMatcher.addURI(mAuthority, TaskContract.SyncState.CONTENT_URI_PATH, SYNCSTATE);
+ mUriMatcher.addURI(mAuthority, TaskContract.SyncState.CONTENT_URI_PATH + "/#", SYNCSTATE_ID);
+
+ ContentOperation.register(mUriMatcher, mAuthority, OPERATIONS);
+
+ boolean result = super.onCreate();
+
+ // create a HandlerThread to perform async operations
+ HandlerThread thread = new HandlerThread("backgroundHandler");
+ thread.start();
+ mAsyncHandler = new Handler(thread.getLooper());
+
+ AccountManager accountManager = AccountManager.get(getContext());
+ accountManager.addOnAccountsUpdatedListener(this, mAsyncHandler, true);
+
+ updateNotifications();
+
+ return result;
+ }
+
+
+ /**
+ * Return true if the caller is a sync adapter (i.e. if the Uri contains the query parameter {@link TaskContract#CALLER_IS_SYNCADAPTER} and its value is
+ * true).
+ *
+ * @param uri
+ * The {@link Uri} to check.
+ *
+ * @return true
if the caller pretends to be a sync adapter, false
otherwise.
+ */
+ @Override
+ public boolean isCallerSyncAdapter(Uri uri)
+ {
+ String param = uri.getQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER);
+ return param != null && !"false".equals(param);
+ }
+
+
+ /**
+ * Return true if the URI indicates to a load extended properties with {@link TaskContract#LOAD_PROPERTIES}.
+ *
+ * @param uri
+ * The {@link Uri} to check.
+ *
+ * @return true
if the URI requests to load extended properties, false
otherwise.
+ */
+ public boolean shouldLoadProperties(Uri uri)
+ {
+ String param = uri.getQueryParameter(TaskContract.LOAD_PROPERTIES);
+ return param != null && !"false".equals(param);
+ }
+
+
+ /**
+ * Get the account name from the given {@link Uri}.
+ *
+ * @param uri
+ * The Uri to check.
+ *
+ * @return The account name or null if no account name has been specified.
+ */
+ protected String getAccountName(Uri uri)
+ {
+ return uri.getQueryParameter(TaskContract.ACCOUNT_NAME);
+ }
+
+
+ /**
+ * Get the account type from the given {@link Uri}.
+ *
+ * @param uri
+ * The Uri to check.
+ *
+ * @return The account type or null if no account type has been specified.
+ */
+ protected String getAccountType(Uri uri)
+ {
+ return uri.getQueryParameter(TaskContract.ACCOUNT_TYPE);
+ }
+
+
+ /**
+ * Get any id from the given {@link Uri}.
+ *
+ * @param uri
+ * The Uri.
+ *
+ * @return The last path segment (which should contain the id).
+ */
+ private long getId(Uri uri)
+ {
+ return Long.parseLong(uri.getPathSegments().get(1));
+ }
+
+
+ /**
+ * Build a selection string that selects the account specified in uri
.
+ *
+ * @param uri
+ * A {@link Uri} that specifies an account.
+ *
+ * @return A {@link StringBuilder} with a selection string for the account.
+ */
+ protected StringBuilder selectAccount(Uri uri)
+ {
+ StringBuilder sb = new StringBuilder(256);
+ return selectAccount(sb, uri);
+ }
+
+
+ /**
+ * Append the selection of the account specified in uri
to the {@link StringBuilder} sb
.
+ *
+ * @param sb
+ * A {@link StringBuilder} that the selection is appended to.
+ * @param uri
+ * A {@link Uri} that specifies an account.
+ *
+ * @return sb
.
+ */
+ protected StringBuilder selectAccount(StringBuilder sb, Uri uri)
+ {
+ String accountName = getAccountName(uri);
+ String accountType = getAccountType(uri);
+
+ if (accountName != null || accountType != null)
+ {
+
+ if (accountName != null)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append(" AND ");
+ }
+
+ sb.append(TaskListSyncColumns.ACCOUNT_NAME);
+ sb.append("=");
+ DatabaseUtils.appendEscapedSQLString(sb, accountName);
+ }
+ if (accountType != null)
+ {
+
+ if (sb.length() > 0)
+ {
+ sb.append(" AND ");
+ }
+
+ sb.append(TaskListSyncColumns.ACCOUNT_TYPE);
+ sb.append("=");
+ DatabaseUtils.appendEscapedSQLString(sb, accountType);
+ }
+ }
+ return sb;
+ }
+
+
+ /**
+ * Append the selection of the account specified in uri
to the an {@link SQLiteQueryBuilder}.
+ *
+ * @param sqlBuilder
+ * A {@link SQLiteQueryBuilder} that the selection is appended to.
+ * @param uri
+ * A {@link Uri} that specifies an account.
+ */
+ protected void selectAccount(SQLiteQueryBuilder sqlBuilder, Uri uri)
+ {
+ String accountName = getAccountName(uri);
+ String accountType = getAccountType(uri);
+
+ if (accountName != null)
+ {
+ sqlBuilder.appendWhere(" AND ");
+ sqlBuilder.appendWhere(TaskListSyncColumns.ACCOUNT_NAME);
+ sqlBuilder.appendWhere("=");
+ sqlBuilder.appendWhereEscapeString(accountName);
+ }
+ if (accountType != null)
+ {
+ sqlBuilder.appendWhere(" AND ");
+ sqlBuilder.appendWhere(TaskListSyncColumns.ACCOUNT_TYPE);
+ sqlBuilder.appendWhere("=");
+ sqlBuilder.appendWhereEscapeString(accountType);
+ }
+ }
+
+
+ private StringBuilder _selectId(StringBuilder sb, long id, String key)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append(" AND ");
+ }
+ sb.append(key);
+ sb.append("=");
+ sb.append(id);
+ return sb;
+ }
+
+
+ protected StringBuilder selectId(Uri uri)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ return selectId(sb, uri);
+ }
+
+
+ protected StringBuilder selectId(StringBuilder sb, Uri uri)
+ {
+ return _selectId(sb, getId(uri), TaskListColumns._ID);
+ }
+
+
+ protected StringBuilder selectTaskId(Uri uri)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ return selectTaskId(sb, uri);
+ }
+
+
+ protected StringBuilder selectTaskId(long id)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ return selectTaskId(sb, id);
+ }
+
+
+ protected StringBuilder selectTaskId(StringBuilder sb, Uri uri)
+ {
+ return selectTaskId(sb, getId(uri));
+ }
+
+
+ protected StringBuilder selectTaskId(StringBuilder sb, long id)
+ {
+ return _selectId(sb, id, Instances.TASK_ID);
+
+ }
+
+
+ protected StringBuilder selectPropertyId(Uri uri)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ return selectPropertyId(sb, uri);
+ }
+
+
+ protected StringBuilder selectPropertyId(StringBuilder sb, Uri uri)
+ {
+ return selectPropertyId(sb, getId(uri));
+ }
+
+
+ protected StringBuilder selectPropertyId(long id)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ return selectPropertyId(sb, id);
+ }
+
+
+ protected StringBuilder selectPropertyId(StringBuilder sb, long id)
+ {
+ return _selectId(sb, id, PropertyColumns.PROPERTY_ID);
+ }
+
+
+ /**
+ * Add a selection by ID to the given {@link SQLiteQueryBuilder}. The id is taken from the given Uri.
+ *
+ * @param sqlBuilder
+ * The {@link SQLiteQueryBuilder} to append the selection to.
+ * @param idColumn
+ * The column that must match the id.
+ * @param uri
+ * An {@link Uri} that contains the id.
+ */
+ protected void selectId(SQLiteQueryBuilder sqlBuilder, String idColumn, Uri uri)
+ {
+ sqlBuilder.appendWhere(" AND ");
+ sqlBuilder.appendWhere(idColumn);
+ sqlBuilder.appendWhere("=");
+ sqlBuilder.appendWhere(String.valueOf(getId(uri)));
+ }
+
+
+ /**
+ * Append any arbitrary selection string to the selection in sb
+ *
+ * @param sb
+ * A {@link StringBuilder} that already contains a selection string.
+ * @param selection
+ * A valid SQL selection string.
+ *
+ * @return A string with the final selection.
+ */
+ protected String updateSelection(StringBuilder sb, String selection)
+ {
+ if (selection != null)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append("AND ( ").append(selection).append(" ) ");
+ }
+ else
+ {
+ sb.append(" ( ").append(selection).append(" ) ");
+ }
+ }
+ return sb.toString();
+ }
+
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
+ {
+ final SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
+ // initialize appendWhere, this allows us to append all other selections with a preceding "AND"
+ sqlBuilder.appendWhere(" 1=1 ");
+ boolean isSyncAdapter = isCallerSyncAdapter(uri);
+
+ switch (mUriMatcher.match(uri))
+ {
+ case SYNCSTATE_ID:
+ // the id is ignored, we only match by account type and name given in the Uri
+ case SYNCSTATE:
+ {
+ if (TextUtils.isEmpty(getAccountName(uri)) || TextUtils.isEmpty(getAccountType(uri)))
+ {
+ throw new IllegalArgumentException("uri must contain an account when accessing syncstate");
+ }
+ selectAccount(sqlBuilder, uri);
+ sqlBuilder.setTables(Tables.SYNCSTATE);
+ break;
+ }
+ case LISTS:
+ // add account to selection if any
+ selectAccount(sqlBuilder, uri);
+ sqlBuilder.setTables(Tables.LISTS);
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.TaskLists.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case LIST_ID:
+ // add account to selection if any
+ selectAccount(sqlBuilder, uri);
+ sqlBuilder.setTables(Tables.LISTS);
+ selectId(sqlBuilder, TaskListColumns._ID, uri);
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.TaskLists.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case TASKS:
+ if (shouldLoadProperties(uri))
+ {
+ // extended properties were requested, therefore change to task view that includes these properties
+ sqlBuilder.setTables(Tables.TASKS_PROPERTY_VIEW);
+ }
+ else
+ {
+ sqlBuilder.setTables(Tables.TASKS_VIEW);
+ }
+ if (!isSyncAdapter)
+ {
+ // do not return deleted rows if caller is not a sync adapter
+ sqlBuilder.appendWhere(" AND ");
+ sqlBuilder.appendWhere(Tasks._DELETED);
+ sqlBuilder.appendWhere("=0");
+ }
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.Tasks.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case TASK_ID:
+ if (shouldLoadProperties(uri))
+ {
+ // extended properties were requested, therefore change to task view that includes these properties
+ sqlBuilder.setTables(Tables.TASKS_PROPERTY_VIEW);
+ }
+ else
+ {
+ sqlBuilder.setTables(Tables.TASKS_VIEW);
+ }
+ selectId(sqlBuilder, TaskColumns._ID, uri);
+ if (!isSyncAdapter)
+ {
+ // do not return deleted rows if caller is not a sync adapter
+ sqlBuilder.appendWhere(" AND ");
+ sqlBuilder.appendWhere(Tasks._DELETED);
+ sqlBuilder.appendWhere("=0");
+ }
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.Tasks.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case INSTANCES:
+ if (shouldLoadProperties(uri))
+ {
+ // extended properties were requested, therefore change to instance view that includes these properties
+ sqlBuilder.setTables(Tables.INSTANCE_PROPERTY_VIEW);
+ }
+ else
+ {
+ sqlBuilder.setTables(Tables.INSTANCE_VIEW);
+ }
+ if (!isSyncAdapter)
+ {
+ // do not return deleted rows if caller is not a sync adapter
+ sqlBuilder.appendWhere(" AND ");
+ sqlBuilder.appendWhere(Tasks._DELETED);
+ sqlBuilder.appendWhere("=0");
+ }
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.Instances.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case INSTANCE_ID:
+ if (shouldLoadProperties(uri))
+ {
+ // extended properties were requested, therefore change to instance view that includes these properties
+ sqlBuilder.setTables(Tables.INSTANCE_PROPERTY_VIEW);
+ }
+ else
+ {
+ sqlBuilder.setTables(Tables.INSTANCE_VIEW);
+ }
+ selectId(sqlBuilder, Instances._ID, uri);
+ if (!isSyncAdapter)
+ {
+ // do not return deleted rows if caller is not a sync adapter
+ sqlBuilder.appendWhere(" AND ");
+ sqlBuilder.appendWhere(Tasks._DELETED);
+ sqlBuilder.appendWhere("=0");
+ }
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.Instances.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case CATEGORIES:
+ selectAccount(sqlBuilder, uri);
+ sqlBuilder.setTables(Tables.CATEGORIES);
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.Categories.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case CATEGORY_ID:
+ selectAccount(sqlBuilder, uri);
+ sqlBuilder.setTables(Tables.CATEGORIES);
+ selectId(sqlBuilder, CategoriesColumns._ID, uri);
+ if (sortOrder == null || sortOrder.length() == 0)
+ {
+ sortOrder = TaskContract.Categories.DEFAULT_SORT_ORDER;
+ }
+ break;
+
+ case PROPERTIES:
+ sqlBuilder.setTables(Tables.PROPERTIES);
+ break;
+
+ case PROPERTY_ID:
+ sqlBuilder.setTables(Tables.PROPERTIES);
+ selectId(sqlBuilder, PropertyColumns.PROPERTY_ID, uri);
+ break;
+
+ case SEARCH:
+ String searchString = uri.getQueryParameter(Tasks.SEARCH_QUERY_PARAMETER);
+ searchString = Uri.decode(searchString);
+ Cursor searchCursor = FTSDatabaseHelper.getTaskSearchCursor(db, searchString, projection, selection, selectionArgs, sortOrder);
+ if (searchCursor != null)
+ {
+ // attach tasks uri for notifications, that way the search results are updated when a task changes
+ searchCursor.setNotificationUri(getContext().getContentResolver(), Tasks.getContentUri(mAuthority));
+ }
+ return searchCursor;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+
+ Cursor c = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+
+ if (c != null)
+ {
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ }
+ return c;
+ }
+
+
+ @Override
+ public int deleteInTransaction(final SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs, final boolean isSyncAdapter)
+ {
+ int count = 0;
+ String accountName = getAccountName(uri);
+ String accountType = getAccountType(uri);
+
+ switch (mUriMatcher.match(uri))
+ {
+ case SYNCSTATE_ID:
+ // the id is ignored, we only match by account type and name given in the Uri
+ case SYNCSTATE:
+ {
+ if (!isSyncAdapter)
+ {
+ throw new IllegalAccessError("only sync adapters may access syncstate");
+ }
+ if (TextUtils.isEmpty(getAccountName(uri)) || TextUtils.isEmpty(getAccountType(uri)))
+ {
+ throw new IllegalArgumentException("uri must contain an account when accessing syncstate");
+ }
+ selection = updateSelection(selectAccount(uri), selection);
+ count = db.delete(Tables.SYNCSTATE, selection, selectionArgs);
+ break;
+ }
+ /*
+ * Deleting task lists is only allowed to sync adapters. They must provide ACCOUNT_NAME and ACCOUNT_TYPE.
*/
- case LIST_ID:
- // add _id to selection and fall through
- selection = updateSelection(selectId(uri), selection);
- case LISTS:
- {
- if (isSyncAdapter)
- {
- if (TextUtils.isEmpty(accountType) || TextUtils.isEmpty(accountName))
- {
- throw new IllegalArgumentException("Sync adapters must specify an account and account type: " + uri);
- }
- }
-
- // iterate over all lists that match the selection. We iterate "manually" to execute any processors before or after deletion.
- final Cursor cursor = db.query(Tables.LISTS, null, selection, selectionArgs, null, null, null, null);
-
- try
- {
- while (cursor.moveToNext())
- {
- final ListAdapter list = new CursorContentValuesListAdapter(ListAdapter._ID.getFrom(cursor), cursor, new ContentValues());
-
- ProviderOperation.DELETE.execute(db, mListProcessors, list, isSyncAdapter, mOperationsLog, mAuthority);
- count++;
- }
- }
- finally
- {
- cursor.close();
- }
-
- break;
-
- }
- /*
+ case LIST_ID:
+ // add _id to selection and fall through
+ selection = updateSelection(selectId(uri), selection);
+ case LISTS:
+ {
+ if (isSyncAdapter)
+ {
+ if (TextUtils.isEmpty(accountType) || TextUtils.isEmpty(accountName))
+ {
+ throw new IllegalArgumentException("Sync adapters must specify an account and account type: " + uri);
+ }
+ }
+
+ // iterate over all lists that match the selection. We iterate "manually" to execute any processors before or after deletion.
+ final Cursor cursor = db.query(Tables.LISTS, null, selection, selectionArgs, null, null, null, null);
+
+ try
+ {
+ while (cursor.moveToNext())
+ {
+ final ListAdapter list = new CursorContentValuesListAdapter(ListAdapter._ID.getFrom(cursor), cursor, new ContentValues());
+
+ ProviderOperation.DELETE.execute(db, mListProcessors, list, isSyncAdapter, mOperationsLog, mAuthority);
+ count++;
+ }
+ }
+ finally
+ {
+ cursor.close();
+ }
+
+ break;
+
+ }
+ /*
* Task won't be removed, just marked as deleted if the caller isn't a sync adapter. Sync adapters can remove tasks immediately.
*/
- case TASK_ID:
- // add id to selection and fall through
- selection = updateSelection(selectId(uri), selection);
-
- case TASKS:
- {
- // TODO: filter by account name and type if present in uri.
-
- if (isSyncAdapter)
- {
- if (TextUtils.isEmpty(accountType) || TextUtils.isEmpty(accountName))
- {
- throw new IllegalArgumentException("Sync adapters must specify an account and account type: " + uri);
- }
- }
-
- // iterate over all tasks that match the selection. We iterate "manually" to execute any processors before or after deletion.
- final Cursor cursor = db.query(Tables.TASKS_VIEW, null, selection, selectionArgs, null, null, null, null);
-
- try
- {
- while (cursor.moveToNext())
- {
- final TaskAdapter task = new CursorContentValuesTaskAdapter(cursor, new ContentValues());
-
- ProviderOperation.DELETE.execute(db, mTaskProcessors, task, isSyncAdapter, mOperationsLog, mAuthority);
- count++;
- }
- }
- finally
- {
- cursor.close();
- }
-
- break;
- }
- case ALARM_ID:
- // add id to selection and fall through
- selection = updateSelection(selectId(uri), selection);
-
- case ALARMS:
-
- count = db.delete(Tables.ALARMS, selection, selectionArgs);
- break;
-
- case PROPERTY_ID:
- selection = updateSelection(selectPropertyId(uri), selection);
-
- case PROPERTIES:
- // fetch all properties that match the selection
- Cursor cursor = db.query(Tables.PROPERTIES, null, selection, selectionArgs, null, null, null);
-
- try
- {
- int propIdCol = cursor.getColumnIndex(Properties.PROPERTY_ID);
- int taskIdCol = cursor.getColumnIndex(Properties.TASK_ID);
- int mimeTypeCol = cursor.getColumnIndex(Properties.MIMETYPE);
- while (cursor.moveToNext())
- {
- long propertyId = cursor.getLong(propIdCol);
- long taskId = cursor.getLong(taskIdCol);
- String mimeType = cursor.getString(mimeTypeCol);
- if (mimeType != null)
- {
- PropertyHandler handler = PropertyHandlerFactory.get(mimeType);
- count += handler.delete(db, taskId, propertyId, cursor, isSyncAdapter);
- }
- }
- }
- finally
- {
- cursor.close();
- }
- postNotifyUri(Properties.getContentUri(mAuthority));
- break;
-
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- if (count > 0)
- {
- postNotifyUri(uri);
- postNotifyUri(Instances.getContentUri(mAuthority));
- postNotifyUri(Tasks.getContentUri(mAuthority));
- }
- return count;
- }
-
-
- @Override
- public Uri insertInTransaction(final SQLiteDatabase db, Uri uri, final ContentValues values, final boolean isSyncAdapter)
- {
- long rowId = 0;
- Uri result_uri = null;
-
- String accountName = getAccountName(uri);
- String accountType = getAccountType(uri);
-
- switch (mUriMatcher.match(uri))
- {
- case SYNCSTATE:
- {
- if (!isSyncAdapter)
- {
- throw new IllegalAccessError("only sync adapters may access syncstate");
- }
- if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType))
- {
- throw new IllegalArgumentException("uri must contain an account when accessing syncstate");
- }
- values.put(SyncState.ACCOUNT_NAME, accountName);
- values.put(SyncState.ACCOUNT_TYPE, accountType);
- rowId = db.replace(Tables.SYNCSTATE, null, values);
- result_uri = TaskContract.SyncState.getContentUri(mAuthority);
- break;
- }
- case LISTS:
- {
- final ListAdapter list = new ContentValuesListAdapter(values);
- list.set(ListAdapter.ACCOUNT_NAME, accountName);
- list.set(ListAdapter.ACCOUNT_TYPE, accountType);
-
- ProviderOperation.INSERT.execute(db, mListProcessors, list, isSyncAdapter, mOperationsLog, mAuthority);
-
- rowId = list.id();
- result_uri = TaskContract.TaskLists.getContentUri(mAuthority);
-
- break;
- }
- case TASKS:
- final TaskAdapter task = new ContentValuesTaskAdapter(values);
-
- ProviderOperation.INSERT.execute(db, mTaskProcessors, task, isSyncAdapter, mOperationsLog, mAuthority);
-
- rowId = task.id();
- result_uri = TaskContract.Tasks.getContentUri(mAuthority);
-
- postNotifyUri(Instances.getContentUri(mAuthority));
- postNotifyUri(Tasks.getContentUri(mAuthority));
-
- break;
-
- case PROPERTIES:
- String mimetype = values.getAsString(Properties.MIMETYPE);
-
- if (mimetype == null)
- {
- throw new IllegalArgumentException("missing mimetype in property values");
- }
-
- Long taskId = values.getAsLong(Properties.TASK_ID);
- if (taskId == null)
- {
- throw new IllegalArgumentException("missing task id in property values");
- }
-
- if (values.containsKey(Properties.PROPERTY_ID))
- {
- throw new IllegalArgumentException("property id can not be written");
- }
-
- PropertyHandler handler = PropertyHandlerFactory.get(mimetype);
- rowId = handler.insert(db, taskId, values, isSyncAdapter);
- result_uri = TaskContract.Properties.getContentUri(mAuthority);
- if (rowId >= 0)
- {
- postNotifyUri(Tasks.getContentUri(mAuthority));
- postNotifyUri(Instances.getContentUri(mAuthority));
- }
- break;
-
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- if (rowId > 0 && result_uri != null)
- {
- result_uri = ContentUris.withAppendedId(result_uri, rowId);
- postNotifyUri(result_uri);
- postNotifyUri(uri);
- return result_uri;
- }
- throw new SQLException("Failed to insert row into " + uri);
- }
-
-
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- @Override
- public int updateInTransaction(final SQLiteDatabase db, Uri uri, final ContentValues values, String selection, String[] selectionArgs,
- final boolean isSyncAdapter)
- {
- int count = 0;
- switch (mUriMatcher.match(uri))
- {
- case SYNCSTATE_ID:
- // the id is ignored, we only match by account type and name given in the Uri
- case SYNCSTATE:
- {
- if (!isSyncAdapter)
- {
- throw new IllegalAccessError("only sync adapters may access syncstate");
- }
-
- String accountName = getAccountName(uri);
- String accountType = getAccountType(uri);
- if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType))
- {
- throw new IllegalArgumentException("uri must contain an account when accessing syncstate");
- }
-
- if (values.size() == 0)
- {
- // we're done
- break;
- }
-
- values.put(SyncState.ACCOUNT_NAME, accountName);
- values.put(SyncState.ACCOUNT_TYPE, accountType);
-
- long id = db.replace(Tables.SYNCSTATE, null, values);
- if (id >= 0)
- {
- count = 1;
- }
- break;
- }
- case LIST_ID:
- // update selection and fall through
- selection = updateSelection(selectId(uri), selection);
-
- case LISTS:
- {
- // iterate over all task lists that match the selection. We iterate "manually" to execute any processors before or after insert.
- final Cursor cursor = db.query(Tables.LISTS, null, selection, selectionArgs, null, null, null, null);
-
- int idCol = cursor.getColumnIndex(TaskContract.TaskLists._ID);
-
- try
- {
- while (cursor.moveToNext())
- {
- final long listId = cursor.getLong(idCol);
-
- // clone list values if we have more than one list to update
- // we need this, because the processors may change the values
- final ListAdapter list = new CursorContentValuesListAdapter(listId, cursor, cursor.getCount() > 1 ? new ContentValues(values) : values);
-
- ProviderOperation.UPDATE.execute(db, mListProcessors, list, isSyncAdapter, mOperationsLog, mAuthority);
- count++;
- }
- }
- finally
- {
- cursor.close();
- }
- break;
- }
- case TASK_ID:
- // update selection and fall through
- selection = updateSelection(selectId(uri), selection);
-
- case TASKS:
- {
- // iterate over all tasks that match the selection. We iterate "manually" to execute any processors before or after insert.
- final Cursor cursor = db.query(Tables.TASKS_VIEW, null, selection, selectionArgs, null, null, null, null);
-
- try
- {
- while (cursor.moveToNext())
- {
- // clone task values if we have more than one task to update
- // we need this, because the processors may change the values
- final TaskAdapter task = new CursorContentValuesTaskAdapter(cursor, cursor.getCount() > 1 ? new ContentValues(values) : values);
-
- ProviderOperation.UPDATE.execute(db, mTaskProcessors, task, isSyncAdapter, mOperationsLog, mAuthority);
- count++;
- }
- }
- finally
- {
- cursor.close();
- }
-
- if (count > 0)
- {
- postNotifyUri(Instances.getContentUri(mAuthority));
- postNotifyUri(Tasks.getContentUri(mAuthority));
- }
- break;
- }
-
- case PROPERTY_ID:
- selection = updateSelection(selectPropertyId(uri), selection);
-
- case PROPERTIES:
- if (values.containsKey(Properties.MIMETYPE))
- {
- throw new IllegalArgumentException("property mimetypes can not be modified");
- }
-
- if (values.containsKey(Properties.TASK_ID))
- {
- throw new IllegalArgumentException("task id can not be changed");
- }
-
- if (values.containsKey(Properties.PROPERTY_ID))
- {
- throw new IllegalArgumentException("property id can not be changed");
- }
-
- // fetch all properties that match the selection
- Cursor cursor = db.query(Tables.PROPERTIES, null, selection, selectionArgs, null, null, null);
-
- try
- {
- int propIdCol = cursor.getColumnIndex(Properties.PROPERTY_ID);
- int taskIdCol = cursor.getColumnIndex(Properties.TASK_ID);
- int mimeTypeCol = cursor.getColumnIndex(Properties.MIMETYPE);
- while (cursor.moveToNext())
- {
- long propertyId = cursor.getLong(propIdCol);
- long taskId = cursor.getLong(taskIdCol);
- String mimeType = cursor.getString(mimeTypeCol);
- if (mimeType != null)
- {
- PropertyHandler handler = PropertyHandlerFactory.get(mimeType);
- count += handler.update(db, taskId, propertyId, values, cursor, isSyncAdapter);
- }
- }
- }
- finally
- {
- cursor.close();
- }
- postNotifyUri(Properties.getContentUri(mAuthority));
- break;
-
- case CATEGORY_ID:
- String newCategorySelection = updateSelection(selectId(uri), selection);
- validateCategoryValues(values, false, isSyncAdapter);
- count = db.update(Tables.CATEGORIES, values, newCategorySelection, selectionArgs);
- break;
- case ALARM_ID:
- String newAlarmSelection = updateSelection(selectId(uri), selection);
- validateAlarmValues(values, false, isSyncAdapter);
- count = db.update(Tables.ALARMS, values, newAlarmSelection, selectionArgs);
- break;
- default:
- ContentOperation operation = ContentOperation.get(mUriMatcher.match(uri), OPERATIONS);
-
- if (operation == null)
- {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- operation.run(getContext(), mAsyncHandler, uri, db, values);
- }
-
- // get the keys in values
- SetisNew
is false
. If isNew
is true
this value is ignored.
- * @param isNew
- * Indicates that the content is new and not an update.
- * @param values
- * The {@link ContentValues} to validate.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The valid {@link ContentValues}.
- *
- * @throws IllegalArgumentException
- * if the {@link ContentValues} are invalid.
- */
- @Override
- public ContentValues validateValues(SQLiteDatabase db, long taskId, long propertyId, boolean isNew, ContentValues values, boolean isSyncAdapter)
- {
- // row id can not be changed or set manually
- if (values.containsKey(Property.Alarm.PROPERTY_ID))
- {
- throw new IllegalArgumentException("_ID can not be set manually");
- }
-
- if (!values.containsKey(Property.Alarm.MINUTES_BEFORE))
- {
- throw new IllegalArgumentException("alarm property requires a time offset");
- }
-
- if (!values.containsKey(Property.Alarm.REFERENCE) || values.getAsInteger(Property.Alarm.REFERENCE) < 0)
- {
- throw new IllegalArgumentException("alarm property requires a valid reference date ");
- }
-
- if (!values.containsKey(Property.Alarm.ALARM_TYPE))
- {
- throw new IllegalArgumentException("alarm property requires an alarm type");
- }
-
- return values;
- }
-
-
- /**
- * Inserts the alarm into the database.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * The id of the task the new property belongs to.
- * @param values
- * The {@link ContentValues} to insert.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The row id of the new alarm as long
- */
- @Override
- public long insert(SQLiteDatabase db, long taskId, ContentValues values, boolean isSyncAdapter)
- {
- values = validateValues(db, taskId, -1, true, values, isSyncAdapter);
- return super.insert(db, taskId, values, isSyncAdapter);
- }
-
-
- /**
- * Updates the alarm in the database.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * The id of the task this property belongs to.
- * @param propertyId
- * The id of the property.
- * @param values
- * The {@link ContentValues} to update.
- * @param oldValues
- * A {@link Cursor} pointing to the old values in the database.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The number of rows affected.
- */
- @Override
- public int update(SQLiteDatabase db, long taskId, long propertyId, ContentValues values, Cursor oldValues, boolean isSyncAdapter)
- {
- values = validateValues(db, taskId, propertyId, false, values, isSyncAdapter);
- return super.update(db, taskId, propertyId, values, oldValues, isSyncAdapter);
- }
+ // private static final String[] ALARM_ID_PROJECTION = { Alarms.ALARM_ID };
+ // private static final String ALARM_SELECTION = Alarms.ALARM_ID + " =?";
+
+
+ /**
+ * Validates the content of the alarm prior to insert and update transactions.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task this property belongs to.
+ * @param propertyId
+ * The id of the property if isNew
is false
. If isNew
is true
this value is ignored.
+ * @param isNew
+ * Indicates that the content is new and not an update.
+ * @param values
+ * The {@link ContentValues} to validate.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The valid {@link ContentValues}.
+ *
+ * @throws IllegalArgumentException
+ * if the {@link ContentValues} are invalid.
+ */
+ @Override
+ public ContentValues validateValues(SQLiteDatabase db, long taskId, long propertyId, boolean isNew, ContentValues values, boolean isSyncAdapter)
+ {
+ // row id can not be changed or set manually
+ if (values.containsKey(Property.Alarm.PROPERTY_ID))
+ {
+ throw new IllegalArgumentException("_ID can not be set manually");
+ }
+
+ if (!values.containsKey(Property.Alarm.MINUTES_BEFORE))
+ {
+ throw new IllegalArgumentException("alarm property requires a time offset");
+ }
+
+ if (!values.containsKey(Property.Alarm.REFERENCE) || values.getAsInteger(Property.Alarm.REFERENCE) < 0)
+ {
+ throw new IllegalArgumentException("alarm property requires a valid reference date ");
+ }
+
+ if (!values.containsKey(Property.Alarm.ALARM_TYPE))
+ {
+ throw new IllegalArgumentException("alarm property requires an alarm type");
+ }
+
+ return values;
+ }
+
+
+ /**
+ * Inserts the alarm into the database.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task the new property belongs to.
+ * @param values
+ * The {@link ContentValues} to insert.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The row id of the new alarm as long
+ */
+ @Override
+ public long insert(SQLiteDatabase db, long taskId, ContentValues values, boolean isSyncAdapter)
+ {
+ values = validateValues(db, taskId, -1, true, values, isSyncAdapter);
+ return super.insert(db, taskId, values, isSyncAdapter);
+ }
+
+
+ /**
+ * Updates the alarm in the database.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task this property belongs to.
+ * @param propertyId
+ * The id of the property.
+ * @param values
+ * The {@link ContentValues} to update.
+ * @param oldValues
+ * A {@link Cursor} pointing to the old values in the database.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The number of rows affected.
+ */
+ @Override
+ public int update(SQLiteDatabase db, long taskId, long propertyId, ContentValues values, Cursor oldValues, boolean isSyncAdapter)
+ {
+ values = validateValues(db, taskId, propertyId, false, values, isSyncAdapter);
+ return super.update(db, taskId, propertyId, values, oldValues, isSyncAdapter);
+ }
}
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/CategoryHandler.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/CategoryHandler.java
index 87d5148aa2baab5f41eab0330a2bfab65b4bdcba..bd3fd9e9c0203806629df3a2677060ad81fd854a 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/CategoryHandler.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/CategoryHandler.java
@@ -17,6 +17,10 @@
package org.dmfs.provider.tasks.handler;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
import org.dmfs.provider.tasks.TaskContract.Categories;
import org.dmfs.provider.tasks.TaskContract.Properties;
import org.dmfs.provider.tasks.TaskContract.Property.Category;
@@ -24,254 +28,251 @@ import org.dmfs.provider.tasks.TaskContract.Tasks;
import org.dmfs.provider.tasks.TaskDatabaseHelper.CategoriesMapping;
import org.dmfs.provider.tasks.TaskDatabaseHelper.Tables;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-
/**
* This class is used to handle category property values during database transactions.
- *
+ *
* @author Tobias Reinsch isNew
is false
. If isNew
is true
this value is ignored.
- * @param isNew
- * Indicates that the content is new and not an update.
- * @param values
- * The {@link ContentValues} to validate.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The valid {@link ContentValues}.
- *
- * @throws IllegalArgumentException
- * if the {@link ContentValues} are invalid.
- */
- @Override
- public ContentValues validateValues(SQLiteDatabase db, long taskId, long propertyId, boolean isNew, ContentValues values, boolean isSyncAdapter)
- {
- // the category requires a name or an id
- if (!values.containsKey(Category.CATEGORY_ID) && !values.containsKey(Category.CATEGORY_NAME))
- {
- throw new IllegalArgumentException("Neiter an id nor a category name was supplied for the category property.");
- }
-
- // get the matching task & account for the property
- if (!values.containsKey(Properties.TASK_ID))
- {
- throw new IllegalArgumentException("No task id was supplied for the category property");
- }
- String[] queryArgs = { values.getAsString(Properties.TASK_ID) };
- String[] queryProjection = { Tasks.ACCOUNT_NAME, Tasks.ACCOUNT_TYPE };
- String querySelection = Tasks._ID + "=?";
- Cursor taskCursor = db.query(Tables.TASKS_VIEW, queryProjection, querySelection, queryArgs, null, null, null);
-
- String accountName = null;
- String accountType = null;
- try
- {
- if (taskCursor.moveToNext())
- {
- accountName = taskCursor.getString(0);
- accountType = taskCursor.getString(1);
-
- values.put(Categories.ACCOUNT_NAME, accountName);
- values.put(Categories.ACCOUNT_TYPE, accountType);
- }
- }
- finally
- {
- if (taskCursor != null)
- {
- taskCursor.close();
- }
- }
-
- if (accountName != null && accountType != null)
- {
- // search for matching categories
- String[] categoryArgs;
- Cursor cursor;
-
- if (values.containsKey(Categories._ID))
- {
- // serach by ID
- categoryArgs = new String[] { values.getAsString(Category.CATEGORY_ID), accountName, accountType };
- cursor = db.query(Tables.CATEGORIES, CATEGORY_ID_PROJECTION, CATEGORY_ID_SELECTION, categoryArgs, null, null, null);
- }
- else
- {
- // search by name
- categoryArgs = new String[] { values.getAsString(Category.CATEGORY_NAME), accountName, accountType };
- cursor = db.query(Tables.CATEGORIES, CATEGORY_ID_PROJECTION, CATEGORY_NAME_SELECTION, categoryArgs, null, null, null);
- }
- try
- {
- if (cursor != null && cursor.getCount() == 1)
- {
- cursor.moveToNext();
- Long categoryID = cursor.getLong(0);
- String categoryName = cursor.getString(1);
- int color = cursor.getInt(2);
-
- values.put(Category.CATEGORY_ID, categoryID);
- values.put(Category.CATEGORY_NAME, categoryName);
- values.put(Category.CATEGORY_COLOR, color);
- values.put(IS_NEW_CATEGORY, false);
- }
- else
- {
- values.put(IS_NEW_CATEGORY, true);
- }
- }
- finally
- {
- if (cursor != null)
- {
- cursor.close();
- }
- }
-
- }
-
- return values;
- }
-
-
- /**
- * Inserts the category into the database.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * The id of the task the new property belongs to.
- * @param values
- * The {@link ContentValues} to insert.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The row id of the new category as long
- */
- @Override
- public long insert(SQLiteDatabase db, long taskId, ContentValues values, boolean isSyncAdapter)
- {
- values = validateValues(db, taskId, -1, true, values, isSyncAdapter);
- values = getOrInsertCategory(db, values);
-
- // insert property row and create relation
- long id = super.insert(db, taskId, values, isSyncAdapter);
- insertRelation(db, taskId, values.getAsLong(Category.CATEGORY_ID), id);
-
- // update FTS entry with category name
- updateFTSEntry(db, taskId, id, values.getAsString(Category.CATEGORY_NAME));
- return id;
- }
-
-
- /**
- * Updates the category in the database.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * The id of the task this property belongs to.
- * @param propertyId
- * The id of the property.
- * @param values
- * The {@link ContentValues} to update.
- * @param oldValues
- * A {@link Cursor} pointing to the old values in the database.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The number of rows affected.
- */
- @Override
- public int update(SQLiteDatabase db, long taskId, long propertyId, ContentValues values, Cursor oldValues, boolean isSyncAdapter)
- {
- values = validateValues(db, taskId, propertyId, false, values, isSyncAdapter);
- values = getOrInsertCategory(db, values);
-
- if (values.containsKey(Category.CATEGORY_NAME))
- {
- // update FTS entry with new category name
- updateFTSEntry(db, taskId, propertyId, values.getAsString(Category.CATEGORY_NAME));
- }
-
- return super.update(db, taskId, propertyId, values, oldValues, isSyncAdapter);
- }
-
-
- /**
- * Check if a category with matching {@link ContentValues} exists and returns the existing category or creates a new category in the database.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param values
- * The {@link ContentValues} of the category.
- * @return The {@link ContentValues} of the existing or new category.
- */
- private ContentValues getOrInsertCategory(SQLiteDatabase db, ContentValues values)
- {
- if (values.getAsBoolean(IS_NEW_CATEGORY))
- {
- // insert new category in category table
- ContentValues newCategoryValues = new ContentValues(4);
- newCategoryValues.put(Categories.ACCOUNT_NAME, values.getAsString(Categories.ACCOUNT_NAME));
- newCategoryValues.put(Categories.ACCOUNT_TYPE, values.getAsString(Categories.ACCOUNT_TYPE));
- newCategoryValues.put(Categories.NAME, values.getAsString(Category.CATEGORY_NAME));
- newCategoryValues.put(Categories.COLOR, values.getAsInteger(Category.CATEGORY_COLOR));
-
- long categoryID = db.insert(Tables.CATEGORIES, "", newCategoryValues);
- values.put(Category.CATEGORY_ID, categoryID);
- }
-
- // remove redundant values
- values.remove(IS_NEW_CATEGORY);
- values.remove(Categories.ACCOUNT_NAME);
- values.remove(Categories.ACCOUNT_TYPE);
-
- return values;
- }
-
-
- /**
- * Inserts a relation entry in the database to link task and category.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * The row id of the task.
- * @param categoryId
- * The row id of the category.
- * @return The row id of the inserted relation.
- */
- private long insertRelation(SQLiteDatabase db, long taskId, long categoryId, long propertyId)
- {
- ContentValues relationValues = new ContentValues(3);
- relationValues.put(CategoriesMapping.TASK_ID, taskId);
- relationValues.put(CategoriesMapping.CATEGORY_ID, categoryId);
- relationValues.put(CategoriesMapping.PROPERTY_ID, propertyId);
- return db.insert(Tables.CATEGORIES_MAPPING, "", relationValues);
- }
+ private static final String[] CATEGORY_ID_PROJECTION = { Categories._ID, Categories.NAME, Categories.COLOR };
+
+ private static final String CATEGORY_ID_SELECTION = Categories._ID + "=? and " + Categories.ACCOUNT_NAME + "=? and " + Categories.ACCOUNT_TYPE + "=?";
+ private static final String CATEGORY_NAME_SELECTION = Categories.NAME + "=? and " + Categories.ACCOUNT_NAME + "=? and " + Categories.ACCOUNT_TYPE + "=?";
+
+ public static final String IS_NEW_CATEGORY = "is_new_category";
+
+
+ /**
+ * Validates the content of the category prior to insert and update transactions.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task this property belongs to.
+ * @param propertyId
+ * The id of the property if isNew
is false
. If isNew
is true
this value is ignored.
+ * @param isNew
+ * Indicates that the content is new and not an update.
+ * @param values
+ * The {@link ContentValues} to validate.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The valid {@link ContentValues}.
+ *
+ * @throws IllegalArgumentException
+ * if the {@link ContentValues} are invalid.
+ */
+ @Override
+ public ContentValues validateValues(SQLiteDatabase db, long taskId, long propertyId, boolean isNew, ContentValues values, boolean isSyncAdapter)
+ {
+ // the category requires a name or an id
+ if (!values.containsKey(Category.CATEGORY_ID) && !values.containsKey(Category.CATEGORY_NAME))
+ {
+ throw new IllegalArgumentException("Neiter an id nor a category name was supplied for the category property.");
+ }
+
+ // get the matching task & account for the property
+ if (!values.containsKey(Properties.TASK_ID))
+ {
+ throw new IllegalArgumentException("No task id was supplied for the category property");
+ }
+ String[] queryArgs = { values.getAsString(Properties.TASK_ID) };
+ String[] queryProjection = { Tasks.ACCOUNT_NAME, Tasks.ACCOUNT_TYPE };
+ String querySelection = Tasks._ID + "=?";
+ Cursor taskCursor = db.query(Tables.TASKS_VIEW, queryProjection, querySelection, queryArgs, null, null, null);
+
+ String accountName = null;
+ String accountType = null;
+ try
+ {
+ if (taskCursor.moveToNext())
+ {
+ accountName = taskCursor.getString(0);
+ accountType = taskCursor.getString(1);
+
+ values.put(Categories.ACCOUNT_NAME, accountName);
+ values.put(Categories.ACCOUNT_TYPE, accountType);
+ }
+ }
+ finally
+ {
+ if (taskCursor != null)
+ {
+ taskCursor.close();
+ }
+ }
+
+ if (accountName != null && accountType != null)
+ {
+ // search for matching categories
+ String[] categoryArgs;
+ Cursor cursor;
+
+ if (values.containsKey(Categories._ID))
+ {
+ // serach by ID
+ categoryArgs = new String[] { values.getAsString(Category.CATEGORY_ID), accountName, accountType };
+ cursor = db.query(Tables.CATEGORIES, CATEGORY_ID_PROJECTION, CATEGORY_ID_SELECTION, categoryArgs, null, null, null);
+ }
+ else
+ {
+ // search by name
+ categoryArgs = new String[] { values.getAsString(Category.CATEGORY_NAME), accountName, accountType };
+ cursor = db.query(Tables.CATEGORIES, CATEGORY_ID_PROJECTION, CATEGORY_NAME_SELECTION, categoryArgs, null, null, null);
+ }
+ try
+ {
+ if (cursor != null && cursor.getCount() == 1)
+ {
+ cursor.moveToNext();
+ Long categoryID = cursor.getLong(0);
+ String categoryName = cursor.getString(1);
+ int color = cursor.getInt(2);
+
+ values.put(Category.CATEGORY_ID, categoryID);
+ values.put(Category.CATEGORY_NAME, categoryName);
+ values.put(Category.CATEGORY_COLOR, color);
+ values.put(IS_NEW_CATEGORY, false);
+ }
+ else
+ {
+ values.put(IS_NEW_CATEGORY, true);
+ }
+ }
+ finally
+ {
+ if (cursor != null)
+ {
+ cursor.close();
+ }
+ }
+
+ }
+
+ return values;
+ }
+
+
+ /**
+ * Inserts the category into the database.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task the new property belongs to.
+ * @param values
+ * The {@link ContentValues} to insert.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The row id of the new category as long
+ */
+ @Override
+ public long insert(SQLiteDatabase db, long taskId, ContentValues values, boolean isSyncAdapter)
+ {
+ values = validateValues(db, taskId, -1, true, values, isSyncAdapter);
+ values = getOrInsertCategory(db, values);
+
+ // insert property row and create relation
+ long id = super.insert(db, taskId, values, isSyncAdapter);
+ insertRelation(db, taskId, values.getAsLong(Category.CATEGORY_ID), id);
+
+ // update FTS entry with category name
+ updateFTSEntry(db, taskId, id, values.getAsString(Category.CATEGORY_NAME));
+ return id;
+ }
+
+
+ /**
+ * Updates the category in the database.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task this property belongs to.
+ * @param propertyId
+ * The id of the property.
+ * @param values
+ * The {@link ContentValues} to update.
+ * @param oldValues
+ * A {@link Cursor} pointing to the old values in the database.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The number of rows affected.
+ */
+ @Override
+ public int update(SQLiteDatabase db, long taskId, long propertyId, ContentValues values, Cursor oldValues, boolean isSyncAdapter)
+ {
+ values = validateValues(db, taskId, propertyId, false, values, isSyncAdapter);
+ values = getOrInsertCategory(db, values);
+
+ if (values.containsKey(Category.CATEGORY_NAME))
+ {
+ // update FTS entry with new category name
+ updateFTSEntry(db, taskId, propertyId, values.getAsString(Category.CATEGORY_NAME));
+ }
+
+ return super.update(db, taskId, propertyId, values, oldValues, isSyncAdapter);
+ }
+
+
+ /**
+ * Check if a category with matching {@link ContentValues} exists and returns the existing category or creates a new category in the database.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param values
+ * The {@link ContentValues} of the category.
+ *
+ * @return The {@link ContentValues} of the existing or new category.
+ */
+ private ContentValues getOrInsertCategory(SQLiteDatabase db, ContentValues values)
+ {
+ if (values.getAsBoolean(IS_NEW_CATEGORY))
+ {
+ // insert new category in category table
+ ContentValues newCategoryValues = new ContentValues(4);
+ newCategoryValues.put(Categories.ACCOUNT_NAME, values.getAsString(Categories.ACCOUNT_NAME));
+ newCategoryValues.put(Categories.ACCOUNT_TYPE, values.getAsString(Categories.ACCOUNT_TYPE));
+ newCategoryValues.put(Categories.NAME, values.getAsString(Category.CATEGORY_NAME));
+ newCategoryValues.put(Categories.COLOR, values.getAsInteger(Category.CATEGORY_COLOR));
+
+ long categoryID = db.insert(Tables.CATEGORIES, "", newCategoryValues);
+ values.put(Category.CATEGORY_ID, categoryID);
+ }
+
+ // remove redundant values
+ values.remove(IS_NEW_CATEGORY);
+ values.remove(Categories.ACCOUNT_NAME);
+ values.remove(Categories.ACCOUNT_TYPE);
+
+ return values;
+ }
+
+
+ /**
+ * Inserts a relation entry in the database to link task and category.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The row id of the task.
+ * @param categoryId
+ * The row id of the category.
+ *
+ * @return The row id of the inserted relation.
+ */
+ private long insertRelation(SQLiteDatabase db, long taskId, long categoryId, long propertyId)
+ {
+ ContentValues relationValues = new ContentValues(3);
+ relationValues.put(CategoriesMapping.TASK_ID, taskId);
+ relationValues.put(CategoriesMapping.CATEGORY_ID, categoryId);
+ relationValues.put(CategoriesMapping.PROPERTY_ID, propertyId);
+ return db.insert(Tables.CATEGORIES_MAPPING, "", relationValues);
+ }
}
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/DefaultPropertyHandler.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/DefaultPropertyHandler.java
index 54cbe66cbf106880a73afdd6a235be8952f1b6a9..84c5f97641de695d8c2650cd53e4ec9d7229bfd4 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/DefaultPropertyHandler.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/DefaultPropertyHandler.java
@@ -23,34 +23,33 @@ import android.database.sqlite.SQLiteDatabase;
/**
* This class is used to handle properties with unknown / unsupported mime-types.
- *
+ *
* @author Tobias Reinsch isNew
is false
. If isNew
is true
this value is ignored.
- * @param isNew
- * Indicates that the content is new and not an update.
- * @param values
- * The {@link ContentValues} to validate.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The valid {@link ContentValues}.
- *
- * @throws IllegalArgumentException
- * if the {@link ContentValues} are invalid.
- */
- public abstract ContentValues validateValues(SQLiteDatabase db, long taskId, long propertyId, boolean isNew, ContentValues values, boolean isSyncAdapter);
-
-
- /**
- * Inserts the property {@link ContentValues} into the database.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * The id of the task the new property belongs to.
- * @param values
- * The {@link ContentValues} to insert.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The row id of the new property as long
- */
- public long insert(SQLiteDatabase db, long taskId, ContentValues values, boolean isSyncAdapter)
- {
- return db.insert(Tables.PROPERTIES, "", values);
- }
-
-
- /**
- * Updates the property {@link ContentValues} in the database.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * The id of the task this property belongs to.
- * @param propertyId
- * The id of the property.
- * @param values
- * The {@link ContentValues} to update.
- * @param oldValues
- * A {@link Cursor} pointing to the old values in the database.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- *
- * @return The number of rows affected.
- */
- public int update(SQLiteDatabase db, long taskId, long propertyId, ContentValues values, Cursor oldValues, boolean isSyncAdapter)
- {
- return db.update(Tables.PROPERTIES, values, Properties.PROPERTY_ID + "=" + propertyId, null);
- }
-
-
- /**
- * Deletes the property in the database.
- *
- * @param db
- * The belonging database.
- * @param taskId
- * The id of the task this property belongs to.
- * @param propertyId
- * The id of the property.
- * @param oldValues
- * A {@link Cursor} pointing to the old values in the database.
- * @param isSyncAdapter
- * Indicates that the transaction was triggered from a SyncAdapter.
- * @return
- */
- public int delete(SQLiteDatabase db, long taskId, long propertyId, Cursor oldValues, boolean isSyncAdapter)
- {
- return db.delete(Tables.PROPERTIES, Properties.PROPERTY_ID + "=" + propertyId, null);
-
- }
-
-
- /**
- * Method hook to insert FTS entries on database migration.
- *
- * @param db
- * The {@link SQLiteDatabase}.
- * @param taskId
- * the row id of the task this property belongs to
- * @param propertyId
- * the id of the property
- * @param text
- * the searchable text of the property. If the property has multiple text snippets to search in, concat them separated by a space.
- */
- protected void updateFTSEntry(SQLiteDatabase db, long taskId, long propertyId, String text)
- {
- FTSDatabaseHelper.updatePropertyFTSEntry(db, taskId, propertyId, text);
-
- }
+ /**
+ * Validates the content of the property prior to insert and update transactions.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task this property belongs to.
+ * @param propertyId
+ * The id of the property if isNew
is false
. If isNew
is true
this value is ignored.
+ * @param isNew
+ * Indicates that the content is new and not an update.
+ * @param values
+ * The {@link ContentValues} to validate.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The valid {@link ContentValues}.
+ *
+ * @throws IllegalArgumentException
+ * if the {@link ContentValues} are invalid.
+ */
+ public abstract ContentValues validateValues(SQLiteDatabase db, long taskId, long propertyId, boolean isNew, ContentValues values, boolean isSyncAdapter);
+
+
+ /**
+ * Inserts the property {@link ContentValues} into the database.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task the new property belongs to.
+ * @param values
+ * The {@link ContentValues} to insert.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The row id of the new property as long
+ */
+ public long insert(SQLiteDatabase db, long taskId, ContentValues values, boolean isSyncAdapter)
+ {
+ return db.insert(Tables.PROPERTIES, "", values);
+ }
+
+
+ /**
+ * Updates the property {@link ContentValues} in the database.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * The id of the task this property belongs to.
+ * @param propertyId
+ * The id of the property.
+ * @param values
+ * The {@link ContentValues} to update.
+ * @param oldValues
+ * A {@link Cursor} pointing to the old values in the database.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return The number of rows affected.
+ */
+ public int update(SQLiteDatabase db, long taskId, long propertyId, ContentValues values, Cursor oldValues, boolean isSyncAdapter)
+ {
+ return db.update(Tables.PROPERTIES, values, Properties.PROPERTY_ID + "=" + propertyId, null);
+ }
+
+
+ /**
+ * Deletes the property in the database.
+ *
+ * @param db
+ * The belonging database.
+ * @param taskId
+ * The id of the task this property belongs to.
+ * @param propertyId
+ * The id of the property.
+ * @param oldValues
+ * A {@link Cursor} pointing to the old values in the database.
+ * @param isSyncAdapter
+ * Indicates that the transaction was triggered from a SyncAdapter.
+ *
+ * @return
+ */
+ public int delete(SQLiteDatabase db, long taskId, long propertyId, Cursor oldValues, boolean isSyncAdapter)
+ {
+ return db.delete(Tables.PROPERTIES, Properties.PROPERTY_ID + "=" + propertyId, null);
+
+ }
+
+
+ /**
+ * Method hook to insert FTS entries on database migration.
+ *
+ * @param db
+ * The {@link SQLiteDatabase}.
+ * @param taskId
+ * the row id of the task this property belongs to
+ * @param propertyId
+ * the id of the property
+ * @param text
+ * the searchable text of the property. If the property has multiple text snippets to search in, concat them separated by a space.
+ */
+ protected void updateFTSEntry(SQLiteDatabase db, long taskId, long propertyId, String text)
+ {
+ FTSDatabaseHelper.updatePropertyFTSEntry(db, taskId, propertyId, text);
+
+ }
}
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/PropertyHandlerFactory.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/PropertyHandlerFactory.java
index e424658add70621f72ca022567b91ada536ea251..449abda76617a1036114915f400d0a24682e8809 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/PropertyHandlerFactory.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/PropertyHandlerFactory.java
@@ -24,39 +24,39 @@ import org.dmfs.provider.tasks.TaskContract.Property.Relation;
/**
* A factory that creates the matching {@link PropertyHandler} for the given mimetype.
- *
+ *
* @author Tobias Reinsch null
- */
- public static PropertyHandler get(String mimeType)
- {
- if (Category.CONTENT_ITEM_TYPE.equals(mimeType))
- {
- return CATEGORY_HANDLER;
- }
- if (Alarm.CONTENT_ITEM_TYPE.equals(mimeType))
- {
- return ALARM_HANDLER;
- }
- if (Relation.CONTENT_ITEM_TYPE.equals(mimeType))
- {
- return RELATION_HANDLER;
- }
- return DEFAULT_PROPERTY_HANDLER;
- }
+ /**
+ * Creates a specific {@link PropertyHandler}.
+ *
+ * @param mimeType
+ * The mimetype of the property.
+ *
+ * @return The matching {@link PropertyHandler} for the given mimetype or null
+ */
+ public static PropertyHandler get(String mimeType)
+ {
+ if (Category.CONTENT_ITEM_TYPE.equals(mimeType))
+ {
+ return CATEGORY_HANDLER;
+ }
+ if (Alarm.CONTENT_ITEM_TYPE.equals(mimeType))
+ {
+ return ALARM_HANDLER;
+ }
+ if (Relation.CONTENT_ITEM_TYPE.equals(mimeType))
+ {
+ return RELATION_HANDLER;
+ }
+ return DEFAULT_PROPERTY_HANDLER;
+ }
}
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/RelationHandler.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/RelationHandler.java
index 574c1b986a9d878270fcf297e8a19a4d6e553ca4..7e2962bceb4cee09ed0d06b6638a71cd26bc5bed 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/RelationHandler.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/handler/RelationHandler.java
@@ -17,255 +17,255 @@
package org.dmfs.provider.tasks.handler;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
import org.dmfs.provider.tasks.TaskContract.Property.Relation;
import org.dmfs.provider.tasks.TaskContract.Property.Relation.RelType;
import org.dmfs.provider.tasks.TaskContract.Tasks;
import org.dmfs.provider.tasks.TaskDatabaseHelper;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-
/**
* Handles any inserts, updates and deletes on the relations table.
- *
+ *
* @author Marten Gajda _id
or _uid
, depending of which value is given. We can't resolve anything if only {@link Relation#RELATED_URI} is
- * given. The given values are update in-place.
- * _id
or _uid
, depending of which value is given. We can't resolve anything if only {@link Relation#RELATED_URI} is
+ * given. The given values are update in-place.
+ *