/*
* DeliciousDroid - http://code.google.com/p/DeliciousDroid/
*
* Copyright (C) 2010 Matt Schmidt
*
* DeliciousDroid 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.
*
* DeliciousDroid 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 DeliciousDroid; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package com.deliciousdroid.providers;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import com.deliciousdroid.Constants;
import com.deliciousdroid.R;
import com.deliciousdroid.providers.BookmarkContent.Bookmark;
import com.deliciousdroid.providers.BundleContent.Bundle;
import com.deliciousdroid.providers.TagContent.Tag;
import com.deliciousdroid.providers.BookmarkContentProvider;
import com.deliciousdroid.util.SyncUtils;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.MatrixCursor;
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.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
public class BookmarkContentProvider extends ContentProvider {
private AccountManager mAccountManager = null;
private Account mAccount = null;
private SQLiteDatabase db;
private DatabaseHelper dbHelper;
private static final String DATABASE_NAME = "DeliciousBookmarks.db";
private static final int DATABASE_VERSION = 23;
private static final String BOOKMARK_TABLE_NAME = "bookmark";
private static final String TAG_TABLE_NAME = "tag";
private static final String BUNDLE_TABLE_NAME = "bundle";
private static final int Bookmarks = 1;
private static final int SearchSuggest = 2;
private static final int Tags = 3;
private static final int TagSearchSuggest = 4;
private static final int BookmarkSearchSuggest = 5;
private static final int Bundles = 6;
private static final String SuggestionLimit = "10";
private static final UriMatcher sURIMatcher = buildUriMatcher();
public static final String AUTHORITY = "com.deliciousdroid.providers.BookmarkContentProvider";
private static class DatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase sqlDb) {
sqlDb.execSQL("Create table " + BOOKMARK_TABLE_NAME +
" (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"ACCOUNT TEXT, " +
"DESCRIPTION TEXT COLLATE NOCASE, " +
"URL TEXT COLLATE NOCASE, " +
"NOTES TEXT, " +
"TAGS TEXT, " +
"HASH TEXT, " +
"META TEXT, " +
"TIME INTEGER, " +
"SHARED INTEGER, " +
"DELETED INTEGER, " +
"SYNCED INTEGER);");
sqlDb.execSQL("CREATE INDEX " + BOOKMARK_TABLE_NAME +
"_ACCOUNT ON " + BOOKMARK_TABLE_NAME + " " +
"(ACCOUNT)");
sqlDb.execSQL("CREATE INDEX " + BOOKMARK_TABLE_NAME +
"_TAGS ON " + BOOKMARK_TABLE_NAME + " " +
"(TAGS)");
sqlDb.execSQL("CREATE INDEX " + BOOKMARK_TABLE_NAME +
"_HASH ON " + BOOKMARK_TABLE_NAME + " " +
"(HASH)");
sqlDb.execSQL("Create table " + TAG_TABLE_NAME +
" (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"ACCOUNT TEXT, " +
"NAME TEXT COLLATE NOCASE, " +
"COUNT INTEGER);");
sqlDb.execSQL("CREATE INDEX " + TAG_TABLE_NAME +
"_ACCOUNT ON " + TAG_TABLE_NAME + " " +
"(ACCOUNT)");
sqlDb.execSQL("Create table " + BUNDLE_TABLE_NAME +
" (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"ACCOUNT TEXT, " +
"NAME TEXT, " +
"TAGS TEXT);");
}
@Override
public void onUpgrade(SQLiteDatabase sqlDb, int oldVersion, int newVersion) {
sqlDb.execSQL("DROP INDEX IF EXISTS " + BOOKMARK_TABLE_NAME + "_ACCOUNT");
sqlDb.execSQL("DROP INDEX IF EXISTS " + BOOKMARK_TABLE_NAME + "_TAGS");
sqlDb.execSQL("DROP INDEX IF EXISTS " + BOOKMARK_TABLE_NAME + "_HASH");
sqlDb.execSQL("DROP INDEX IF EXISTS " + TAG_TABLE_NAME + "_ACCOUNT");
sqlDb.execSQL("DROP TABLE IF EXISTS " + BOOKMARK_TABLE_NAME);
sqlDb.execSQL("DROP TABLE IF EXISTS " + TAG_TABLE_NAME);
sqlDb.execSQL("DROP TABLE IF EXISTS " + BUNDLE_TABLE_NAME);
onCreate(sqlDb);
SyncUtils.clearSyncMarkers(mContext);
}
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (sURIMatcher.match(uri)) {
case Bookmarks:
count = db.delete(BOOKMARK_TABLE_NAME, where, whereArgs);
getContext().getContentResolver().notifyChange(uri, null, false);
break;
case Tags:
count = db.delete(TAG_TABLE_NAME, where, whereArgs);
getContext().getContentResolver().notifyChange(uri, null, false);
break;
case Bundles:
count = db.delete(BUNDLE_TABLE_NAME, where, whereArgs);
getContext().getContentResolver().notifyChange(uri, null, false);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return count;
}
@Override
public String getType(Uri uri) {
switch(sURIMatcher.match(uri)){
case Bookmarks:
return Bookmark.CONTENT_TYPE;
case SearchSuggest:
return SearchManager.SUGGEST_MIME_TYPE;
case Tags:
return Tag.CONTENT_TYPE;
case Bundles:
return Bundle.CONTENT_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
switch(sURIMatcher.match(uri)) {
case Bookmarks:
return insertBookmark(uri, values);
case Tags:
return insertTag(uri, values);
case Bundles:
return insertBundle(uri, values);
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
private Uri insertBookmark(Uri uri, ContentValues values){
db = dbHelper.getWritableDatabase();
long rowId = db.insert(BOOKMARK_TABLE_NAME, "", values);
if(rowId > 0) {
Uri rowUri = ContentUris.appendId(BookmarkContent.Bookmark.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(rowUri, null, true);
return rowUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
private Uri insertTag(Uri uri, ContentValues values){
db = dbHelper.getWritableDatabase();
long rowId = db.insert(TAG_TABLE_NAME, "", values);
if(rowId > 0) {
Uri rowUri = ContentUris.appendId(TagContent.Tag.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(rowUri, null);
return rowUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
private Uri insertBundle(Uri uri, ContentValues values){
db = dbHelper.getWritableDatabase();
long rowId = db.insert(BUNDLE_TABLE_NAME, "", values);
if(rowId > 0) {
Uri rowUri = ContentUris.appendId(BundleContent.Bundle.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(rowUri, null);
return rowUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return !(dbHelper == null);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch(sURIMatcher.match(uri)) {
case Bookmarks:
return getBookmarks(uri, projection, selection, selectionArgs, sortOrder);
case SearchSuggest:
String query = uri.getLastPathSegment().toLowerCase();
return getSearchSuggestions(query);
case Tags:
return getTags(uri, projection, selection, selectionArgs, sortOrder);
case Bundles:
return getBundles(uri, projection, selection, selectionArgs, sortOrder);
case TagSearchSuggest:
String tagQuery = uri.getLastPathSegment().toLowerCase();
return getSearchCursor(getTagSearchSuggestions(tagQuery));
case BookmarkSearchSuggest:
String bookmarkQuery = uri.getLastPathSegment().toLowerCase();
return getSearchCursor(getBookmarkSearchSuggestions(bookmarkQuery));
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
private Cursor getBookmarks(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return getBookmarks(uri, projection, selection, selectionArgs, sortOrder, null);
}
private Cursor getBookmarks(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, String limit) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase rdb = dbHelper.getReadableDatabase();
qb.setTables(BOOKMARK_TABLE_NAME);
Cursor c = qb.query(rdb, projection, selection, selectionArgs, null, null, sortOrder, limit);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
private Cursor getTags(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return getTags(uri, projection, selection, selectionArgs, sortOrder, null);
}
private Cursor getTags(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, String limit) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase rdb = dbHelper.getReadableDatabase();
qb.setTables(TAG_TABLE_NAME);
Cursor c = qb.query(rdb, projection, selection, selectionArgs, null, null, sortOrder, limit);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
private Cursor getBundles(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return getBundles(uri, projection, selection, selectionArgs, sortOrder, null);
}
private Cursor getBundles(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, String limit) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase rdb = dbHelper.getReadableDatabase();
qb.setTables(BUNDLE_TABLE_NAME);
Cursor c = qb.query(rdb, projection, selection, selectionArgs, null, null, sortOrder, limit);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
private Cursor getSearchSuggestions(String query) {
Log.d("getSearchSuggestions", query);
mAccountManager = AccountManager.get(getContext());
mAccount = mAccountManager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
Map<String, SearchSuggestionContent> tagSuggestions = new TreeMap<String, SearchSuggestionContent>();
Map<String, SearchSuggestionContent> bookmarkSuggestions = new TreeMap<String, SearchSuggestionContent>();
tagSuggestions = getTagSearchSuggestions(query);
bookmarkSuggestions = getBookmarkSearchSuggestions(query);
SortedMap<String, SearchSuggestionContent> s = new TreeMap<String, SearchSuggestionContent>();
s.putAll(tagSuggestions);
s.putAll(bookmarkSuggestions);
return getSearchCursor(s);
}
private Map<String, SearchSuggestionContent> getBookmarkSearchSuggestions(String query) {
Log.d("getBookmarkSearchSuggestions", query);
String[] bookmarks = query.split(" ");
mAccountManager = AccountManager.get(getContext());
mAccount = mAccountManager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
Map<String, SearchSuggestionContent> suggestions = new TreeMap<String, SearchSuggestionContent>();
// Title/description/notes search suggestions
SQLiteQueryBuilder bookmarkqb = new SQLiteQueryBuilder();
bookmarkqb.setTables(BOOKMARK_TABLE_NAME);
ArrayList<String> bookmarkList = new ArrayList<String>();
final ArrayList<String> selectionlist = new ArrayList<String>();
for(String s : bookmarks) {
bookmarkList.add("(" + Bookmark.Description + " LIKE ? OR " +
Bookmark.Notes + " LIKE ?)");
selectionlist.add("%" + s + "%");
selectionlist.add("%" + s + "%");
}
String selection = TextUtils.join(" AND ", bookmarkList);
String[] projection = new String[] {BaseColumns._ID, Bookmark.Description, Bookmark.Url};
Cursor c = getBookmarks(Bookmark.CONTENT_URI, projection, selection, selectionlist.toArray(new String[]{}), null, SuggestionLimit);
if(c.moveToFirst()){
int descColumn = c.getColumnIndex(Bookmark.Description);
int idColumn = c.getColumnIndex(BaseColumns._ID);
int urlColumn = c.getColumnIndex(Bookmark.Url);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this.getContext());
String defaultAction = settings.getString("pref_view_bookmark_default_action", "browser");
do {
Uri data;
Uri.Builder builder = new Uri.Builder();
if(defaultAction.equals("browser")) {
data = Uri.parse(c.getString(urlColumn));
} else if(defaultAction.equals("read")){
String readUrl = "";
try {
readUrl = Constants.TEXT_EXTRACTOR_URL + URLEncoder.encode(c.getString(urlColumn), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
data = Uri.parse(readUrl);
} else {
builder.scheme(Constants.CONTENT_SCHEME);
builder.encodedAuthority(mAccount.name + "@" + BookmarkContentProvider.AUTHORITY);
builder.appendEncodedPath("bookmarks");
builder.appendEncodedPath(c.getString(idColumn));
data = builder.build();
}
String title = c.getString(descColumn);
suggestions.put(title, new SearchSuggestionContent(title,
c.getString(urlColumn), c.getString(urlColumn), R.drawable.ic_main, R.drawable.ic_bookmark,
data.toString()));
} while(c.moveToNext());
}
c.close();
return suggestions;
}
private Map<String, SearchSuggestionContent> getTagSearchSuggestions(String query) {
Log.d("getTagSearchSuggestions", query);
String[] tags = query.split(" ");
mAccountManager = AccountManager.get(getContext());
mAccount = mAccountManager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
Map<String, SearchSuggestionContent> suggestions = new TreeMap<String, SearchSuggestionContent>();
// Tag search suggestions
SQLiteQueryBuilder tagqb = new SQLiteQueryBuilder();
tagqb.setTables(TAG_TABLE_NAME);
ArrayList<String> tagList = new ArrayList<String>();
final ArrayList<String> selectionlist = new ArrayList<String>();
for(String s : tags){
tagList.add(Tag.Name + " LIKE ?");
selectionlist.add("%" + s + "%");
}
String selection = TextUtils.join(" OR ", tagList);
String[] projection = new String[] {BaseColumns._ID, Tag.Name, Tag.Count};
Cursor c = getTags(Tag.CONTENT_URI, projection, selection, selectionlist.toArray(new String[]{}), null, SuggestionLimit);
if(c.moveToFirst()){
int nameColumn = c.getColumnIndex(Tag.Name);
int countColumn = c.getColumnIndex(Tag.Count);
do {
Uri.Builder data = new Uri.Builder();
data.scheme(Constants.CONTENT_SCHEME);
data.encodedAuthority(mAccount.name + "@" + BookmarkContentProvider.AUTHORITY);
data.appendEncodedPath("bookmarks");
data.appendQueryParameter("tagname", c.getString(nameColumn));
int count = c.getInt(countColumn);
String name = c.getString(nameColumn);
suggestions.put(name, new SearchSuggestionContent(name,
Integer.toString(count) + " bookmark" + (count > 1 ? "s" : ""),
R.drawable.ic_main, R.drawable.ic_tag, data.build().toString()));
} while(c.moveToNext());
}
c.close();
return suggestions;
}
private Cursor getSearchCursor(Map<String, SearchSuggestionContent> list) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this.getContext());
Boolean icons = settings.getBoolean("pref_searchicons", true);
String suggestText2Url = Constants.SUGGEST_COLUMN_TEXT_2_URL;
if(android.os.Build.VERSION.SDK_INT >= 8) {
suggestText2Url = SearchManager.SUGGEST_COLUMN_TEXT_2_URL;
}
MatrixCursor mc;
if(icons) {
mc = new MatrixCursor(new String[] {BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_TEXT_2, suggestText2Url,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_ICON_1, SearchManager.SUGGEST_COLUMN_ICON_2});
int i = 0;
for(SearchSuggestionContent s : list.values()) {
mc.addRow(new Object[]{ i++, s.getText1(), s.getText2(), s.getText2Url(), s.getIntentData(),
s.getIcon1(), s.getIcon2() });
}
} else {
mc = new MatrixCursor(new String[] {BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_TEXT_2, suggestText2Url,
SearchManager.SUGGEST_COLUMN_INTENT_DATA});
int i = 0;
for(SearchSuggestionContent s : list.values()) {
mc.addRow(new Object[]{ i++, s.getText1(), s.getText2(), s.getText2Url(), s.getIntentData() });
}
}
return mc;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (sURIMatcher.match(uri)) {
case Bookmarks:
count = db.update(BOOKMARK_TABLE_NAME, values, selection, selectionArgs);
break;
case Tags:
count = db.update(TAG_TABLE_NAME, values, selection, selectionArgs);
break;
case Bundles:
count = db.update(BUNDLE_TABLE_NAME, values, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
boolean syncOnly = values.size() == 1 && values.containsKey(Bookmark.Synced) && values.getAsBoolean(Bookmark.Synced);
getContext().getContentResolver().notifyChange(uri, null, !syncOnly);
return count;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values){
switch(sURIMatcher.match(uri)) {
case Bookmarks:
return bulkLoad(BOOKMARK_TABLE_NAME, values);
case Tags:
return bulkLoad(TAG_TABLE_NAME, values);
case Bundles:
return bulkLoad(BUNDLE_TABLE_NAME, values);
default:
throw new IllegalArgumentException("Unknown Uri: " + uri);
}
}
private int bulkLoad(String table, ContentValues[] values){
db = dbHelper.getWritableDatabase();
int inserted = 0;
db.beginTransaction();
try{
final DatabaseUtils.InsertHelper ih = new DatabaseUtils.InsertHelper(db, table);
for(ContentValues v : values) {
ih.insert(v);
}
db.setTransactionSuccessful();
inserted = values.length;
}
finally{
db.endTransaction();
}
return inserted;
}
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY, "bookmark", Bookmarks);
matcher.addURI(AUTHORITY, "tag", Tags);
matcher.addURI(AUTHORITY, "bundle", Bundles);
matcher.addURI(AUTHORITY, "main/" + SearchManager.SUGGEST_URI_PATH_QUERY, SearchSuggest);
matcher.addURI(AUTHORITY, "main/" + SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SearchSuggest);
matcher.addURI(AUTHORITY, "tag/" + SearchManager.SUGGEST_URI_PATH_QUERY, TagSearchSuggest);
matcher.addURI(AUTHORITY, "tag/" + SearchManager.SUGGEST_URI_PATH_QUERY + "/*", TagSearchSuggest);
matcher.addURI(AUTHORITY, "bookmark/" + SearchManager.SUGGEST_URI_PATH_QUERY, BookmarkSearchSuggest);
matcher.addURI(AUTHORITY, "bookmark/" + SearchManager.SUGGEST_URI_PATH_QUERY + "/*", BookmarkSearchSuggest);
return matcher;
}
}