зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central a=merge
This commit is contained in:
Коммит
db958a2de5
|
@ -81,7 +81,7 @@ this.StorageUI = function StorageUI(front, target, panelWin) {
|
|||
|
||||
this.front.listStores().then(storageTypes => {
|
||||
this.populateStorageTree(storageTypes);
|
||||
});
|
||||
}).then(null, Cu.reportError);
|
||||
this.onUpdate = this.onUpdate.bind(this);
|
||||
this.front.on("stores-update", this.onUpdate);
|
||||
|
||||
|
@ -275,7 +275,7 @@ StorageUI.prototype = {
|
|||
}
|
||||
this.populateTable(data, reason);
|
||||
this.emit("store-objects-updated");
|
||||
});
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -139,7 +139,7 @@ StyleEditorUI.prototype = {
|
|||
this._resetStyleSheetList(styleSheets);
|
||||
this._target.on("will-navigate", this._clear);
|
||||
this._target.on("navigate", this._onNewDocument);
|
||||
});
|
||||
}, Cu.reportError);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -207,7 +207,7 @@ StyleEditorUI.prototype = {
|
|||
_onNewDocument: function() {
|
||||
this._debuggee.getStyleSheets().then((styleSheets) => {
|
||||
this._resetStyleSheetList(styleSheets);
|
||||
})
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -285,7 +285,7 @@ StyleEditorUI.prototype = {
|
|||
this._addStyleSheetEditor(source);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -317,7 +317,8 @@ StyleEditorUI.prototype = {
|
|||
|
||||
this.editors.push(editor);
|
||||
|
||||
editor.fetchSource(this._sourceLoaded.bind(this, editor));
|
||||
editor.fetchSource(this._sourceLoaded.bind(this, editor))
|
||||
.then(null, Cu.reportError);
|
||||
return editor;
|
||||
},
|
||||
|
||||
|
@ -558,8 +559,8 @@ StyleEditorUI.prototype = {
|
|||
this.emit("error", { key: "error-compressed", level: "info" });
|
||||
}
|
||||
}
|
||||
});
|
||||
}, console.error);
|
||||
}, Cu.reportError);
|
||||
}, Cu.reportError);
|
||||
}.bind(this)).then(null, Cu.reportError);
|
||||
}.bind(this)
|
||||
});
|
||||
|
|
|
@ -115,7 +115,7 @@ function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
|
|||
this.styleSheet.on("error", this._onError);
|
||||
this.mediaRules = [];
|
||||
if (this.cssSheet.getMediaRules) {
|
||||
this.cssSheet.getMediaRules().then(this._onMediaRulesChanged);
|
||||
this.cssSheet.getMediaRules().then(this._onMediaRulesChanged, Cu.reportError);
|
||||
}
|
||||
this.cssSheet.on("media-rules-changed", this._onMediaRulesChanged);
|
||||
this.savedFile = file;
|
||||
|
|
|
@ -183,7 +183,14 @@ HighlightersOverlay.prototype = {
|
|||
_hideCurrent: function() {
|
||||
if (this.highlighterShown) {
|
||||
this._getHighlighter(this.highlighterShown).then(highlighter => {
|
||||
highlighter.hide();
|
||||
// For some reason, the call to highlighter.hide doesn't always return a
|
||||
// promise. This causes some tests to fail when trying to install a
|
||||
// rejection handler on the result of the call. To avoid this, check
|
||||
// whether the result is truthy before installing the handler.
|
||||
let promise = highlighter.hide();
|
||||
if (promise) {
|
||||
promise.then(null, Cu.reportError);
|
||||
}
|
||||
this.highlighterShown = null;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -287,11 +287,13 @@ public class FennecNativeDriver implements Driver {
|
|||
log(LogLevel.ERROR, e);
|
||||
} finally {
|
||||
try {
|
||||
br.close();
|
||||
if (br != null) {
|
||||
br.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
return text.toString();
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -349,7 +351,7 @@ public class FennecNativeDriver implements Driver {
|
|||
|
||||
public static void log(LogLevel level, String message, Throwable t) {
|
||||
if (mLogFile == null) {
|
||||
assert(false);
|
||||
throw new RuntimeException("No log file specified!");
|
||||
}
|
||||
|
||||
if (level.isEnabled(mLogLevel)) {
|
||||
|
@ -365,11 +367,14 @@ public class FennecNativeDriver implements Driver {
|
|||
} catch (IOException ioe) {
|
||||
Log.e("Robocop", "exception with file writer on: " + mLogFile);
|
||||
} finally {
|
||||
pw.close();
|
||||
if (pw != null) {
|
||||
pw.close();
|
||||
}
|
||||
}
|
||||
|
||||
// PrintWriter doesn't throw IOE but sets an error flag instead,
|
||||
// so check for that
|
||||
if (pw.checkError()) {
|
||||
if (pw != null && pw.checkError()) {
|
||||
Log.e("Robocop", "exception with file writer on: " + mLogFile);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ package org.mozilla.gecko;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.lang.Override;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.EnumSet;
|
||||
|
@ -17,10 +15,8 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
import android.support.v4.app.Fragment;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.DynamicToolbar.PinReason;
|
||||
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
|
||||
|
@ -30,8 +26,8 @@ import org.mozilla.gecko.animation.PropertyAnimator;
|
|||
import org.mozilla.gecko.animation.TransitionsTracker;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.LocalBrowserDB;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
|
@ -95,7 +91,7 @@ import org.mozilla.gecko.widget.GeckoActionProvider;
|
|||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
|
@ -118,6 +114,7 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.os.StrictMode;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
|
@ -500,7 +497,13 @@ public class BrowserApp extends GeckoApp
|
|||
mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
|
||||
|
||||
final Intent intent = getIntent();
|
||||
|
||||
// Note that we're calling GeckoProfile.get *before GeckoApp.onCreate*.
|
||||
// This means we're reliant on the logic in GeckoProfile to correctly
|
||||
// look up our launch intent (via BrowserApp's Activity-ness) and pull
|
||||
// out the arguments. Be careful if you change that!
|
||||
final GeckoProfile p = GeckoProfile.get(this);
|
||||
|
||||
if (p != null && !p.inGuestMode()) {
|
||||
// This is *only* valid because we never want to use the guest mode
|
||||
// profile concurrently with a normal profile -- no syncing to it,
|
||||
|
@ -619,7 +622,8 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
// Init suggested sites engine in BrowserDB.
|
||||
final SuggestedSites suggestedSites = new SuggestedSites(appContext, distribution);
|
||||
BrowserDB.setSuggestedSites(suggestedSites);
|
||||
final BrowserDB db = getProfile().getDB();
|
||||
db.setSuggestedSites(suggestedSites);
|
||||
|
||||
JavaAddonManager.getInstance().init(appContext);
|
||||
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
|
||||
|
@ -1522,16 +1526,13 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
|
||||
} else if ("Telemetry:Gather".equals(event)) {
|
||||
Telemetry.addToHistogram("PLACES_PAGES_COUNT",
|
||||
BrowserDB.getCount(getContentResolver(), "history"));
|
||||
Telemetry.addToHistogram("PLACES_BOOKMARKS_COUNT",
|
||||
BrowserDB.getCount(getContentResolver(), "bookmarks"));
|
||||
Telemetry.addToHistogram("FENNEC_FAVICONS_COUNT",
|
||||
BrowserDB.getCount(getContentResolver(), "favicons"));
|
||||
Telemetry.addToHistogram("FENNEC_THUMBNAILS_COUNT",
|
||||
BrowserDB.getCount(getContentResolver(), "thumbnails"));
|
||||
Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT",
|
||||
BrowserDB.getCount(getContentResolver(), "readinglist"));
|
||||
final BrowserDB db = getProfile().getDB();
|
||||
final ContentResolver cr = getContentResolver();
|
||||
Telemetry.addToHistogram("PLACES_PAGES_COUNT", db.getCount(cr, "history"));
|
||||
Telemetry.addToHistogram("PLACES_BOOKMARKS_COUNT", db.getCount(cr, "bookmarks"));
|
||||
Telemetry.addToHistogram("FENNEC_FAVICONS_COUNT", db.getCount(cr, "favicons"));
|
||||
Telemetry.addToHistogram("FENNEC_THUMBNAILS_COUNT", db.getCount(cr, "thumbnails"));
|
||||
Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getCount(getContentResolver(), "readinglist"));
|
||||
Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
|
||||
if (Versions.feature16Plus) {
|
||||
Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT", (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0));
|
||||
|
@ -2025,6 +2026,7 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
|
||||
// Otherwise, check for a bookmark keyword.
|
||||
final BrowserDB db = getProfile().getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -2040,7 +2042,7 @@ public class BrowserApp extends GeckoApp
|
|||
keywordSearch = url.substring(index + 1);
|
||||
}
|
||||
|
||||
final String keywordUrl = BrowserDB.getUrlForKeyword(getContentResolver(), keyword);
|
||||
final String keywordUrl = db.getUrlForKeyword(getContentResolver(), keyword);
|
||||
|
||||
// If there isn't a bookmark keyword, load the url. This may result in a query
|
||||
// using the default search engine.
|
||||
|
@ -2090,17 +2092,22 @@ public class BrowserApp extends GeckoApp
|
|||
* @param query
|
||||
* a search query to store. We won't store empty queries.
|
||||
*/
|
||||
private void storeSearchQuery(String query) {
|
||||
private void storeSearchQuery(final String query) {
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(SearchHistory.QUERY, query);
|
||||
final GeckoProfile profile = getProfile();
|
||||
// Don't bother storing search queries in guest mode
|
||||
if (profile.inGuestMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BrowserDB db = profile.getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getContentResolver().insert(SearchHistory.CONTENT_URI, values);
|
||||
db.getSearches().insert(getContentResolver(), query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -3149,22 +3156,23 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
|
||||
private void getLastUrl(final EventCallback callback) {
|
||||
final BrowserDB db = getProfile().getDB();
|
||||
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public synchronized String doInBackground() {
|
||||
// Get the most recent URL stored in browser history.
|
||||
String url = "";
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = BrowserDB.getRecentHistory(getContentResolver(), 1);
|
||||
if (c.moveToFirst()) {
|
||||
url = c.getString(c.getColumnIndexOrThrow(Combined.URL));
|
||||
}
|
||||
} finally {
|
||||
if (c != null)
|
||||
c.close();
|
||||
final Cursor c = db.getRecentHistory(getContentResolver(), 1);
|
||||
if (c == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
return c.getString(c.getColumnIndexOrThrow(Combined.URL));
|
||||
}
|
||||
return "";
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3260,6 +3268,20 @@ public class BrowserApp extends GeckoApp
|
|||
return GeckoProfile.getDefaultProfileName(this);
|
||||
}
|
||||
|
||||
// We want a real BrowserDB.
|
||||
@Override
|
||||
protected BrowserDB.Factory getBrowserDBFactory() {
|
||||
return new BrowserDB.Factory() {
|
||||
@Override
|
||||
public BrowserDB get(String profileName, File profileDir) {
|
||||
// Note that we don't use the profile directory -- we
|
||||
// send operations to the ContentProvider, which does
|
||||
// its own thing.
|
||||
return new LocalBrowserDB(profileName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch UI that lets the user update Firefox.
|
||||
*
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UIAsyncTask;
|
||||
|
||||
|
@ -141,10 +141,11 @@ public class EditBookmarkDialog {
|
|||
*/
|
||||
public void show(final String url) {
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
final BrowserDB db = GeckoProfile.get(mContext).getDB();
|
||||
(new UIAsyncTask.WithoutParams<Bookmark>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public Bookmark doInBackground() {
|
||||
final Cursor cursor = BrowserDB.getBookmarkForUrl(cr, url);
|
||||
final Cursor cursor = db.getBookmarkForUrl(cr, url);
|
||||
if (cursor == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -199,6 +200,7 @@ public class EditBookmarkDialog {
|
|||
locationText.setText(url);
|
||||
keywordText.setText(keyword);
|
||||
|
||||
final BrowserDB db = GeckoProfile.get(mContext).getDB();
|
||||
editPrompt.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
|
@ -207,7 +209,8 @@ public class EditBookmarkDialog {
|
|||
public Void doInBackground() {
|
||||
String newUrl = locationText.getText().toString().trim();
|
||||
String newKeyword = keywordText.getText().toString().trim();
|
||||
BrowserDB.updateBookmark(context.getContentResolver(), id, newUrl, nameText.getText().toString(), newKeyword);
|
||||
|
||||
db.updateBookmark(context.getContentResolver(), id, newUrl, nameText.getText().toString(), newKeyword);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -125,8 +125,16 @@ public abstract class GeckoApp
|
|||
LocationListener,
|
||||
NativeEventListener,
|
||||
SensorEventListener,
|
||||
Tabs.OnTabsChangedListener
|
||||
{
|
||||
Tabs.OnTabsChangedListener {
|
||||
|
||||
protected GeckoApp() {
|
||||
// We need to do this before any access to the profile; it controls
|
||||
// which database class is used.
|
||||
// We thus need to do this before our GeckoView is inflated, because
|
||||
// GeckoView implicitly accesses the profile.
|
||||
GeckoProfile.setBrowserDBFactory(getBrowserDBFactory());
|
||||
}
|
||||
|
||||
private static final String LOGTAG = "GeckoApp";
|
||||
private static final int ONE_DAY_MS = 1000*60*60*24;
|
||||
|
||||
|
@ -230,6 +238,8 @@ public abstract class GeckoApp
|
|||
return GeckoSharedPrefs.forApp(this);
|
||||
}
|
||||
|
||||
protected abstract BrowserDB.Factory getBrowserDBFactory();
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
|
@ -523,7 +533,13 @@ public abstract class GeckoApp
|
|||
}
|
||||
|
||||
void handleClearHistory() {
|
||||
BrowserDB.clearHistory(getContentResolver());
|
||||
final BrowserDB db = getProfile().getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
db.clearHistory(getContentResolver());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addTab() { }
|
||||
|
@ -558,10 +574,11 @@ public abstract class GeckoApp
|
|||
final String url = message.getString("url");
|
||||
final String title = message.getString("title");
|
||||
final Context context = this;
|
||||
final BrowserDB db = getProfile().getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.addBookmark(getContentResolver(), title, url);
|
||||
db.addBookmark(getContentResolver(), title, url);
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -1145,33 +1162,15 @@ public abstract class GeckoApp
|
|||
// business later and dispose of the reference.
|
||||
GeckoLoader.setLastIntent(intent);
|
||||
|
||||
if (mProfile == null) {
|
||||
String profileName = null;
|
||||
String profilePath = null;
|
||||
if (args != null) {
|
||||
if (args.contains("-P")) {
|
||||
Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
|
||||
Matcher m = p.matcher(args);
|
||||
if (m.find()) {
|
||||
profileName = m.group(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.contains("-profile")) {
|
||||
Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
|
||||
Matcher m = p.matcher(args);
|
||||
if (m.find()) {
|
||||
profilePath = m.group(1);
|
||||
}
|
||||
if (profileName == null) {
|
||||
profileName = GeckoProfile.DEFAULT_PROFILE;
|
||||
}
|
||||
GeckoProfile.sIsUsingCustomProfile = true;
|
||||
}
|
||||
|
||||
if (profileName != null || profilePath != null) {
|
||||
mProfile = GeckoProfile.get(this, profileName, profilePath);
|
||||
}
|
||||
// If we don't already have a profile, but we do have arguments,
|
||||
// let's see if they're enough to find one.
|
||||
// Note that subclasses must ensure that if they try to access
|
||||
// the profile prior to this code being run, then they do something
|
||||
// similar.
|
||||
if (mProfile == null && args != null) {
|
||||
final GeckoProfile p = GeckoProfile.getFromArgs(this, args);
|
||||
if (p != null) {
|
||||
mProfile = p;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1483,8 +1482,6 @@ public abstract class GeckoApp
|
|||
|
||||
initializeChrome();
|
||||
|
||||
BrowserDB.initialize(getProfile().getName());
|
||||
|
||||
// If we are doing a restore, read the session data and send it to Gecko
|
||||
if (!mIsRestoringActivity) {
|
||||
String restoreMessage = null;
|
||||
|
|
|
@ -31,9 +31,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.gfx.PanZoomController;
|
||||
|
@ -1886,38 +1886,32 @@ public class GeckoAppShell
|
|||
boolean isTegra = (new File("/system/lib/hw/gralloc.tegra.so")).exists() ||
|
||||
(new File("/system/lib/hw/gralloc.tegra3.so")).exists();
|
||||
if (isTegra) {
|
||||
// disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421)
|
||||
File vfile = new File("/proc/version");
|
||||
FileReader vreader = null;
|
||||
try {
|
||||
if (vfile.canRead()) {
|
||||
vreader = new FileReader(vfile);
|
||||
String version = new BufferedReader(vreader).readLine();
|
||||
if (version.indexOf("CM9") != -1 ||
|
||||
version.indexOf("cyanogen") != -1 ||
|
||||
version.indexOf("Nova") != -1)
|
||||
{
|
||||
Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// nothing
|
||||
} finally {
|
||||
try {
|
||||
if (vreader != null) {
|
||||
vreader.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
// disable on KitKat (bug 957694)
|
||||
if (Versions.feature19Plus) {
|
||||
Log.w(LOGTAG, "Blocking plugins because of Tegra (bug 957694)");
|
||||
return null;
|
||||
}
|
||||
|
||||
// disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421)
|
||||
final File vfile = new File("/proc/version");
|
||||
try {
|
||||
if (vfile.canRead()) {
|
||||
final BufferedReader reader = new BufferedReader(new FileReader(vfile));
|
||||
try {
|
||||
final String version = reader.readLine();
|
||||
if (version.indexOf("CM9") != -1 ||
|
||||
version.indexOf("cyanogen") != -1 ||
|
||||
version.indexOf("Nova") != -1) {
|
||||
Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)");
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<String> directories = new ArrayList<String>();
|
||||
|
@ -2301,20 +2295,24 @@ public class GeckoAppShell
|
|||
|
||||
@WrapElementForJNI(stubName = "MarkURIVisited")
|
||||
static void markUriVisited(final String uri) {
|
||||
final Context context = getContext();
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GlobalHistory.getInstance().add(uri);
|
||||
GlobalHistory.getInstance().add(context, db, uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@WrapElementForJNI(stubName = "SetURITitle")
|
||||
static void setUriTitle(final String uri, final String title) {
|
||||
final Context context = getContext();
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GlobalHistory.getInstance().update(uri, title);
|
||||
GlobalHistory.getInstance().update(context.getContentResolver(), db, uri, title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -90,11 +90,11 @@ public class GeckoApplication extends Application
|
|||
GeckoAppShell.sendEventToGecko(GeckoEvent.createAppBackgroundingEvent());
|
||||
mPausedGecko = true;
|
||||
|
||||
final BrowserDB db = GeckoProfile.get(this).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.expireHistory(getContentResolver(),
|
||||
BrowserContract.ExpirePriority.NORMAL);
|
||||
db.expireHistory(getContentResolver(), BrowserContract.ExpirePriority.NORMAL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,10 +14,14 @@ import java.nio.charset.Charset;
|
|||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.LocalBrowserDB;
|
||||
import org.mozilla.gecko.db.StubBrowserDB;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.util.INIParser;
|
||||
|
@ -26,15 +30,23 @@ import org.mozilla.gecko.util.INISection;
|
|||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
public final class GeckoProfile {
|
||||
private static final String LOGTAG = "GeckoProfile";
|
||||
|
||||
// Only tests should need to do this.
|
||||
// We can default this to AppConstants.RELEASE_BUILD once we fix Bug 1069687.
|
||||
private static volatile boolean sAcceptDirectoryChanges = true;
|
||||
|
||||
@RobocopTarget
|
||||
public static void enableDirectoryChanges() {
|
||||
Log.w(LOGTAG, "Directory changes should only be enabled for tests. And even then it's a bad idea.");
|
||||
sAcceptDirectoryChanges = true;
|
||||
}
|
||||
|
||||
// Used to "lock" the guest profile, so that we'll always restart in it
|
||||
private static final String LOCK_FILE_NAME = ".active_lock";
|
||||
public static final String DEFAULT_PROFILE = "default";
|
||||
|
@ -54,6 +66,8 @@ public final class GeckoProfile {
|
|||
private final boolean mIsWebAppProfile;
|
||||
private final Context mApplicationContext;
|
||||
|
||||
private final BrowserDB mDB;
|
||||
|
||||
/**
|
||||
* Access to this member should be synchronized to avoid
|
||||
* races during creation -- particularly between getDir and GeckoView#init.
|
||||
|
@ -77,6 +91,46 @@ public final class GeckoProfile {
|
|||
UNDEFINED
|
||||
};
|
||||
|
||||
/**
|
||||
* Warning: has a side-effect of setting sIsUsingCustomProfile.
|
||||
* Can return null.
|
||||
*/
|
||||
public static GeckoProfile getFromArgs(final Context context, final String args) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String profileName = null;
|
||||
String profilePath = null;
|
||||
if (args.contains("-P")) {
|
||||
final Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
|
||||
final Matcher m = p.matcher(args);
|
||||
if (m.find()) {
|
||||
profileName = m.group(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.contains("-profile")) {
|
||||
final Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
|
||||
final Matcher m = p.matcher(args);
|
||||
if (m.find()) {
|
||||
profilePath = m.group(1);
|
||||
}
|
||||
|
||||
if (profileName == null) {
|
||||
profileName = GeckoProfile.DEFAULT_PROFILE;
|
||||
}
|
||||
|
||||
GeckoProfile.sIsUsingCustomProfile = true;
|
||||
}
|
||||
|
||||
if (profileName == null && profilePath == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return GeckoProfile.get(context, profileName, profilePath);
|
||||
}
|
||||
|
||||
public static GeckoProfile get(Context context) {
|
||||
boolean isGeckoApp = false;
|
||||
try {
|
||||
|
@ -100,13 +154,21 @@ public final class GeckoProfile {
|
|||
}
|
||||
|
||||
if (GuestSession.shouldUse(context, args)) {
|
||||
GeckoProfile p = GeckoProfile.getOrCreateGuestProfile(context);
|
||||
final GeckoProfile p = GeckoProfile.getOrCreateGuestProfile(context);
|
||||
if (isGeckoApp) {
|
||||
((GeckoApp) context).mProfile = p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
final GeckoProfile fromArgs = GeckoProfile.getFromArgs(context, args);
|
||||
if (fromArgs != null) {
|
||||
if (isGeckoApp) {
|
||||
((GeckoApp) context).mProfile = fromArgs;
|
||||
}
|
||||
return fromArgs;
|
||||
}
|
||||
|
||||
if (isGeckoApp) {
|
||||
final GeckoApp geckoApp = (GeckoApp) context;
|
||||
String defaultProfileName;
|
||||
|
@ -146,14 +208,36 @@ public final class GeckoProfile {
|
|||
return get(context, profileName, dir);
|
||||
}
|
||||
|
||||
// Extension hook.
|
||||
private static volatile BrowserDB.Factory sDBFactory;
|
||||
public static void setBrowserDBFactory(BrowserDB.Factory factory) {
|
||||
sDBFactory = factory;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static GeckoProfile get(Context context, String profileName, File profileDir) {
|
||||
if (sDBFactory == null) {
|
||||
// We do this so that GeckoView consumers don't need to know anything about BrowserDB.
|
||||
// It's a bit of a broken abstraction, but very tightly coupled, so we work around it
|
||||
// for now. We can't just have GeckoView set this, because then it would collide in
|
||||
// Fennec's use of GeckoView.
|
||||
Log.d(LOGTAG, "Defaulting to StubBrowserDB.");
|
||||
sDBFactory = StubBrowserDB.getFactory();
|
||||
}
|
||||
return GeckoProfile.get(context, profileName, profileDir, sDBFactory);
|
||||
}
|
||||
|
||||
// Note that the profile cache respects only the profile name!
|
||||
// If the directory changes, the returned GeckoProfile instance will be mutated.
|
||||
// If the factory differs, it will be *ignored*.
|
||||
public static GeckoProfile get(Context context, String profileName, File profileDir, BrowserDB.Factory dbFactory) {
|
||||
Log.v(LOGTAG, "Fetching profile: '" + profileName + "', '" + profileDir + "'");
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("context must be non-null");
|
||||
}
|
||||
|
||||
// if no profile was passed in, look for the default profile listed in profiles.ini
|
||||
// if that doesn't exist, look for a profile called 'default'
|
||||
// If no profile was passed in, look for the default profile listed in profiles.ini.
|
||||
// If that doesn't exist, look for a profile called 'default'.
|
||||
if (TextUtils.isEmpty(profileName) && profileDir == null) {
|
||||
try {
|
||||
profileName = GeckoProfile.getDefaultProfileName(context);
|
||||
|
@ -163,22 +247,39 @@ public final class GeckoProfile {
|
|||
}
|
||||
}
|
||||
|
||||
// actually try to look up the profile
|
||||
// Actually try to look up the profile.
|
||||
synchronized (sProfileCache) {
|
||||
GeckoProfile profile = sProfileCache.get(profileName);
|
||||
if (profile == null) {
|
||||
try {
|
||||
profile = new GeckoProfile(context, profileName);
|
||||
profile = new GeckoProfile(context, profileName, profileDir, dbFactory);
|
||||
} catch (NoMozillaDirectoryException e) {
|
||||
// We're unable to do anything sane here.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
profile.setDir(profileDir);
|
||||
sProfileCache.put(profileName, profile);
|
||||
} else {
|
||||
profile.setDir(profileDir);
|
||||
return profile;
|
||||
}
|
||||
return profile;
|
||||
|
||||
if (profileDir == null) {
|
||||
// Fine.
|
||||
return profile;
|
||||
}
|
||||
|
||||
if (profile.getDir().equals(profileDir)) {
|
||||
// Great! We're consistent.
|
||||
return profile;
|
||||
}
|
||||
|
||||
if (sAcceptDirectoryChanges) {
|
||||
if (AppConstants.RELEASE_BUILD) {
|
||||
Log.e(LOGTAG, "Release build trying to switch out profile dir. This is an error, but let's do what we can.");
|
||||
}
|
||||
profile.setDir(profileDir);
|
||||
return profile;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Refusing to reuse profile with a different directory.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,7 +422,7 @@ public final class GeckoProfile {
|
|||
return file.delete();
|
||||
}
|
||||
|
||||
private GeckoProfile(Context context, String profileName) throws NoMozillaDirectoryException {
|
||||
private GeckoProfile(Context context, String profileName, File profileDir, BrowserDB.Factory dbFactory) throws NoMozillaDirectoryException {
|
||||
if (TextUtils.isEmpty(profileName)) {
|
||||
throw new IllegalArgumentException("Unable to create GeckoProfile for empty profile name.");
|
||||
}
|
||||
|
@ -330,6 +431,18 @@ public final class GeckoProfile {
|
|||
mName = profileName;
|
||||
mIsWebAppProfile = profileName.startsWith("webapp");
|
||||
mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context);
|
||||
|
||||
// This apes the behavior of setDir.
|
||||
if (profileDir != null && profileDir.exists() && profileDir.isDirectory()) {
|
||||
mProfileDir = profileDir;
|
||||
}
|
||||
|
||||
// N.B., mProfileDir can be null at this point.
|
||||
mDB = dbFactory.get(profileName, mProfileDir);
|
||||
}
|
||||
|
||||
public BrowserDB getDB() {
|
||||
return mDB;
|
||||
}
|
||||
|
||||
// Warning, Changing the lock file state from outside apis will cause this to become out of sync
|
||||
|
|
|
@ -5,19 +5,22 @@
|
|||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.mozglue.GeckoLoader;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.mozglue.GeckoLoader;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
@ -25,16 +28,10 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class GeckoView extends LayerView
|
||||
implements ContextGetter {
|
||||
|
||||
|
@ -152,7 +149,8 @@ public class GeckoView extends LayerView
|
|||
// If you want to use GeckoNetworkManager, start it.
|
||||
|
||||
GeckoLoader.loadMozGlue(context);
|
||||
BrowserDB.setEnableContentProviders(false);
|
||||
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
}
|
||||
|
||||
if (url != null) {
|
||||
|
@ -186,7 +184,6 @@ public class GeckoView extends LayerView
|
|||
if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
|
||||
// This is the first launch, so finish initialization and go.
|
||||
GeckoProfile profile = GeckoProfile.get(context).forceCreate();
|
||||
BrowserDB.initialize(profile.getName());
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createObjectEvent(
|
||||
GeckoEvent.ACTION_OBJECT_LAYER_CLIENT, getLayerClientObject()));
|
||||
|
|
|
@ -14,6 +14,8 @@ import java.util.Set;
|
|||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
@ -38,59 +40,70 @@ class GlobalHistory {
|
|||
private static final long BATCHING_DELAY_MS = 100;
|
||||
|
||||
private final Handler mHandler; // a background thread on which we can process requests
|
||||
private final Queue<String> mPendingUris; // URIs that need to be checked
|
||||
private SoftReference<Set<String>> 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
|
||||
|
||||
// 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 = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@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);
|
||||
mNotifierRunnable = new Runnable() {
|
||||
@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 = BrowserDB.getAllVisitedHistory(GeckoAppShell.getContext().getContentResolver());
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void addToGeckoOnly(String uri) {
|
||||
|
@ -101,9 +114,12 @@ class GlobalHistory {
|
|||
GeckoAppShell.notifyUriVisited(uri);
|
||||
}
|
||||
|
||||
public void add(String uri) {
|
||||
public void add(final Context context, final BrowserDB db, String uri) {
|
||||
ThreadUtils.assertOnBackgroundThread();
|
||||
final long start = SystemClock.uptimeMillis();
|
||||
BrowserDB.updateVisitedHistory(GeckoAppShell.getContext().getContentResolver(), uri);
|
||||
|
||||
db.updateVisitedHistory(context.getContentResolver(), uri);
|
||||
|
||||
final long end = SystemClock.uptimeMillis();
|
||||
final long took = end - start;
|
||||
Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_ADD, (int) Math.min(took, Integer.MAX_VALUE));
|
||||
|
@ -111,15 +127,19 @@ class GlobalHistory {
|
|||
}
|
||||
|
||||
@SuppressWarnings("static-method")
|
||||
public void update(String uri, String title) {
|
||||
public void update(final ContentResolver cr, final BrowserDB db, String uri, String title) {
|
||||
ThreadUtils.assertOnBackgroundThread();
|
||||
final long start = SystemClock.uptimeMillis();
|
||||
BrowserDB.updateHistoryTitle(GeckoAppShell.getContext().getContentResolver(), uri, title);
|
||||
|
||||
db.updateHistoryTitle(cr, uri, 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 NotifierRunnable runnable = new NotifierRunnable(GeckoAppShell.getContext());
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -132,7 +152,7 @@ class GlobalHistory {
|
|||
return;
|
||||
}
|
||||
mProcessing = true;
|
||||
mHandler.postDelayed(mNotifierRunnable, BATCHING_DELAY_MS);
|
||||
mHandler.postDelayed(runnable, BATCHING_DELAY_MS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
@ -215,8 +216,19 @@ class MemoryMonitor extends BroadcastReceiver {
|
|||
|
||||
private static class StorageReducer implements Runnable {
|
||||
private final Context mContext;
|
||||
private final BrowserDB mDB;
|
||||
|
||||
public StorageReducer(final Context context) {
|
||||
this.mContext = context;
|
||||
// Since this may be called while Fennec is in the background, we don't want to risk accidentally
|
||||
// using the wrong context. If the profile we get is a guest profile, use the default profile instead.
|
||||
GeckoProfile profile = GeckoProfile.get(mContext);
|
||||
if (profile.inGuestMode()) {
|
||||
// If it was the guest profile, switch to the default one.
|
||||
profile = GeckoProfile.get(mContext, GeckoProfile.DEFAULT_PROFILE);
|
||||
}
|
||||
|
||||
mDB = profile.getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -232,9 +244,10 @@ class MemoryMonitor extends BroadcastReceiver {
|
|||
return;
|
||||
}
|
||||
|
||||
BrowserDB.expireHistory(mContext.getContentResolver(),
|
||||
BrowserContract.ExpirePriority.AGGRESSIVE);
|
||||
BrowserDB.removeThumbnails(mContext.getContentResolver());
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
mDB.expireHistory(cr, BrowserContract.ExpirePriority.AGGRESSIVE);
|
||||
mDB.removeThumbnails(cr);
|
||||
|
||||
// TODO: drop or shrink disk caches
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ package org.mozilla.gecko;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
|
||||
public class PrivateTab extends Tab {
|
||||
public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
|
||||
super(context, id, url, external, parentId, title);
|
||||
|
@ -18,7 +20,7 @@ public class PrivateTab extends Tab {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void saveThumbnailToDB() {}
|
||||
protected void saveThumbnailToDB(final BrowserDB db) {}
|
||||
|
||||
@Override
|
||||
public boolean isPrivate() {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
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.favicons.Favicons;
|
||||
|
@ -14,13 +16,9 @@ import org.mozilla.gecko.util.NativeJSObject;
|
|||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UIAsyncTask;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -84,10 +82,11 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
|||
final ContentResolver cr = context.getContentResolver();
|
||||
final String url = message.optString("url");
|
||||
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (BrowserDB.isReadingListItem(cr, url)) {
|
||||
if (db.isReadingListItem(cr, url)) {
|
||||
showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
|
||||
|
||||
} else {
|
||||
|
@ -97,7 +96,7 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
|||
values.put(ReadingListItems.LENGTH, message.optInt("length"));
|
||||
values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
|
||||
values.put(ReadingListItems.CONTENT_STATUS, message.optInt("status"));
|
||||
BrowserDB.addReadingListItem(cr, values);
|
||||
db.addReadingListItem(cr, values);
|
||||
|
||||
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
@ -112,10 +111,11 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
|||
* document head for display.
|
||||
*/
|
||||
private void handleReaderModeFaviconRequest(final String url) {
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public String doInBackground() {
|
||||
return Favicons.getFaviconURLForPageURL(context, url);
|
||||
return Favicons.getFaviconURLForPageURL(db, context.getContentResolver(), url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -142,10 +142,11 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
|||
* or by tapping the readinglist-remove icon in the ReaderMode banner.
|
||||
*/
|
||||
private void handleRemoveFromList(final String url) {
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.removeReadingListItemWithURL(context.getContentResolver(), url);
|
||||
db.removeReadingListItemWithURL(context.getContentResolver(), url);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:Removed", url));
|
||||
showToast(R.string.page_removed, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
@ -157,11 +158,11 @@ public final class ReadingListHelper implements GeckoEventListener, NativeEventL
|
|||
* the proper ReaderMode banner icon (readinglist-add / readinglist-remove).
|
||||
*/
|
||||
private void handleReadingListStatusRequest(final EventCallback callback, final String url) {
|
||||
final BrowserDB db = GeckoProfile.get(context).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final int inReadingList =
|
||||
BrowserDB.isReadingListItem(context.getContentResolver(), url) ? 1 : 0;
|
||||
final int inReadingList = db.isReadingListItem(context.getContentResolver(), url) ? 1 : 0;
|
||||
|
||||
final JSONObject json = new JSONObject();
|
||||
try {
|
||||
|
|
|
@ -8,7 +8,7 @@ package org.mozilla.gecko;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteClient;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
|
|
|
@ -7,8 +7,8 @@ package org.mozilla.gecko;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteClient;
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteTab;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
import org.mozilla.gecko.db.RemoteTab;
|
||||
import org.mozilla.gecko.home.TwoLinePageRow;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -47,7 +47,7 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
|
|||
public RemoteTabsExpandableListAdapter(int groupLayoutId, int childLayoutId, List<RemoteClient> clients) {
|
||||
this.groupLayoutId = groupLayoutId;
|
||||
this.childLayoutId = childLayoutId;
|
||||
this.clients = new ArrayList<TabsAccessor.RemoteClient>();
|
||||
this.clients = new ArrayList<RemoteClient>();
|
||||
if (clients != null) {
|
||||
this.clients.addAll(clients);
|
||||
}
|
||||
|
@ -125,7 +125,11 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
|
|||
|
||||
final TextView lastModifiedView = (TextView) view.findViewById(R.id.last_synced);
|
||||
final long now = System.currentTimeMillis();
|
||||
lastModifiedView.setText(TabsAccessor.getLastSyncedString(context, now, client.lastModified));
|
||||
|
||||
// It's OK to access the DB on the main thread here, as we're just
|
||||
// getting a string.
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
lastModifiedView.setText(profile.getDB().getTabsAccessor().getLastSyncedString(context, now, client.lastModified));
|
||||
|
||||
// These views exists only in some of our group views: they are present
|
||||
// for the home panel groups and not for the tabs panel groups.
|
||||
|
|
|
@ -40,6 +40,7 @@ public class Tab {
|
|||
|
||||
private static Pattern sColorPattern;
|
||||
private final int mId;
|
||||
private final BrowserDB mDB;
|
||||
private long mLastUsed;
|
||||
private String mUrl;
|
||||
private String mBaseDomain;
|
||||
|
@ -105,6 +106,7 @@ public class Tab {
|
|||
|
||||
public Tab(Context context, int id, String url, boolean external, int parentId, String title) {
|
||||
mAppContext = context.getApplicationContext();
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
mId = id;
|
||||
mUrl = url;
|
||||
mBaseDomain = "";
|
||||
|
@ -226,11 +228,11 @@ public class Tab {
|
|||
try {
|
||||
mThumbnail = new BitmapDrawable(mAppContext.getResources(), b);
|
||||
if (mState == Tab.STATE_SUCCESS && cachePolicy == ThumbnailHelper.CachePolicy.STORE) {
|
||||
saveThumbnailToDB();
|
||||
saveThumbnailToDB(mDB);
|
||||
} else {
|
||||
// If the page failed to load, or requested that we not cache info about it, clear any previous
|
||||
// thumbnails we've stored.
|
||||
clearThumbnailFromDB();
|
||||
clearThumbnailFromDB(mDB);
|
||||
}
|
||||
} catch (OutOfMemoryError oom) {
|
||||
Log.w(LOGTAG, "Unable to create/scale bitmap.", oom);
|
||||
|
@ -300,11 +302,13 @@ public class Tab {
|
|||
}
|
||||
|
||||
final ContentResolver cr = mAppContext.getContentResolver();
|
||||
final Map<String, Object> data = URLMetadata.fromJSON(metadata);
|
||||
final URLMetadata urlMetadata = mDB.getURLMetadata();
|
||||
|
||||
final Map<String, Object> data = urlMetadata.fromJSON(metadata);
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
URLMetadata.save(cr, mUrl, data);
|
||||
urlMetadata.save(cr, mUrl, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -482,7 +486,7 @@ public class Tab {
|
|||
return;
|
||||
}
|
||||
|
||||
mBookmark = BrowserDB.isBookmark(getContentResolver(), url);
|
||||
mBookmark = mDB.isBookmark(getContentResolver(), url);
|
||||
Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.MENU_UPDATED);
|
||||
}
|
||||
});
|
||||
|
@ -496,7 +500,7 @@ public class Tab {
|
|||
if (url == null)
|
||||
return;
|
||||
|
||||
BrowserDB.addBookmark(getContentResolver(), mTitle, url);
|
||||
mDB.addBookmark(getContentResolver(), mTitle, url);
|
||||
Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.BOOKMARK_ADDED);
|
||||
}
|
||||
});
|
||||
|
@ -510,7 +514,7 @@ public class Tab {
|
|||
if (url == null)
|
||||
return;
|
||||
|
||||
BrowserDB.removeBookmarksWithURL(getContentResolver(), url);
|
||||
mDB.removeBookmarksWithURL(getContentResolver(), url);
|
||||
Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.BOOKMARK_REMOVED);
|
||||
}
|
||||
});
|
||||
|
@ -629,6 +633,7 @@ public class Tab {
|
|||
final String oldURL = getURL();
|
||||
final Tab tab = this;
|
||||
tab.setLoadProgress(LOAD_PROGRESS_STOP);
|
||||
|
||||
ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -636,7 +641,7 @@ public class Tab {
|
|||
if (!TextUtils.equals(oldURL, getURL()))
|
||||
return;
|
||||
|
||||
ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
|
||||
ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab, mDB);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
@ -645,7 +650,7 @@ public class Tab {
|
|||
setLoadProgressIfLoading(LOAD_PROGRESS_LOADED);
|
||||
}
|
||||
|
||||
protected void saveThumbnailToDB() {
|
||||
protected void saveThumbnailToDB(final BrowserDB db) {
|
||||
final BitmapDrawable thumbnail = mThumbnail;
|
||||
if (thumbnail == null) {
|
||||
return;
|
||||
|
@ -657,20 +662,20 @@ public class Tab {
|
|||
return;
|
||||
}
|
||||
|
||||
BrowserDB.updateThumbnailForUrl(getContentResolver(), url, thumbnail);
|
||||
db.updateThumbnailForUrl(getContentResolver(), url, thumbnail);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private void clearThumbnailFromDB() {
|
||||
private void clearThumbnailFromDB(final BrowserDB db) {
|
||||
try {
|
||||
String url = getURL();
|
||||
if (url == null)
|
||||
return;
|
||||
|
||||
// Passing in a null thumbnail will delete the stored thumbnail for this url
|
||||
BrowserDB.updateThumbnailForUrl(getContentResolver(), url, null);
|
||||
db.updateThumbnailForUrl(getContentResolver(), url, null);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
|
|
@ -11,10 +11,8 @@ import java.util.List;
|
|||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
|
@ -70,16 +68,26 @@ public class Tabs implements GeckoEventListener {
|
|||
|
||||
private Context mAppContext;
|
||||
private ContentObserver mContentObserver;
|
||||
private PersistTabsRunnable mPersistTabsRunnable;
|
||||
|
||||
private static class PersistTabsRunnable implements Runnable {
|
||||
private final BrowserDB db;
|
||||
private final Context context;
|
||||
private final Iterable<Tab> tabs;
|
||||
|
||||
public PersistTabsRunnable(final Context context, Iterable<Tab> tabsInOrder) {
|
||||
this.context = context;
|
||||
this.db = GeckoProfile.get(context).getDB();
|
||||
this.tabs = tabsInOrder;
|
||||
}
|
||||
|
||||
private final Runnable mPersistTabsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final Context context = getAppContext();
|
||||
boolean syncIsSetup = SyncAccounts.syncAccountsExist(context) ||
|
||||
FirefoxAccounts.firefoxAccountsExist(context);
|
||||
if (syncIsSetup) {
|
||||
TabsAccessor.persistLocalTabs(getContentResolver(), getTabsInOrder());
|
||||
db.getTabsAccessor().persistLocalTabs(context.getContentResolver(), tabs);
|
||||
}
|
||||
} catch (SecurityException se) {} // will fail without android.permission.GET_ACCOUNTS
|
||||
}
|
||||
|
@ -133,7 +141,9 @@ public class Tabs implements GeckoEventListener {
|
|||
mAccountManager.addOnAccountsUpdatedListener(mAccountListener, ThreadUtils.getBackgroundHandler(), false);
|
||||
|
||||
if (mContentObserver != null) {
|
||||
BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
|
||||
// It's safe to use the db here since we aren't doing any I/O.
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
profile.getDB().registerBookmarkObserver(getContentResolver(), mContentObserver);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,7 +190,10 @@ public class Tabs implements GeckoEventListener {
|
|||
}
|
||||
}
|
||||
};
|
||||
BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
|
||||
|
||||
// It's safe to use the db here since we aren't doing any I/O.
|
||||
final GeckoProfile profile = GeckoProfile.get(mAppContext);
|
||||
profile.getDB().registerBookmarkObserver(getContentResolver(), mContentObserver);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,11 +544,12 @@ public class Tabs implements GeckoEventListener {
|
|||
|
||||
public void refreshThumbnails() {
|
||||
final ThumbnailHelper helper = ThumbnailHelper.getInstance();
|
||||
final BrowserDB db = GeckoProfile.get(mAppContext).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (final Tab tab : mOrder) {
|
||||
helper.getAndProcessThumbnailFor(tab);
|
||||
helper.getAndProcessThumbnailFor(tab, db);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -632,6 +646,9 @@ public class Tabs implements GeckoEventListener {
|
|||
|
||||
// This method persists the current ordered list of tabs in our tabs content provider.
|
||||
public void persistAllTabs() {
|
||||
// If there is already a mPersistTabsRunnable in progress, the backgroundThread will hold onto
|
||||
// it and ensure these still happen in the correct order.
|
||||
mPersistTabsRunnable = new PersistTabsRunnable(mAppContext, getTabsInOrder());
|
||||
ThreadUtils.postToBackgroundThread(mPersistTabsRunnable);
|
||||
}
|
||||
|
||||
|
@ -641,8 +658,15 @@ public class Tabs implements GeckoEventListener {
|
|||
* those requests are removed.
|
||||
*/
|
||||
private void queuePersistAllTabs() {
|
||||
Handler backgroundHandler = ThreadUtils.getBackgroundHandler();
|
||||
backgroundHandler.removeCallbacks(mPersistTabsRunnable);
|
||||
final Handler backgroundHandler = ThreadUtils.getBackgroundHandler();
|
||||
|
||||
// Note: Its safe to modify the runnable here because all of the callers are on the same thread.
|
||||
if (mPersistTabsRunnable != null) {
|
||||
backgroundHandler.removeCallbacks(mPersistTabsRunnable);
|
||||
mPersistTabsRunnable = null;
|
||||
}
|
||||
|
||||
mPersistTabsRunnable = new PersistTabsRunnable(mAppContext, getTabsInOrder());
|
||||
backgroundHandler.postDelayed(mPersistTabsRunnable, PERSIST_TABS_AFTER_MILLISECONDS);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.mozilla.gecko;
|
|||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.gfx.IntSize;
|
||||
import org.mozilla.gecko.mozglue.DirectBufferAllocator;
|
||||
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
|
||||
|
||||
|
@ -69,16 +68,21 @@ public final class ThumbnailHelper {
|
|||
mHeight = -1;
|
||||
}
|
||||
|
||||
public void getAndProcessThumbnailFor(Tab tab) {
|
||||
public void getAndProcessThumbnailFor(Tab tab, final BrowserDB db) {
|
||||
if (AboutPages.isAboutHome(tab.getURL())) {
|
||||
tab.updateThumbnail(null, CachePolicy.NO_STORE);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a database, there's nothing left to do.
|
||||
if (db == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab.getState() == Tab.STATE_DELAYED) {
|
||||
String url = tab.getURL();
|
||||
if (url != null) {
|
||||
byte[] thumbnail = BrowserDB.getThumbnailForUrl(GeckoAppShell.getContext().getContentResolver(), url);
|
||||
byte[] thumbnail = db.getThumbnailForUrl(GeckoAppShell.getContext().getContentResolver(), url);
|
||||
if (thumbnail != null) {
|
||||
// Since this thumbnail is from the database, its ok to store it
|
||||
setTabThumbnail(tab, null, thumbnail, CachePolicy.STORE);
|
||||
|
|
|
@ -1,277 +1,186 @@
|
|||
/* -*- 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
|
||||
/* 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 java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
||||
/**
|
||||
* A utility wrapper for accessing a static {@link LocalBrowserDB},
|
||||
* manually initialized with a particular profile, and similarly
|
||||
* managing a static instance of {@link SuggestedSites}.
|
||||
* Interface for interactions with all databases. If you want an instance
|
||||
* that implements this, you should go through GeckoProfile. E.g.,
|
||||
* <code>GeckoProfile.get(context).getDB()</code>.
|
||||
*
|
||||
* Be careful using this class: if you're not BrowserApp, you probably
|
||||
* want to manually instantiate and use LocalBrowserDB itself.
|
||||
*
|
||||
* Also manages some flags.
|
||||
* GeckoProfile itself will construct an appropriate subclass using
|
||||
* a factory that the containing application can set with
|
||||
* {@link GeckoProfile#setBrowserDBFactory(BrowserDB.Factory)}.
|
||||
*/
|
||||
public class BrowserDB {
|
||||
public interface BrowserDB {
|
||||
public interface Factory {
|
||||
public BrowserDB get(String profileName, File profileDir);
|
||||
}
|
||||
|
||||
public static enum FilterFlags {
|
||||
EXCLUDE_PINNED_SITES
|
||||
}
|
||||
|
||||
private static volatile LocalBrowserDB sDb;
|
||||
private static volatile SuggestedSites sSuggestedSites;
|
||||
private static volatile boolean sAreContentProvidersEnabled = true;
|
||||
public abstract Searches getSearches();
|
||||
public abstract TabsAccessor getTabsAccessor();
|
||||
public abstract URLMetadata getURLMetadata();
|
||||
|
||||
public static void initialize(String profile) {
|
||||
sDb = new LocalBrowserDB(profile);
|
||||
}
|
||||
/**
|
||||
* Add default bookmarks to the database.
|
||||
* Takes an offset; returns a new offset.
|
||||
*/
|
||||
public abstract int addDefaultBookmarks(Context context, ContentResolver cr, int offset);
|
||||
|
||||
public static int addDefaultBookmarks(Context context, ContentResolver cr, final int offset) {
|
||||
return sDb.addDefaultBookmarks(context, cr, offset);
|
||||
}
|
||||
/**
|
||||
* Add bookmarks from the provided distribution.
|
||||
* Takes an offset; returns a new offset.
|
||||
*/
|
||||
public abstract int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset);
|
||||
|
||||
public static int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset) {
|
||||
return sDb.addDistributionBookmarks(cr, distribution, offset);
|
||||
}
|
||||
/**
|
||||
* Invalidate cached data.
|
||||
*/
|
||||
public abstract void invalidate();
|
||||
|
||||
public static void setSuggestedSites(SuggestedSites suggestedSites) {
|
||||
sSuggestedSites = suggestedSites;
|
||||
}
|
||||
public abstract int getCount(ContentResolver cr, String database);
|
||||
|
||||
public static boolean hideSuggestedSite(String url) {
|
||||
return sSuggestedSites.hideSite(url);
|
||||
}
|
||||
/**
|
||||
* @return a cursor representing the contents of the DB filtered according to the arguments.
|
||||
* Can return <code>null</code>. <code>CursorLoader</code> will handle this correctly.
|
||||
*/
|
||||
public abstract Cursor filter(ContentResolver cr, CharSequence constraint,
|
||||
int limit, EnumSet<BrowserDB.FilterFlags> flags);
|
||||
|
||||
public static void invalidateCachedState() {
|
||||
sDb.invalidateCachedState();
|
||||
}
|
||||
/**
|
||||
* @return a cursor over top sites (high-ranking bookmarks and history).
|
||||
* Can return <code>null</code>.
|
||||
*/
|
||||
public abstract Cursor getTopSites(ContentResolver cr, int limit);
|
||||
|
||||
@RobocopTarget
|
||||
public static Cursor filter(ContentResolver cr, CharSequence constraint, int limit,
|
||||
EnumSet<FilterFlags> flags) {
|
||||
return sDb.filter(cr, constraint, limit, flags);
|
||||
}
|
||||
/**
|
||||
* @return a cursor over top sites (high-ranking bookmarks and history).
|
||||
* Can return <code>null</code>.
|
||||
* Returns no more than <code>maxLimit</code> results.
|
||||
*/
|
||||
public abstract Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit);
|
||||
|
||||
private static void appendUrlsFromCursor(List<String> urls, Cursor c) {
|
||||
c.moveToPosition(-1);
|
||||
while (c.moveToNext()) {
|
||||
String url = c.getString(c.getColumnIndex(History.URL));
|
||||
public abstract void updateVisitedHistory(ContentResolver cr, String uri);
|
||||
|
||||
// Do a simpler check before decoding to avoid parsing
|
||||
// all URLs unnecessarily.
|
||||
if (StringUtils.isUserEnteredUrl(url)) {
|
||||
url = StringUtils.decodeUserEnteredUrl(url);
|
||||
}
|
||||
public abstract void updateHistoryTitle(ContentResolver cr, String uri, String title);
|
||||
|
||||
urls.add(url);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Can return <code>null</code>.
|
||||
*/
|
||||
public abstract Cursor getAllVisitedHistory(ContentResolver cr);
|
||||
|
||||
public static Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit) {
|
||||
// Note this is not a single query anymore, but actually returns a mixture
|
||||
// of two queries, one for topSites and one for pinned sites.
|
||||
Cursor pinnedSites = sDb.getPinnedSites(cr, minLimit);
|
||||
/**
|
||||
* Can return <code>null</code>.
|
||||
*/
|
||||
public abstract Cursor getRecentHistory(ContentResolver cr, int limit);
|
||||
|
||||
int pinnedCount = pinnedSites.getCount();
|
||||
Cursor topSites = sDb.getTopSites(cr, maxLimit - pinnedCount);
|
||||
int topCount = topSites.getCount();
|
||||
public abstract void expireHistory(ContentResolver cr, ExpirePriority priority);
|
||||
|
||||
Cursor suggestedSites = null;
|
||||
if (sSuggestedSites != null) {
|
||||
final int count = minLimit - pinnedCount - topCount;
|
||||
if (count > 0) {
|
||||
final List<String> excludeUrls = new ArrayList<String>(pinnedCount + topCount);
|
||||
appendUrlsFromCursor(excludeUrls, pinnedSites);
|
||||
appendUrlsFromCursor(excludeUrls, topSites);
|
||||
public abstract void removeHistoryEntry(ContentResolver cr, String url);
|
||||
|
||||
suggestedSites = sSuggestedSites.get(count, excludeUrls);
|
||||
}
|
||||
}
|
||||
public abstract void clearHistory(ContentResolver cr);
|
||||
|
||||
return new TopSitesCursorWrapper(pinnedSites, topSites, suggestedSites, minLimit);
|
||||
}
|
||||
|
||||
public static void updateVisitedHistory(ContentResolver cr, String uri) {
|
||||
if (sAreContentProvidersEnabled) {
|
||||
sDb.updateVisitedHistory(cr, uri);
|
||||
}
|
||||
}
|
||||
public abstract String getUrlForKeyword(ContentResolver cr, String keyword);
|
||||
|
||||
public static void updateHistoryTitle(ContentResolver cr, String uri, String title) {
|
||||
if (sAreContentProvidersEnabled) {
|
||||
sDb.updateHistoryTitle(cr, uri, title);
|
||||
}
|
||||
}
|
||||
public abstract boolean isBookmark(ContentResolver cr, String uri);
|
||||
public abstract void addBookmark(ContentResolver cr, String title, String uri);
|
||||
public abstract Cursor getBookmarkForUrl(ContentResolver cr, String url);
|
||||
public abstract void removeBookmarksWithURL(ContentResolver cr, String uri);
|
||||
public abstract void registerBookmarkObserver(ContentResolver cr, ContentObserver observer);
|
||||
public abstract void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
|
||||
|
||||
@RobocopTarget
|
||||
public static Cursor getAllVisitedHistory(ContentResolver cr) {
|
||||
return (sAreContentProvidersEnabled ? sDb.getAllVisitedHistory(cr) : null);
|
||||
}
|
||||
/**
|
||||
* Can return <code>null</code>.
|
||||
*/
|
||||
public abstract Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
|
||||
|
||||
public static Cursor getRecentHistory(ContentResolver cr, int limit) {
|
||||
return sDb.getRecentHistory(cr, limit);
|
||||
}
|
||||
/**
|
||||
* Can return <code>null</code>.
|
||||
*/
|
||||
public abstract Cursor getReadingList(ContentResolver cr);
|
||||
public abstract boolean isReadingListItem(ContentResolver cr, String uri);
|
||||
public abstract void addReadingListItem(ContentResolver cr, ContentValues values);
|
||||
public abstract void removeReadingListItemWithURL(ContentResolver cr, String uri);
|
||||
|
||||
public static void expireHistory(ContentResolver cr, ExpirePriority priority) {
|
||||
if (sDb == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
sDb.expireHistory(cr, priority == null ? ExpirePriority.NORMAL : priority);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static void removeHistoryEntry(ContentResolver cr, String url) {
|
||||
sDb.removeHistoryEntry(cr, url);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static void clearHistory(ContentResolver cr) {
|
||||
sDb.clearHistory(cr);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
|
||||
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);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static boolean isBookmark(ContentResolver cr, String uri) {
|
||||
return (sAreContentProvidersEnabled && sDb.isBookmark(cr, uri));
|
||||
}
|
||||
|
||||
public static boolean isReadingListItem(ContentResolver cr, String uri) {
|
||||
return (sAreContentProvidersEnabled && sDb.isReadingListItem(cr, uri));
|
||||
}
|
||||
|
||||
public static void addBookmark(ContentResolver cr, String title, String uri) {
|
||||
sDb.addBookmark(cr, title, uri);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static void removeBookmarksWithURL(ContentResolver cr, String uri) {
|
||||
sDb.removeBookmarksWithURL(cr, uri);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
|
||||
sDb.updateBookmark(cr, id, uri, title, keyword);
|
||||
}
|
||||
|
||||
public static void addReadingListItem(ContentResolver cr, ContentValues values) {
|
||||
sDb.addReadingListItem(cr, values);
|
||||
}
|
||||
|
||||
public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
||||
sDb.removeReadingListItemWithURL(cr, uri);
|
||||
}
|
||||
|
||||
public static LoadFaviconResult getFaviconForFaviconUrl(ContentResolver cr, String faviconURL) {
|
||||
return sDb.getFaviconForUrl(cr, faviconURL);
|
||||
}
|
||||
/**
|
||||
* Get the favicon from the database, if any, associated with the given favicon URL. (That is,
|
||||
* the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
|
||||
* @param cr The ContentResolver to use.
|
||||
* @param faviconURL The URL of the favicon to fetch from the database.
|
||||
* @return The decoded Bitmap from the database, if any. null if none is stored.
|
||||
*/
|
||||
public abstract LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL);
|
||||
|
||||
/**
|
||||
* Try to find a usable favicon URL in the history or bookmarks table.
|
||||
*/
|
||||
public static String getFaviconURLFromPageURL(ContentResolver cr, String url) {
|
||||
return sDb.getFaviconURLFromPageURL(cr, url);
|
||||
}
|
||||
public abstract String getFaviconURLFromPageURL(ContentResolver cr, String uri);
|
||||
|
||||
public static void updateFaviconForUrl(ContentResolver cr, String pageUri, byte[] encodedFavicon, String faviconUri) {
|
||||
sDb.updateFaviconForUrl(cr, pageUri, encodedFavicon, faviconUri);
|
||||
}
|
||||
public abstract void updateFaviconForUrl(ContentResolver cr, String pageUri, byte[] encodedFavicon, String faviconUri);
|
||||
|
||||
public static void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail) {
|
||||
sDb.updateThumbnailForUrl(cr, uri, thumbnail);
|
||||
}
|
||||
public abstract byte[] getThumbnailForUrl(ContentResolver cr, String uri);
|
||||
public abstract void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail);
|
||||
|
||||
@RobocopTarget
|
||||
public static byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
|
||||
return sDb.getThumbnailForUrl(cr, uri);
|
||||
}
|
||||
/**
|
||||
* Query for non-null thumbnails matching the provided <code>urls</code>.
|
||||
* The returned cursor will have no more than, but possibly fewer than,
|
||||
* the requested number of thumbnails.
|
||||
*
|
||||
* Returns null if the provided list of URLs is empty or null.
|
||||
*/
|
||||
public abstract Cursor getThumbnailsForUrls(ContentResolver cr,
|
||||
List<String> urls);
|
||||
|
||||
public static Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
|
||||
return sDb.getThumbnailsForUrls(cr, urls);
|
||||
}
|
||||
public abstract void removeThumbnails(ContentResolver cr);
|
||||
|
||||
@RobocopTarget
|
||||
public static void removeThumbnails(ContentResolver cr) {
|
||||
sDb.removeThumbnails(cr);
|
||||
}
|
||||
// Utility function for updating existing history using batch operations
|
||||
public abstract void updateHistoryInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations, String url,
|
||||
String title, long date, int visits);
|
||||
|
||||
public static void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
|
||||
sDb.registerBookmarkObserver(cr, observer);
|
||||
}
|
||||
public abstract void updateBookmarkInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations, String url,
|
||||
String title, String guid, long parent, long added, long modified,
|
||||
long position, String keyword, int type);
|
||||
|
||||
public static int getCount(ContentResolver cr, String database) {
|
||||
return sDb.getCount(cr, database);
|
||||
}
|
||||
public abstract void updateFaviconInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations, String url,
|
||||
String faviconUrl, String faviconGuid, byte[] data);
|
||||
|
||||
public static void pinSite(ContentResolver cr, String url, String title, int position) {
|
||||
sDb.pinSite(cr, url, title, position);
|
||||
}
|
||||
|
||||
public static void unpinSite(ContentResolver cr, int position) {
|
||||
sDb.unpinSite(cr, position);
|
||||
}
|
||||
public abstract Cursor getPinnedSites(ContentResolver cr, int limit);
|
||||
public abstract void pinSite(ContentResolver cr, String url, String title, int position);
|
||||
public abstract void unpinSite(ContentResolver cr, int position);
|
||||
|
||||
@RobocopTarget
|
||||
public static Cursor getBookmarkForUrl(ContentResolver cr, String url) {
|
||||
return sDb.getBookmarkForUrl(cr, url);
|
||||
}
|
||||
|
||||
public static void setEnableContentProviders(boolean enableContentProviders) {
|
||||
sAreContentProvidersEnabled = enableContentProviders;
|
||||
}
|
||||
|
||||
public static boolean hasSuggestedImageUrl(String url) {
|
||||
return sSuggestedSites.contains(url);
|
||||
}
|
||||
|
||||
public static String getSuggestedImageUrlForUrl(String url) {
|
||||
return sSuggestedSites.getImageUrlForUrl(url);
|
||||
}
|
||||
|
||||
public static int getSuggestedBackgroundColorForUrl(String url) {
|
||||
final String bgColor = sSuggestedSites.getBackgroundColorForUrl(url);
|
||||
if (bgColor != null) {
|
||||
return Color.parseColor(bgColor);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int getTrackingIdForUrl(String url) {
|
||||
return sSuggestedSites.getTrackingIdForUrl(url);
|
||||
}
|
||||
public abstract boolean hideSuggestedSite(String url);
|
||||
public abstract void setSuggestedSites(SuggestedSites suggestedSites);
|
||||
public abstract boolean hasSuggestedImageUrl(String url);
|
||||
public abstract String getSuggestedImageUrlForUrl(String url);
|
||||
public abstract int getSuggestedBackgroundColorForUrl(String url);
|
||||
public abstract int getTrackingIdForUrl(String url);
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
|
||||
static {
|
||||
sTables = new Table[] {
|
||||
// See awful shortcut assumption hack in getURLMetadataTable.
|
||||
new URLMetadataTable()
|
||||
};
|
||||
// We will reuse this.
|
||||
|
@ -234,6 +235,12 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// Convenience accessor.
|
||||
// Assumes structure of sTables!
|
||||
private URLMetadataTable getURLMetadataTable() {
|
||||
return (URLMetadataTable) sTables[0];
|
||||
}
|
||||
|
||||
private static boolean hasFaviconsInProjection(String[] projection) {
|
||||
if (projection == null) return true;
|
||||
for (int i = 0; i < projection.length; ++i) {
|
||||
|
@ -1360,8 +1367,7 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
|
||||
return deleteFavicons(uri, faviconSelection, null) +
|
||||
deleteThumbnails(uri, thumbnailSelection, null) +
|
||||
URLMetadata.deleteUnused(getContext().getContentResolver(),
|
||||
uri.getQueryParameter(BrowserContract.PARAM_PROFILE));
|
||||
getURLMetadataTable().deleteUnused(getWritableDatabase(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,10 +6,12 @@ package org.mozilla.gecko.db;
|
|||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
|
@ -160,4 +162,15 @@ public class DBUtils {
|
|||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static Uri appendProfile(final String profile, final Uri uri) {
|
||||
return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, profile).build();
|
||||
}
|
||||
|
||||
public static Uri appendProfileWithDefault(final String profile, final Uri uri) {
|
||||
if (TextUtils.isEmpty(profile)) {
|
||||
return appendProfile(GeckoProfile.DEFAULT_PROFILE, uri);
|
||||
}
|
||||
return appendProfile(profile, uri);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ 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.BrowserDB.FilterFlags;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
|
||||
|
@ -42,6 +41,7 @@ import org.mozilla.gecko.gfx.BitmapUtils;
|
|||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
|
@ -51,6 +51,7 @@ import android.database.ContentObserver;
|
|||
import android.database.Cursor;
|
||||
import android.database.CursorWrapper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.Uri;
|
||||
import android.provider.Browser;
|
||||
|
@ -61,7 +62,7 @@ import org.mozilla.gecko.util.IOUtils;
|
|||
import static org.mozilla.gecko.util.IOUtils.ConsumedInputStream;
|
||||
import static org.mozilla.gecko.favicons.LoadFaviconTask.DEFAULT_FAVICON_BUFFER_SIZE;
|
||||
|
||||
public class LocalBrowserDB {
|
||||
public class LocalBrowserDB implements BrowserDB {
|
||||
// Calculate these once, at initialization. isLoggable is too expensive to
|
||||
// have in-line in each log call.
|
||||
private static final String LOGTAG = "GeckoLocalBrowserDB";
|
||||
|
@ -69,6 +70,9 @@ public class LocalBrowserDB {
|
|||
// Sentinel value used to indicate a failure to locate an ID for a default favicon.
|
||||
private static final int FAVICON_ID_NOT_FOUND = Integer.MIN_VALUE;
|
||||
|
||||
// Constant used to indicate that no folder was found for particular GUID.
|
||||
private static final long FOLDER_NOT_FOUND = -1L;
|
||||
|
||||
private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
|
||||
protected static void debug(String message) {
|
||||
if (logDebug) {
|
||||
|
@ -84,6 +88,8 @@ public class LocalBrowserDB {
|
|||
// Use wrapped Boolean so that we can have a null state
|
||||
private volatile Boolean mDesktopBookmarksExist;
|
||||
|
||||
private volatile SuggestedSites mSuggestedSites;
|
||||
|
||||
private final Uri mBookmarksUriWithProfile;
|
||||
private final Uri mParentsUriWithProfile;
|
||||
private final Uri mHistoryUriWithProfile;
|
||||
|
@ -94,6 +100,10 @@ public class LocalBrowserDB {
|
|||
private final Uri mThumbnailsUriWithProfile;
|
||||
private final Uri mReadingListUriWithProfile;
|
||||
|
||||
private LocalSearches searches;
|
||||
private LocalTabsAccessor tabsAccessor;
|
||||
private LocalURLMetadata urlMetadata;
|
||||
|
||||
private static final String[] DEFAULT_BOOKMARK_COLUMNS =
|
||||
new String[] { Bookmarks._ID,
|
||||
Bookmarks.GUID,
|
||||
|
@ -106,18 +116,39 @@ public class LocalBrowserDB {
|
|||
mProfile = profile;
|
||||
mFolderIdMap = new HashMap<String, Long>();
|
||||
|
||||
mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI);
|
||||
mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI);
|
||||
mHistoryUriWithProfile = appendProfile(History.CONTENT_URI);
|
||||
mHistoryExpireUriWithProfile = appendProfile(History.CONTENT_OLD_URI);
|
||||
mCombinedUriWithProfile = appendProfile(Combined.CONTENT_URI);
|
||||
mFaviconsUriWithProfile = appendProfile(Favicons.CONTENT_URI);
|
||||
mThumbnailsUriWithProfile = appendProfile(Thumbnails.CONTENT_URI);
|
||||
mReadingListUriWithProfile = appendProfile(ReadingListItems.CONTENT_URI);
|
||||
mBookmarksUriWithProfile = DBUtils.appendProfile(profile, Bookmarks.CONTENT_URI);
|
||||
mParentsUriWithProfile = DBUtils.appendProfile(profile, Bookmarks.PARENTS_CONTENT_URI);
|
||||
mHistoryUriWithProfile = DBUtils.appendProfile(profile, History.CONTENT_URI);
|
||||
mHistoryExpireUriWithProfile = DBUtils.appendProfile(profile, History.CONTENT_OLD_URI);
|
||||
mCombinedUriWithProfile = DBUtils.appendProfile(profile, Combined.CONTENT_URI);
|
||||
mFaviconsUriWithProfile = DBUtils.appendProfile(profile, Favicons.CONTENT_URI);
|
||||
mThumbnailsUriWithProfile = DBUtils.appendProfile(profile, Thumbnails.CONTENT_URI);
|
||||
mReadingListUriWithProfile = DBUtils.appendProfile(profile, ReadingListItems.CONTENT_URI);
|
||||
|
||||
mUpdateHistoryUriWithProfile = mHistoryUriWithProfile.buildUpon().
|
||||
appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").
|
||||
appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
|
||||
mUpdateHistoryUriWithProfile =
|
||||
mHistoryUriWithProfile.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
|
||||
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
|
||||
.build();
|
||||
|
||||
searches = new LocalSearches(mProfile);
|
||||
tabsAccessor = new LocalTabsAccessor(mProfile);
|
||||
urlMetadata = new LocalURLMetadata(mProfile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Searches getSearches() {
|
||||
return searches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabsAccessor getTabsAccessor() {
|
||||
return tabsAccessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URLMetadata getURLMetadata() {
|
||||
return urlMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,9 +185,10 @@ public class LocalBrowserDB {
|
|||
* Add default bookmarks to the database.
|
||||
* Takes an offset; returns a new offset.
|
||||
*/
|
||||
@Override
|
||||
public int addDefaultBookmarks(Context context, ContentResolver cr, final int offset) {
|
||||
long folderID = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
|
||||
if (folderID == -1L) {
|
||||
final long folderID = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
|
||||
if (folderID == FOLDER_NOT_FOUND) {
|
||||
Log.e(LOGTAG, "No mobile folder: cannot add default bookmarks.");
|
||||
return offset;
|
||||
}
|
||||
|
@ -251,6 +283,7 @@ public class LocalBrowserDB {
|
|||
* Add bookmarks from the provided distribution.
|
||||
* Takes an offset; returns a new offset.
|
||||
*/
|
||||
@Override
|
||||
public int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset) {
|
||||
if (!distribution.exists()) {
|
||||
Log.d(LOGTAG, "No distribution from which to add bookmarks.");
|
||||
|
@ -263,8 +296,8 @@ public class LocalBrowserDB {
|
|||
return offset;
|
||||
}
|
||||
|
||||
long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
|
||||
if (folderId == -1L) {
|
||||
final long folderID = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
|
||||
if (folderID == FOLDER_NOT_FOUND) {
|
||||
Log.e(LOGTAG, "No mobile folder: cannot add distribution bookmarks.");
|
||||
return offset;
|
||||
}
|
||||
|
@ -292,7 +325,7 @@ public class LocalBrowserDB {
|
|||
parent = Bookmarks.FIXED_PINNED_LIST_ID;
|
||||
pos = pinnedPos++;
|
||||
} else {
|
||||
parent = folderId;
|
||||
parent = folderID;
|
||||
pos = mobilePos++;
|
||||
}
|
||||
|
||||
|
@ -462,57 +495,47 @@ public class LocalBrowserDB {
|
|||
}
|
||||
|
||||
// Invalidate cached data
|
||||
public void invalidateCachedState() {
|
||||
@Override
|
||||
public void invalidate() {
|
||||
mDesktopBookmarksExist = null;
|
||||
}
|
||||
|
||||
private Uri bookmarksUriWithLimit(int limit) {
|
||||
return mBookmarksUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT,
|
||||
String.valueOf(limit)).build();
|
||||
return mBookmarksUriWithProfile.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_LIMIT,
|
||||
String.valueOf(limit))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Uri combinedUriWithLimit(int limit) {
|
||||
return mCombinedUriWithProfile.buildUpon().appendQueryParameter(BrowserContract.PARAM_LIMIT,
|
||||
String.valueOf(limit)).build();
|
||||
return mCombinedUriWithProfile.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_LIMIT,
|
||||
String.valueOf(limit))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Uri appendProfile(Uri uri) {
|
||||
return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, mProfile).build();
|
||||
}
|
||||
|
||||
private Uri getAllBookmarksUri() {
|
||||
Uri.Builder uriBuilder = mBookmarksUriWithProfile.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1");
|
||||
return uriBuilder.build();
|
||||
}
|
||||
|
||||
private Uri getAllHistoryUri() {
|
||||
Uri.Builder uriBuilder = mHistoryUriWithProfile.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1");
|
||||
return uriBuilder.build();
|
||||
}
|
||||
|
||||
private Uri getAllFaviconsUri() {
|
||||
Uri.Builder uriBuilder = mFaviconsUriWithProfile.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1");
|
||||
return uriBuilder.build();
|
||||
private static Uri withDeleted(final Uri uri) {
|
||||
return uri.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1")
|
||||
.build();
|
||||
}
|
||||
|
||||
private Cursor filterAllSites(ContentResolver cr, String[] projection, CharSequence constraint,
|
||||
int limit, CharSequence urlFilter, String selection, String[] selectionArgs) {
|
||||
// The combined history/bookmarks selection queries for sites with a url or title containing
|
||||
int limit, CharSequence urlFilter, String selection, String[] selectionArgs) {
|
||||
// The combined history/bookmarks selection queries for sites with a URL or title containing
|
||||
// the constraint string(s), treating space-separated words as separate constraints
|
||||
if (!TextUtils.isEmpty(constraint)) {
|
||||
String[] constraintWords = constraint.toString().split(" ");
|
||||
// Only create a filter query with a maximum of 10 constraint words
|
||||
int constraintCount = Math.min(constraintWords.length, 10);
|
||||
for (int i = 0; i < constraintCount; i++) {
|
||||
selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " LIKE ? OR " +
|
||||
Combined.TITLE + " LIKE ?)");
|
||||
String constraintWord = "%" + constraintWords[i] + "%";
|
||||
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
||||
new String[] { constraintWord, constraintWord });
|
||||
}
|
||||
final String[] constraintWords = constraint.toString().split(" ");
|
||||
|
||||
// Only create a filter query with a maximum of 10 constraint words.
|
||||
final int constraintCount = Math.min(constraintWords.length, 10);
|
||||
for (int i = 0; i < constraintCount; i++) {
|
||||
selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " LIKE ? OR " +
|
||||
Combined.TITLE + " LIKE ?)");
|
||||
String constraintWord = "%" + constraintWords[i] + "%";
|
||||
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
||||
new String[] { constraintWord, constraintWord });
|
||||
}
|
||||
}
|
||||
|
||||
if (urlFilter != null) {
|
||||
|
@ -536,11 +559,13 @@ public class LocalBrowserDB {
|
|||
sortOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount(ContentResolver cr, String database) {
|
||||
int count = 0;
|
||||
String[] columns = null;
|
||||
String constraint = null;
|
||||
Uri uri = null;
|
||||
|
||||
if ("history".equals(database)) {
|
||||
uri = mHistoryUriWithProfile;
|
||||
columns = new String[] { History._ID };
|
||||
|
@ -560,6 +585,7 @@ public class LocalBrowserDB {
|
|||
uri = mReadingListUriWithProfile;
|
||||
columns = new String[] { ReadingListItems._ID };
|
||||
}
|
||||
|
||||
if (uri != null) {
|
||||
final Cursor cursor = cr.query(uri, columns, constraint, null, null);
|
||||
|
||||
|
@ -569,10 +595,12 @@ public class LocalBrowserDB {
|
|||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
debug("Got count " + count + " for " + database);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit,
|
||||
EnumSet<FilterFlags> flags) {
|
||||
|
@ -599,6 +627,7 @@ public class LocalBrowserDB {
|
|||
selection, selectionArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getTopSites(ContentResolver cr, int limit) {
|
||||
// Filter out unvisited bookmarks and the ones that don't have real
|
||||
// parents (e.g. pinned sites or reading list items).
|
||||
|
@ -622,6 +651,7 @@ public class LocalBrowserDB {
|
|||
selectionArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVisitedHistory(ContentResolver cr, String uri) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
|
@ -637,6 +667,7 @@ public class LocalBrowserDB {
|
|||
new String[] { uri });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHistoryTitle(ContentResolver cr, String uri, String title) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(History.TITLE, title);
|
||||
|
@ -647,6 +678,7 @@ public class LocalBrowserDB {
|
|||
new String[] { uri });
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public Cursor getAllVisitedHistory(ContentResolver cr) {
|
||||
return cr.query(mHistoryUriWithProfile,
|
||||
|
@ -656,6 +688,7 @@ public class LocalBrowserDB {
|
|||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getRecentHistory(ContentResolver cr, int limit) {
|
||||
return cr.query(combinedUriWithLimit(limit),
|
||||
new String[] { Combined._ID,
|
||||
|
@ -670,12 +703,14 @@ public class LocalBrowserDB {
|
|||
History.DATE_LAST_VISITED + " DESC");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireHistory(ContentResolver cr, ExpirePriority priority) {
|
||||
Uri url = mHistoryExpireUriWithProfile;
|
||||
url = url.buildUpon().appendQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY, priority.toString()).build();
|
||||
cr.delete(url, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public void removeHistoryEntry(ContentResolver cr, String url) {
|
||||
cr.delete(mHistoryUriWithProfile,
|
||||
|
@ -683,10 +718,12 @@ public class LocalBrowserDB {
|
|||
new String[] { url });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHistory(ContentResolver cr) {
|
||||
cr.delete(mHistoryUriWithProfile, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
|
||||
final boolean addDesktopFolder;
|
||||
|
@ -737,6 +774,7 @@ public class LocalBrowserDB {
|
|||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getReadingList(ContentResolver cr) {
|
||||
return cr.query(mReadingListUriWithProfile,
|
||||
ReadingListItems.DEFAULT_PROJECTION,
|
||||
|
@ -775,6 +813,7 @@ public class LocalBrowserDB {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public boolean isBookmark(ContentResolver cr, String uri) {
|
||||
final Cursor c = cr.query(bookmarksUriWithLimit(1),
|
||||
|
@ -795,6 +834,7 @@ public class LocalBrowserDB {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadingListItem(ContentResolver cr, String uri) {
|
||||
final Cursor c = cr.query(mReadingListUriWithProfile,
|
||||
new String[] { ReadingListItems._ID },
|
||||
|
@ -814,6 +854,7 @@ public class LocalBrowserDB {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrlForKeyword(ContentResolver cr, String keyword) {
|
||||
final Cursor c = cr.query(mBookmarksUriWithProfile,
|
||||
new String[] { Bookmarks.URL },
|
||||
|
@ -844,7 +885,7 @@ public class LocalBrowserDB {
|
|||
try {
|
||||
final int col = c.getColumnIndexOrThrow(Bookmarks._ID);
|
||||
if (!c.moveToFirst() || c.isNull(col)) {
|
||||
return -1;
|
||||
return FOLDER_NOT_FOUND;
|
||||
}
|
||||
|
||||
final long id = c.getLong(col);
|
||||
|
@ -921,12 +962,14 @@ public class LocalBrowserDB {
|
|||
debug("Updated " + updated + " rows to new modified time.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public void addBookmark(ContentResolver cr, String title, String uri) {
|
||||
long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
|
||||
addBookmarkItem(cr, title, uri, folderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public void removeBookmarksWithURL(ContentResolver cr, String uri) {
|
||||
Uri contentUri = mBookmarksUriWithProfile;
|
||||
|
@ -940,6 +983,7 @@ public class LocalBrowserDB {
|
|||
cr.delete(contentUri, urlEquals, urlArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addReadingListItem(ContentResolver cr, ContentValues values) {
|
||||
// Check that required fields are present.
|
||||
for (String field: ReadingListItems.REQUIRED_FIELDS) {
|
||||
|
@ -965,14 +1009,17 @@ public class LocalBrowserDB {
|
|||
debug("Updated " + updated + " rows to new modified time.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
||||
cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
|
||||
cr.registerContentObserver(mBookmarksUriWithProfile, false, observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
@ -994,6 +1041,7 @@ public class LocalBrowserDB {
|
|||
* @param faviconURL The URL of the favicon to fetch from the database.
|
||||
* @return The decoded Bitmap from the database, if any. null if none is stored.
|
||||
*/
|
||||
@Override
|
||||
public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL) {
|
||||
final Cursor c = cr.query(mFaviconsUriWithProfile,
|
||||
new String[] { Favicons.DATA },
|
||||
|
@ -1041,6 +1089,7 @@ public class LocalBrowserDB {
|
|||
/**
|
||||
* Try to find a usable favicon URL in the history or bookmarks table.
|
||||
*/
|
||||
@Override
|
||||
public String getFaviconURLFromPageURL(ContentResolver cr, String uri) {
|
||||
// Check first in the history table.
|
||||
Cursor c = cr.query(mHistoryUriWithProfile,
|
||||
|
@ -1082,6 +1131,16 @@ public class LocalBrowserDB {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hideSuggestedSite(String url) {
|
||||
if (mSuggestedSites == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mSuggestedSites.hideSite(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFaviconForUrl(ContentResolver cr, String pageUri,
|
||||
byte[] encodedFavicon, String faviconUri) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
@ -1090,7 +1149,7 @@ public class LocalBrowserDB {
|
|||
values.put(Favicons.DATA, encodedFavicon);
|
||||
|
||||
// Update or insert
|
||||
Uri faviconsUri = getAllFaviconsUri().buildUpon().
|
||||
Uri faviconsUri = withDeleted(mFaviconsUriWithProfile).buildUpon().
|
||||
appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
|
||||
|
||||
final int updated = cr.update(faviconsUri,
|
||||
|
@ -1155,9 +1214,9 @@ public class LocalBrowserDB {
|
|||
new String[] { pageURL });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThumbnailForUrl(ContentResolver cr, String uri,
|
||||
BitmapDrawable thumbnail) {
|
||||
|
||||
// If a null thumbnail was passed in, delete the stored thumbnail for this url.
|
||||
if (thumbnail == null) {
|
||||
cr.delete(mThumbnailsUriWithProfile, Thumbnails.URL + " == ?", new String[] { uri });
|
||||
|
@ -1186,6 +1245,7 @@ public class LocalBrowserDB {
|
|||
new String[] { uri });
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
|
||||
final Cursor c = cr.query(mThumbnailsUriWithProfile,
|
||||
|
@ -1214,11 +1274,8 @@ public class LocalBrowserDB {
|
|||
*
|
||||
* Returns null if the provided list of URLs is empty or null.
|
||||
*/
|
||||
@Override
|
||||
public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
|
||||
if (urls == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int urlCount = urls.size();
|
||||
if (urlCount == 0) {
|
||||
return null;
|
||||
|
@ -1236,17 +1293,18 @@ public class LocalBrowserDB {
|
|||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public void removeThumbnails(ContentResolver cr) {
|
||||
cr.delete(mThumbnailsUriWithProfile, null, null);
|
||||
}
|
||||
|
||||
// Utility function for updating existing history using batch operations
|
||||
@Override
|
||||
public void updateHistoryInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations,
|
||||
String url, String title,
|
||||
long date, int visits) {
|
||||
|
||||
final String[] projection = {
|
||||
History._ID,
|
||||
History.VISITS,
|
||||
|
@ -1255,7 +1313,7 @@ public class LocalBrowserDB {
|
|||
|
||||
|
||||
// We need to get the old visit count.
|
||||
final Cursor cursor = cr.query(getAllHistoryUri(),
|
||||
final Cursor cursor = cr.query(withDeleted(mHistoryUriWithProfile),
|
||||
projection,
|
||||
History.URL + " = ?",
|
||||
new String[] { url },
|
||||
|
@ -1285,7 +1343,7 @@ public class LocalBrowserDB {
|
|||
}
|
||||
values.put(History.URL, url);
|
||||
|
||||
Uri historyUri = getAllHistoryUri().buildUpon().
|
||||
Uri historyUri = withDeleted(mHistoryUriWithProfile).buildUpon().
|
||||
appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
|
||||
|
||||
// Update or insert
|
||||
|
@ -1301,6 +1359,7 @@ public class LocalBrowserDB {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBookmarkInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations,
|
||||
String url, String title, String guid,
|
||||
|
@ -1341,7 +1400,7 @@ public class LocalBrowserDB {
|
|||
values.put(Bookmarks.PARENT, parent);
|
||||
values.put(Bookmarks.TYPE, type);
|
||||
|
||||
Uri bookmarkUri = getAllBookmarksUri().buildUpon().
|
||||
Uri bookmarkUri = withDeleted(mBookmarksUriWithProfile).buildUpon().
|
||||
appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
|
||||
// Update or insert
|
||||
ContentProviderOperation.Builder builder =
|
||||
|
@ -1357,8 +1416,8 @@ public class LocalBrowserDB {
|
|||
// Or their title and parent folder. (Folders!)
|
||||
builder.withSelection(Bookmarks.TITLE + " = ? AND "
|
||||
+ Bookmarks.PARENT + " = ?",
|
||||
new String[] { title,
|
||||
Long.toString(parent)
|
||||
new String[]{ title,
|
||||
Long.toString(parent)
|
||||
});
|
||||
} else if (type == Bookmarks.TYPE_SEPARATOR) {
|
||||
// Or their their position (separators)
|
||||
|
@ -1376,6 +1435,7 @@ public class LocalBrowserDB {
|
|||
operations.add(builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFaviconInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations,
|
||||
String url, String faviconUrl,
|
||||
|
@ -1388,7 +1448,7 @@ public class LocalBrowserDB {
|
|||
}
|
||||
|
||||
// Update or insert
|
||||
Uri faviconsUri = getAllFaviconsUri().buildUpon().
|
||||
Uri faviconsUri = withDeleted(mFaviconsUriWithProfile).buildUpon().
|
||||
appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
|
||||
// Update or insert
|
||||
ContentProviderOperation.Builder builder =
|
||||
|
@ -1477,6 +1537,7 @@ public class LocalBrowserDB {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pinSite(ContentResolver cr, String url, String title, int position) {
|
||||
ContentValues values = new ContentValues();
|
||||
final long now = System.currentTimeMillis();
|
||||
|
@ -1501,6 +1562,7 @@ public class LocalBrowserDB {
|
|||
String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getPinnedSites(ContentResolver cr, int limit) {
|
||||
return cr.query(bookmarksUriWithLimit(limit),
|
||||
new String[] { Bookmarks._ID,
|
||||
|
@ -1512,6 +1574,7 @@ public class LocalBrowserDB {
|
|||
Bookmarks.POSITION + " ASC");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unpinSite(ContentResolver cr, int position) {
|
||||
cr.delete(mBookmarksUriWithProfile,
|
||||
Bookmarks.PARENT + " == ? AND " + Bookmarks.POSITION + " = ?",
|
||||
|
@ -1521,6 +1584,7 @@ public class LocalBrowserDB {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RobocopTarget
|
||||
public Cursor getBookmarkForUrl(ContentResolver cr, String url) {
|
||||
Cursor c = cr.query(bookmarksUriWithLimit(1),
|
||||
|
@ -1539,4 +1603,86 @@ public class LocalBrowserDB {
|
|||
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuggestedSites(SuggestedSites suggestedSites) {
|
||||
mSuggestedSites = suggestedSites;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSuggestedImageUrl(String url) {
|
||||
if (mSuggestedSites == null) {
|
||||
return false;
|
||||
}
|
||||
return mSuggestedSites.contains(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSuggestedImageUrlForUrl(String url) {
|
||||
if (mSuggestedSites == null) {
|
||||
return null;
|
||||
}
|
||||
return mSuggestedSites.getImageUrlForUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSuggestedBackgroundColorForUrl(String url) {
|
||||
if (mSuggestedSites == null) {
|
||||
return 0;
|
||||
}
|
||||
final String bgColor = mSuggestedSites.getBackgroundColorForUrl(url);
|
||||
if (bgColor != null) {
|
||||
return Color.parseColor(bgColor);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackingIdForUrl(String url) {
|
||||
return mSuggestedSites.getTrackingIdForUrl(url);
|
||||
}
|
||||
|
||||
private static void appendUrlsFromCursor(List<String> urls, Cursor c) {
|
||||
if (!c.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
String url = c.getString(c.getColumnIndex(History.URL));
|
||||
|
||||
// Do a simpler check before decoding to avoid parsing
|
||||
// all URLs unnecessarily.
|
||||
if (StringUtils.isUserEnteredUrl(url)) {
|
||||
url = StringUtils.decodeUserEnteredUrl(url);
|
||||
}
|
||||
|
||||
urls.add(url);
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit) {
|
||||
// Note this is not a single query anymore, but actually returns a mixture
|
||||
// of two queries, one for topSites and one for pinned sites.
|
||||
Cursor pinnedSites = getPinnedSites(cr, minLimit);
|
||||
|
||||
int pinnedCount = pinnedSites.getCount();
|
||||
Cursor topSites = getTopSites(cr, maxLimit - pinnedCount);
|
||||
int topCount = topSites.getCount();
|
||||
|
||||
Cursor suggestedSites = null;
|
||||
if (mSuggestedSites != null) {
|
||||
final int count = minLimit - pinnedCount - topCount;
|
||||
if (count > 0) {
|
||||
final List<String> excludeUrls = new ArrayList<String>(pinnedCount + topCount);
|
||||
appendUrlsFromCursor(excludeUrls, pinnedSites);
|
||||
appendUrlsFromCursor(excludeUrls, topSites);
|
||||
|
||||
suggestedSites = mSuggestedSites.get(count, excludeUrls);
|
||||
}
|
||||
}
|
||||
|
||||
return new TopSitesCursorWrapper(pinnedSites, topSites, suggestedSites, minLimit);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- 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.db;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Helper class for dealing with the search provider inside Fennec.
|
||||
*/
|
||||
public class LocalSearches implements Searches {
|
||||
private final Uri uriWithProfile;
|
||||
|
||||
public LocalSearches(String mProfile) {
|
||||
uriWithProfile = DBUtils.appendProfileWithDefault(mProfile, BrowserContract.SearchHistory.CONTENT_URI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(ContentResolver cr, String query) {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(BrowserContract.SearchHistory.QUERY, query);
|
||||
cr.insert(uriWithProfile, values);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
* 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;
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -11,7 +11,8 @@ import java.util.regex.Pattern;
|
|||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UIAsyncTask;
|
||||
|
||||
|
@ -20,13 +21,11 @@ import android.content.ContentValues;
|
|||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
|
||||
public final class TabsAccessor {
|
||||
public class LocalTabsAccessor implements TabsAccessor {
|
||||
private static final String LOGTAG = "GeckoTabsAccessor";
|
||||
|
||||
public static final String[] TABS_PROJECTION_COLUMNS = new String[] {
|
||||
|
@ -54,141 +53,12 @@ public final class TabsAccessor {
|
|||
|
||||
private static final Pattern FILTERED_URL_PATTERN = Pattern.compile("^(about|chrome|wyciwyg|file):");
|
||||
|
||||
/**
|
||||
* A thin representation of a remote client.
|
||||
* <p>
|
||||
* We use the hash of the client's GUID as the ID in
|
||||
* {@link RemoteTabsExpandableListAdapter#getGroupId(int)}.
|
||||
*/
|
||||
public static class RemoteClient implements Parcelable {
|
||||
public final String guid;
|
||||
public final String name;
|
||||
public final long lastModified;
|
||||
public final String deviceType;
|
||||
public final ArrayList<RemoteTab> tabs;
|
||||
private final Uri tabsUriWithProfile;
|
||||
private final Uri clientsUriWithProfile;
|
||||
|
||||
public RemoteClient(String guid, String name, long lastModified, String deviceType) {
|
||||
this.guid = guid;
|
||||
this.name = name;
|
||||
this.lastModified = lastModified;
|
||||
this.deviceType = deviceType;
|
||||
this.tabs = new ArrayList<RemoteTab>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(guid);
|
||||
parcel.writeString(name);
|
||||
parcel.writeLong(lastModified);
|
||||
parcel.writeString(deviceType);
|
||||
parcel.writeTypedList(tabs);
|
||||
}
|
||||
|
||||
public static final Creator<RemoteClient> CREATOR = new Creator<RemoteClient>() {
|
||||
@Override
|
||||
public RemoteClient createFromParcel(final Parcel source) {
|
||||
final String guid = source.readString();
|
||||
final String name = source.readString();
|
||||
final long lastModified = source.readLong();
|
||||
final String deviceType = source.readString();
|
||||
|
||||
final RemoteClient client = new RemoteClient(guid, name, lastModified, deviceType);
|
||||
source.readTypedList(client.tabs, RemoteTab.CREATOR);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteClient[] newArray(final int size) {
|
||||
return new RemoteClient[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A thin representation of a remote tab.
|
||||
* <p>
|
||||
* We use the hash of the tab as the ID in
|
||||
* {@link RemoteTabsExpandableListAdapter#getClientId(int)}, and therefore we
|
||||
* must implement equality as well. These are generated functions.
|
||||
*/
|
||||
public static class RemoteTab implements Parcelable {
|
||||
public final String title;
|
||||
public final String url;
|
||||
|
||||
public RemoteTab(String title, String url) {
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(title);
|
||||
parcel.writeString(url);
|
||||
}
|
||||
|
||||
public static final Creator<RemoteTab> CREATOR = new Creator<RemoteTab>() {
|
||||
@Override
|
||||
public RemoteTab createFromParcel(final Parcel source) {
|
||||
final String title = source.readString();
|
||||
final String url = source.readString();
|
||||
|
||||
return new RemoteTab(title, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteTab[] newArray(final int size) {
|
||||
return new RemoteTab[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||
result = prime * result + ((url == null) ? 0 : url.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RemoteTab other = (RemoteTab) obj;
|
||||
if (title == null) {
|
||||
if (other.title != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!title.equals(other.title)) {
|
||||
return false;
|
||||
}
|
||||
if (url == null) {
|
||||
if (other.url != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!url.equals(other.url)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public LocalTabsAccessor(String mProfile) {
|
||||
tabsUriWithProfile = DBUtils.appendProfileWithDefault(mProfile, BrowserContract.Tabs.CONTENT_URI);
|
||||
clientsUriWithProfile = DBUtils.appendProfileWithDefault(mProfile, BrowserContract.Clients.CONTENT_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -203,8 +73,9 @@ public final class TabsAccessor {
|
|||
* by client GUID.
|
||||
* @return list of clients, each containing list of tabs.
|
||||
*/
|
||||
public static List<RemoteClient> getClientsFromCursor(final Cursor cursor) {
|
||||
final ArrayList<RemoteClient> clients = new ArrayList<TabsAccessor.RemoteClient>();
|
||||
@Override
|
||||
public List<RemoteClient> getClientsFromCursor(final Cursor cursor) {
|
||||
final ArrayList<RemoteClient> clients = new ArrayList<RemoteClient>();
|
||||
|
||||
final int originalPosition = cursor.getPosition();
|
||||
try {
|
||||
|
@ -246,12 +117,14 @@ public final class TabsAccessor {
|
|||
return clients;
|
||||
}
|
||||
|
||||
public static Cursor getRemoteTabsCursor(Context context) {
|
||||
@Override
|
||||
public Cursor getRemoteTabsCursor(Context context) {
|
||||
return getRemoteTabsCursor(context, -1);
|
||||
}
|
||||
|
||||
public static Cursor getRemoteTabsCursor(Context context, int limit) {
|
||||
Uri uri = BrowserContract.Tabs.CONTENT_URI;
|
||||
@Override
|
||||
public Cursor getRemoteTabsCursor(Context context, int limit) {
|
||||
Uri uri = tabsUriWithProfile;
|
||||
|
||||
if (limit > 0) {
|
||||
uri = uri.buildUpon()
|
||||
|
@ -267,19 +140,17 @@ public final class TabsAccessor {
|
|||
return cursor;
|
||||
}
|
||||
|
||||
public interface OnQueryTabsCompleteListener {
|
||||
public void onQueryTabsComplete(List<RemoteClient> clients);
|
||||
}
|
||||
|
||||
// This method returns all tabs from all remote clients,
|
||||
// ordered by most recent client first, most recent tab first
|
||||
public static void getTabs(final Context context, final OnQueryTabsCompleteListener listener) {
|
||||
@Override
|
||||
public void getTabs(final Context context, final OnQueryTabsCompleteListener listener) {
|
||||
getTabs(context, 0, listener);
|
||||
}
|
||||
|
||||
// This method returns limited number of tabs from all remote clients,
|
||||
// ordered by most recent client first, most recent tab first
|
||||
public static void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) {
|
||||
@Override
|
||||
public void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) {
|
||||
// If there is no listener, no point in doing work.
|
||||
if (listener == null)
|
||||
return;
|
||||
|
@ -306,15 +177,16 @@ public final class TabsAccessor {
|
|||
}
|
||||
|
||||
// Updates the modified time of the local client with the current time.
|
||||
private static void updateLocalClient(final ContentResolver cr) {
|
||||
private void updateLocalClient(final ContentResolver cr) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis());
|
||||
cr.update(BrowserContract.Clients.CONTENT_URI, values, LOCAL_CLIENT_SELECTION, null);
|
||||
|
||||
cr.update(clientsUriWithProfile, values, LOCAL_CLIENT_SELECTION, null);
|
||||
}
|
||||
|
||||
// Deletes all local tabs.
|
||||
private static void deleteLocalTabs(final ContentResolver cr) {
|
||||
cr.delete(BrowserContract.Tabs.CONTENT_URI, LOCAL_TABS_SELECTION, null);
|
||||
private void deleteLocalTabs(final ContentResolver cr) {
|
||||
cr.delete(tabsUriWithProfile, LOCAL_TABS_SELECTION, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,7 +199,7 @@ public final class TabsAccessor {
|
|||
* - POSITION should always be numeric.
|
||||
* - CLIENT_GUID should always be null to represent the local client.
|
||||
*/
|
||||
private static void insertLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
|
||||
private void insertLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
|
||||
// Reuse this for serializing individual history URLs as JSON.
|
||||
JSONArray history = new JSONArray();
|
||||
ArrayList<ContentValues> valuesToInsert = new ArrayList<ContentValues>();
|
||||
|
@ -368,11 +240,12 @@ public final class TabsAccessor {
|
|||
}
|
||||
|
||||
ContentValues[] valuesToInsertArray = valuesToInsert.toArray(new ContentValues[valuesToInsert.size()]);
|
||||
cr.bulkInsert(BrowserContract.Tabs.CONTENT_URI, valuesToInsertArray);
|
||||
cr.bulkInsert(tabsUriWithProfile, valuesToInsertArray);
|
||||
}
|
||||
|
||||
// Deletes all local tabs and replaces them with a new list of tabs.
|
||||
public static synchronized void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
|
||||
@Override
|
||||
public synchronized void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
|
||||
deleteLocalTabs(cr);
|
||||
insertLocalTabs(cr, tabs);
|
||||
updateLocalClient(cr);
|
||||
|
@ -383,7 +256,7 @@ public final class TabsAccessor {
|
|||
*
|
||||
* @return true if the supplied URL should be skipped; false otherwise.
|
||||
*/
|
||||
private static boolean isFilteredURL(String url) {
|
||||
private boolean isFilteredURL(String url) {
|
||||
return FILTERED_URL_PATTERN.matcher(url).lookingAt();
|
||||
}
|
||||
|
||||
|
@ -394,7 +267,8 @@ public final class TabsAccessor {
|
|||
* @param time to format string for.
|
||||
* @return string describing time span
|
||||
*/
|
||||
public static String getLastSyncedString(Context context, long now, long time) {
|
||||
@Override
|
||||
public String getLastSyncedString(Context context, long now, long time) {
|
||||
final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
|
||||
return context.getResources().getString(R.string.remote_tabs_last_synced, relativeTimeSpanString);
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/* -*- 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.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
// Holds metadata info about URLs. Supports some helper functions for getting back a HashMap of key value data.
|
||||
public class LocalURLMetadata implements URLMetadata {
|
||||
private static final String LOGTAG = "GeckoURLMetadata";
|
||||
private final Uri uriWithProfile;
|
||||
|
||||
public LocalURLMetadata(String mProfile) {
|
||||
uriWithProfile = DBUtils.appendProfileWithDefault(mProfile, URLMetadataTable.CONTENT_URI);
|
||||
}
|
||||
|
||||
// This returns a list of columns in the table. It's used to simplify some loops for reading/writing data.
|
||||
@SuppressWarnings("serial")
|
||||
private final Set<String> getModel() {
|
||||
return new HashSet<String>() {{
|
||||
add(URLMetadataTable.URL_COLUMN);
|
||||
add(URLMetadataTable.TILE_IMAGE_URL_COLUMN);
|
||||
add(URLMetadataTable.TILE_COLOR_COLUMN);
|
||||
}};
|
||||
}
|
||||
|
||||
// Store a cache of recent results. This number is chosen to match the max number of tiles on about:home
|
||||
private static final int CACHE_SIZE = 9;
|
||||
// Note: Members of this cache are unmodifiable.
|
||||
private final LruCache<String, Map<String, Object>> cache = new LruCache<String, Map<String, Object>>(CACHE_SIZE);
|
||||
|
||||
/**
|
||||
* Converts a JSON object into a unmodifiable Map of known metadata properties.
|
||||
* Will throw away any properties that aren't stored in the database.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> fromJSON(JSONObject obj) {
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
|
||||
Set<String> model = getModel();
|
||||
for (String key : model) {
|
||||
if (obj.has(key)) {
|
||||
data.put(key, obj.optString(key));
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Cursor into a unmodifiable Map of known metadata properties.
|
||||
* Will throw away any properties that aren't stored in the database.
|
||||
* Will also not iterate through multiple rows in the cursor.
|
||||
*/
|
||||
private Map<String, Object> fromCursor(Cursor c) {
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
|
||||
Set<String> model = getModel();
|
||||
String[] columns = c.getColumnNames();
|
||||
for (String column : columns) {
|
||||
if (model.contains(column)) {
|
||||
try {
|
||||
data.put(column, c.getString(c.getColumnIndexOrThrow(column)));
|
||||
} catch (Exception ex) {
|
||||
Log.i(LOGTAG, "Error getting data for " + column, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable Map of url->Metadata (i.e. A second HashMap) for a list of urls.
|
||||
* Must not be called from UI or Gecko threads.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Map<String, Object>> getForURLs(final ContentResolver cr,
|
||||
final List<String> urls,
|
||||
final List<String> columns) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
ThreadUtils.assertNotOnGeckoThread();
|
||||
|
||||
final Map<String, Map<String, Object>> data = new HashMap<String, Map<String, Object>>();
|
||||
|
||||
// Nothing to query for
|
||||
if (urls.isEmpty() || columns.isEmpty()) {
|
||||
Log.e(LOGTAG, "Queried metadata for nothing");
|
||||
return data;
|
||||
}
|
||||
|
||||
// Search the cache for any of these urls
|
||||
List<String> urlsToQuery = new ArrayList<String>();
|
||||
for (String url : urls) {
|
||||
final Map<String, Object> hit = cache.get(url);
|
||||
if (hit != null) {
|
||||
// Cache hit!
|
||||
data.put(url, hit);
|
||||
} else {
|
||||
urlsToQuery.add(url);
|
||||
}
|
||||
}
|
||||
|
||||
Telemetry.addToHistogram("FENNEC_TILES_CACHE_HIT", data.size());
|
||||
|
||||
// If everything was in the cache, we're done!
|
||||
if (urlsToQuery.size() == 0) {
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
final String selection = DBUtils.computeSQLInClause(urlsToQuery.size(), URLMetadataTable.URL_COLUMN);
|
||||
// We need the url to build our final HashMap, so we force it to be included in the query.
|
||||
if (!columns.contains(URLMetadataTable.URL_COLUMN)) {
|
||||
columns.add(URLMetadataTable.URL_COLUMN);
|
||||
}
|
||||
|
||||
final Cursor cursor = cr.query(uriWithProfile,
|
||||
columns.toArray(new String[columns.size()]), // columns,
|
||||
selection, // selection
|
||||
urlsToQuery.toArray(new String[urlsToQuery.size()]), // selectionargs
|
||||
null);
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
do {
|
||||
final Map<String, Object> metadata = fromCursor(cursor);
|
||||
final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLMetadataTable.URL_COLUMN));
|
||||
|
||||
data.put(url, metadata);
|
||||
cache.put(url, metadata);
|
||||
} while(cursor.moveToNext());
|
||||
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a HashMap of metadata into the database. Will iterate through columns
|
||||
* in the Database and only save rows with matching keys in the HashMap.
|
||||
* Must not be called from UI or Gecko threads.
|
||||
*/
|
||||
@Override
|
||||
public void save(final ContentResolver cr, final String url, final Map<String, Object> data) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
ThreadUtils.assertNotOnGeckoThread();
|
||||
|
||||
try {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
Set<String> model = getModel();
|
||||
for (String key : model) {
|
||||
if (data.containsKey(key)) {
|
||||
values.put(key, (String) data.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
if (values.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = uriWithProfile.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
|
||||
.build();
|
||||
cr.update(uri, values, URLMetadataTable.URL_COLUMN + "=?", new String[] {
|
||||
(String) data.get(URLMetadataTable.URL_COLUMN)
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Log.e(LOGTAG, "error saving", ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* 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 java.util.ArrayList;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* A thin representation of a remote client.
|
||||
* <p>
|
||||
* We use the hash of the client's GUID as the ID elsewhere.
|
||||
*/
|
||||
public class RemoteClient implements Parcelable {
|
||||
public final String guid;
|
||||
public final String name;
|
||||
public final long lastModified;
|
||||
public final String deviceType;
|
||||
public final ArrayList<RemoteTab> tabs;
|
||||
|
||||
public RemoteClient(String guid, String name, long lastModified, String deviceType) {
|
||||
this.guid = guid;
|
||||
this.name = name;
|
||||
this.lastModified = lastModified;
|
||||
this.deviceType = deviceType;
|
||||
this.tabs = new ArrayList<RemoteTab>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(guid);
|
||||
parcel.writeString(name);
|
||||
parcel.writeLong(lastModified);
|
||||
parcel.writeString(deviceType);
|
||||
parcel.writeTypedList(tabs);
|
||||
}
|
||||
|
||||
public static final Creator<RemoteClient> CREATOR = new Creator<RemoteClient>() {
|
||||
@Override
|
||||
public RemoteClient createFromParcel(final Parcel source) {
|
||||
final String guid = source.readString();
|
||||
final String name = source.readString();
|
||||
final long lastModified = source.readLong();
|
||||
final String deviceType = source.readString();
|
||||
|
||||
final RemoteClient client = new RemoteClient(guid, name, lastModified, deviceType);
|
||||
source.readTypedList(client.tabs, RemoteTab.CREATOR);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteClient[] newArray(final int size) {
|
||||
return new RemoteClient[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/* 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.RemoteTabsExpandableListAdapter;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* A thin representation of a remote tab.
|
||||
* <p>
|
||||
* We use the hash of the tab as the ID in
|
||||
* {@link RemoteTabsExpandableListAdapter#getClientId(int)}, and therefore we
|
||||
* must implement equality as well. These are generated functions.
|
||||
*/
|
||||
public class RemoteTab implements Parcelable {
|
||||
public final String title;
|
||||
public final String url;
|
||||
|
||||
public RemoteTab(String title, String url) {
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(title);
|
||||
parcel.writeString(url);
|
||||
}
|
||||
|
||||
public static final Creator<RemoteTab> CREATOR = new Creator<RemoteTab>() {
|
||||
@Override
|
||||
public RemoteTab createFromParcel(final Parcel source) {
|
||||
final String title = source.readString();
|
||||
final String url = source.readString();
|
||||
|
||||
return new RemoteTab(title, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteTab[] newArray(final int size) {
|
||||
return new RemoteTab[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||
result = prime * result + ((url == null) ? 0 : url.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RemoteTab other = (RemoteTab) obj;
|
||||
if (title == null) {
|
||||
if (other.title != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!title.equals(other.title)) {
|
||||
return false;
|
||||
}
|
||||
if (url == null) {
|
||||
if (other.url != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!url.equals(other.url)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/* -*- 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.db;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
|
||||
public interface Searches {
|
||||
public void insert(ContentResolver cr, String query);
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
/* 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 java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
||||
class StubSearches implements Searches {
|
||||
public StubSearches() {
|
||||
}
|
||||
|
||||
public void insert(ContentResolver cr, String query) {
|
||||
}
|
||||
}
|
||||
|
||||
class StubURLMetadata implements URLMetadata {
|
||||
public StubURLMetadata() {
|
||||
}
|
||||
|
||||
public Map<String, Object> fromJSON(JSONObject obj) {
|
||||
return new HashMap<String, Object>();
|
||||
}
|
||||
|
||||
public Map<String, Map<String, Object>> getForURLs(final ContentResolver cr,
|
||||
final List<String> urls,
|
||||
final List<String> columns) {
|
||||
return new HashMap<String, Map<String, Object>>();
|
||||
}
|
||||
|
||||
public void save(final ContentResolver cr, final String url, final Map<String, Object> data) {
|
||||
}
|
||||
}
|
||||
|
||||
class StubTabsAccessor implements TabsAccessor {
|
||||
public StubTabsAccessor() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RemoteClient> getClientsFromCursor(final Cursor cursor) {
|
||||
return new ArrayList<RemoteClient>();
|
||||
}
|
||||
|
||||
public Cursor getRemoteTabsCursor(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getRemoteTabsCursor(Context context, int limit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void getTabs(final Context context, final OnQueryTabsCompleteListener listener) {
|
||||
listener.onQueryTabsComplete(new ArrayList<RemoteClient>());
|
||||
}
|
||||
public void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) {
|
||||
listener.onQueryTabsComplete(new ArrayList<RemoteClient>());
|
||||
}
|
||||
|
||||
public synchronized void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) { }
|
||||
|
||||
public String getLastSyncedString(Context context, long now, long time) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This base implementation just stubs all methods. For the
|
||||
* real implementations, see LocalBrowserDB.java.
|
||||
*/
|
||||
public class StubBrowserDB implements BrowserDB {
|
||||
private final StubSearches searches = new StubSearches();
|
||||
private final StubTabsAccessor tabsAccessor = new StubTabsAccessor();
|
||||
private final StubURLMetadata urlMetadata = new StubURLMetadata();
|
||||
|
||||
@Override
|
||||
public Searches getSearches() {
|
||||
return searches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabsAccessor getTabsAccessor() {
|
||||
return tabsAccessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URLMetadata getURLMetadata() {
|
||||
return urlMetadata;
|
||||
}
|
||||
|
||||
protected static final Integer FAVICON_ID_NOT_FOUND = Integer.MIN_VALUE;
|
||||
|
||||
public StubBrowserDB(String profile) {
|
||||
}
|
||||
|
||||
public void invalidate() { }
|
||||
|
||||
public int addDefaultBookmarks(Context context, ContentResolver cr, final int offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getCount(ContentResolver cr, String database) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit,
|
||||
EnumSet<BrowserDB.FilterFlags> flags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getTopSites(ContentResolver cr, int limit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updateVisitedHistory(ContentResolver cr, String uri) {
|
||||
}
|
||||
|
||||
public void updateHistoryTitle(ContentResolver cr, String uri, String title) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public Cursor getAllVisitedHistory(ContentResolver cr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getRecentHistory(ContentResolver cr, int limit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void expireHistory(ContentResolver cr, BrowserContract.ExpirePriority priority) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public void removeHistoryEntry(ContentResolver cr, String url) {
|
||||
}
|
||||
|
||||
public void clearHistory(ContentResolver cr) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getReadingList(ContentResolver cr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public boolean isBookmark(ContentResolver cr, String uri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isReadingListItem(ContentResolver cr, String uri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getUrlForKeyword(ContentResolver cr, String keyword) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void bumpParents(ContentResolver cr, String param, String value) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public void addBookmark(ContentResolver cr, String title, String uri) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public void removeBookmarksWithURL(ContentResolver cr, String uri) {
|
||||
}
|
||||
|
||||
public void addReadingListItem(ContentResolver cr, ContentValues values) {
|
||||
}
|
||||
|
||||
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
||||
}
|
||||
|
||||
public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
|
||||
}
|
||||
|
||||
public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFaviconURLFromPageURL(ContentResolver cr, String uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updateFaviconForUrl(ContentResolver cr, String pageUri,
|
||||
byte[] encodedFavicon, String faviconUri) {
|
||||
}
|
||||
|
||||
public boolean hideSuggestedSite(String url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void updateThumbnailForUrl(ContentResolver cr, String uri,
|
||||
BitmapDrawable thumbnail) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public void removeThumbnails(ContentResolver cr) {
|
||||
}
|
||||
|
||||
public void updateHistoryInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations,
|
||||
String url, String title,
|
||||
long date, int visits) {
|
||||
}
|
||||
|
||||
public void updateBookmarkInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations,
|
||||
String url, String title, String guid,
|
||||
long parent, long added,
|
||||
long modified, long position,
|
||||
String keyword, int type) {
|
||||
}
|
||||
|
||||
public void updateFaviconInBatch(ContentResolver cr,
|
||||
Collection<ContentProviderOperation> operations,
|
||||
String url, String faviconUrl,
|
||||
String faviconGuid, byte[] data) {
|
||||
}
|
||||
|
||||
public void pinSite(ContentResolver cr, String url, String title, int position) {
|
||||
}
|
||||
|
||||
public Cursor getPinnedSites(ContentResolver cr, int limit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void unpinSite(ContentResolver cr, int position) {
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public Cursor getBookmarkForUrl(ContentResolver cr, String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setSuggestedSites(SuggestedSites suggestedSites) {
|
||||
}
|
||||
|
||||
public boolean hasSuggestedImageUrl(String url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getSuggestedImageUrlForUrl(String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getSuggestedBackgroundColorForUrl(String url) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getTrackingIdForUrl(String url) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Cursor getTopSites(ContentResolver cr, int minLimit, int maxLimit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Factory getFactory() {
|
||||
return new Factory() {
|
||||
@Override
|
||||
public BrowserDB get(String profileName, File profileDir) {
|
||||
return new StubBrowserDB(profileName);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* 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 android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.mozilla.gecko.Tab;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TabsAccessor {
|
||||
public interface OnQueryTabsCompleteListener {
|
||||
public void onQueryTabsComplete(List<RemoteClient> clients);
|
||||
}
|
||||
|
||||
public Cursor getRemoteTabsCursor(Context context);
|
||||
public Cursor getRemoteTabsCursor(Context context, int limit);
|
||||
public List<RemoteClient> getClientsFromCursor(final Cursor cursor);
|
||||
public void getTabs(final Context context, final OnQueryTabsCompleteListener listener);
|
||||
public void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener);
|
||||
public void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs);
|
||||
public String getLastSyncedString(Context context, long now, long time);
|
||||
}
|
|
@ -21,7 +21,6 @@ import android.database.sqlite.SQLiteDatabase;
|
|||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDatabaseHelper> {
|
||||
|
|
|
@ -5,208 +5,17 @@
|
|||
*/
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
// Holds metadata info about urls. Supports some helper functions for getting back a HashMap of key value data.
|
||||
public class URLMetadata {
|
||||
private static final String LOGTAG = "GeckoURLMetadata";
|
||||
|
||||
// This returns a list of columns in the table. It's used to simplify some loops for reading/writing data.
|
||||
@SuppressWarnings("serial")
|
||||
private static final Set<String> getModel() {
|
||||
return new HashSet<String>() {{
|
||||
add(URLMetadataTable.URL_COLUMN);
|
||||
add(URLMetadataTable.TILE_IMAGE_URL_COLUMN);
|
||||
add(URLMetadataTable.TILE_COLOR_COLUMN);
|
||||
}};
|
||||
}
|
||||
|
||||
// Store a cache of recent results. This number is chosen to match the max number of tiles on about:home
|
||||
private static final int CACHE_SIZE = 9;
|
||||
// Note: Members of this cache are unmodifiable.
|
||||
private static final LruCache<String, Map<String, Object>> cache = new LruCache<String, Map<String, Object>>(CACHE_SIZE);
|
||||
|
||||
/**
|
||||
* Converts a JSON object into a unmodifiable Map of known metadata properties.
|
||||
* Will throw away any properties that aren't stored in the database.
|
||||
*/
|
||||
public static Map<String, Object> fromJSON(JSONObject obj) {
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
|
||||
Set<String> model = getModel();
|
||||
for (String key : model) {
|
||||
if (obj.has(key)) {
|
||||
data.put(key, obj.optString(key));
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Cursor into a unmodifiable Map of known metadata properties.
|
||||
* Will throw away any properties that aren't stored in the database.
|
||||
* Will also not iterate through multiple rows in the cursor.
|
||||
*/
|
||||
private static Map<String, Object> fromCursor(Cursor c) {
|
||||
Map<String, Object> data = new HashMap<String, Object>();
|
||||
|
||||
Set<String> model = getModel();
|
||||
String[] columns = c.getColumnNames();
|
||||
for (String column : columns) {
|
||||
if (model.contains(column)) {
|
||||
try {
|
||||
data.put(column, c.getString(c.getColumnIndexOrThrow(column)));
|
||||
} catch (Exception ex) {
|
||||
Log.i(LOGTAG, "Error getting data for " + column, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable Map of url->Metadata (i.e. A second HashMap) for a list of urls.
|
||||
* Must not be called from UI or Gecko threads.
|
||||
*/
|
||||
public static Map<String, Map<String, Object>> getForUrls(final ContentResolver cr,
|
||||
final List<String> urls,
|
||||
final List<String> columns) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
ThreadUtils.assertNotOnGeckoThread();
|
||||
|
||||
final Map<String, Map<String, Object>> data = new HashMap<String, Map<String, Object>>();
|
||||
|
||||
// Nothing to query for
|
||||
if (urls.isEmpty() || columns.isEmpty()) {
|
||||
Log.e(LOGTAG, "Queried metadata for nothing");
|
||||
return data;
|
||||
}
|
||||
|
||||
// Search the cache for any of these urls
|
||||
List<String> urlsToQuery = new ArrayList<String>();
|
||||
for (String url : urls) {
|
||||
final Map<String, Object> hit = cache.get(url);
|
||||
if (hit != null) {
|
||||
// Cache hit!
|
||||
data.put(url, hit);
|
||||
} else {
|
||||
urlsToQuery.add(url);
|
||||
}
|
||||
}
|
||||
|
||||
Telemetry.addToHistogram("FENNEC_TILES_CACHE_HIT", data.size());
|
||||
|
||||
// If everything was in the cache, we're done!
|
||||
if (urlsToQuery.size() == 0) {
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
final String selection = DBUtils.computeSQLInClause(urlsToQuery.size(), URLMetadataTable.URL_COLUMN);
|
||||
// We need the url to build our final HashMap, so we force it to be included in the query.
|
||||
if (!columns.contains(URLMetadataTable.URL_COLUMN)) {
|
||||
columns.add(URLMetadataTable.URL_COLUMN);
|
||||
}
|
||||
|
||||
final Cursor cursor = cr.query(URLMetadataTable.CONTENT_URI,
|
||||
columns.toArray(new String[columns.size()]), // columns,
|
||||
selection, // selection
|
||||
urlsToQuery.toArray(new String[urlsToQuery.size()]), // selectionargs
|
||||
null);
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
do {
|
||||
final Map<String, Object> metadata = fromCursor(cursor);
|
||||
final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLMetadataTable.URL_COLUMN));
|
||||
|
||||
data.put(url, metadata);
|
||||
cache.put(url, metadata);
|
||||
} while(cursor.moveToNext());
|
||||
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a HashMap of metadata into the database. Will iterate through columns
|
||||
* in the Database and only save rows with matching keys in the HashMap.
|
||||
* Must not be called from UI or Gecko threads.
|
||||
*/
|
||||
public static void save(final ContentResolver cr, final String url, final Map<String, Object> data) {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
ThreadUtils.assertNotOnGeckoThread();
|
||||
|
||||
try {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
Set<String> model = getModel();
|
||||
for (String key : model) {
|
||||
if (data.containsKey(key)) {
|
||||
values.put(key, (String) data.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
if (values.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = URLMetadataTable.CONTENT_URI.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
|
||||
.build();
|
||||
cr.update(uri, values, URLMetadataTable.URL_COLUMN + "=?", new String[] {
|
||||
(String) data.get(URLMetadataTable.URL_COLUMN)
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Log.e(LOGTAG, "error saving", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static int deleteUnused(final ContentResolver cr, final String profile) {
|
||||
final String selection = URLMetadataTable.URL_COLUMN + " NOT IN "
|
||||
+ "(SELECT " + History.URL
|
||||
+ " FROM " + History.TABLE_NAME
|
||||
+ " WHERE " + History.IS_DELETED + " = 0"
|
||||
+ " UNION "
|
||||
+ " SELECT " + Bookmarks.URL
|
||||
+ " FROM " + Bookmarks.TABLE_NAME
|
||||
+ " WHERE " + Bookmarks.IS_DELETED + " = 0 "
|
||||
+ " AND " + Bookmarks.URL + " IS NOT NULL)";
|
||||
|
||||
Uri uri = URLMetadataTable.CONTENT_URI;
|
||||
if (!TextUtils.isEmpty(profile)) {
|
||||
uri = uri.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_PROFILE, profile)
|
||||
.build();
|
||||
}
|
||||
|
||||
return cr.delete(uri, selection, null);
|
||||
}
|
||||
public interface URLMetadata {
|
||||
public Map<String, Object> fromJSON(JSONObject obj);
|
||||
public Map<String, Map<String, Object>> getForURLs(final ContentResolver cr,
|
||||
final List<String> urls,
|
||||
final List<String> columns);
|
||||
public void save(final ContentResolver cr, final String url, final Map<String, Object> data);
|
||||
}
|
||||
|
|
|
@ -6,19 +6,11 @@
|
|||
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
// Holds metadata info about urls. Supports some helper functions for getting back a HashMap of key value data.
|
||||
public class URLMetadataTable extends BaseTable {
|
||||
|
@ -28,7 +20,7 @@ public class URLMetadataTable extends BaseTable {
|
|||
private static final int TABLE_ID_NUMBER = 1200;
|
||||
|
||||
// Uri for querying this table
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");
|
||||
static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");
|
||||
|
||||
// Columns in the table
|
||||
public static final String ID_COLUMN = "id";
|
||||
|
@ -69,4 +61,18 @@ public class URLMetadataTable extends BaseTable {
|
|||
new Table.ContentProviderInfo(TABLE_ID_NUMBER, TABLE)
|
||||
};
|
||||
}
|
||||
|
||||
public int deleteUnused(final SQLiteDatabase db) {
|
||||
final String selection = URL_COLUMN + " NOT IN " +
|
||||
"(SELECT " + History.URL +
|
||||
" FROM " + History.TABLE_NAME +
|
||||
" WHERE " + History.IS_DELETED + " = 0" +
|
||||
" UNION " +
|
||||
" SELECT " + Bookmarks.URL +
|
||||
" FROM " + Bookmarks.TABLE_NAME +
|
||||
" WHERE " + Bookmarks.IS_DELETED + " = 0 " +
|
||||
" AND " + Bookmarks.URL + " IS NOT NULL)";
|
||||
|
||||
return db.delete(getTable(), selection, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -299,11 +299,13 @@ public class Favicons {
|
|||
* Helper method to determine the URL of the Favicon image for a given page URL by querying the
|
||||
* history database. Should only be called from the background thread - does database access.
|
||||
*
|
||||
* @param db The LocalBrowserDB to use when accessing favicons.
|
||||
* @param cr A ContentResolver to run queries through.
|
||||
* @param pageURL The URL of a webpage with a Favicon.
|
||||
* @return The URL of the Favicon used by that webpage, according to either the History database
|
||||
* or a somewhat educated guess.
|
||||
*/
|
||||
public static String getFaviconURLForPageURL(Context context, String pageURL) {
|
||||
public static String getFaviconURLForPageURL(final BrowserDB db, final ContentResolver cr, final String pageURL) {
|
||||
// Attempt to determine the Favicon URL from the Tabs datastructure. Can dodge having to use
|
||||
// the database sometimes by doing this.
|
||||
String targetURL;
|
||||
|
@ -316,8 +318,7 @@ public class Favicons {
|
|||
}
|
||||
|
||||
// Try to find the faviconURL in the history and/or bookmarks table.
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
targetURL = BrowserDB.getFaviconURLFromPageURL(resolver, pageURL);
|
||||
targetURL = db.getFaviconURLFromPageURL(cr, pageURL);
|
||||
if (targetURL != null) {
|
||||
return targetURL;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.apache.http.HttpEntity;
|
|||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
|
||||
|
@ -61,6 +62,7 @@ public class LoadFaviconTask {
|
|||
private String faviconURL;
|
||||
private final OnFaviconLoadedListener listener;
|
||||
private final int flags;
|
||||
private final BrowserDB db;
|
||||
|
||||
private final boolean onlyFromLocal;
|
||||
volatile boolean mCancelled;
|
||||
|
@ -81,6 +83,7 @@ public class LoadFaviconTask {
|
|||
id = nextFaviconLoadId.incrementAndGet();
|
||||
|
||||
this.context = context;
|
||||
db = GeckoProfile.get(context).getDB();
|
||||
this.pageUrl = pageURL;
|
||||
this.faviconURL = faviconURL;
|
||||
this.listener = listener;
|
||||
|
@ -90,13 +93,13 @@ public class LoadFaviconTask {
|
|||
}
|
||||
|
||||
// Runs in background thread
|
||||
private LoadFaviconResult loadFaviconFromDb() {
|
||||
private LoadFaviconResult loadFaviconFromDb(final BrowserDB db) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
return BrowserDB.getFaviconForFaviconUrl(resolver, faviconURL);
|
||||
return db.getFaviconForUrl(resolver, faviconURL);
|
||||
}
|
||||
|
||||
// Runs in background thread
|
||||
private void saveFaviconToDb(final byte[] encodedFavicon) {
|
||||
private void saveFaviconToDb(final BrowserDB db, final byte[] encodedFavicon) {
|
||||
if (encodedFavicon == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -106,7 +109,7 @@ public class LoadFaviconTask {
|
|||
}
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
BrowserDB.updateFaviconForUrl(resolver, pageUrl, encodedFavicon, faviconURL);
|
||||
db.updateFaviconForUrl(resolver, pageUrl, encodedFavicon, faviconURL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -301,7 +304,7 @@ public class LoadFaviconTask {
|
|||
Favicons.longRunningExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Bitmap result = doInBackground();
|
||||
final Bitmap result = doInBackground(db);
|
||||
|
||||
ThreadUtils.getUiHandler().post(new Runnable() {
|
||||
@Override
|
||||
|
@ -330,7 +333,7 @@ public class LoadFaviconTask {
|
|||
return mCancelled;
|
||||
}
|
||||
|
||||
Bitmap doInBackground() {
|
||||
Bitmap doInBackground(final BrowserDB db) {
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -353,11 +356,12 @@ public class LoadFaviconTask {
|
|||
// If favicon is empty, fall back to the stored one.
|
||||
if (isEmpty) {
|
||||
// Try to get the favicon URL from the memory cache.
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURLFromCache(pageUrl);
|
||||
|
||||
// If that failed, try to get the URL from the database.
|
||||
if (storedFaviconUrl == null) {
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURL(context, pageUrl);
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURL(db, cr, pageUrl);
|
||||
if (storedFaviconUrl != null) {
|
||||
// If that succeeded, cache the URL loaded from the database in memory.
|
||||
Favicons.putFaviconURLForPageURLInCache(pageUrl, storedFaviconUrl);
|
||||
|
@ -413,7 +417,7 @@ public class LoadFaviconTask {
|
|||
}
|
||||
|
||||
// If there are no valid bitmaps decoded, the returned LoadFaviconResult is null.
|
||||
LoadFaviconResult loadedBitmaps = loadFaviconFromDb();
|
||||
LoadFaviconResult loadedBitmaps = loadFaviconFromDb(db);
|
||||
if (loadedBitmaps != null) {
|
||||
return pushToCacheAndGetResult(loadedBitmaps);
|
||||
}
|
||||
|
@ -443,7 +447,7 @@ public class LoadFaviconTask {
|
|||
// Fetching bytes to store can fail. saveFaviconToDb will
|
||||
// do the right thing, but we still choose to cache the
|
||||
// downloaded icon in memory.
|
||||
saveFaviconToDb(loadedBitmaps.getBytesForDatabaseStorage());
|
||||
saveFaviconToDb(db, loadedBitmaps.getBytesForDatabaseStorage());
|
||||
return pushToCacheAndGetResult(loadedBitmaps);
|
||||
}
|
||||
|
||||
|
@ -478,7 +482,7 @@ public class LoadFaviconTask {
|
|||
}
|
||||
|
||||
if (loadedBitmaps != null) {
|
||||
saveFaviconToDb(loadedBitmaps.getBytesForDatabaseStorage());
|
||||
saveFaviconToDb(db, loadedBitmaps.getBytesForDatabaseStorage());
|
||||
return pushToCacheAndGetResult(loadedBitmaps);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.lang.reflect.Field;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
@ -152,7 +153,8 @@ public final class BitmapUtils {
|
|||
}
|
||||
}
|
||||
});
|
||||
ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
|
||||
final GeckoProfile profile = GeckoProfile.get(context);
|
||||
ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab, profile.getDB());
|
||||
}
|
||||
|
||||
public static Bitmap decodeByteArray(byte[] bytes) {
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.home;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
|
@ -19,10 +20,8 @@ import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -176,31 +175,31 @@ public class BookmarksPanel extends HomeFragment {
|
|||
private static class BookmarksLoader extends SimpleCursorLoader {
|
||||
private final FolderInfo mFolderInfo;
|
||||
private final RefreshType mRefreshType;
|
||||
private final BrowserDB mDB;
|
||||
|
||||
public BookmarksLoader(Context context) {
|
||||
super(context);
|
||||
final Resources res = context.getResources();
|
||||
final String title = res.getString(R.string.bookmarks_title);
|
||||
mFolderInfo = new FolderInfo(Bookmarks.FIXED_ROOT_ID, title);
|
||||
mRefreshType = RefreshType.CHILD;
|
||||
this(context,
|
||||
new FolderInfo(Bookmarks.FIXED_ROOT_ID, context.getResources().getString(R.string.bookmarks_title)),
|
||||
RefreshType.CHILD);
|
||||
}
|
||||
|
||||
public BookmarksLoader(Context context, FolderInfo folderInfo, RefreshType refreshType) {
|
||||
super(context);
|
||||
mFolderInfo = folderInfo;
|
||||
mRefreshType = refreshType;
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
return BrowserDB.getBookmarksInFolder(getContext().getContentResolver(), mFolderInfo.id);
|
||||
return mDB.getBookmarksInFolder(getContext().getContentResolver(), mFolderInfo.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
// Invalidate the cached value that keeps track of whether or
|
||||
// not desktop bookmarks exist.
|
||||
BrowserDB.invalidateCachedState();
|
||||
mDB.invalidate();
|
||||
super.onContentChanged();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import android.database.Cursor;
|
|||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.json.JSONObject;
|
|||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
@ -21,7 +22,6 @@ import org.mozilla.gecko.db.BrowserContract.History;
|
|||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
|
@ -30,14 +30,13 @@ import android.content.DialogInterface;
|
|||
import android.database.Cursor;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextPaint;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -191,15 +190,17 @@ public class HistoryPanel extends HomeFragment {
|
|||
private static class HistoryCursorLoader extends SimpleCursorLoader {
|
||||
// Max number of history results
|
||||
private static final int HISTORY_LIMIT = 100;
|
||||
private final BrowserDB mDB;
|
||||
|
||||
public HistoryCursorLoader(Context context) {
|
||||
super(context);
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
|
||||
return mDB.getRecentHistory(cr, HISTORY_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ import org.mozilla.gecko.R;
|
|||
import org.mozilla.gecko.ReaderModeUtils;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
|
||||
|
@ -326,6 +326,7 @@ public abstract class HomeFragment extends Fragment {
|
|||
private final String mUrl;
|
||||
private final RemoveItemType mType;
|
||||
private final int mPosition;
|
||||
private final BrowserDB mDB;
|
||||
|
||||
/**
|
||||
* Remove bookmark/history/reading list type item by url, and also unpin the
|
||||
|
@ -338,6 +339,7 @@ public abstract class HomeFragment extends Fragment {
|
|||
mUrl = url;
|
||||
mType = type;
|
||||
mPosition = position;
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -345,23 +347,23 @@ public abstract class HomeFragment extends Fragment {
|
|||
ContentResolver cr = mContext.getContentResolver();
|
||||
|
||||
if (mPosition > -1) {
|
||||
BrowserDB.unpinSite(cr, mPosition);
|
||||
if (BrowserDB.hideSuggestedSite(mUrl)) {
|
||||
mDB.unpinSite(cr, mPosition);
|
||||
if (mDB.hideSuggestedSite(mUrl)) {
|
||||
cr.notifyChange(SuggestedSites.CONTENT_URI, null);
|
||||
}
|
||||
}
|
||||
|
||||
switch(mType) {
|
||||
case BOOKMARKS:
|
||||
BrowserDB.removeBookmarksWithURL(cr, mUrl);
|
||||
mDB.removeBookmarksWithURL(cr, mUrl);
|
||||
break;
|
||||
|
||||
case HISTORY:
|
||||
BrowserDB.removeHistoryEntry(cr, mUrl);
|
||||
mDB.removeHistoryEntry(cr, mUrl);
|
||||
break;
|
||||
|
||||
case READING_LIST:
|
||||
BrowserDB.removeReadingListItemWithURL(cr, mUrl);
|
||||
mDB.removeReadingListItemWithURL(cr, mUrl);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:Removed", mUrl));
|
||||
break;
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.mozilla.gecko.home;
|
|||
import java.util.EnumSet;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.db.BrowserContract.URLColumns;
|
||||
import org.mozilla.gecko.db.BrowserDB.FilterFlags;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.home;
|
|||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.ReaderModeUtils;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
|
@ -20,7 +21,6 @@ import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
|||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
@ -164,13 +164,16 @@ public class ReadingListPanel extends HomeFragment {
|
|||
* Cursor loader for the list of reading list items.
|
||||
*/
|
||||
private static class ReadingListLoader extends SimpleCursorLoader {
|
||||
private final BrowserDB mDB;
|
||||
|
||||
public ReadingListLoader(Context context) {
|
||||
super(context);
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
return BrowserDB.getReadingList(getContext().getContentResolver());
|
||||
return mDB.getReadingList(getContext().getContentResolver());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,18 +23,15 @@ import org.mozilla.gecko.TelemetryContract;
|
|||
import org.mozilla.gecko.db.BrowserContract.CommonColumns;
|
||||
import org.mozilla.gecko.db.BrowserContract.URLColumns;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoRequest;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
|
@ -10,17 +10,18 @@ import java.util.EnumSet;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.RemoteClientsDialogFragment;
|
||||
import org.mozilla.gecko.RemoteClientsDialogFragment.ChoiceMode;
|
||||
import org.mozilla.gecko.RemoteClientsDialogFragment.RemoteClientsListener;
|
||||
import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
|
||||
import org.mozilla.gecko.TabsAccessor;
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteClient;
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteTab;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
import org.mozilla.gecko.db.RemoteTab;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.widget.GeckoSwipeRefreshLayout;
|
||||
|
@ -30,7 +31,6 @@ import android.accounts.Account;
|
|||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
|
@ -382,25 +382,31 @@ public class RemoteTabsExpandableListFragment extends HomeFragment implements Re
|
|||
}
|
||||
|
||||
private static class RemoteTabsCursorLoader extends SimpleCursorLoader {
|
||||
private final GeckoProfile mProfile;
|
||||
|
||||
public RemoteTabsCursorLoader(Context context) {
|
||||
super(context);
|
||||
mProfile = GeckoProfile.get(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
return TabsAccessor.getRemoteTabsCursor(getContext());
|
||||
return mProfile.getDB().getTabsAccessor().getRemoteTabsCursor(getContext());
|
||||
}
|
||||
}
|
||||
|
||||
private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks {
|
||||
private BrowserDB mDB; // Pseudo-final: set in onCreateLoader.
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
mDB = GeckoProfile.get(getActivity()).getDB();
|
||||
return new RemoteTabsCursorLoader(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinishedAfterTransitions(Loader<Cursor> loader, Cursor c) {
|
||||
final List<RemoteClient> clients = TabsAccessor.getClientsFromCursor(c);
|
||||
final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
|
||||
|
||||
// Filter the hidden clients out of the clients list. The clients
|
||||
// list is updated in place; the hidden clients list is built
|
||||
|
|
|
@ -7,8 +7,8 @@ package org.mozilla.gecko.home;
|
|||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserDB.FilterFlags;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -78,25 +78,27 @@ class SearchLoader {
|
|||
public static class SearchCursorLoader extends SimpleCursorLoader {
|
||||
private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_SEARCH_LOADER_TIME_MS";
|
||||
|
||||
// Max number of search results
|
||||
// Max number of search results.
|
||||
private static final int SEARCH_LIMIT = 100;
|
||||
|
||||
// The target search term associated with the loader
|
||||
// The target search term associated with the loader.
|
||||
private final String mSearchTerm;
|
||||
|
||||
// The filter flags associated with the loader
|
||||
// The filter flags associated with the loader.
|
||||
private final EnumSet<FilterFlags> mFlags;
|
||||
private final GeckoProfile mProfile;
|
||||
|
||||
public SearchCursorLoader(Context context, String searchTerm, EnumSet<FilterFlags> flags) {
|
||||
super(context);
|
||||
mSearchTerm = searchTerm;
|
||||
mFlags = flags;
|
||||
mProfile = GeckoProfile.get(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final long start = SystemClock.uptimeMillis();
|
||||
final Cursor cursor = BrowserDB.filter(getContext().getContentResolver(), mSearchTerm, SEARCH_LIMIT, mFlags);
|
||||
final Cursor cursor = mProfile.getDB().filter(getContext().getContentResolver(), mSearchTerm, SEARCH_LIMIT, mFlags);
|
||||
final long end = SystemClock.uptimeMillis();
|
||||
final long took = end - start;
|
||||
Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.mozilla.gecko.TelemetryContract;
|
|||
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||
import org.mozilla.gecko.db.BrowserContract.TopSites;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.URLMetadata;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
|
@ -292,6 +291,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
private List<Tile> getTilesSnapshot() {
|
||||
final int count = mGrid.getCount();
|
||||
final ArrayList<Tile> snapshot = new ArrayList<>();
|
||||
final BrowserDB db = GeckoProfile.get(getActivity()).getDB();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final Cursor cursor = (Cursor) mGrid.getItemAtPosition(i);
|
||||
final int type = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.TYPE));
|
||||
|
@ -302,7 +302,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
}
|
||||
|
||||
final String url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
|
||||
final int id = BrowserDB.getTrackingIdForUrl(url);
|
||||
final int id = db.getTrackingIdForUrl(url);
|
||||
final boolean pinned = (type == TopSites.TYPE_PINNED);
|
||||
snapshot.add(new Tile(id, pinned));
|
||||
}
|
||||
|
@ -400,6 +400,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
|
||||
|
||||
final int itemId = item.getItemId();
|
||||
final BrowserDB db = GeckoProfile.get(getActivity()).getDB();
|
||||
|
||||
if (itemId == R.id.top_sites_pin) {
|
||||
final String url = info.url;
|
||||
|
@ -410,7 +411,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
|
||||
db.pinSite(context.getContentResolver(), url, title, position);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -425,7 +426,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.unpinSite(context.getContentResolver(), position);
|
||||
db.unpinSite(context.getContentResolver(), position);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -488,10 +489,11 @@ public class TopSitesPanel extends HomeFragment {
|
|||
public void onSiteSelected(final String url, final String title) {
|
||||
final int position = mPosition;
|
||||
final Context context = getActivity().getApplicationContext();
|
||||
final BrowserDB db = GeckoProfile.get(getActivity()).getDB();
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BrowserDB.pinSite(context.getContentResolver(), url, title, position);
|
||||
db.pinSite(context.getContentResolver(), url, title, position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -515,17 +517,19 @@ public class TopSitesPanel extends HomeFragment {
|
|||
// Max number of search results.
|
||||
private static final int SEARCH_LIMIT = 30;
|
||||
private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_TOPSITES_LOADER_TIME_MS";
|
||||
private final BrowserDB mDB;
|
||||
private final int mMaxGridEntries;
|
||||
|
||||
public TopSitesLoader(Context context) {
|
||||
super(context);
|
||||
mMaxGridEntries = context.getResources().getInteger(R.integer.number_of_top_sites);
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final long start = SystemClock.uptimeMillis();
|
||||
final Cursor cursor = BrowserDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
|
||||
final Cursor cursor = mDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
|
||||
final long end = SystemClock.uptimeMillis();
|
||||
final long took = end - start;
|
||||
Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
|
||||
|
@ -564,12 +568,14 @@ public class TopSitesPanel extends HomeFragment {
|
|||
}
|
||||
|
||||
public class TopSitesGridAdapter extends CursorAdapter {
|
||||
private final BrowserDB mDB;
|
||||
// Cache to store the thumbnails.
|
||||
// Ensure that this is only accessed from the UI thread.
|
||||
private Map<String, ThumbnailInfo> mThumbnailInfos;
|
||||
|
||||
public TopSitesGridAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor, 0);
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -639,9 +645,9 @@ public class TopSitesPanel extends HomeFragment {
|
|||
|
||||
// Suggested images have precedence over thumbnails, no need to wait
|
||||
// for them to be loaded. See: CursorLoaderCallbacks.onLoadFinished()
|
||||
final String imageUrl = BrowserDB.getSuggestedImageUrlForUrl(decodedUrl);
|
||||
final String imageUrl = mDB.getSuggestedImageUrlForUrl(decodedUrl);
|
||||
if (!TextUtils.isEmpty(imageUrl)) {
|
||||
final int bgColor = BrowserDB.getSuggestedBackgroundColorForUrl(decodedUrl);
|
||||
final int bgColor = mDB.getSuggestedBackgroundColorForUrl(decodedUrl);
|
||||
view.displayThumbnail(imageUrl, bgColor);
|
||||
return;
|
||||
}
|
||||
|
@ -736,7 +742,8 @@ public class TopSitesPanel extends HomeFragment {
|
|||
|
||||
// Only try to fetch thumbnails for non-empty URLs that
|
||||
// don't have an associated suggested image URL.
|
||||
if (TextUtils.isEmpty(url) || BrowserDB.hasSuggestedImageUrl(url)) {
|
||||
final GeckoProfile profile = GeckoProfile.get(getActivity());
|
||||
if (TextUtils.isEmpty(url) || profile.getDB().hasSuggestedImageUrl(url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -811,6 +818,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
*/
|
||||
@SuppressWarnings("serial")
|
||||
static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, ThumbnailInfo>> {
|
||||
private final BrowserDB mDB;
|
||||
private Map<String, ThumbnailInfo> mThumbnailInfos;
|
||||
private final ArrayList<String> mUrls;
|
||||
|
||||
|
@ -822,6 +830,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
public ThumbnailsLoader(Context context, ArrayList<String> urls) {
|
||||
super(context);
|
||||
mUrls = urls;
|
||||
mDB = GeckoProfile.get(context).getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -833,7 +842,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
|
||||
// Query the DB for tile images.
|
||||
final ContentResolver cr = getContext().getContentResolver();
|
||||
final Map<String, Map<String, Object>> metadata = URLMetadata.getForUrls(cr, mUrls, COLUMNS);
|
||||
final Map<String, Map<String, Object>> metadata = mDB.getURLMetadata().getForURLs(cr, mUrls, COLUMNS);
|
||||
|
||||
// Keep a list of urls that don't have tiles images. We'll use thumbnails for them instead.
|
||||
final List<String> thumbnailUrls = new ArrayList<String>();
|
||||
|
@ -853,7 +862,7 @@ public class TopSitesPanel extends HomeFragment {
|
|||
}
|
||||
|
||||
// Query the DB for tile thumbnails.
|
||||
final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, thumbnailUrls);
|
||||
final Cursor cursor = mDB.getThumbnailsForUrls(cr, thumbnailUrls);
|
||||
if (cursor == null) {
|
||||
return thumbnails;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import org.mozilla.gecko.R;
|
|||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.LinearLayout;
|
||||
|
|
|
@ -158,15 +158,23 @@ gbjar.sources += [
|
|||
'db/FormHistoryProvider.java',
|
||||
'db/HomeProvider.java',
|
||||
'db/LocalBrowserDB.java',
|
||||
'db/LocalSearches.java',
|
||||
'db/LocalTabsAccessor.java',
|
||||
'db/LocalURLMetadata.java',
|
||||
'db/PasswordsProvider.java',
|
||||
'db/PerProfileDatabaseProvider.java',
|
||||
'db/PerProfileDatabases.java',
|
||||
'db/ReadingListProvider.java',
|
||||
'db/RemoteClient.java',
|
||||
'db/RemoteTab.java',
|
||||
'db/Searches.java',
|
||||
'db/SearchHistoryProvider.java',
|
||||
'db/SharedBrowserDatabaseProvider.java',
|
||||
'db/SQLiteBridgeContentProvider.java',
|
||||
'db/StubBrowserDB.java',
|
||||
'db/SuggestedSites.java',
|
||||
'db/Table.java',
|
||||
'db/TabsAccessor.java',
|
||||
'db/TabsProvider.java',
|
||||
'db/TopSitesCursorWrapper.java',
|
||||
'db/URLMetadata.java',
|
||||
|
@ -415,7 +423,6 @@ gbjar.sources += [
|
|||
'tabs/TabsLayoutItemView.java',
|
||||
'tabs/TabsListLayout.java',
|
||||
'tabs/TabsPanel.java',
|
||||
'TabsAccessor.java',
|
||||
'Telemetry.java',
|
||||
'TelemetryContract.java',
|
||||
'TextSelection.java',
|
||||
|
|
|
@ -20,12 +20,8 @@ import android.content.res.ColorStateList;
|
|||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Region;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
/* 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/. */
|
||||
|
||||
/* 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.tests;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -26,7 +34,7 @@ class DatabaseHelper {
|
|||
*/
|
||||
protected boolean isBookmark(String url) {
|
||||
final ContentResolver resolver = mActivity.getContentResolver();
|
||||
return BrowserDB.isBookmark(resolver, url);
|
||||
return getProfileDB().isBookmark(resolver, url);
|
||||
}
|
||||
|
||||
protected Uri buildUri(BrowserDataType dataType) {
|
||||
|
@ -48,7 +56,7 @@ class DatabaseHelper {
|
|||
*/
|
||||
protected void addOrUpdateMobileBookmark(String title, String url) {
|
||||
final ContentResolver resolver = mActivity.getContentResolver();
|
||||
BrowserDB.addBookmark(resolver, title, url);
|
||||
getProfileDB().addBookmark(resolver, title, url);
|
||||
mAsserter.ok(true, "Inserting/updating a new bookmark", "Inserting/updating the bookmark with the title = " + title + " and the url = " + url);
|
||||
}
|
||||
|
||||
|
@ -62,14 +70,14 @@ class DatabaseHelper {
|
|||
// Get the id for the bookmark with the given URL.
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = BrowserDB.getBookmarkForUrl(resolver, url);
|
||||
c = getProfileDB().getBookmarkForUrl(resolver, url);
|
||||
if (!c.moveToFirst()) {
|
||||
mAsserter.ok(false, "Getting bookmark with url", "Couldn't find bookmark with url = " + url);
|
||||
return;
|
||||
}
|
||||
|
||||
int id = c.getInt(c.getColumnIndexOrThrow("_id"));
|
||||
BrowserDB.updateBookmark(resolver, id, url, title, keyword);
|
||||
getProfileDB().updateBookmark(resolver, id, url, title, keyword);
|
||||
|
||||
mAsserter.ok(true, "Updating bookmark", "Updating bookmark with url = " + url);
|
||||
} finally {
|
||||
|
@ -81,19 +89,20 @@ class DatabaseHelper {
|
|||
|
||||
protected void deleteBookmark(String url) {
|
||||
final ContentResolver resolver = mActivity.getContentResolver();
|
||||
BrowserDB.removeBookmarksWithURL(resolver, url);
|
||||
getProfileDB().removeBookmarksWithURL(resolver, url);
|
||||
}
|
||||
|
||||
protected void deleteHistoryItem(String url) {
|
||||
final ContentResolver resolver = mActivity.getContentResolver();
|
||||
BrowserDB.removeHistoryEntry(resolver, url);
|
||||
getProfileDB().removeHistoryEntry(resolver, url);
|
||||
}
|
||||
|
||||
// About the same implementation as getFolderIdFromGuid from LocalBrowserDB because it is declared private and we can't use reflections to access it
|
||||
protected long getFolderIdFromGuid(String guid) {
|
||||
ContentResolver resolver = mActivity.getContentResolver();
|
||||
final ContentResolver resolver = mActivity.getContentResolver();
|
||||
long folderId = -1L;
|
||||
Uri bookmarksUri = buildUri(BrowserDataType.BOOKMARKS);
|
||||
final Uri bookmarksUri = buildUri(BrowserDataType.BOOKMARKS);
|
||||
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = resolver.query(bookmarksUri,
|
||||
|
@ -104,6 +113,7 @@ class DatabaseHelper {
|
|||
if (c.moveToFirst()) {
|
||||
folderId = c.getLong(c.getColumnIndexOrThrow("_id"));
|
||||
}
|
||||
|
||||
if (folderId == -1) {
|
||||
mAsserter.ok(false, "Trying to get the folder id" ,"We did not get the correct folder id");
|
||||
}
|
||||
|
@ -115,37 +125,48 @@ class DatabaseHelper {
|
|||
return folderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param a BrowserDataType value - either HISTORY or BOOKMARKS
|
||||
* @return an ArrayList of the urls in the Firefox for Android Bookmarks or History databases
|
||||
/**
|
||||
* Returns all of the bookmarks or history entries in a database.
|
||||
*
|
||||
* @return an ArrayList of the urls in the Firefox for Android Bookmarks or History databases.
|
||||
*/
|
||||
protected ArrayList<String> getBrowserDBUrls(BrowserDataType dataType) {
|
||||
ArrayList<String> browserData = new ArrayList<String>();
|
||||
ContentResolver resolver = mActivity.getContentResolver();
|
||||
final ArrayList<String> browserData = new ArrayList<String>();
|
||||
final ContentResolver resolver = mActivity.getContentResolver();
|
||||
|
||||
Cursor cursor = null;
|
||||
Uri uri = buildUri(dataType);
|
||||
final BrowserDB db = getProfileDB();
|
||||
if (dataType == BrowserDataType.HISTORY) {
|
||||
cursor = BrowserDB.getAllVisitedHistory(resolver);
|
||||
cursor = db.getAllVisitedHistory(resolver);
|
||||
} else if (dataType == BrowserDataType.BOOKMARKS) {
|
||||
cursor = BrowserDB.getBookmarksInFolder(resolver, getFolderIdFromGuid("mobile"));
|
||||
cursor = db.getBookmarksInFolder(resolver, getFolderIdFromGuid("mobile"));
|
||||
}
|
||||
if (cursor != null) {
|
||||
cursor.moveToFirst();
|
||||
for (int i = 0; i < cursor.getCount(); i++ ) {
|
||||
// The url field may be null for folders in the structure of the Bookmarks table for Firefox so we should eliminate those
|
||||
|
||||
if (cursor == null) {
|
||||
mAsserter.ok(false, "We could not retrieve any data from the database", "The cursor was null");
|
||||
return browserData;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
// Nothing here, but that's OK -- maybe there are zero results. The calling test will fail.
|
||||
return browserData;
|
||||
}
|
||||
|
||||
do {
|
||||
// The URL field may be null for folders in the structure of the Bookmarks table for Firefox. Eliminate those.
|
||||
if (cursor.getString(cursor.getColumnIndex("url")) != null) {
|
||||
browserData.add(cursor.getString(cursor.getColumnIndex("url")));
|
||||
}
|
||||
if(!cursor.isLast()) {
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mAsserter.ok(false, "We could not retrieve any data from the database", "The cursor was null");
|
||||
}
|
||||
if (cursor != null) {
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
return browserData;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
return browserData;
|
||||
}
|
||||
|
||||
protected BrowserDB getProfileDB() {
|
||||
return GeckoProfile.get(mActivity).getDB();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.home.HomeConfig;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.home.HomeConfig;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
@ -55,4 +59,4 @@ public class testAppMenuPathways extends UITest {
|
|||
// NOTE: save as pdf functionality must be done at the end as it is slow and cause other test operations to fail.
|
||||
mAppMenu.pressMenuItem(AppMenuComponent.PageMenuItem.SAVE_AS_PDF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
public class testAwesomebar extends BaseTest {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import android.support.v4.app.Fragment;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import com.jayway.android.robotium.solo.Condition;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
|
@ -29,6 +36,7 @@ public class testClearPrivateData extends PixelTest {
|
|||
String blank1 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
|
||||
String blank2 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
|
||||
String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE;
|
||||
|
||||
inputAndLoadUrl(blank1);
|
||||
verifyUrlBarTitle(blank1);
|
||||
mDatabaseHelper.addOrUpdateMobileBookmark(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, blank2);
|
||||
|
@ -47,10 +55,10 @@ public class testClearPrivateData extends PixelTest {
|
|||
}
|
||||
|
||||
private void verifyHistoryCount(final int expectedCount) {
|
||||
boolean match = waitForTest( new BooleanTest() {
|
||||
boolean match = waitForCondition(new Condition() {
|
||||
@Override
|
||||
public boolean test() {
|
||||
return (mDatabaseHelper.getBrowserDBUrls(DatabaseHelper.BrowserDataType.HISTORY).size() == expectedCount);
|
||||
public boolean isSatisfied() {
|
||||
return mDatabaseHelper.getBrowserDBUrls(DatabaseHelper.BrowserDataType.HISTORY).size() == expectedCount;
|
||||
}
|
||||
}, TEST_WAIT_MS);
|
||||
mAsserter.ok(match, "Checking that the number of history items is correct", String.valueOf(expectedCount) + " history items present in the database");
|
||||
|
@ -60,6 +68,7 @@ public class testClearPrivateData extends PixelTest {
|
|||
String shareStrings[] = {"Share your location with", "Share", "Don't share", "There are no settings to clear"};
|
||||
String titleGeolocation = StringHelper.ROBOCOP_GEOLOCATION_TITLE;
|
||||
String url = getAbsoluteUrl(StringHelper.ROBOCOP_GEOLOCATION_URL);
|
||||
|
||||
loadCheckDismiss(shareStrings[1], url, shareStrings[0]);
|
||||
checkOption(shareStrings[1], "Clear");
|
||||
checkOption(shareStrings[3], "Cancel");
|
||||
|
@ -72,6 +81,7 @@ public class testClearPrivateData extends PixelTest {
|
|||
String passwordStrings[] = {"Save password", "Save", "Don't save"};
|
||||
String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE;
|
||||
String loginUrl = getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_URL);
|
||||
|
||||
loadCheckDismiss(passwordStrings[1], loginUrl, passwordStrings[0]);
|
||||
checkOption(passwordStrings[1], "Clear");
|
||||
loadCheckDismiss(passwordStrings[2], loginUrl, passwordStrings[0]);
|
||||
|
@ -110,6 +120,7 @@ public class testClearPrivateData extends PixelTest {
|
|||
selectMenuItem(StringHelper.PAGE_LABEL);
|
||||
mAsserter.ok(waitForText(StringHelper.CONTEXT_MENU_ITEMS_IN_URL_BAR[2]), "Waiting for the submenu to open", "Submenu was opened");
|
||||
}
|
||||
|
||||
mSolo.clickOnText(StringHelper.CONTEXT_MENU_ITEMS_IN_URL_BAR[2]);
|
||||
mAsserter.ok(waitForText(option), "Verify that the option: " + option + " is in the list", "The option is in the list. There are settings to clear");
|
||||
mSolo.clickOnButton(button);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* 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/. */
|
||||
* 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.tests;
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -17,9 +21,9 @@ import org.json.JSONObject;
|
|||
import org.mozilla.gecko.Actions;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.distribution.ReferrerDescriptor;
|
||||
|
@ -131,7 +135,7 @@ public class testDistribution extends ContentProviderTest {
|
|||
clearDistributionPref();
|
||||
Distribution dist = initDistribution(mockPackagePath);
|
||||
SuggestedSites suggestedSites = new SuggestedSites(mActivity, dist);
|
||||
BrowserDB.setSuggestedSites(suggestedSites);
|
||||
GeckoProfile.get(mActivity).getDB().setSuggestedSites(suggestedSites);
|
||||
|
||||
// Test tiles uploading for an en-US OS locale with no app locale.
|
||||
setOSLocale(Locale.US);
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import android.widget.CheckBox;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.PrivateTab;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.TabsAccessor;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.TabsProvider;
|
||||
|
||||
|
@ -102,8 +106,10 @@ public class testFilterOpenTab extends ContentProviderTest {
|
|||
tabs.add(tab5);
|
||||
tabs.add(tab6);
|
||||
|
||||
// Persist the created tabs.
|
||||
TabsAccessor.persistLocalTabs(mResolver, tabs);
|
||||
// Persist the created tabs. Normally, you should be careful that you get a profile on the
|
||||
// original thread, and do the work in a background one, but for testing we don't.
|
||||
final DatabaseHelper helper = new DatabaseHelper(getActivity(), mAsserter);
|
||||
helper.getProfileDB().getTabsAccessor().persistLocalTabs(mResolver, tabs);
|
||||
|
||||
// Get the persisted tab and check if urls are filtered.
|
||||
Cursor c = getTabsFromLocalClient();
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import android.view.View;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import android.content.ContentUris;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertEquals;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import org.mozilla.gecko.Actions;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* 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.tests;
|
||||
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче