summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java471
1 files changed, 471 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java
new file mode 100644
index 000000000..d48604f03
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java
@@ -0,0 +1,471 @@
+/* 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.HashMap;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoThread;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.sqlite.SQLiteBridge;
+import org.mozilla.gecko.sqlite.SQLiteBridgeException;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+/*
+ * Provides a basic ContentProvider that sets up and sends queries through
+ * SQLiteBridge. Content providers should extend this by setting the appropriate
+ * table and version numbers in onCreate, and implementing the abstract methods:
+ *
+ * public abstract String getTable(Uri uri);
+ * public abstract String getSortOrder(Uri uri, String aRequested);
+ * public abstract void setupDefaults(Uri uri, ContentValues values);
+ * public abstract void initGecko();
+ */
+
+public abstract class SQLiteBridgeContentProvider extends ContentProvider {
+ private static final String ERROR_MESSAGE_DATABASE_IS_LOCKED = "Can't step statement: (5) database is locked";
+
+ private HashMap<String, SQLiteBridge> mDatabasePerProfile;
+ protected Context mContext;
+ private final String mLogTag;
+
+ protected SQLiteBridgeContentProvider(String logTag) {
+ mLogTag = logTag;
+ }
+
+ /**
+ * Subclasses must override this to allow error reporting code to compose
+ * the correct histogram name.
+ *
+ * Ensure that you define the new histograms if you define a new class!
+ */
+ protected abstract String getTelemetryPrefix();
+
+ /**
+ * Errors are recorded in telemetry using an enumerated histogram.
+ *
+ * <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/
+ * Adding_a_new_Telemetry_probe#Choosing_a_Histogram_Type>
+ *
+ * These are the allowable enumeration values. Keep these in sync with the
+ * histogram definition!
+ *
+ */
+ private static enum TelemetryErrorOp {
+ BULKINSERT (0),
+ DELETE (1),
+ INSERT (2),
+ QUERY (3),
+ UPDATE (4);
+
+ private final int bucket;
+
+ TelemetryErrorOp(final int bucket) {
+ this.bucket = bucket;
+ }
+
+ public int getBucket() {
+ return bucket;
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ if (mDatabasePerProfile == null) {
+ return;
+ }
+
+ synchronized (this) {
+ for (SQLiteBridge bridge : mDatabasePerProfile.values()) {
+ if (bridge != null) {
+ try {
+ bridge.close();
+ } catch (Exception ex) { }
+ }
+ }
+ mDatabasePerProfile = null;
+ }
+ super.shutdown();
+ }
+
+ @Override
+ public void finalize() {
+ shutdown();
+ }
+
+ /**
+ * Return true of the query is from Firefox Sync.
+ * @param uri query URI
+ */
+ public static boolean isCallerSync(Uri uri) {
+ String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
+ return !TextUtils.isEmpty(isSync);
+ }
+
+ private SQLiteBridge getDB(Context context, final String databasePath) {
+ SQLiteBridge bridge = null;
+
+ boolean dbNeedsSetup = true;
+ try {
+ String resourcePath = context.getPackageResourcePath();
+ GeckoLoader.loadSQLiteLibs(context, resourcePath);
+ GeckoLoader.loadNSSLibs(context, resourcePath);
+ bridge = SQLiteBridge.openDatabase(databasePath, null, 0);
+ int version = bridge.getVersion();
+ dbNeedsSetup = version != getDBVersion();
+ } catch (SQLiteBridgeException ex) {
+ // close the database
+ if (bridge != null) {
+ bridge.close();
+ }
+
+ // this will throw if the database can't be found
+ // we should attempt to set it up if Gecko is running
+ dbNeedsSetup = true;
+ Log.e(mLogTag, "Error getting version ", ex);
+
+ // if Gecko is not running, we should bail out. Otherwise we try to
+ // let Gecko build the database for us
+ if (!GeckoThread.isRunning()) {
+ Log.e(mLogTag, "Can not set up database. Gecko is not running");
+ return null;
+ }
+ }
+
+ // If the database is not set up yet, or is the wrong schema version, we send an initialize
+ // call to Gecko. Gecko will handle building the database file correctly, as well as any
+ // migrations that are necessary
+ if (dbNeedsSetup) {
+ bridge = null;
+ initGecko();
+ }
+ return bridge;
+ }
+
+ /**
+ * Returns the absolute path of a database file depending on the specified profile and dbName.
+ * @param profile
+ * the profile whose dbPath must be returned
+ * @param dbName
+ * the name of the db file whose absolute path must be returned
+ * @return the absolute path of the db file or <code>null</code> if it was not possible to retrieve a valid path
+ *
+ */
+ private String getDatabasePathForProfile(String profile, String dbName) {
+ // Depends on the vagaries of GeckoProfile.get, so null check for safety.
+ File profileDir = GeckoProfile.get(mContext, profile).getDir();
+ if (profileDir == null) {
+ return null;
+ }
+
+ String databasePath = new File(profileDir, dbName).getAbsolutePath();
+ return databasePath;
+ }
+
+ /**
+ * Returns a SQLiteBridge object according to the specified profile id and to the name of db related to the
+ * current provider instance.
+ * @param profile
+ * the id of the profile to be used to retrieve the related SQLiteBridge
+ * @return the <code>SQLiteBridge</code> related to the specified profile id or <code>null</code> if it was
+ * not possible to retrieve a valid SQLiteBridge
+ */
+ private SQLiteBridge getDatabaseForProfile(String profile) {
+ if (profile == null) {
+ profile = GeckoProfile.get(mContext).getName();
+ Log.d(mLogTag, "No profile provided, using '" + profile + "'");
+ }
+
+ final String dbName = getDBName();
+ String mapKey = profile + "/" + dbName;
+
+ SQLiteBridge db = null;
+ synchronized (this) {
+ db = mDatabasePerProfile.get(mapKey);
+ if (db != null) {
+ return db;
+ }
+ final String dbPath = getDatabasePathForProfile(profile, dbName);
+ if (dbPath == null) {
+ Log.e(mLogTag, "Failed to get a valid db path for profile '" + profile + "'' dbName '" + dbName + "'");
+ return null;
+ }
+ db = getDB(mContext, dbPath);
+ if (db != null) {
+ mDatabasePerProfile.put(mapKey, db);
+ }
+ }
+ return db;
+ }
+
+ /**
+ * Returns a SQLiteBridge object according to the specified profile path and to the name of db related to the
+ * current provider instance.
+ * @param profilePath
+ * the profilePath to be used to retrieve the related SQLiteBridge
+ * @return the <code>SQLiteBridge</code> related to the specified profile path or <code>null</code> if it was
+ * not possible to retrieve a valid <code>SQLiteBridge</code>
+ */
+ private SQLiteBridge getDatabaseForProfilePath(String profilePath) {
+ File profileDir = new File(profilePath, getDBName());
+ final String dbPath = profileDir.getPath();
+ return getDatabaseForDBPath(dbPath);
+ }
+
+ /**
+ * Returns a SQLiteBridge object according to the specified file path.
+ * @param dbPath
+ * the path of the file to be used to retrieve the related SQLiteBridge
+ * @return the <code>SQLiteBridge</code> related to the specified file path or <code>null</code> if it was
+ * not possible to retrieve a valid <code>SQLiteBridge</code>
+ *
+ */
+ private SQLiteBridge getDatabaseForDBPath(String dbPath) {
+ SQLiteBridge db = null;
+ synchronized (this) {
+ db = mDatabasePerProfile.get(dbPath);
+ if (db != null) {
+ return db;
+ }
+ db = getDB(mContext, dbPath);
+ if (db != null) {
+ mDatabasePerProfile.put(dbPath, db);
+ }
+ }
+ return db;
+ }
+
+ /**
+ * Returns a SQLiteBridge object to be used to perform operations on the given <code>Uri</code>.
+ * @param uri
+ * the <code>Uri</code> to be used to retrieve the related SQLiteBridge
+ * @return a <code>SQLiteBridge</code> object to be used on the given uri or <code>null</code> if it was
+ * not possible to retrieve a valid <code>SQLiteBridge</code>
+ *
+ */
+ private SQLiteBridge getDatabase(Uri uri) {
+ String profile = null;
+ String profilePath = null;
+
+ profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
+ profilePath = uri.getQueryParameter(BrowserContract.PARAM_PROFILE_PATH);
+
+ // Testing will specify the absolute profile path
+ if (profilePath != null) {
+ return getDatabaseForProfilePath(profilePath);
+ }
+ return getDatabaseForProfile(profile);
+ }
+
+ @Override
+ public boolean onCreate() {
+ mContext = getContext();
+ synchronized (this) {
+ mDatabasePerProfile = new HashMap<String, SQLiteBridge>();
+ }
+ return true;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int deleted = 0;
+ final SQLiteBridge db = getDatabase(uri);
+ if (db == null) {
+ return deleted;
+ }
+
+ try {
+ deleted = db.delete(getTable(uri), selection, selectionArgs);
+ } catch (SQLiteBridgeException ex) {
+ reportError(ex, TelemetryErrorOp.DELETE);
+ throw ex;
+ }
+
+ return deleted;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ long id = -1;
+ final SQLiteBridge db = getDatabase(uri);
+
+ // If we can not get a SQLiteBridge instance, its likely that the database
+ // has not been set up and Gecko is not running. We return null and expect
+ // callers to try again later
+ if (db == null) {
+ return null;
+ }
+
+ setupDefaults(uri, values);
+
+ boolean useTransaction = !db.inTransaction();
+ try {
+ if (useTransaction) {
+ db.beginTransaction();
+ }
+
+ // onPreInsert does a check for the item in the deleted table in some cases
+ // so we put it inside this transaction
+ onPreInsert(values, uri, db);
+ id = db.insert(getTable(uri), null, values);
+
+ if (useTransaction) {
+ db.setTransactionSuccessful();
+ }
+ } catch (SQLiteBridgeException ex) {
+ reportError(ex, TelemetryErrorOp.INSERT);
+ throw ex;
+ } finally {
+ if (useTransaction) {
+ db.endTransaction();
+ }
+ }
+
+ return ContentUris.withAppendedId(uri, id);
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] allValues) {
+ final SQLiteBridge db = getDatabase(uri);
+ // If we can not get a SQLiteBridge instance, its likely that the database
+ // has not been set up and Gecko is not running. We return 0 and expect
+ // callers to try again later
+ if (db == null) {
+ return 0;
+ }
+
+ int rowsAdded = 0;
+
+ String table = getTable(uri);
+
+ try {
+ db.beginTransaction();
+ for (ContentValues initialValues : allValues) {
+ ContentValues values = new ContentValues(initialValues);
+ setupDefaults(uri, values);
+ onPreInsert(values, uri, db);
+ db.insert(table, null, values);
+ rowsAdded++;
+ }
+ db.setTransactionSuccessful();
+ } catch (SQLiteBridgeException ex) {
+ reportError(ex, TelemetryErrorOp.BULKINSERT);
+ throw ex;
+ } finally {
+ db.endTransaction();
+ }
+
+ if (rowsAdded > 0) {
+ final boolean shouldSyncToNetwork = !isCallerSync(uri);
+ mContext.getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
+ }
+
+ return rowsAdded;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ int updated = 0;
+ final SQLiteBridge db = getDatabase(uri);
+
+ // If we can not get a SQLiteBridge instance, its likely that the database
+ // has not been set up and Gecko is not running. We return null and expect
+ // callers to try again later
+ if (db == null) {
+ return updated;
+ }
+
+ onPreUpdate(values, uri, db);
+
+ try {
+ updated = db.update(getTable(uri), values, selection, selectionArgs);
+ } catch (SQLiteBridgeException ex) {
+ reportError(ex, TelemetryErrorOp.UPDATE);
+ throw ex;
+ }
+
+ return updated;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ Cursor cursor = null;
+ final SQLiteBridge db = getDatabase(uri);
+
+ // If we can not get a SQLiteBridge instance, its likely that the database
+ // has not been set up and Gecko is not running. We return null and expect
+ // callers to try again later
+ if (db == null) {
+ return cursor;
+ }
+
+ sortOrder = getSortOrder(uri, sortOrder);
+
+ try {
+ cursor = db.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder, null);
+ onPostQuery(cursor, uri, db);
+ } catch (SQLiteBridgeException ex) {
+ reportError(ex, TelemetryErrorOp.QUERY);
+ throw ex;
+ }
+
+ return cursor;
+ }
+
+ private String getHistogram(SQLiteBridgeException e) {
+ // If you add values here, make sure to update
+ // toolkit/components/telemetry/Histograms.json.
+ if (ERROR_MESSAGE_DATABASE_IS_LOCKED.equals(e.getMessage())) {
+ return getTelemetryPrefix() + "_LOCKED";
+ }
+ return null;
+ }
+
+ protected void reportError(SQLiteBridgeException e, TelemetryErrorOp op) {
+ Log.e(mLogTag, "Error in database " + op.name(), e);
+ final String histogram = getHistogram(e);
+ if (histogram == null) {
+ return;
+ }
+
+ Telemetry.addToHistogram(histogram, op.getBucket());
+ }
+
+ protected abstract String getDBName();
+
+ protected abstract int getDBVersion();
+
+ protected abstract String getTable(Uri uri);
+
+ protected abstract String getSortOrder(Uri uri, String aRequested);
+
+ protected abstract void setupDefaults(Uri uri, ContentValues values);
+
+ protected abstract void initGecko();
+
+ protected abstract void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db);
+
+ protected abstract void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db);
+
+ protected abstract void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db);
+}