summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java178
1 files changed, 178 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java b/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
new file mode 100644
index 000000000..c1d9c4939
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
@@ -0,0 +1,178 @@
+/* -*- 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;
+
+import java.lang.ref.SoftReference;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Set;
+
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.reader.ReaderModeUtils;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+
+class GlobalHistory {
+ private static final String LOGTAG = "GeckoGlobalHistory";
+
+ public static final String EVENT_URI_AVAILABLE_IN_HISTORY = "URI_INSERTED_TO_HISTORY";
+ public static final String EVENT_PARAM_URI = "uri";
+
+ private static final String TELEMETRY_HISTOGRAM_ADD = "FENNEC_GLOBALHISTORY_ADD_MS";
+ private static final String TELEMETRY_HISTOGRAM_UPDATE = "FENNEC_GLOBALHISTORY_UPDATE_MS";
+ private static final String TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK = "FENNEC_GLOBALHISTORY_VISITED_BUILD_MS";
+
+ private static final GlobalHistory sInstance = new GlobalHistory();
+
+ static GlobalHistory getInstance() {
+ return sInstance;
+ }
+
+ // this is the delay between receiving a URI check request and processing it.
+ // this allows batching together multiple requests and processing them together,
+ // which is more efficient.
+ private static final long BATCHING_DELAY_MS = 100;
+
+ private final Handler mHandler; // a background thread on which we can process requests
+
+ // Note: These fields are accessed through the NotificationRunnable inner class.
+ final Queue<String> mPendingUris; // URIs that need to be checked
+ SoftReference<Set<String>> mVisitedCache; // cache of the visited URI list
+ boolean mProcessing; // = false // whether or not the runnable is queued/working
+
+ private class NotifierRunnable implements Runnable {
+ private final ContentResolver mContentResolver;
+ private final BrowserDB mDB;
+
+ public NotifierRunnable(final Context context) {
+ mContentResolver = context.getContentResolver();
+ mDB = BrowserDB.from(context);
+ }
+
+ @Override
+ public void run() {
+ Set<String> visitedSet = mVisitedCache.get();
+ if (visitedSet == null) {
+ // The cache was wiped. Repopulate it.
+ Log.w(LOGTAG, "Rebuilding visited link set...");
+ final long start = SystemClock.uptimeMillis();
+ final Cursor c = mDB.getAllVisitedHistory(mContentResolver);
+ if (c == null) {
+ return;
+ }
+
+ try {
+ visitedSet = new HashSet<String>();
+ if (c.moveToFirst()) {
+ do {
+ visitedSet.add(c.getString(0));
+ } while (c.moveToNext());
+ }
+ mVisitedCache = new SoftReference<Set<String>>(visitedSet);
+ final long end = SystemClock.uptimeMillis();
+ final long took = end - start;
+ Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK, (int) Math.min(took, Integer.MAX_VALUE));
+ } finally {
+ c.close();
+ }
+ }
+
+ // This runs on the same handler thread as the checkUriVisited code,
+ // so no synchronization is needed.
+ while (true) {
+ final String uri = mPendingUris.poll();
+ if (uri == null) {
+ break;
+ }
+
+ if (visitedSet.contains(uri)) {
+ GeckoAppShell.notifyUriVisited(uri);
+ }
+ }
+
+ mProcessing = false;
+ }
+ };
+
+ private GlobalHistory() {
+ mHandler = ThreadUtils.getBackgroundHandler();
+ mPendingUris = new LinkedList<String>();
+ mVisitedCache = new SoftReference<Set<String>>(null);
+ }
+
+ public void addToGeckoOnly(String uri) {
+ Set<String> visitedSet = mVisitedCache.get();
+ if (visitedSet != null) {
+ visitedSet.add(uri);
+ }
+ GeckoAppShell.notifyUriVisited(uri);
+ }
+
+ public void add(final Context context, final BrowserDB db, String uri) {
+ ThreadUtils.assertOnBackgroundThread();
+ final long start = SystemClock.uptimeMillis();
+
+ // stripAboutReaderUrl only removes about:reader if present, in all other cases the original string is returned
+ final String uriToStore = ReaderModeUtils.stripAboutReaderUrl(uri);
+
+ db.updateVisitedHistory(context.getContentResolver(), uriToStore);
+
+ final long end = SystemClock.uptimeMillis();
+ final long took = end - start;
+ Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_ADD, (int) Math.min(took, Integer.MAX_VALUE));
+ addToGeckoOnly(uriToStore);
+ dispatchUriAvailableMessage(uri);
+ }
+
+ @SuppressWarnings("static-method")
+ public void update(final ContentResolver cr, final BrowserDB db, String uri, String title) {
+ ThreadUtils.assertOnBackgroundThread();
+ final long start = SystemClock.uptimeMillis();
+
+ final String uriToStore = ReaderModeUtils.stripAboutReaderUrl(uri);
+
+ db.updateHistoryTitle(cr, uriToStore, title);
+
+ final long end = SystemClock.uptimeMillis();
+ final long took = end - start;
+ Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_UPDATE, (int) Math.min(took, Integer.MAX_VALUE));
+ }
+
+ public void checkUriVisited(final String uri) {
+ final String storedURI = ReaderModeUtils.stripAboutReaderUrl(uri);
+
+ final NotifierRunnable runnable = new NotifierRunnable(GeckoAppShell.getContext());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // this runs on the same handler thread as the processing loop,
+ // so no synchronization needed
+ mPendingUris.add(storedURI);
+ if (mProcessing) {
+ // there's already a runnable queued up or working away, so
+ // no need to post another
+ return;
+ }
+ mProcessing = true;
+ mHandler.postDelayed(runnable, BATCHING_DELAY_MS);
+ }
+ });
+ }
+
+ private void dispatchUriAvailableMessage(String uri) {
+ final Bundle message = new Bundle();
+ message.putString(EVENT_PARAM_URI, uri);
+ EventDispatcher.getInstance().dispatch(EVENT_URI_AVAILABLE_IN_HISTORY, message);
+ }
+}