diff -r 000000000000 -r 6474c204b198 mobile/android/base/GlobalHistory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/base/GlobalHistory.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,123 @@ +/* -*- 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 org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.util.ThreadUtils; + +import android.database.Cursor; +import android.net.Uri; +import android.os.Handler; +import android.util.Log; + +import java.lang.ref.SoftReference; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Set; + +class GlobalHistory { + private static final String LOGTAG = "GeckoGlobalHistory"; + + private static 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 + private final Queue mPendingUris; // URIs that need to be checked + private SoftReference> mVisitedCache; // cache of the visited URI list + private final Runnable mNotifierRunnable; // off-thread runnable used to check URIs + private boolean mProcessing; // = false // whether or not the runnable is queued/working + + private GlobalHistory() { + mHandler = ThreadUtils.getBackgroundHandler(); + mPendingUris = new LinkedList(); + mVisitedCache = new SoftReference>(null); + mNotifierRunnable = new Runnable() { + @Override + public void run() { + Set visitedSet = mVisitedCache.get(); + if (visitedSet == null) { + // the cache was wiped away, repopulate it + Log.w(LOGTAG, "Rebuilding visited link set..."); + visitedSet = new HashSet(); + Cursor c = null; + try { + c = BrowserDB.getAllVisitedHistory(GeckoAppShell.getContext().getContentResolver()); + if (c == null) { + return; + } + + if (c.moveToFirst()) { + do { + visitedSet.add(c.getString(0)); + } while (c.moveToNext()); + } + mVisitedCache = new SoftReference>(visitedSet); + } finally { + if (c != null) + c.close(); + } + } + + // this runs on the same handler thread as the checkUriVisited code, + // so no synchronization needed + while (true) { + String uri = mPendingUris.poll(); + if (uri == null) { + break; + } + if (visitedSet.contains(uri)) { + GeckoAppShell.notifyUriVisited(uri); + } + } + mProcessing = false; + } + }; + } + + public void addToGeckoOnly(String uri) { + Set visitedSet = mVisitedCache.get(); + if (visitedSet != null) { + visitedSet.add(uri); + } + GeckoAppShell.notifyUriVisited(uri); + } + + public void add(String uri) { + BrowserDB.updateVisitedHistory(GeckoAppShell.getContext().getContentResolver(), uri); + addToGeckoOnly(uri); + } + + public void update(String uri, String title) { + BrowserDB.updateHistoryTitle(GeckoAppShell.getContext().getContentResolver(), uri, title); + } + + public void checkUriVisited(final String uri) { + 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(uri); + if (mProcessing) { + // there's already a runnable queued up or working away, so + // no need to post another + return; + } + mProcessing = true; + mHandler.postDelayed(mNotifierRunnable, BATCHING_DELAY_MS); + } + }); + } +}