2012-02-29 11:29:09 -08:00
|
|
|
/* 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/. */
|
|
|
|
|
|
|
|
#filter substitution
|
|
|
|
package @ANDROID_PACKAGE_NAME@.db;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
import org.mozilla.gecko.GeckoProfile;
|
|
|
|
import org.mozilla.gecko.db.BrowserContract.Clients;
|
|
|
|
import org.mozilla.gecko.db.BrowserContract.Tabs;
|
|
|
|
import org.mozilla.gecko.db.BrowserContract;
|
|
|
|
import org.mozilla.gecko.db.DBUtils;
|
2013-03-15 03:52:53 -07:00
|
|
|
import org.mozilla.gecko.util.ThreadUtils;
|
2012-02-29 11:29:09 -08:00
|
|
|
|
|
|
|
import android.content.ContentProvider;
|
|
|
|
import android.content.ContentUris;
|
|
|
|
import android.content.ContentValues;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.UriMatcher;
|
|
|
|
import android.database.Cursor;
|
2012-08-09 13:52:58 -07:00
|
|
|
import android.database.SQLException;
|
2012-02-29 11:29:09 -08:00
|
|
|
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;
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
public class TabsProvider extends ContentProvider {
|
|
|
|
private static final String LOGTAG = "GeckoTabsProvider";
|
|
|
|
private Context mContext;
|
|
|
|
|
|
|
|
static final String DATABASE_NAME = "tabs.db";
|
|
|
|
|
2012-08-09 13:52:58 -07:00
|
|
|
static final int DATABASE_VERSION = 2;
|
2012-02-29 11:29:09 -08:00
|
|
|
|
|
|
|
static final String TABLE_TABS = "tabs";
|
|
|
|
static final String TABLE_CLIENTS = "clients";
|
|
|
|
|
|
|
|
static final int TABS = 600;
|
|
|
|
static final int TABS_ID = 601;
|
|
|
|
static final int CLIENTS = 602;
|
|
|
|
static final int CLIENTS_ID = 603;
|
|
|
|
|
2012-03-09 10:42:43 -08:00
|
|
|
static final String DEFAULT_TABS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC, " + Tabs.LAST_USED + " DESC";
|
2012-02-29 11:29:09 -08:00
|
|
|
static final String DEFAULT_CLIENTS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC";
|
|
|
|
|
|
|
|
static final String INDEX_TABS_GUID = "tabs_guid_index";
|
|
|
|
static final String INDEX_TABS_POSITION = "tabs_position_index";
|
|
|
|
static final String INDEX_CLIENTS_GUID = "clients_guid_index";
|
|
|
|
|
|
|
|
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
|
|
|
|
|
|
|
|
static final Map<String, String> TABS_PROJECTION_MAP;
|
|
|
|
static final Map<String, String> CLIENTS_PROJECTION_MAP;
|
|
|
|
|
|
|
|
static {
|
|
|
|
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs", TABS);
|
|
|
|
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs/#", TABS_ID);
|
|
|
|
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients", CLIENTS);
|
|
|
|
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients/#", CLIENTS_ID);
|
|
|
|
|
|
|
|
HashMap<String, String> map;
|
|
|
|
|
|
|
|
map = new HashMap<String, String>();
|
|
|
|
map.put(Tabs._ID, Tabs._ID);
|
|
|
|
map.put(Tabs.TITLE, Tabs.TITLE);
|
|
|
|
map.put(Tabs.URL, Tabs.URL);
|
|
|
|
map.put(Tabs.HISTORY, Tabs.HISTORY);
|
|
|
|
map.put(Tabs.FAVICON, Tabs.FAVICON);
|
|
|
|
map.put(Tabs.LAST_USED, Tabs.LAST_USED);
|
|
|
|
map.put(Tabs.POSITION, Tabs.POSITION);
|
2012-03-09 10:42:43 -08:00
|
|
|
map.put(Clients.GUID, Clients.GUID);
|
|
|
|
map.put(Clients.NAME, Clients.NAME);
|
|
|
|
map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED);
|
2012-02-29 11:29:09 -08:00
|
|
|
TABS_PROJECTION_MAP = Collections.unmodifiableMap(map);
|
|
|
|
|
|
|
|
map = new HashMap<String, String>();
|
|
|
|
map.put(Clients.GUID, Clients.GUID);
|
|
|
|
map.put(Clients.NAME, Clients.NAME);
|
|
|
|
map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED);
|
|
|
|
CLIENTS_PROJECTION_MAP = Collections.unmodifiableMap(map);
|
|
|
|
}
|
|
|
|
|
|
|
|
private HashMap<String, DatabaseHelper> mDatabasePerProfile;
|
|
|
|
|
|
|
|
static final String selectColumn(String table, String column) {
|
|
|
|
return table + "." + column + " = ?";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate these once, at initialization. isLoggable is too expensive to
|
|
|
|
// have in-line in each log call.
|
|
|
|
private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
|
|
|
|
private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
|
|
|
|
protected static void trace(String message) {
|
|
|
|
if (logVerbose) {
|
|
|
|
Log.v(LOGTAG, message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static void debug(String message) {
|
|
|
|
if (logDebug) {
|
|
|
|
Log.d(LOGTAG, message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final class DatabaseHelper extends SQLiteOpenHelper {
|
|
|
|
public DatabaseHelper(Context context, String databasePath) {
|
|
|
|
super(context, databasePath, null, DATABASE_VERSION);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCreate(SQLiteDatabase db) {
|
|
|
|
debug("Creating tabs.db: " + db.getPath());
|
|
|
|
debug("Creating " + TABLE_TABS + " table");
|
|
|
|
|
|
|
|
// Table for each tab on any client.
|
|
|
|
db.execSQL("CREATE TABLE " + TABLE_TABS + "(" +
|
|
|
|
Tabs._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
|
|
|
Tabs.CLIENT_GUID + " TEXT," +
|
|
|
|
Tabs.TITLE + " TEXT," +
|
|
|
|
Tabs.URL + " TEXT," +
|
|
|
|
Tabs.HISTORY + " TEXT," +
|
|
|
|
Tabs.FAVICON + " TEXT," +
|
|
|
|
Tabs.LAST_USED + " INTEGER," +
|
|
|
|
Tabs.POSITION + " INTEGER" +
|
|
|
|
");");
|
|
|
|
|
|
|
|
// Indices on CLIENT_GUID and POSITION.
|
|
|
|
db.execSQL("CREATE INDEX " + INDEX_TABS_GUID + " ON " + TABLE_TABS + "("
|
|
|
|
+ Tabs.CLIENT_GUID + ")");
|
|
|
|
|
|
|
|
db.execSQL("CREATE INDEX " + INDEX_TABS_POSITION + " ON " + TABLE_TABS + "("
|
|
|
|
+ Tabs.POSITION + ")");
|
|
|
|
|
|
|
|
debug("Creating " + TABLE_CLIENTS + " table");
|
|
|
|
|
|
|
|
// Table for client's name-guid mapping.
|
|
|
|
db.execSQL("CREATE TABLE " + TABLE_CLIENTS + "(" +
|
|
|
|
Clients.GUID + " TEXT PRIMARY KEY," +
|
|
|
|
Clients.NAME + " TEXT," +
|
|
|
|
Clients.LAST_MODIFIED + " INTEGER" +
|
|
|
|
");");
|
|
|
|
|
|
|
|
// Index on GUID.
|
|
|
|
db.execSQL("CREATE INDEX " + INDEX_CLIENTS_GUID + " ON " + TABLE_CLIENTS + "("
|
|
|
|
+ Clients.GUID + ")");
|
2012-08-09 13:52:58 -07:00
|
|
|
|
|
|
|
createLocalClient(db);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert a client row for our local Fennec client.
|
|
|
|
private void createLocalClient(SQLiteDatabase db) {
|
|
|
|
debug("Inserting local Fennec client into " + TABLE_CLIENTS + " table");
|
|
|
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis());
|
|
|
|
db.insertOrThrow(TABLE_CLIENTS, null, values);
|
2012-02-29 11:29:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
|
|
debug("Upgrading tabs.db: " + db.getPath() + " from " +
|
|
|
|
oldVersion + " to " + newVersion);
|
2012-08-09 13:52:58 -07:00
|
|
|
|
|
|
|
// We have to do incremental upgrades until we reach the current
|
|
|
|
// database schema version.
|
|
|
|
for (int v = oldVersion + 1; v <= newVersion; v++) {
|
|
|
|
switch(v) {
|
|
|
|
case 2:
|
|
|
|
createLocalClient(db);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-02-29 11:29:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onOpen(SQLiteDatabase db) {
|
|
|
|
debug("Opening tabs.db: " + db.getPath());
|
|
|
|
|
2013-03-12 13:19:47 -07:00
|
|
|
Cursor cursor = null;
|
|
|
|
try {
|
|
|
|
cursor = db.rawQuery("PRAGMA synchronous=OFF", null);
|
|
|
|
} finally {
|
|
|
|
if (cursor != null)
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
|
2012-02-29 11:29:09 -08:00
|
|
|
// From Honeycomb on, it's possible to run several db
|
|
|
|
// commands in parallel using multiple connections.
|
|
|
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
|
|
db.enableWriteAheadLogging();
|
2013-04-05 11:54:20 -07:00
|
|
|
db.setLockingEnabled(false);
|
2012-02-29 11:29:09 -08:00
|
|
|
} else {
|
|
|
|
// Pre-Honeycomb, we can do some lesser optimizations.
|
|
|
|
cursor = null;
|
|
|
|
try {
|
|
|
|
cursor = db.rawQuery("PRAGMA journal_mode=PERSIST", null);
|
|
|
|
} finally {
|
|
|
|
if (cursor != null)
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private DatabaseHelper getDatabaseHelperForProfile(String profile) {
|
|
|
|
// Each profile has a separate tabs.db database. The target
|
|
|
|
// profile is provided using a URI query argument in each request
|
|
|
|
// to our content provider.
|
|
|
|
|
|
|
|
// Always fallback to default profile if none has been provided.
|
|
|
|
if (TextUtils.isEmpty(profile)) {
|
2012-06-15 09:02:11 -07:00
|
|
|
profile = GeckoProfile.get(getContext()).getName();
|
2012-02-29 11:29:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
DatabaseHelper dbHelper;
|
|
|
|
synchronized (this) {
|
|
|
|
dbHelper = mDatabasePerProfile.get(profile);
|
|
|
|
if (dbHelper != null) {
|
|
|
|
return dbHelper;
|
|
|
|
}
|
2012-04-25 14:24:48 -07:00
|
|
|
|
|
|
|
String databasePath = getDatabasePath(profile);
|
|
|
|
|
2012-11-30 10:52:10 -08:00
|
|
|
// Before bug 768532, the database was located outside if the
|
|
|
|
// profile on Android 2.2. Make sure it is moved inside the profile
|
|
|
|
// directory.
|
|
|
|
if (Build.VERSION.SDK_INT == 8) {
|
|
|
|
File oldPath = mContext.getDatabasePath("tabs-" + profile + ".db");
|
|
|
|
if (oldPath.exists()) {
|
|
|
|
oldPath.renameTo(new File(databasePath));
|
|
|
|
}
|
2012-04-25 14:24:48 -07:00
|
|
|
}
|
|
|
|
|
2012-11-30 10:52:10 -08:00
|
|
|
dbHelper = new DatabaseHelper(getContext(), databasePath);
|
|
|
|
mDatabasePerProfile.put(profile, dbHelper);
|
|
|
|
|
2012-04-25 14:24:48 -07:00
|
|
|
DBUtils.ensureDatabaseIsNotLocked(dbHelper, databasePath);
|
2012-02-29 11:29:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
debug("Created database helper for profile: " + profile);
|
|
|
|
return dbHelper;
|
|
|
|
}
|
|
|
|
|
|
|
|
private String getDatabasePath(String profile) {
|
|
|
|
trace("Getting database path for profile: " + profile);
|
|
|
|
|
|
|
|
File profileDir = GeckoProfile.get(mContext, profile).getDir();
|
|
|
|
if (profileDir == null) {
|
|
|
|
debug("Couldn't find directory for profile: " + profile);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
String databasePath = new File(profileDir, DATABASE_NAME).getAbsolutePath();
|
|
|
|
debug("Successfully created database path for profile: " + databasePath);
|
|
|
|
|
|
|
|
return databasePath;
|
|
|
|
}
|
|
|
|
|
|
|
|
private SQLiteDatabase getReadableDatabase(Uri uri) {
|
|
|
|
trace("Getting readable database for URI: " + uri);
|
|
|
|
|
|
|
|
String profile = null;
|
|
|
|
|
|
|
|
if (uri != null)
|
|
|
|
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
|
|
|
|
|
|
|
|
return getDatabaseHelperForProfile(profile).getReadableDatabase();
|
|
|
|
}
|
|
|
|
|
|
|
|
private SQLiteDatabase getWritableDatabase(Uri uri) {
|
|
|
|
trace("Getting writable database for URI: " + uri);
|
|
|
|
|
|
|
|
String profile = null;
|
|
|
|
|
|
|
|
if (uri != null)
|
|
|
|
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
|
|
|
|
|
|
|
|
return getDatabaseHelperForProfile(profile).getWritableDatabase();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onCreate() {
|
|
|
|
debug("Creating TabsProvider");
|
|
|
|
|
2013-03-15 03:52:53 -07:00
|
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2012-02-29 11:29:09 -08:00
|
|
|
public void run() {
|
|
|
|
// Kick this off early. It is synchronized so that other callers will wait
|
|
|
|
try {
|
|
|
|
GeckoProfile.get(getContext()).getDir();
|
|
|
|
} catch (Exception ex) {
|
|
|
|
Log.e(LOGTAG, "Error getting profile dir", ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
synchronized (this) {
|
|
|
|
mContext = getContext();
|
|
|
|
mDatabasePerProfile = new HashMap<String, DatabaseHelper>();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getType(Uri uri) {
|
|
|
|
final int match = URI_MATCHER.match(uri);
|
|
|
|
|
|
|
|
trace("Getting URI type: " + uri);
|
|
|
|
|
|
|
|
switch (match) {
|
|
|
|
case TABS:
|
|
|
|
trace("URI is TABS: " + uri);
|
|
|
|
return Tabs.CONTENT_TYPE;
|
|
|
|
|
|
|
|
case TABS_ID:
|
|
|
|
trace("URI is TABS_ID: " + uri);
|
|
|
|
return Tabs.CONTENT_ITEM_TYPE;
|
|
|
|
|
|
|
|
case CLIENTS:
|
|
|
|
trace("URI is CLIENTS: " + uri);
|
|
|
|
return Clients.CONTENT_TYPE;
|
|
|
|
|
|
|
|
case CLIENTS_ID:
|
|
|
|
trace("URI is CLIENTS_ID: " + uri);
|
|
|
|
return Clients.CONTENT_ITEM_TYPE;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("URI has unrecognized type: " + uri);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
|
|
|
trace("Calling delete on URI: " + uri);
|
|
|
|
|
|
|
|
final SQLiteDatabase db = getWritableDatabase(uri);
|
|
|
|
int deleted = 0;
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
|
|
trace("Beginning delete transaction: " + uri);
|
|
|
|
db.beginTransaction();
|
|
|
|
try {
|
|
|
|
deleted = deleteInTransaction(uri, selection, selectionArgs);
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
trace("Successful delete transaction: " + uri);
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
deleted = deleteInTransaction(uri, selection, selectionArgs);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deleted > 0)
|
|
|
|
getContext().getContentResolver().notifyChange(uri, null);
|
|
|
|
|
|
|
|
return deleted;
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("fallthrough")
|
|
|
|
public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
|
|
|
|
trace("Calling delete in transaction on URI: " + uri);
|
|
|
|
|
|
|
|
final int match = URI_MATCHER.match(uri);
|
|
|
|
int deleted = 0;
|
|
|
|
|
|
|
|
switch (match) {
|
|
|
|
case CLIENTS_ID:
|
|
|
|
trace("Delete on CLIENTS_ID: " + uri);
|
|
|
|
selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID));
|
|
|
|
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
|
|
|
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
|
|
|
// fall through
|
|
|
|
case CLIENTS:
|
|
|
|
trace("Delete on CLIENTS: " + uri);
|
|
|
|
// Delete from both TABLE_TABS and TABLE_CLIENTS.
|
|
|
|
deleteValues(uri, selection, selectionArgs, TABLE_TABS);
|
|
|
|
deleted = deleteValues(uri, selection, selectionArgs, TABLE_CLIENTS);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TABS_ID:
|
|
|
|
trace("Delete on TABS_ID: " + uri);
|
|
|
|
selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
|
|
|
|
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
|
|
|
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
|
|
|
// fall through
|
|
|
|
case TABS:
|
|
|
|
trace("Deleting on TABS: " + uri);
|
|
|
|
deleted = deleteValues(uri, selection, selectionArgs, TABLE_TABS);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new UnsupportedOperationException("Unknown delete URI " + uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("Deleted " + deleted + " rows for URI: " + uri);
|
|
|
|
|
|
|
|
return deleted;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Uri insert(Uri uri, ContentValues values) {
|
|
|
|
trace("Calling insert on URI: " + uri);
|
|
|
|
|
|
|
|
final SQLiteDatabase db = getWritableDatabase(uri);
|
|
|
|
Uri result = null;
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
|
|
trace("Beginning insert transaction: " + uri);
|
|
|
|
db.beginTransaction();
|
|
|
|
try {
|
|
|
|
result = insertInTransaction(uri, values);
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
trace("Successful insert transaction: " + uri);
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result = insertInTransaction(uri, values);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result != null)
|
|
|
|
getContext().getContentResolver().notifyChange(uri, null);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Uri insertInTransaction(Uri uri, ContentValues values) {
|
|
|
|
trace("Calling insert in transaction on URI: " + uri);
|
|
|
|
|
|
|
|
final SQLiteDatabase db = getWritableDatabase(uri);
|
|
|
|
int match = URI_MATCHER.match(uri);
|
|
|
|
long id = -1;
|
|
|
|
|
|
|
|
switch (match) {
|
|
|
|
case CLIENTS:
|
|
|
|
String guid = values.getAsString(Clients.GUID);
|
|
|
|
debug("Inserting client in database with GUID: " + guid);
|
|
|
|
id = db.insertOrThrow(TABLE_CLIENTS, Clients.GUID, values);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TABS:
|
|
|
|
String url = values.getAsString(Tabs.URL);
|
|
|
|
debug("Inserting tab in database with URL: " + url);
|
|
|
|
id = db.insertOrThrow(TABLE_TABS, Tabs.TITLE, values);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new UnsupportedOperationException("Unknown insert URI " + uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("Inserted ID in database: " + id);
|
|
|
|
|
|
|
|
if (id >= 0)
|
|
|
|
return ContentUris.withAppendedId(uri, id);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int update(Uri uri, ContentValues values, String selection,
|
|
|
|
String[] selectionArgs) {
|
|
|
|
trace("Calling update on URI: " + uri);
|
|
|
|
|
|
|
|
final SQLiteDatabase db = getWritableDatabase(uri);
|
|
|
|
int updated = 0;
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
|
|
trace("Beginning update transaction: " + uri);
|
|
|
|
db.beginTransaction();
|
|
|
|
try {
|
|
|
|
updated = updateInTransaction(uri, values, selection, selectionArgs);
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
trace("Successful update transaction: " + uri);
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
updated = updateInTransaction(uri, values, selection, selectionArgs);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (updated > 0)
|
|
|
|
getContext().getContentResolver().notifyChange(uri, null);
|
|
|
|
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int updateInTransaction(Uri uri, ContentValues values, String selection,
|
|
|
|
String[] selectionArgs) {
|
|
|
|
trace("Calling update in transaction on URI: " + uri);
|
|
|
|
|
|
|
|
int match = URI_MATCHER.match(uri);
|
|
|
|
int updated = 0;
|
|
|
|
|
|
|
|
switch (match) {
|
|
|
|
case CLIENTS_ID:
|
|
|
|
trace("Update on CLIENTS_ID: " + uri);
|
|
|
|
selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID));
|
|
|
|
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
|
|
|
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
|
|
|
// fall through
|
|
|
|
case CLIENTS:
|
|
|
|
trace("Update on CLIENTS: " + uri);
|
|
|
|
updated = updateValues(uri, values, selection, selectionArgs, TABLE_CLIENTS);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TABS_ID:
|
|
|
|
trace("Update on TABS_ID: " + uri);
|
|
|
|
selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
|
|
|
|
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
|
|
|
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
|
|
|
// fall through
|
|
|
|
case TABS:
|
|
|
|
trace("Update on TABS: " + uri);
|
|
|
|
updated = updateValues(uri, values, selection, selectionArgs, TABLE_TABS);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new UnsupportedOperationException("Unknown update URI " + uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("Updated " + updated + " rows for URI: " + uri);
|
|
|
|
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@SuppressWarnings("fallthrough")
|
|
|
|
public Cursor query(Uri uri, String[] projection, String selection,
|
|
|
|
String[] selectionArgs, String sortOrder) {
|
|
|
|
SQLiteDatabase db = getReadableDatabase(uri);
|
|
|
|
final int match = URI_MATCHER.match(uri);
|
|
|
|
|
|
|
|
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
2012-03-12 12:48:55 -07:00
|
|
|
String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
|
2012-02-29 11:29:09 -08:00
|
|
|
|
|
|
|
switch (match) {
|
|
|
|
case TABS_ID:
|
|
|
|
trace("Query is on TABS_ID: " + uri);
|
|
|
|
selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
|
|
|
|
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
|
|
|
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
|
|
|
// fall through
|
|
|
|
case TABS:
|
|
|
|
trace("Query is on TABS: " + uri);
|
|
|
|
if (TextUtils.isEmpty(sortOrder)) {
|
|
|
|
sortOrder = DEFAULT_TABS_SORT_ORDER;
|
|
|
|
} else {
|
|
|
|
debug("Using sort order " + sortOrder + ".");
|
|
|
|
}
|
|
|
|
|
|
|
|
qb.setProjectionMap(TABS_PROJECTION_MAP);
|
2012-03-09 10:42:43 -08:00
|
|
|
qb.setTables(TABLE_TABS + " LEFT OUTER JOIN " + TABLE_CLIENTS + " ON (" + TABLE_TABS + "." + Tabs.CLIENT_GUID + " = " + TABLE_CLIENTS + "." + Clients.GUID + ")");
|
2012-02-29 11:29:09 -08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CLIENTS_ID:
|
|
|
|
trace("Query is on CLIENTS_ID: " + uri);
|
|
|
|
selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID));
|
|
|
|
selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
|
|
|
|
new String[] { Long.toString(ContentUris.parseId(uri)) });
|
|
|
|
// fall through
|
|
|
|
case CLIENTS:
|
|
|
|
trace("Query is on CLIENTS: " + uri);
|
|
|
|
if (TextUtils.isEmpty(sortOrder)) {
|
|
|
|
sortOrder = DEFAULT_CLIENTS_SORT_ORDER;
|
|
|
|
} else {
|
|
|
|
debug("Using sort order " + sortOrder + ".");
|
|
|
|
}
|
|
|
|
|
|
|
|
qb.setProjectionMap(CLIENTS_PROJECTION_MAP);
|
|
|
|
qb.setTables(TABLE_CLIENTS);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new UnsupportedOperationException("Unknown query URI " + uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
trace("Running built query.");
|
|
|
|
Cursor cursor = qb.query(db, projection, selection, selectionArgs, null,
|
2012-03-12 12:48:55 -07:00
|
|
|
null, sortOrder, limit);
|
2012-02-29 11:29:09 -08:00
|
|
|
cursor.setNotificationUri(getContext().getContentResolver(),
|
|
|
|
BrowserContract.TABS_AUTHORITY_URI);
|
|
|
|
|
|
|
|
return cursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
int updateValues(Uri uri, ContentValues values, String selection, String[] selectionArgs, String table) {
|
|
|
|
trace("Updating tabs on URI: " + uri);
|
|
|
|
|
|
|
|
final SQLiteDatabase db = getWritableDatabase(uri);
|
|
|
|
|
|
|
|
return db.update(table, values, selection, selectionArgs);
|
|
|
|
}
|
|
|
|
|
|
|
|
int deleteValues(Uri uri, String selection, String[] selectionArgs, String table) {
|
|
|
|
debug("Deleting tabs for URI: " + uri);
|
|
|
|
|
|
|
|
final SQLiteDatabase db = getWritableDatabase(uri);
|
|
|
|
|
|
|
|
return db.delete(table, selection, selectionArgs);
|
|
|
|
}
|
2012-08-09 13:52:58 -07:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public int bulkInsert(Uri uri, ContentValues[] values) {
|
|
|
|
if (values == null)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
int numValues = values.length;
|
|
|
|
int successes = 0;
|
|
|
|
|
|
|
|
final SQLiteDatabase db = getWritableDatabase(uri);
|
|
|
|
|
|
|
|
db.beginTransaction();
|
|
|
|
try {
|
|
|
|
for (int i = 0; i < numValues; i++) {
|
|
|
|
try {
|
|
|
|
insertInTransaction(uri, values[i]);
|
|
|
|
successes++;
|
|
|
|
} catch (SQLException e) {
|
|
|
|
Log.e(LOGTAG, "SQLException in bulkInsert", e);
|
|
|
|
|
|
|
|
// Restart the transaction to continue insertions.
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
db.endTransaction();
|
|
|
|
db.beginTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
trace("Flushing DB bulkinsert...");
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
|
2013-01-24 11:14:44 -08:00
|
|
|
if (successes > 0)
|
|
|
|
mContext.getContentResolver().notifyChange(uri, null);
|
|
|
|
|
2012-08-09 13:52:58 -07:00
|
|
|
return successes;
|
|
|
|
}
|
2012-02-29 11:29:09 -08:00
|
|
|
}
|