Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android

This commit is contained in:
Matt A. Tobin 2019-04-23 15:32:23 -04:00 committed by Roy Tam
commit c9b411d6cd
3891 changed files with 0 additions and 428084 deletions

View file

@ -1,72 +0,0 @@
/* 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.reader;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.util.StringUtils;
import android.net.Uri;
public class ReaderModeUtils {
private static final String LOGTAG = "ReaderModeUtils";
/**
* Extract the URL from a valid about:reader URL. You may want to use stripAboutReaderUrl
* instead to always obtain a valid String.
*
* @see #stripAboutReaderUrl(String) for a safer version that returns the original URL for malformed/invalid
* URLs.
* @return <code>null</code> if the URL is malformed or doesn't contain a URL parameter.
*/
private static String getUrlFromAboutReader(String aboutReaderUrl) {
return StringUtils.getQueryParameter(aboutReaderUrl, "url");
}
public static boolean isEnteringReaderMode(String oldURL, String newURL) {
if (oldURL == null || newURL == null) {
return false;
}
if (!AboutPages.isAboutReader(newURL)) {
return false;
}
String urlFromAboutReader = getUrlFromAboutReader(newURL);
if (urlFromAboutReader == null) {
return false;
}
return urlFromAboutReader.equals(oldURL);
}
public static String getAboutReaderForUrl(String url) {
return getAboutReaderForUrl(url, -1);
}
/**
* Obtain the underlying URL from an about:reader URL.
* This will return the input URL if either of the following is true:
* 1. the input URL is a non about:reader URL
* 2. the input URL is an invalid/unparseable about:reader URL
*/
public static String stripAboutReaderUrl(String url) {
if (!AboutPages.isAboutReader(url)) {
return url;
}
final String strippedUrl = getUrlFromAboutReader(url);
return strippedUrl != null ? strippedUrl : url;
}
public static String getAboutReaderForUrl(String url, int tabId) {
String aboutReaderUrl = AboutPages.READER + "?url=" + Uri.encode(url);
if (tabId >= 0) {
aboutReaderUrl += "&tabId=" + tabId;
}
return aboutReaderUrl;
}
}

View file

@ -1,154 +0,0 @@
/* 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.reader;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.icons.IconRequest;
import org.mozilla.gecko.icons.Icons;
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.Context;
import android.util.Log;
import java.util.concurrent.ExecutionException;
public final class ReadingListHelper implements NativeEventListener {
private static final String LOGTAG = "GeckoReadingListHelper";
protected final Context context;
private final BrowserDB db;
public ReadingListHelper(Context context, GeckoProfile profile) {
this.context = context;
this.db = BrowserDB.from(profile);
GeckoApp.getEventDispatcher().registerGeckoThreadListener((NativeEventListener) this,
"Reader:FaviconRequest", "Reader:AddedToCache");
}
public void uninit() {
GeckoApp.getEventDispatcher().unregisterGeckoThreadListener((NativeEventListener) this,
"Reader:FaviconRequest", "Reader:AddedToCache");
}
@Override
public void handleMessage(final String event, final NativeJSObject message,
final EventCallback callback) {
switch (event) {
case "Reader:FaviconRequest": {
handleReaderModeFaviconRequest(callback, message.getString("url"));
break;
}
case "Reader:AddedToCache": {
// AddedToCache is a one way message: callback will be null, and we therefore shouldn't
// attempt to handle it.
handleAddedToCache(message.getString("url"), message.getString("path"), message.getInt("size"));
break;
}
}
}
/**
* 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() {
// This is a bit ridiculous if you look at the bigger picture: Reader mode extracts
// the article content. We insert the content into a new document (about:reader).
// Some events are exchanged to lookup the icon URL for the actual website. This
// URL is then added to the markup which will then trigger our icon loading code in
// the Tab class.
//
// The Tab class could just lookup and load the icon itself. All it needs to do is
// to strip the about:reader URL and perform a normal icon load from cache.
//
// A more global solution (looking at desktop and iOS) would be to copy the <link>
// markup from the original page to the about:reader page and then rely on our normal
// icon loading code. This would work even if we do not have anything in the cache
// for some kind of reason.
final IconRequest request = Icons.with(context)
.pageUrl(url)
.prepareOnly()
.build();
try {
request.execute(null).get();
if (request.getIconCount() > 0) {
return request.getBestIcon().getUrl();
}
} catch (InterruptedException | ExecutionException e) {
// Ignore
}
return null;
}
@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();
}
private void handleAddedToCache(final String url, final String path, final int size) {
final SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
rch.put(url, path, size);
}
public static void cacheReaderItem(final String url, final int tabID, Context context) {
if (AboutPages.isAboutReader(url)) {
throw new IllegalArgumentException("Page url must be original (not about:reader) url");
}
SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
if (!rch.isURLCached(url)) {
GeckoAppShell.notifyObservers("Reader:AddToCache", Integer.toString(tabID));
}
}
public static void removeCachedReaderItem(final String url, Context context) {
if (AboutPages.isAboutReader(url)) {
throw new IllegalArgumentException("Page url must be original (not about:reader) url");
}
SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
if (rch.isURLCached(url)) {
GeckoAppShell.notifyObservers("Reader:RemoveFromCache", url);
}
// When removing items from the cache we can probably spare ourselves the async callback
// that we use when adding cached items. We know the cached item will be gone, hence
// we no longer need to track it in the SavedReaderViewHelper
rch.remove(url);
}
}

View file

@ -1,247 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.reader;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.UrlAnnotations;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
/**
* Helper to keep track of items that are stored in the reader view cache. This is an in-memory list
* of the reader view items that are cached on disk. It is intended to allow quickly determining whether
* a given URL is in the cache, and also how many cached items there are.
*
* Currently we have 1:1 correspondence of reader view items (in the URL-annotations table)
* to cached items. This is _not_ a true cache, we never purge/cleanup items here - we only remove
* items when we un-reader-view/bookmark them. This is an acceptable model while we can guarantee the
* 1:1 correspondence.
*
* It isn't strictly necessary to mirror cached items in SQL at this stage, however it seems sensible
* to maintain URL anotations to avoid additional DB migrations in future.
* It is also simpler to implement the reading list smart-folder using the annotations (even if we do
* all other decoration from our in-memory cache record), as that is what we will need when
* we move away from the 1:1 correspondence.
*
* Bookmarks can be in one of two states - plain bookmark, or reader view bookmark that is also saved
* offline. We're hoping to introduce real cache management / cleanup in future, in which case a
* third user-visible state (reader view bookmark without a cache entry) will be added. However that logic is
* much more complicated and requires substantial changes in how we decorate reader view bookmarks.
* With the current 1:1 correspondence we can use this in-memory helper to quickly decorate
* bookmarks (in all the various lists and panels that are used), whereas supporting
* the third state requires significant changes in order to allow joining with the
* URL-annotations table wherever bookmarks might be retrieved (i.e. multiple homepanels, each with
* their own loaders and adapter).
*
* If/when cache cleanup and sync are implemented, URL annotations will be the canonical record of
* user intent, and the cache will no longer represent all reader view bookmarks. We will have (A)
* cached items that are not a bookmark, or bookmarks without the reader view annotation (both of
* these would need purging), and (B) bookmarks with a reader view annotation, but not stored in
* the cache (which we might want to download in the background). Supporting (B) is currently difficult,
* see previous paragraph.
*/
public class SavedReaderViewHelper {
private static final String LOG_TAG = "SavedReaderViewHelper";
private static final String PATH = "path";
private static final String SIZE = "size";
private static final String DIRECTORY = "readercache";
private static final String FILE_NAME = "items.json";
private static final String FILE_PATH = DIRECTORY + "/" + FILE_NAME;
// We use null to indicate that the cache hasn't yet been loaded. Loading has to be explicitly
// requested by client code, and must happen on the background thread. Attempting to access
// items (which happens mainly on the UI thread) before explicitly loading them is not permitted.
private JSONObject mItems = null;
private final Context mContext;
private static SavedReaderViewHelper instance = null;
private SavedReaderViewHelper(Context context) {
mContext = context;
}
public static synchronized SavedReaderViewHelper getSavedReaderViewHelper(final Context context) {
if (instance == null) {
instance = new SavedReaderViewHelper(context);
}
return instance;
}
/**
* Load the reader view cache list from our JSON file.
*
* Must not be run on the UI thread due to file access.
*/
public synchronized void loadItems() {
// TODO bug 1264489
// This is a band aid fix for Bug 1264134. We need to figure out the root cause and reenable this
// assertion.
// ThreadUtils.assertNotOnUiThread();
if (mItems != null) {
return;
}
try {
mItems = GeckoProfile.get(mContext).readJSONObjectFromFile(FILE_PATH);
} catch (IOException e) {
mItems = new JSONObject();
}
}
private synchronized void assertItemsLoaded() {
if (mItems == null) {
throw new IllegalStateException("SavedReaderView items must be explicitly loaded using loadItems() before access.");
}
}
private JSONObject makeItem(@NonNull String path, long size) throws JSONException {
final JSONObject item = new JSONObject();
item.put(PATH, path);
item.put(SIZE, size);
return item;
}
public synchronized boolean isURLCached(@NonNull final String URL) {
assertItemsLoaded();
return mItems.has(URL);
}
/**
* Insert an item into the list of cached items.
*
* This may be called from any thread.
*/
public synchronized void put(@NonNull final String pageURL, @NonNull final String path, final long size) {
assertItemsLoaded();
try {
mItems.put(pageURL, makeItem(path, size));
} catch (JSONException e) {
Log.w(LOG_TAG, "Item insertion failed:", e);
// This should never happen, absent any errors in our own implementation
throw new IllegalStateException("Failure inserting into SavedReaderViewHelper json");
}
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
UrlAnnotations annotations = BrowserDB.from(mContext).getUrlAnnotations();
annotations.insertReaderViewUrl(mContext.getContentResolver(), pageURL);
commit();
}
});
}
protected synchronized void remove(@NonNull final String pageURL) {
assertItemsLoaded();
mItems.remove(pageURL);
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
UrlAnnotations annotations = BrowserDB.from(mContext).getUrlAnnotations();
annotations.deleteReaderViewUrl(mContext.getContentResolver(), pageURL);
commit();
}
});
}
@RobocopTarget
public synchronized int size() {
assertItemsLoaded();
return mItems.length();
}
private synchronized void commit() {
ThreadUtils.assertOnBackgroundThread();
GeckoProfile profile = GeckoProfile.get(mContext);
File cacheDir = new File(profile.getDir(), DIRECTORY);
if (!cacheDir.exists()) {
Log.i(LOG_TAG, "No preexisting cache directory, creating now");
boolean cacheDirCreated = cacheDir.mkdir();
if (!cacheDirCreated) {
throw new IllegalStateException("Couldn't create cache directory, unable to track reader view cache");
}
}
profile.writeFile(FILE_PATH, mItems.toString());
}
/**
* Return the Reader View URL for a given URL if it is contained in the cache. Returns the
* plain URL if the page is not cached.
*/
public static String getReaderURLIfCached(final Context context, @NonNull final String pageURL) {
SavedReaderViewHelper rvh = getSavedReaderViewHelper(context);
if (rvh.isURLCached(pageURL)) {
return ReaderModeUtils.getAboutReaderForUrl(pageURL);
} else {
return pageURL;
}
}
/**
* Obtain the total disk space used for saved reader view items, in KB.
*
* @return Total disk space used (KB), or Integer.MAX_VALUE on overflow.
*/
public synchronized int getDiskSpacedUsedKB() {
// JSONObject is not thread safe - we need to be synchronized to avoid issues (most likely to
// occur if items are removed during iteration).
final Iterator<String> keys = mItems.keys();
long bytes = 0;
while (keys.hasNext()) {
final String pageURL = keys.next();
try {
final JSONObject item = mItems.getJSONObject(pageURL);
bytes += item.getLong(SIZE);
// Overflow is highly unlikely (we will hit device storage limits before we hit integer limits),
// but we should still handle this for correctness.
// We definitely can't store our output in an int if we overflow the long here.
if (bytes < 0) {
return Integer.MAX_VALUE;
}
} catch (JSONException e) {
// This shouldn't ever happen:
throw new IllegalStateException("Must be able to access items in saved reader view list", e);
}
}
long kb = bytes / 1024;
if (kb > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) kb;
}
}
}