mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 959290 - Make ContentProvider for Reading List. r=lucasr
This commit is contained in:
parent
43381ad91d
commit
a01edc17fd
@ -307,6 +307,10 @@
|
||||
android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
|
||||
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
||||
|
||||
<provider android:name="org.mozilla.gecko.db.ReadingListProvider"
|
||||
android:authorities="@ANDROID_PACKAGE_NAME@.db.readinglist"
|
||||
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
||||
|
||||
<service
|
||||
android:exported="false"
|
||||
android:name="org.mozilla.gecko.updater.UpdateService"
|
||||
|
@ -27,6 +27,9 @@ public class BrowserContract {
|
||||
public static final String HOME_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.home";
|
||||
public static final Uri HOME_AUTHORITY_URI = Uri.parse("content://" + HOME_AUTHORITY);
|
||||
|
||||
public static final String READING_LIST_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.readinglist";
|
||||
public static final Uri READING_LIST_AUTHORITY_URI = Uri.parse("content://" + READING_LIST_AUTHORITY);
|
||||
|
||||
public static final String PARAM_PROFILE = "profile";
|
||||
public static final String PARAM_PROFILE_PATH = "profilePath";
|
||||
public static final String PARAM_LIMIT = "limit";
|
||||
@ -378,4 +381,22 @@ public class BrowserContract {
|
||||
|
||||
static final String FAVICON_DB = "favicon_urls.db";
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static final class ReadingListItems implements CommonColumns, URLColumns, SyncColumns {
|
||||
private ReadingListItems() {}
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(READING_LIST_AUTHORITY_URI, "items");
|
||||
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/readinglistitem";
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/readinglistitem";
|
||||
|
||||
public static final String EXCERPT = "excerpt";
|
||||
public static final String READ = "read";
|
||||
public static final String LENGTH = "length";
|
||||
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
|
||||
public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, LENGTH };
|
||||
|
||||
public static final String TABLE_NAME = "reading_list";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -68,6 +68,8 @@ public class BrowserDB {
|
||||
@RobocopTarget
|
||||
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
|
||||
|
||||
public Cursor getReadingList(ContentResolver cr);
|
||||
|
||||
public boolean isVisited(ContentResolver cr, String uri);
|
||||
|
||||
public int getReadingListCount(ContentResolver cr);
|
||||
@ -100,6 +102,8 @@ public class BrowserDB {
|
||||
|
||||
public void removeReadingListItemWithURL(ContentResolver cr, String uri);
|
||||
|
||||
public void removeReadingListItem(ContentResolver cr, int id);
|
||||
|
||||
public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String uri);
|
||||
|
||||
public String getFaviconUrlForHistoryUrl(ContentResolver cr, String url);
|
||||
@ -216,6 +220,11 @@ public class BrowserDB {
|
||||
return sDb.getBookmarksInFolder(cr, folderId);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static Cursor getReadingList(ContentResolver cr) {
|
||||
return sDb.getReadingList(cr);
|
||||
}
|
||||
|
||||
public static String getUrlForKeyword(ContentResolver cr, String keyword) {
|
||||
return sDb.getUrlForKeyword(cr, keyword);
|
||||
}
|
||||
@ -270,6 +279,10 @@ public class BrowserDB {
|
||||
sDb.removeReadingListItemWithURL(cr, uri);
|
||||
}
|
||||
|
||||
public static void removeReadingListItem(ContentResolver cr, int id) {
|
||||
sDb.removeReadingListItem(cr, id);
|
||||
}
|
||||
|
||||
public static LoadFaviconResult getFaviconForFaviconUrl(ContentResolver cr, String faviconURL) {
|
||||
return sDb.getFaviconForUrl(cr, faviconURL);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
|
||||
import org.mozilla.gecko.db.BrowserContract.Favicons;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.db.BrowserContract.Obsolete;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
@ -49,7 +50,7 @@ import android.util.Log;
|
||||
final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String LOGTAG = "GeckoBrowserDBHelper";
|
||||
public static final int DATABASE_VERSION = 17;
|
||||
public static final int DATABASE_VERSION = 18;
|
||||
public static final String DATABASE_NAME = "browser.db";
|
||||
|
||||
final protected Context mContext;
|
||||
@ -58,6 +59,7 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
static final String TABLE_HISTORY = History.TABLE_NAME;
|
||||
static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
|
||||
static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
|
||||
static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
|
||||
|
||||
static final String VIEW_COMBINED = Combined.VIEW_NAME;
|
||||
static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
|
||||
@ -766,6 +768,8 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
// Create distribution bookmarks before our own default bookmarks
|
||||
int pos = createDistributionBookmarks(db);
|
||||
createDefaultBookmarks(db, pos);
|
||||
|
||||
createReadingListTable(db);
|
||||
}
|
||||
|
||||
private String getLocalizedProperty(JSONObject bookmark, String property, Locale locale) throws JSONException {
|
||||
@ -856,6 +860,27 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
return pos;
|
||||
}
|
||||
|
||||
private void createReadingListTable(SQLiteDatabase db) {
|
||||
debug("Creating " + TABLE_READING_LIST + " table");
|
||||
|
||||
db.execSQL("CREATE TABLE " + TABLE_READING_LIST + "(" +
|
||||
ReadingListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
ReadingListItems.URL + " TEXT NOT NULL, " +
|
||||
ReadingListItems.TITLE + " TEXT, " +
|
||||
ReadingListItems.EXCERPT + " TEXT, " +
|
||||
ReadingListItems.READ + " TINYINT DEFAULT 0, " +
|
||||
ReadingListItems.IS_DELETED + " TINYINT DEFAULT 0, " +
|
||||
ReadingListItems.GUID + " TEXT UNIQUE NOT NULL, " +
|
||||
ReadingListItems.DATE_MODIFIED + " INTEGER NOT NULL, " +
|
||||
ReadingListItems.DATE_CREATED + " INTEGER NOT NULL, " +
|
||||
ReadingListItems.LENGTH + " INTEGER DEFAULT 0 ); ");
|
||||
|
||||
db.execSQL("CREATE INDEX reading_list_url ON " + TABLE_READING_LIST + "("
|
||||
+ ReadingListItems.URL + ")");
|
||||
db.execSQL("CREATE UNIQUE INDEX reading_list_guid ON " + TABLE_READING_LIST + "("
|
||||
+ ReadingListItems.GUID + ")");
|
||||
}
|
||||
|
||||
// Inserts default bookmarks, starting at a specified position
|
||||
private void createDefaultBookmarks(SQLiteDatabase db, int pos) {
|
||||
Class<?> stringsClass = R.string.class;
|
||||
@ -1523,6 +1548,68 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves reading list items from 'bookmarks' table to 'reading_list' table. Uses the
|
||||
* same item GUID.
|
||||
*/
|
||||
private void upgradeDatabaseFrom17to18(SQLiteDatabase db) {
|
||||
debug("Moving reading list items from 'bookmarks' table to 'reading_list' table");
|
||||
|
||||
final String selection = Bookmarks.PARENT + " = ? AND " + Bookmarks.IS_DELETED + " = ? ";
|
||||
final String[] selectionArgs = { String.valueOf(Bookmarks.FIXED_READING_LIST_ID), "0" };
|
||||
final String[] projection = { Bookmarks._ID,
|
||||
Bookmarks.GUID,
|
||||
Bookmarks.URL,
|
||||
Bookmarks.DATE_MODIFIED,
|
||||
Bookmarks.DATE_CREATED,
|
||||
Bookmarks.TITLE };
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
// Start transaction
|
||||
db.beginTransaction();
|
||||
|
||||
// Create 'reading_list' table
|
||||
createReadingListTable(db);
|
||||
|
||||
// Get all the reading list items from bookmarks table
|
||||
cursor = db.query(TABLE_BOOKMARKS, projection, selection, selectionArgs,
|
||||
null, null, null);
|
||||
|
||||
// Insert reading list items into reading_list table
|
||||
while (cursor.moveToNext()) {
|
||||
debug(DatabaseUtils.dumpCurrentRowToString(cursor));
|
||||
ContentValues values = new ContentValues();
|
||||
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.URL, values, ReadingListItems.URL);
|
||||
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.GUID, values, ReadingListItems.GUID);
|
||||
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.TITLE, values, ReadingListItems.TITLE);
|
||||
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_CREATED, values, ReadingListItems.DATE_CREATED);
|
||||
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_MODIFIED, values, ReadingListItems.DATE_MODIFIED);
|
||||
|
||||
db.insertOrThrow(TABLE_READING_LIST, null, values);
|
||||
}
|
||||
|
||||
// Delete reading list items from bookmarks table
|
||||
db.delete(TABLE_BOOKMARKS,
|
||||
Bookmarks.PARENT + " = ? ",
|
||||
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
|
||||
|
||||
// Delete reading list special folder
|
||||
db.delete(TABLE_BOOKMARKS,
|
||||
Bookmarks._ID + " = ? ",
|
||||
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
|
||||
// Done
|
||||
db.setTransactionSuccessful();
|
||||
|
||||
} catch (SQLException e) {
|
||||
Log.e(LOGTAG, "Error migrating reading list items", e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
debug("Upgrading browser.db: " + db.getPath() + " from " +
|
||||
@ -1595,6 +1682,10 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
case 17:
|
||||
upgradeDatabaseFrom16to17(db);
|
||||
break;
|
||||
|
||||
case 18:
|
||||
upgradeDatabaseFrom17to18(db);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,12 +43,6 @@ import android.util.Log;
|
||||
public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper> {
|
||||
private static final String LOGTAG = "GeckoBrowserProvider";
|
||||
|
||||
// Maximum age of deleted records to be cleaned up (20 days in ms)
|
||||
static final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20;
|
||||
|
||||
// Number of records marked as deleted to be removed
|
||||
static final long DELETED_RECORDS_PURGE_LIMIT = 5;
|
||||
|
||||
// How many records to reposition in a single query.
|
||||
// This should be less than the SQLite maximum number of query variables
|
||||
// (currently 999) divided by the number of variables used per positioning
|
||||
@ -272,88 +266,6 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This utility is replicated from RepoUtils, which is managed by android-sync.
|
||||
*/
|
||||
private static String computeSQLInClause(int items, String field) {
|
||||
final StringBuilder builder = new StringBuilder(field);
|
||||
builder.append(" IN (");
|
||||
int i = 0;
|
||||
for (; i < items - 1; ++i) {
|
||||
builder.append("?, ");
|
||||
}
|
||||
if (i < items) {
|
||||
builder.append("?");
|
||||
}
|
||||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a single-column cursor of longs into a single SQL "IN" clause.
|
||||
* We can do this without using selection arguments because Long isn't
|
||||
* vulnerable to injection.
|
||||
*/
|
||||
private static String computeSQLInClauseFromLongs(final Cursor cursor, String field) {
|
||||
final StringBuilder builder = new StringBuilder(field);
|
||||
builder.append(" IN (");
|
||||
final int commaLimit = cursor.getCount() - 1;
|
||||
int i = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
builder.append(cursor.getLong(0));
|
||||
if (i++ < commaLimit) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up some deleted records from the specified table.
|
||||
*
|
||||
* If called in an existing transaction, it is the caller's responsibility
|
||||
* to ensure that the transaction is already upgraded to a writer, because
|
||||
* this method issues a read followed by a write, and thus is potentially
|
||||
* vulnerable to an unhandled SQLITE_BUSY failure during the upgrade.
|
||||
*
|
||||
* If not called in an existing transaction, no new explicit transaction
|
||||
* will be begun.
|
||||
*/
|
||||
private void cleanupSomeDeletedRecords(Uri fromUri, Uri targetUri, String tableName) {
|
||||
Log.d(LOGTAG, "Cleaning up deleted records from " + tableName);
|
||||
|
||||
// We clean up records marked as deleted that are older than a
|
||||
// predefined max age. It's important not be too greedy here and
|
||||
// remove only a few old deleted records at a time.
|
||||
|
||||
// Android SQLite doesn't have LIMIT on DELETE. Instead, query for the
|
||||
// IDs of matching rows, then delete them in one go.
|
||||
final long now = System.currentTimeMillis();
|
||||
final String selection = SyncColumns.IS_DELETED + " = 1 AND " +
|
||||
SyncColumns.DATE_MODIFIED + " <= " +
|
||||
(now - MAX_AGE_OF_DELETED_RECORDS);
|
||||
|
||||
final String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
|
||||
final SQLiteDatabase db = getWritableDatabaseForProfile(profile, isTest(fromUri));
|
||||
final String[] ids;
|
||||
final String limit = Long.toString(DELETED_RECORDS_PURGE_LIMIT, 10);
|
||||
final Cursor cursor = db.query(tableName, new String[] { CommonColumns._ID }, selection, null, null, null, null, limit);
|
||||
try {
|
||||
ids = new String[cursor.getCount()];
|
||||
int i = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
ids[i++] = Long.toString(cursor.getLong(0), 10);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
final String inClause = computeSQLInClause(ids.length,
|
||||
CommonColumns._ID);
|
||||
db.delete(tableName, inClause, ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove enough history items to bring the database count below <code>retain</code>,
|
||||
* removing no items with a modified time after <code>keepAfter</code>.
|
||||
@ -418,21 +330,6 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
|
||||
db.execSQL(sql);
|
||||
}
|
||||
|
||||
private boolean isCallerSync(Uri uri) {
|
||||
String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
|
||||
return !TextUtils.isEmpty(isSync);
|
||||
}
|
||||
|
||||
private boolean shouldShowDeleted(Uri uri) {
|
||||
String showDeleted = uri.getQueryParameter(BrowserContract.PARAM_SHOW_DELETED);
|
||||
return !TextUtils.isEmpty(showDeleted);
|
||||
}
|
||||
|
||||
private boolean shouldUpdateOrInsert(Uri uri) {
|
||||
String insertIfNeeded = uri.getQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED);
|
||||
return Boolean.parseBoolean(insertIfNeeded);
|
||||
}
|
||||
|
||||
private boolean shouldIncrementVisits(Uri uri) {
|
||||
String incrementVisits = uri.getQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS);
|
||||
return Boolean.parseBoolean(incrementVisits);
|
||||
|
@ -17,6 +17,7 @@ import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
|
||||
import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
|
||||
import org.mozilla.gecko.db.BrowserContract.Favicons;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserContract.SyncColumns;
|
||||
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||
import org.mozilla.gecko.db.BrowserContract.URLColumns;
|
||||
@ -65,6 +66,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
private final Uri mUpdateHistoryUriWithProfile;
|
||||
private final Uri mFaviconsUriWithProfile;
|
||||
private final Uri mThumbnailsUriWithProfile;
|
||||
private final Uri mReadingListUriWithProfile;
|
||||
|
||||
private static final String[] DEFAULT_BOOKMARK_COLUMNS =
|
||||
new String[] { Bookmarks._ID,
|
||||
@ -87,6 +89,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
mCombinedUriWithProfile = appendProfile(Combined.CONTENT_URI);
|
||||
mFaviconsUriWithProfile = appendProfile(Favicons.CONTENT_URI);
|
||||
mThumbnailsUriWithProfile = appendProfile(Thumbnails.CONTENT_URI);
|
||||
mReadingListUriWithProfile = appendProfile(ReadingListItems.CONTENT_URI);
|
||||
|
||||
mDeletedHistoryUriWithProfile = mHistoryUriWithProfile.buildUpon().
|
||||
appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1").build();
|
||||
@ -421,6 +424,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
return new LocalDBCursor(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getReadingList(ContentResolver cr) {
|
||||
return cr.query(mReadingListUriWithProfile,
|
||||
ReadingListItems.DEFAULT_PROJECTION,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
// Returns true if any desktop bookmarks exist, which will be true if the user
|
||||
// has set up sync at one point, or done a profile migration from XUL fennec.
|
||||
private boolean desktopBookmarksExist(ContentResolver cr) {
|
||||
@ -454,18 +467,18 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
|
||||
@Override
|
||||
public int getReadingListCount(ContentResolver cr) {
|
||||
// This method is about the Reading List, not normal bookmarks
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = cr.query(mBookmarksUriWithProfile,
|
||||
new String[] { Bookmarks._ID },
|
||||
Bookmarks.PARENT + " = ?",
|
||||
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) },
|
||||
c = cr.query(mReadingListUriWithProfile,
|
||||
new String[] { ReadingListItems._ID },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
return c.getCount();
|
||||
} finally {
|
||||
if (c != null)
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -498,14 +511,11 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
public boolean isReadingListItem(ContentResolver cr, String uri) {
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = cr.query(mBookmarksUriWithProfile,
|
||||
new String[] { Bookmarks._ID },
|
||||
Bookmarks.URL + " = ? AND " +
|
||||
Bookmarks.PARENT + " == ?",
|
||||
new String[] { uri,
|
||||
String.valueOf(Bookmarks.FIXED_READING_LIST_ID) },
|
||||
Bookmarks.URL);
|
||||
|
||||
c = cr.query(mReadingListUriWithProfile,
|
||||
new String[] { ReadingListItems._ID },
|
||||
ReadingListItems.URL + " = ? ",
|
||||
new String[] { uri },
|
||||
null);
|
||||
return c.getCount() > 0;
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(LOGTAG, "NullPointerException in isReadingListItem");
|
||||
@ -690,20 +700,33 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
|
||||
@Override
|
||||
public void addReadingListItem(ContentResolver cr, String title, String uri) {
|
||||
addBookmarkItem(cr, title, uri, Bookmarks.FIXED_READING_LIST_ID);
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ReadingListItems.IS_DELETED, 0);
|
||||
values.put(ReadingListItems.URL, uri);
|
||||
values.put(ReadingListItems.TITLE, title);
|
||||
|
||||
// Restore deleted record if possible
|
||||
final Uri insertUri = mReadingListUriWithProfile
|
||||
.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
|
||||
.build();
|
||||
|
||||
final int updated = cr.update(insertUri,
|
||||
values,
|
||||
ReadingListItems.URL + " = ? ",
|
||||
new String[] { uri });
|
||||
|
||||
debug("Updated " + updated + " rows to new modified time.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
||||
Uri contentUri = mBookmarksUriWithProfile;
|
||||
cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri });
|
||||
}
|
||||
|
||||
// Do this now so that the items still exist!
|
||||
bumpParents(cr, Bookmarks.URL, uri);
|
||||
|
||||
final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_READING_LIST_ID) };
|
||||
final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " == ?";
|
||||
|
||||
cr.delete(contentUri, urlEquals, urlArgs);
|
||||
@Override
|
||||
public void removeReadingListItem(ContentResolver cr, int id) {
|
||||
cr.delete(mReadingListUriWithProfile, ReadingListItems._ID + " = ? ", new String[] { String.valueOf(id) });
|
||||
}
|
||||
|
||||
@Override
|
||||
|
261
mobile/android/base/db/ReadingListProvider.java
Normal file
261
mobile/android/base/db/ReadingListProvider.java
Normal file
@ -0,0 +1,261 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
public class ReadingListProvider extends TransactionalProvider<BrowserDatabaseHelper> {
|
||||
private static final String LOGTAG = "GeckoReadingListProv";
|
||||
|
||||
static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
|
||||
|
||||
static final int ITEMS = 101;
|
||||
static final int ITEMS_ID = 102;
|
||||
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
|
||||
static {
|
||||
URI_MATCHER.addURI(BrowserContract.READING_LIST_AUTHORITY, "items", ITEMS);
|
||||
URI_MATCHER.addURI(BrowserContract.READING_LIST_AUTHORITY, "items/#", ITEMS_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates items that match the selection criteria. If no such items is found
|
||||
* one is inserted with the attributes passed in. Returns 0 if no item updated.
|
||||
*
|
||||
* @return Number of items updated or inserted
|
||||
*/
|
||||
public int updateOrInsertItem(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
int updated = updateItems(uri, values, selection, selectionArgs);
|
||||
if (updated <= 0) {
|
||||
updated = insertItem(uri, values) != -1 ? 1 : 0;
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates items that match the selection criteria.
|
||||
*
|
||||
* @return Number of items updated or inserted
|
||||
*/
|
||||
public int updateItems(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
trace("Updating ReadingListItems on URI: " + uri);
|
||||
final SQLiteDatabase db = getWritableDatabase(uri);
|
||||
if (!values.containsKey(ReadingListItems.DATE_MODIFIED)) {
|
||||
values.put(ReadingListItems.DATE_MODIFIED, System.currentTimeMillis());
|
||||
}
|
||||
return db.update(TABLE_READING_LIST, values, selection, selectionArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new item into the DB. DATE_CREATED, DATE_MODIFIED
|
||||
* and GUID fields are generated if they are not specified.
|
||||
*
|
||||
* @return ID of the newly inserted item
|
||||
*/
|
||||
long insertItem(Uri uri, ContentValues values) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (!values.containsKey(ReadingListItems.DATE_CREATED)) {
|
||||
values.put(ReadingListItems.DATE_CREATED, now);
|
||||
}
|
||||
|
||||
if (!values.containsKey(ReadingListItems.DATE_MODIFIED)) {
|
||||
values.put(ReadingListItems.DATE_MODIFIED, now);
|
||||
}
|
||||
|
||||
if (!values.containsKey(ReadingListItems.GUID)) {
|
||||
values.put(ReadingListItems.GUID, Utils.generateGuid());
|
||||
}
|
||||
|
||||
String url = values.getAsString(ReadingListItems.URL);
|
||||
debug("Inserting item in database with URL: " + url);
|
||||
return getWritableDatabase(uri)
|
||||
.insertOrThrow(TABLE_READING_LIST, null, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes items. Item is marked as 'deleted' so that sync can
|
||||
* detect the change.
|
||||
*
|
||||
* @return Number of deleted items
|
||||
*/
|
||||
int deleteItems(Uri uri, String selection, String[] selectionArgs) {
|
||||
debug("Deleting item entry for URI: " + uri);
|
||||
final SQLiteDatabase db = getWritableDatabase(uri);
|
||||
|
||||
if (isCallerSync(uri)) {
|
||||
return db.delete(TABLE_READING_LIST, selection, selectionArgs);
|
||||
}
|
||||
|
||||
debug("Marking item entry as deleted for URI: " + uri);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ReadingListItems.IS_DELETED, 1);
|
||||
|
||||
cleanupSomeDeletedRecords(uri, ReadingListItems.CONTENT_URI, TABLE_READING_LIST);
|
||||
return updateItems(uri, values, selection, selectionArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("fallthrough")
|
||||
public int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
trace("Calling update in transaction on URI: " + uri);
|
||||
|
||||
int updated = 0;
|
||||
int match = URI_MATCHER.match(uri);
|
||||
|
||||
switch (match) {
|
||||
case ITEMS_ID:
|
||||
debug("Update on ITEMS_ID: " + uri);
|
||||
selection = DBUtils.concatenateWhere(selection, TABLE_READING_LIST + "._id = ?");
|
||||
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
||||
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
||||
|
||||
case ITEMS: {
|
||||
debug("Updating ITEMS: " + uri);
|
||||
updated = shouldUpdateOrInsert(uri) ?
|
||||
updateOrInsertItem(uri, values, selection, selectionArgs) :
|
||||
updateItems(uri, values, selection, selectionArgs);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown update URI " + uri);
|
||||
}
|
||||
|
||||
debug("Updated " + updated + " rows for URI: " + uri);
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("fallthrough")
|
||||
public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
|
||||
trace("Calling delete in transaction on URI: " + uri);
|
||||
|
||||
int numDeleted = 0;
|
||||
int match = URI_MATCHER.match(uri);
|
||||
|
||||
switch (match) {
|
||||
case ITEMS_ID:
|
||||
debug("Deleting on ITEMS_ID: " + uri);
|
||||
selection = DBUtils.concatenateWhere(selection, TABLE_READING_LIST + "._id = ?");
|
||||
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
||||
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
||||
|
||||
case ITEMS:
|
||||
debug("Deleting ITEMS: " + uri);
|
||||
numDeleted = deleteItems(uri, selection, selectionArgs);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown update URI " + uri);
|
||||
}
|
||||
|
||||
debug("Deleted " + numDeleted + " rows for URI: " + uri);
|
||||
return numDeleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insertInTransaction(Uri uri, ContentValues values) {
|
||||
trace("Calling insert in transaction on URI: " + uri);
|
||||
long id = -1;
|
||||
int match = URI_MATCHER.match(uri);
|
||||
|
||||
switch (match) {
|
||||
case ITEMS:
|
||||
trace("Insert on ITEMS: " + uri);
|
||||
id = insertItem(uri, values);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown insert URI " + uri);
|
||||
}
|
||||
|
||||
debug("Inserted ID in database: " + id);
|
||||
|
||||
if (id >= 0) {
|
||||
return ContentUris.withAppendedId(uri, id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
String groupBy = null;
|
||||
SQLiteDatabase db = getReadableDatabase(uri);
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
|
||||
|
||||
final int match = URI_MATCHER.match(uri);
|
||||
switch (match) {
|
||||
case ITEMS_ID:
|
||||
trace("Query on ITEMS_ID: " + uri);
|
||||
selection = DBUtils.concatenateWhere(selection, ReadingListItems._ID + " = ?");
|
||||
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
||||
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
||||
|
||||
case ITEMS:
|
||||
trace("Query on ITEMS: " + uri);
|
||||
if (!shouldShowDeleted(uri))
|
||||
selection = DBUtils.concatenateWhere(ReadingListItems.IS_DELETED + " = 0", selection);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown query URI " + uri);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = ReadingListItems.DEFAULT_SORT_ORDER;
|
||||
}
|
||||
|
||||
trace("Running built query.");
|
||||
qb.setTables(TABLE_READING_LIST);
|
||||
Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
trace("Getting URI type: " + uri);
|
||||
|
||||
final int match = URI_MATCHER.match(uri);
|
||||
switch (match) {
|
||||
case ITEMS:
|
||||
trace("URI is ITEMS: " + uri);
|
||||
return ReadingListItems.CONTENT_TYPE;
|
||||
|
||||
case ITEMS_ID:
|
||||
trace("URI is ITEMS_ID: " + uri);
|
||||
return ReadingListItems.CONTENT_ITEM_TYPE;
|
||||
}
|
||||
|
||||
debug("URI has unrecognized type: " + uri);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BrowserDatabaseHelper createDatabaseHelper(Context context,
|
||||
String databasePath) {
|
||||
return new BrowserDatabaseHelper(context, databasePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDatabaseName() {
|
||||
return BrowserDatabaseHelper.DATABASE_NAME;
|
||||
}
|
||||
}
|
@ -4,11 +4,15 @@
|
||||
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.CommonColumns;
|
||||
import org.mozilla.gecko.db.BrowserContract.SyncColumns;
|
||||
import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
@ -111,6 +115,45 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
|
||||
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true of the query is from Firefox Sync.
|
||||
* @param uri query URI
|
||||
*/
|
||||
public static boolean isCallerSync(Uri uri) {
|
||||
String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
|
||||
return !TextUtils.isEmpty(isSync);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a query should include deleted fields
|
||||
* based on the URI.
|
||||
* @param uri query URI
|
||||
*/
|
||||
public static boolean shouldShowDeleted(Uri uri) {
|
||||
String showDeleted = uri.getQueryParameter(BrowserContract.PARAM_SHOW_DELETED);
|
||||
return !TextUtils.isEmpty(showDeleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether an insertion should be made if a record doesn't
|
||||
* exist, based on the URI.
|
||||
* @param uri query URI
|
||||
*/
|
||||
public static boolean shouldUpdateOrInsert(Uri uri) {
|
||||
String insertIfNeeded = uri.getQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED);
|
||||
return Boolean.parseBoolean(insertIfNeeded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether query is a test based on the URI.
|
||||
* @param uri query URI
|
||||
*/
|
||||
public static boolean isTest(Uri uri) {
|
||||
String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
|
||||
return !TextUtils.isEmpty(isTest);
|
||||
}
|
||||
|
||||
protected SQLiteDatabase getWritableDatabaseForProfile(String profile, boolean isTest) {
|
||||
return mDatabases.getDatabaseHelperForProfile(profile, isTest).getWritableDatabase();
|
||||
}
|
||||
@ -246,6 +289,43 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
|
||||
isInBatchOperation.set(Boolean.FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* This utility is replicated from RepoUtils, which is managed by android-sync.
|
||||
*/
|
||||
protected static String computeSQLInClause(int items, String field) {
|
||||
final StringBuilder builder = new StringBuilder(field);
|
||||
builder.append(" IN (");
|
||||
int i = 0;
|
||||
for (; i < items - 1; ++i) {
|
||||
builder.append("?, ");
|
||||
}
|
||||
if (i < items) {
|
||||
builder.append("?");
|
||||
}
|
||||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a single-column cursor of longs into a single SQL "IN" clause.
|
||||
* We can do this without using selection arguments because Long isn't
|
||||
* vulnerable to injection.
|
||||
*/
|
||||
protected static String computeSQLInClauseFromLongs(final Cursor cursor, String field) {
|
||||
final StringBuilder builder = new StringBuilder(field);
|
||||
builder.append(" IN (");
|
||||
final int commaLimit = cursor.getCount() - 1;
|
||||
int i = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
builder.append(cursor.getLong(0));
|
||||
if (i++ < commaLimit) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
trace("Calling delete on URI: " + uri + ", " + selection + ", " + selectionArgs);
|
||||
@ -348,9 +428,59 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
|
||||
return successes;
|
||||
}
|
||||
|
||||
protected boolean isTest(Uri uri) {
|
||||
String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
|
||||
return !TextUtils.isEmpty(isTest);
|
||||
/**
|
||||
* Clean up some deleted records from the specified table.
|
||||
*
|
||||
* If called in an existing transaction, it is the caller's responsibility
|
||||
* to ensure that the transaction is already upgraded to a writer, because
|
||||
* this method issues a read followed by a write, and thus is potentially
|
||||
* vulnerable to an unhandled SQLITE_BUSY failure during the upgrade.
|
||||
*
|
||||
* If not called in an existing transaction, no new explicit transaction
|
||||
* will be begun.
|
||||
*/
|
||||
protected void cleanupSomeDeletedRecords(Uri fromUri, Uri targetUri, String tableName) {
|
||||
Log.d(LOGTAG, "Cleaning up deleted records from " + tableName);
|
||||
|
||||
// We clean up records marked as deleted that are older than a
|
||||
// predefined max age. It's important not be too greedy here and
|
||||
// remove only a few old deleted records at a time.
|
||||
|
||||
// we cleanup records marked as deleted that are older than a
|
||||
// predefined max age. It's important not be too greedy here and
|
||||
// remove only a few old deleted records at a time.
|
||||
|
||||
// Maximum age of deleted records to be cleaned up (20 days in ms)
|
||||
final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20;
|
||||
|
||||
// Number of records marked as deleted to be removed
|
||||
final long DELETED_RECORDS_PURGE_LIMIT = 5;
|
||||
|
||||
// Android SQLite doesn't have LIMIT on DELETE. Instead, query for the
|
||||
// IDs of matching rows, then delete them in one go.
|
||||
final long now = System.currentTimeMillis();
|
||||
final String selection = SyncColumns.IS_DELETED + " = 1 AND " +
|
||||
SyncColumns.DATE_MODIFIED + " <= " +
|
||||
(now - MAX_AGE_OF_DELETED_RECORDS);
|
||||
|
||||
final String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
|
||||
final SQLiteDatabase db = getWritableDatabaseForProfile(profile, isTest(fromUri));
|
||||
final String[] ids;
|
||||
final String limit = Long.toString(DELETED_RECORDS_PURGE_LIMIT, 10);
|
||||
final Cursor cursor = db.query(tableName, new String[] { CommonColumns._ID }, selection, null, null, null, null, limit);
|
||||
try {
|
||||
ids = new String[cursor.getCount()];
|
||||
int i = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
ids[i++] = Long.toString(cursor.getLong(0), 10);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
final String inClause = computeSQLInClause(ids.length,
|
||||
CommonColumns._ID);
|
||||
db.delete(tableName, inClause, ids);
|
||||
}
|
||||
|
||||
// Calculate these once, at initialization. isLoggable is too expensive to
|
||||
|
@ -21,10 +21,10 @@ public class HomeContextMenuInfo extends AdapterContextMenuInfo {
|
||||
public String url;
|
||||
public String title;
|
||||
public boolean isFolder = false;
|
||||
public boolean inReadingList = false;
|
||||
public int display = Combined.DISPLAY_NORMAL;
|
||||
public int historyId = -1;
|
||||
public int bookmarkId = -1;
|
||||
public int readingListItemId = -1;
|
||||
|
||||
public HomeContextMenuInfo(View targetView, int position, long id) {
|
||||
super(targetView, position, id);
|
||||
@ -39,7 +39,11 @@ public class HomeContextMenuInfo extends AdapterContextMenuInfo {
|
||||
}
|
||||
|
||||
public boolean isInReadingList() {
|
||||
return inReadingList;
|
||||
return readingListItemId > -1;
|
||||
}
|
||||
|
||||
public boolean canRemove() {
|
||||
return hasBookmarkId() || hasHistoryId() || isInReadingList();
|
||||
}
|
||||
|
||||
public String getDisplayTitle() {
|
||||
|
@ -91,8 +91,8 @@ abstract class HomeFragment extends Fragment {
|
||||
menu.findItem(R.id.home_edit_bookmark).setVisible(false);
|
||||
}
|
||||
|
||||
// Hide the "Remove" menuitem if this item doesn't have a bookmark or history ID.
|
||||
if (!info.hasBookmarkId() && !info.hasHistoryId()) {
|
||||
// Hide the "Remove" menuitem if this item not removable.
|
||||
if (!info.canRemove()) {
|
||||
menu.findItem(R.id.home_remove).setVisible(false);
|
||||
}
|
||||
|
||||
@ -176,7 +176,12 @@ abstract class HomeFragment extends Fragment {
|
||||
}
|
||||
|
||||
if (info.hasBookmarkId()) {
|
||||
new RemoveBookmarkTask(context, info.bookmarkId, info.url, info.isInReadingList()).execute();
|
||||
new RemoveBookmarkTask(context, info.bookmarkId).execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (info.isInReadingList()) {
|
||||
(new RemoveReadingListItemTask(context, info.readingListItemId, info.url)).execute();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -223,36 +228,49 @@ abstract class HomeFragment extends Fragment {
|
||||
private static class RemoveBookmarkTask extends UiAsyncTask<Void, Void, Void> {
|
||||
private final Context mContext;
|
||||
private final int mId;
|
||||
private final String mUrl;
|
||||
private final boolean mInReadingList;
|
||||
|
||||
public RemoveBookmarkTask(Context context, int id, String url, boolean inReadingList) {
|
||||
public RemoveBookmarkTask(Context context, int id) {
|
||||
super(ThreadUtils.getBackgroundHandler());
|
||||
|
||||
mContext = context;
|
||||
mId = id;
|
||||
mUrl = url;
|
||||
mInReadingList = inReadingList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void doInBackground(Void... params) {
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
BrowserDB.removeBookmark(cr, mId);
|
||||
if (mInReadingList) {
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", mUrl);
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Void result) {
|
||||
// The remove from reading list toast is handled in Reader:Removed,
|
||||
// so handle only the bookmark removed toast here.
|
||||
if (!mInReadingList) {
|
||||
Toast.makeText(mContext, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
Toast.makeText(mContext, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class RemoveReadingListItemTask extends UiAsyncTask<Void, Void, Void> {
|
||||
private final int mId;
|
||||
private final String mUrl;
|
||||
private final Context mContext;
|
||||
|
||||
public RemoveReadingListItemTask(Context context, int id, String url) {
|
||||
super(ThreadUtils.getBackgroundHandler());
|
||||
mId = id;
|
||||
mUrl = url;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void doInBackground(Void... params) {
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
BrowserDB.removeReadingListItem(cr, mId);
|
||||
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", mUrl);
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import java.util.EnumSet;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.ReaderModeUtils;
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
@ -36,6 +36,7 @@ import android.widget.TextView;
|
||||
* Fragment that displays reading list contents in a ListView.
|
||||
*/
|
||||
public class ReadingListPanel extends HomeFragment {
|
||||
|
||||
// Cursor loader ID for reading list
|
||||
private static final int LOADER_ID_READING_LIST = 0;
|
||||
|
||||
@ -113,10 +114,9 @@ public class ReadingListPanel extends HomeFragment {
|
||||
@Override
|
||||
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
|
||||
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
|
||||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
|
||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
|
||||
info.bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
|
||||
info.inReadingList = true;
|
||||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.URL));
|
||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.TITLE));
|
||||
info.readingListItemId = cursor.getInt(cursor.getColumnIndexOrThrow(ReadingListItems._ID));
|
||||
return info;
|
||||
}
|
||||
});
|
||||
@ -201,7 +201,7 @@ public class ReadingListPanel extends HomeFragment {
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
return BrowserDB.getBookmarksInFolder(getContext().getContentResolver(), Bookmarks.FIXED_READING_LIST_ID);
|
||||
return BrowserDB.getReadingList(getContext().getContentResolver());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,7 @@ gbjar.sources += [
|
||||
'db/LocalBrowserDB.java',
|
||||
'db/PasswordsProvider.java',
|
||||
'db/PerProfileDatabases.java',
|
||||
'db/ReadingListProvider.java',
|
||||
'db/SQLiteBridgeContentProvider.java',
|
||||
'db/TabsProvider.java',
|
||||
'db/TransactionalProvider.java',
|
||||
|
Loading…
Reference in New Issue
Block a user