зеркало из https://github.com/mozilla/gecko-dev.git
326 строки
12 KiB
Java
326 строки
12 KiB
Java
/* 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;
|
|
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
|
import org.mozilla.gecko.db.BrowserDB;
|
|
import org.mozilla.gecko.db.ReadingListAccessor;
|
|
import org.mozilla.gecko.favicons.Favicons;
|
|
import org.mozilla.gecko.mozglue.RobocopTarget;
|
|
import org.mozilla.gecko.util.EventCallback;
|
|
import org.mozilla.gecko.util.NativeEventListener;
|
|
import org.mozilla.gecko.util.NativeJSObject;
|
|
import org.mozilla.gecko.util.ThreadUtils;
|
|
import org.mozilla.gecko.util.UIAsyncTask;
|
|
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.database.ContentObserver;
|
|
import android.database.Cursor;
|
|
import android.util.Log;
|
|
|
|
public final class ReadingListHelper implements NativeEventListener {
|
|
private static final String LOGTAG = "GeckoReadingListHelper";
|
|
|
|
public interface OnReadingListEventListener {
|
|
void onAddedToReadingList(String url);
|
|
void onRemovedFromReadingList(String url);
|
|
void onAlreadyInReadingList(String url);
|
|
}
|
|
|
|
private enum ReadingListEvent {
|
|
ADDED,
|
|
REMOVED,
|
|
ALREADY_EXISTS
|
|
}
|
|
|
|
protected final Context context;
|
|
private final BrowserDB db;
|
|
private final ReadingListAccessor readingListAccessor;
|
|
private final ContentObserver contentObserver;
|
|
private final OnReadingListEventListener onReadingListEventListener;
|
|
|
|
volatile boolean fetchInBackground = true;
|
|
|
|
public ReadingListHelper(Context context, GeckoProfile profile, OnReadingListEventListener listener) {
|
|
this.context = context;
|
|
this.db = profile.getDB();
|
|
this.readingListAccessor = db.getReadingListAccessor();
|
|
|
|
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
|
|
"Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
|
|
|
|
|
|
contentObserver = new ContentObserver(null) {
|
|
@Override
|
|
public void onChange(boolean selfChange) {
|
|
if (fetchInBackground) {
|
|
fetchContent();
|
|
}
|
|
}
|
|
};
|
|
|
|
this.readingListAccessor.registerContentObserver(context, contentObserver);
|
|
|
|
onReadingListEventListener = listener;
|
|
}
|
|
|
|
public void uninit() {
|
|
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
|
|
"Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
|
|
|
|
context.getContentResolver().unregisterContentObserver(contentObserver);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(final String event, final NativeJSObject message,
|
|
final EventCallback callback) {
|
|
switch(event) {
|
|
case "Reader:AddToList": {
|
|
handleAddToList(callback, message);
|
|
break;
|
|
}
|
|
case "Reader:UpdateList": {
|
|
handleUpdateList(message);
|
|
break;
|
|
}
|
|
case "Reader:FaviconRequest": {
|
|
handleReaderModeFaviconRequest(callback, message.getString("url"));
|
|
break;
|
|
}
|
|
case "Reader:RemoveFromList": {
|
|
handleRemoveFromList(message.getString("url"));
|
|
break;
|
|
}
|
|
case "Reader:ListStatusRequest": {
|
|
handleReadingListStatusRequest(callback, message.getString("url"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A page can be added to the ReadingList by long-tap of the page-action
|
|
* icon, or by tapping the readinglist-add icon in the ReaderMode banner.
|
|
*
|
|
* This method will only add new items, not update existing items.
|
|
*/
|
|
private void handleAddToList(final EventCallback callback, final NativeJSObject message) {
|
|
final ContentResolver cr = context.getContentResolver();
|
|
final String url = message.getString("url");
|
|
|
|
// We can't access a NativeJSObject from the background thread, so we need to get the
|
|
// values here, even if we may not use them to insert an item into the DB.
|
|
final ContentValues values = getContentValues(message);
|
|
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (readingListAccessor.isReadingListItem(cr, url)) {
|
|
handleEvent(ReadingListEvent.ALREADY_EXISTS, url);
|
|
callback.sendError("URL already in reading list: " + url);
|
|
} else {
|
|
readingListAccessor.addReadingListItem(cr, values);
|
|
handleEvent(ReadingListEvent.ADDED, url);
|
|
callback.sendSuccess(url);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates a reading list item with new meta data.
|
|
*/
|
|
private void handleUpdateList(final NativeJSObject message) {
|
|
final ContentResolver cr = context.getContentResolver();
|
|
final ContentValues values = getContentValues(message);
|
|
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
readingListAccessor.updateReadingListItem(cr, values);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates reading list item content values from JS message.
|
|
*/
|
|
private ContentValues getContentValues(NativeJSObject message) {
|
|
final ContentValues values = new ContentValues();
|
|
if (message.has("id")) {
|
|
values.put(ReadingListItems._ID, message.getInt("id"));
|
|
}
|
|
|
|
// url is actually required...
|
|
String url = null;
|
|
if (message.has("url")) {
|
|
url = message.getString("url");
|
|
values.put(ReadingListItems.URL, url);
|
|
}
|
|
|
|
String title = null;
|
|
if (message.has("title")) {
|
|
title = message.getString("title");
|
|
values.put(ReadingListItems.TITLE, title);
|
|
}
|
|
|
|
// TODO: message actually has "length", but that's no use for us. See Bug 1127451.
|
|
if (message.has("word_count")) {
|
|
values.put(ReadingListItems.WORD_COUNT, message.getInt("word_count"));
|
|
}
|
|
|
|
if (message.has("excerpt")) {
|
|
values.put(ReadingListItems.EXCERPT, message.getString("excerpt"));
|
|
}
|
|
|
|
if (message.has("status")) {
|
|
final int status = message.getInt("status");
|
|
values.put(ReadingListItems.CONTENT_STATUS, status);
|
|
if (status == ReadingListItems.STATUS_FETCHED_ARTICLE) {
|
|
if (message.has("resolved_title")) {
|
|
values.put(ReadingListItems.RESOLVED_TITLE, message.getString("resolved_title"));
|
|
} else {
|
|
if (title != null) {
|
|
values.put(ReadingListItems.RESOLVED_TITLE, title);
|
|
}
|
|
}
|
|
if (message.has("resolved_url")) {
|
|
values.put(ReadingListItems.RESOLVED_URL, message.getString("resolved_url"));
|
|
} else {
|
|
if (url != null) {
|
|
values.put(ReadingListItems.RESOLVED_URL, url);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* Gecko (ReaderMode) requests the page favicon to append to the
|
|
* document head for display.
|
|
*/
|
|
private void handleReaderModeFaviconRequest(final EventCallback callback, final String url) {
|
|
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
|
|
@Override
|
|
public String doInBackground() {
|
|
return Favicons.getFaviconURLForPageURL(db, context.getContentResolver(), url);
|
|
}
|
|
|
|
@Override
|
|
public void onPostExecute(String faviconUrl) {
|
|
JSONObject args = new JSONObject();
|
|
if (faviconUrl != null) {
|
|
try {
|
|
args.put("url", url);
|
|
args.put("faviconUrl", faviconUrl);
|
|
} catch (JSONException e) {
|
|
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
|
|
}
|
|
}
|
|
callback.sendSuccess(args.toString());
|
|
}
|
|
}).execute();
|
|
}
|
|
|
|
/**
|
|
* A page can be removed from the ReadingList by panel context menu,
|
|
* or by tapping the readinglist-remove icon in the ReaderMode banner.
|
|
*/
|
|
private void handleRemoveFromList(final String url) {
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
readingListAccessor.removeReadingListItemWithURL(context.getContentResolver(), url);
|
|
handleEvent(ReadingListEvent.REMOVED, url);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Gecko (ReaderMode) requests the page ReadingList status, to display
|
|
* the proper ReaderMode banner icon (readinglist-add / readinglist-remove).
|
|
*/
|
|
private void handleReadingListStatusRequest(final EventCallback callback, final String url) {
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
final int inReadingList = readingListAccessor.isReadingListItem(context.getContentResolver(), url) ? 1 : 0;
|
|
|
|
final JSONObject json = new JSONObject();
|
|
try {
|
|
json.put("url", url);
|
|
json.put("inReadingList", inReadingList);
|
|
} catch (JSONException e) {
|
|
Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
|
|
}
|
|
|
|
// Return the json object to fulfill the promise.
|
|
callback.sendSuccess(json.toString());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle various reading list events (and display appropriate toasts).
|
|
*/
|
|
private void handleEvent(final ReadingListEvent event, final String url) {
|
|
ThreadUtils.postToUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
switch(event) {
|
|
case ADDED:
|
|
onReadingListEventListener.onAddedToReadingList(url);
|
|
break;
|
|
case REMOVED:
|
|
onReadingListEventListener.onRemovedFromReadingList(url);
|
|
break;
|
|
case ALREADY_EXISTS:
|
|
onReadingListEventListener.onAlreadyInReadingList(url);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void fetchContent() {
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
final Cursor c = readingListAccessor.getReadingListUnfetched(context.getContentResolver());
|
|
try {
|
|
while (c.moveToNext()) {
|
|
JSONObject json = new JSONObject();
|
|
try {
|
|
json.put("id", c.getInt(c.getColumnIndexOrThrow(ReadingListItems._ID)));
|
|
json.put("url", c.getString(c.getColumnIndexOrThrow(ReadingListItems.URL)));
|
|
GeckoAppShell.sendEventToGecko(
|
|
GeckoEvent.createBroadcastEvent("Reader:FetchContent", json.toString()));
|
|
} catch (JSONException e) {
|
|
Log.e(LOGTAG, "Failed to fetch reading list content for item");
|
|
}
|
|
}
|
|
} finally {
|
|
c.close();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@RobocopTarget
|
|
/**
|
|
* Test code will want to disable background fetches to avoid upsetting
|
|
* the test harness. Call this by accessing the instance from BrowserApp.
|
|
*/
|
|
public void disableBackgroundFetches() {
|
|
fetchInBackground = false;
|
|
}
|
|
}
|