merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-01-13 14:27:06 +01:00
Родитель f3a2dc46bd a8c8dc1004
Коммит db958a2de5
123 изменённых файлов: 2054 добавлений и 1017 удалений

Просмотреть файл

@ -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.*;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше