/*
* Copyright (c) 2015 Jonas Kalderstam.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.notepad.data.local.sql;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.util.Log;
import com.nononsenseapps.notepad.data.local.UpdateNotifier;
import com.nononsenseapps.notepad.data.model.sql.RemoteTask;
import com.nononsenseapps.notepad.data.model.sql.RemoteTaskList;
import com.nononsenseapps.notepad.data.model.sql.DAO;
import com.nononsenseapps.notepad.data.model.sql.Notification;
import com.nononsenseapps.notepad.data.model.sql.Task;
import com.nononsenseapps.notepad.data.model.sql.TaskList;
import java.util.ArrayList;
public class MyContentProvider extends ContentProvider {
public static final String AUTHORITY = "com.nononsenseapps.NotePad";
public static final String SCHEME = "content://";
private static final UriMatcher sURIMatcher = new UriMatcher(
UriMatcher.NO_MATCH);
static {
TaskList.addMatcherUris(sURIMatcher);
Task.addMatcherUris(sURIMatcher);
Notification.addMatcherUris(sURIMatcher);
RemoteTaskList.addMatcherUris(sURIMatcher);
RemoteTask.addMatcherUris(sURIMatcher);
}
public MyContentProvider() {
}
@Override
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case Notification.BASEITEMCODE:
case Notification.BASEURICODE:
case Notification.WITHTASKQUERYCODE:
case Notification.WITHTASKQUERYITEMCODE:
return Notification.CONTENT_TYPE;
case TaskList.BASEITEMCODE:
case TaskList.BASEURICODE:
case TaskList.LEGACYBASEITEMCODE:
case TaskList.LEGACYBASEURICODE:
case TaskList.LEGACYVISIBLEITEMCODE:
case TaskList.LEGACYVISIBLEURICODE:
return TaskList.CONTENT_TYPE;
case Task.BASEITEMCODE:
case Task.BASEURICODE:
case Task.SECTIONEDDATEITEMCODE:
case Task.SECTIONEDDATEQUERYCODE:
case Task.LEGACYBASEITEMCODE:
case Task.LEGACYBASEURICODE:
case Task.LEGACYVISIBLEITEMCODE:
case Task.LEGACYVISIBLEURICODE:
case Task.SEARCHCODE:
case Task.SEARCHSUGGESTIONSCODE:
return Task.CONTENT_TYPE;
default:
// throw new IllegalArgumentException("Unknown URI " + uri);
}
// Legacy URIs, above didn't work for some reason
if (uri.toString().startsWith(
LegacyDBHelper.NotePad.Lists.CONTENT_URI.toString())
|| uri.toString().startsWith(
LegacyDBHelper.NotePad.Lists.CONTENT_VISIBLE_URI
.toString())) {
return TaskList.CONTENT_TYPE;
}
else if (uri.toString().startsWith(
LegacyDBHelper.NotePad.Notes.CONTENT_URI.toString())
|| uri.toString().startsWith(
LegacyDBHelper.NotePad.Notes.CONTENT_VISIBLE_URI
.toString())) {
return Task.CONTENT_TYPE;
}
throw new IllegalArgumentException("Unknown URI " + uri);
}
@Override
public boolean onCreate() {
return true;
}
@Override
synchronized public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = DatabaseHandler.getInstance(getContext())
.getWritableDatabase();
Uri result = null;
db.beginTransaction();
// Do not add legacy URIs
try {
final DAO item;
switch (sURIMatcher.match(uri)) {
case TaskList.BASEURICODE:
item = new TaskList(values);
break;
case Task.BASEURICODE:
item = new Task(values);
break;
case Notification.BASEURICODE:
case Notification.WITHTASKQUERYITEMCODE:
item = new Notification(values);
break;
case RemoteTaskList.BASEURICODE:
item = new RemoteTaskList(values);
break;
case RemoteTask.BASEURICODE:
item = new RemoteTask(values);
break;
default:
throw new IllegalArgumentException(
"Faulty insertURI provided: " + uri.toString());
}
result = item.insert(getContext(), db);
db.setTransactionSuccessful();
}
catch (SQLException e) {
// Crap...
}
finally {
db.endTransaction();
}
if (result != null) {
DAO.notifyProviderOnChange(getContext(), uri);
DAO.notifyProviderOnChange(getContext(), TaskList.URI_WITH_COUNT);
UpdateNotifier.updateWidgets(getContext());
UpdateNotifier.notifyChangeList(getContext());
}
return result;
}
@Override
synchronized public int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
final SQLiteDatabase db = DatabaseHandler.getInstance(getContext())
.getWritableDatabase();
int result = 0;
final Task t;
final SQLiteStatement stmt;
final String sql;
final ArrayList<Uri> updateUris = new ArrayList<Uri>();
db.beginTransaction();
try {
// Do not add legacy URIs
switch (sURIMatcher.match(uri)) {
case TaskList.BASEITEMCODE:
updateUris.add(TaskList.URI);
updateUris.add(TaskList.URI_WITH_COUNT);
final TaskList list = new TaskList(uri, values);
result += db.update(TaskList.TABLE_NAME, list.getContent(),
TaskList.whereIdIs(selection),
TaskList.whereIdArg(list._id, selectionArgs));
break;
case Task.MOVEITEMLEFTCODE:
updateUris.add(Task.URI);
t = new Task(values);
sql = t.getSQLMoveItemLeft(values);
if (sql != null) {
stmt = db.compileStatement(sql);
result += stmt.executeUpdateDelete();
}
break;
case Task.MOVEITEMRIGHTCODE:
updateUris.add(Task.URI);
t = new Task(values);
sql = t.getSQLMoveItemRight(values);
if (sql != null) {
stmt = db.compileStatement(sql);
result += stmt.executeUpdateDelete();
}
break;
case Task.BASEITEMCODE:
updateUris.add(Task.URI);
updateUris.add(Task.URI_SECTIONED_BY_DATE);
updateUris.add(Task.URI_TASK_HISTORY);
updateUris.add(TaskList.URI);
updateUris.add(TaskList.URI_WITH_COUNT);
// regular update
t = new Task(uri, values);
if (t.getContent().size() > 0) {
// Something changed in task
result += db.update(Task.TABLE_NAME, t.getContent(),
Task.whereIdIs(selection),
Task.whereIdArg(t._id, selectionArgs));
}
break;
case Task.BASEURICODE:
updateUris.add(Task.URI);
updateUris.add(TaskList.URI);
updateUris.add(TaskList.URI_WITH_COUNT);
// Batch. No checks made
result += db.update(Task.TABLE_NAME, values, selection,
selectionArgs);
break;
case Notification.BASEITEMCODE:
case Notification.WITHTASKQUERYITEMCODE:
updateUris.add(Notification.URI);
updateUris.add(Notification.URI_WITH_TASK_PATH);
// final Notification n = new Notification(uri, values);
result += db.update(Notification.TABLE_NAME, values,
Notification.whereIdIs(selection), Notification
.whereIdArg(Long.parseLong(uri
.getLastPathSegment()), selectionArgs));
break;
case Notification.BASEURICODE:
updateUris.add(Notification.URI);
updateUris.add(Notification.URI_WITH_TASK_PATH);
// No checks
result += db.update(Notification.TABLE_NAME, values, selection,
selectionArgs);
break;
case RemoteTaskList.BASEITEMCODE:
updateUris.add(RemoteTaskList.URI);
result += db.update(RemoteTaskList.TABLE_NAME, values,
RemoteTaskList.whereIdIs(selection),
RemoteTaskList.whereIdArg(Long.parseLong(uri
.getLastPathSegment()), selectionArgs)
);
break;
case RemoteTask.BASEITEMCODE:
updateUris.add(RemoteTask.URI);
result += db.update(RemoteTask.TABLE_NAME, values,
RemoteTask.whereIdIs(selection),
RemoteTask.whereIdArg(Long.parseLong(uri
.getLastPathSegment()), selectionArgs)
);
break;
default:
throw new IllegalArgumentException("Faulty URI provided: "
+ uri.toString());
}
if (result >= 0) {
db.setTransactionSuccessful();
}
}
finally {
db.endTransaction();
}
if (result >= 0) {
for (Uri u: updateUris) {
DAO.notifyProviderOnChange(getContext(), u);
}
UpdateNotifier.updateWidgets(getContext());
UpdateNotifier.notifyChangeList(getContext());
}
return result;
}
synchronized private int safeDeleteItem(final SQLiteDatabase db,
final String tableName, final Uri uri, final String selection,
final String[] selectionArgs) {
db.beginTransaction();
int result = 0;
try {
result += db.delete(
tableName,
DAO.whereIdIs(selection),
DAO.joinArrays(selectionArgs,
new String[] { uri.getLastPathSegment() }));
db.setTransactionSuccessful();
}
finally {
db.endTransaction();
}
return result;
}
@Override
synchronized public int delete(Uri uri, String selection,
String[] selectionArgs) {
final SQLiteDatabase db = DatabaseHandler.getInstance(getContext())
.getWritableDatabase();
int result = 0;
// Do not add legacy URIs
switch (sURIMatcher.match(uri)) {
case TaskList.BASEITEMCODE:
result += safeDeleteItem(db, TaskList.TABLE_NAME, uri, selection,
selectionArgs);
break;
case TaskList.BASEURICODE:
result += db.delete(TaskList.TABLE_NAME, selection, selectionArgs);
break;
case Task.BASEITEMCODE:
result += safeDeleteItem(db, Task.TABLE_NAME, uri, selection,
selectionArgs);
break;
case Task.BASEURICODE:
result += db.delete(Task.TABLE_NAME, selection, selectionArgs);
break;
case Notification.BASEURICODE:
result += db.delete(Notification.TABLE_NAME, selection,
selectionArgs);
break;
case Notification.BASEITEMCODE:
case Notification.WITHTASKQUERYITEMCODE:
result += safeDeleteItem(db, Notification.TABLE_NAME, uri,
selection, selectionArgs);
break;
case RemoteTaskList.BASEURICODE:
result += db.delete(RemoteTaskList.TABLE_NAME, selection,
selectionArgs);
break;
case RemoteTaskList.BASEITEMCODE:
result += safeDeleteItem(db, RemoteTaskList.TABLE_NAME, uri,
selection, selectionArgs);
break;
case RemoteTask.BASEURICODE:
result += db
.delete(RemoteTask.TABLE_NAME, selection, selectionArgs);
break;
case RemoteTask.BASEITEMCODE:
result += safeDeleteItem(db, RemoteTask.TABLE_NAME, uri, selection,
selectionArgs);
break;
case Task.DELETEDQUERYCODE:
result += db.delete(Task.DELETE_TABLE_NAME, selection,
selectionArgs);
break;
case Task.DELETEDITEMCODE:
result += safeDeleteItem(db, Task.DELETE_TABLE_NAME, uri,
selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Faulty delete-URI provided: "
+ uri.toString());
}
if (result > 0) {
DAO.notifyProviderOnChange(getContext(), uri);
DAO.notifyProviderOnChange(getContext(), TaskList.URI_WITH_COUNT);
UpdateNotifier.updateWidgets(getContext());
UpdateNotifier.notifyChangeList(getContext());
}
return result;
}
@Override
synchronized public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
Cursor result = null;
final long id;
// if (selection != null) Log.d("nononsenseapps", selection);
// if (selectionArgs != null)
// Log.d("nononsenseapps", DAO.arrayToCommaString(selectionArgs));
switch (sURIMatcher.match(uri)) {
case TaskList.BASEURICODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(TaskList.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(),
TaskList.URI);
break;
case TaskList.BASEITEMCODE:
id = Long.parseLong(uri.getLastPathSegment());
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(TaskList.TABLE_NAME,
projection,
TaskList.whereIdIs(selection),
TaskList.joinArrays(selectionArgs,
new String[] { String.valueOf(id) }), null,
null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case TaskList.VIEWCOUNTCODE:
// Create view if not exists
DatabaseHandler.getInstance(getContext()).getWritableDatabase()
.execSQL(TaskList.CREATE_COUNT_VIEW);
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(TaskList.VIEWCOUNT_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case Task.DELETEDQUERYCODE:
final String[] query = sanitize(selectionArgs);
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.DELETE_TABLE_NAME,
Task.Columns.DELETEFIELDS,
Task.Columns._ID
+ " IN (SELECT "
+ Task.Columns._ID
+ " FROM "
+ Task.FTS3_DELETE_TABLE_NAME
+ ((query[0].isEmpty() || query[0]
.equals("'*'")) ? ")"
: (" WHERE "
+ Task.FTS3_DELETE_TABLE_NAME + " MATCH ?)")),
(query[0].isEmpty() || query[0].equals("'*'")) ? null
: query, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(),
Task.URI_DELETED_QUERY);
break;
case Task.BASEURICODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(),
Task.URI);
break;
case Task.BASEITEMCODE:
id = Long.parseLong(uri.getLastPathSegment());
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.TABLE_NAME,
projection,
Task.whereIdIs(selection),
Task.joinArrays(selectionArgs,
new String[] { String.valueOf(id) }), null,
null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case Task.SECTIONEDDATEQUERYCODE:
// Add list null because that's what the headers will have
final String listId;
if (selectionArgs == null || selectionArgs.length == 0) {
listId = null;
// throw new SQLException(
// "Need a listid as first arg at the moment for this view!");
}
else {
listId = selectionArgs[0];
}
// Create view if not exists
DatabaseHandler.getInstance(getContext()).getWritableDatabase()
.execSQL(Task.CREATE_SECTIONED_DATE_VIEW(listId));
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.getSECTION_DATE_VIEW_NAME(listId),
projection,
selection,
selectionArgs,
null,
null,
Task.SECRET_TYPEID + "," + Task.Columns.DUE + ","
+ Task.SECRET_TYPEID2);
result.setNotificationUri(getContext().getContentResolver(),
Task.URI);
break;
case Task.HISTORYQUERYCODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.HISTORY_TABLE_NAME, projection, selection,
selectionArgs, null, null,
Task.Columns.UPDATED + " ASC");
// SQLite timestamp in updated column.
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case Notification.BASEITEMCODE:
id = Long.parseLong(uri.getLastPathSegment());
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Notification.TABLE_NAME,
projection,
Notification.whereIdIs(selection),
Notification.joinArrays(selectionArgs,
new String[] { String.valueOf(id) }), null,
null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case Notification.WITHTASKQUERYITEMCODE:
// Create view if not exists
DatabaseHandler.getInstance(getContext()).getWritableDatabase()
.execSQL(Notification.CREATE_JOINED_VIEW);
id = Long.parseLong(uri.getLastPathSegment());
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Notification.WITH_TASK_VIEW_NAME,
projection,
Notification.whereIdIs(selection),
Notification.joinArrays(selectionArgs,
new String[] { String.valueOf(id) }), null,
null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case Notification.BASEURICODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Notification.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case Notification.WITHTASKQUERYCODE:
// Create view if not exists
DatabaseHandler.getInstance(getContext()).getWritableDatabase()
.execSQL(Notification.CREATE_JOINED_VIEW);
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Notification.WITH_TASK_VIEW_NAME, projection,
selection, selectionArgs, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case RemoteTaskList.BASEURICODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(RemoteTaskList.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case RemoteTask.BASEURICODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(RemoteTask.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
break;
case Task.SEARCHCODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.TABLE_NAME,
Task.Columns.FIELDS,
Task.Columns._ID + " IN (SELECT "
+ Task.Columns._ID + " FROM "
+ Task.FTS3_TABLE_NAME + " WHERE "
+ Task.FTS3_TABLE_NAME + " MATCH ?)",
sanitize(selectionArgs), null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(),
Task.URI_SEARCH);
break;
case TaskList.LEGACYBASEURICODE:
case TaskList.LEGACYVISIBLEURICODE:
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(TaskList.TABLE_NAME,
LegacyDBHelper.convertLegacyColumns(projection),
null, null, null, null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(),
TaskList.URI);
break;
case TaskList.LEGACYBASEITEMCODE:
case TaskList.LEGACYVISIBLEITEMCODE:
id = Long.parseLong(uri.getLastPathSegment());
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(TaskList.TABLE_NAME,
LegacyDBHelper.convertLegacyColumns(projection),
TaskList.whereIdIs(selection),
TaskList.joinArrays(selectionArgs,
new String[] { String.valueOf(id) }), null,
null, sortOrder);
result.setNotificationUri(getContext().getContentResolver(),
TaskList.getUri(id));
break;
case Task.LEGACYBASEURICODE:
case Task.LEGACYVISIBLEURICODE:
final Cursor c = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.TABLE_NAME,
LegacyDBHelper.convertLegacyColumns(projection),
null, null, null, null, Task.Columns.DUE);
result = new MatrixCursor(projection);
while (c.moveToNext()) {
((MatrixCursor) result).addRow(LegacyDBHelper
.convertLegacyTaskValues(c));
}
c.close();
result.setNotificationUri(getContext().getContentResolver(),
Task.URI);
break;
case Task.SEARCHSUGGESTIONSCODE:
final String limit = uri
.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT);
result = DatabaseHandler
.getInstance(getContext())
.getReadableDatabase()
.query(Task.FTS3_TABLE_NAME,
new String[] {
Task.Columns._ID,
Task.Columns._ID
+ " AS "
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID,
Task.Columns.TITLE
+ " AS "
+ SearchManager.SUGGEST_COLUMN_TEXT_1,
Task.Columns.NOTE
+ " AS "
+ SearchManager.SUGGEST_COLUMN_TEXT_2 },
Task.FTS3_TABLE_NAME + " MATCH ?",
sanitize(selectionArgs), null, null,
SearchManager.SUGGEST_COLUMN_TEXT_1, limit);
result.setNotificationUri(getContext().getContentResolver(),
Task.URI_SEARCH);
break;
// These legacy URIs will not be supported
case Task.LEGACYBASEITEMCODE:
case Task.LEGACYVISIBLEITEMCODE:
default:
Log.d("nononsenseapps db",
"Faulty queryURI provided: " + uri.toString());
return null;
// throw new IllegalArgumentException("Faulty queryURI provided: "
// + uri.toString());
}
return result;
}
private String[] sanitize(final String... args) {
if (args.length == 0) return new String[] { "" };
final StringBuilder result = new StringBuilder();
for (String query : args) {
// for (String part : query.split("\\s")) {
if (result.length() > 0) result.append(" AND ");
// Wrap each word in quotes and add star to the end
result.append("'" + query + "*'");
// }
}
return new String[] { result.toString() };
}
}