gecko-dev/mobile/android/base/db/BrowserDatabaseHelper.java

1877 строки
97 KiB
Java

/* -*- 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.io.ByteArrayOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
import org.mozilla.gecko.db.BrowserContract.Favicons;
import org.mozilla.gecko.db.BrowserContract.History;
import org.mozilla.gecko.db.BrowserContract.Obsolete;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.util.GeckoJarReader;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
final class BrowserDatabaseHelper extends SQLiteOpenHelper {
private static final String LOGTAG = "GeckoBrowserDBHelper";
public static final int DATABASE_VERSION = 18;
public static final String DATABASE_NAME = "browser.db";
final protected Context mContext;
static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
static final String TABLE_HISTORY = History.TABLE_NAME;
static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
static final String VIEW_COMBINED = Combined.VIEW_NAME;
static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
static final String TABLE_BOOKMARKS_JOIN_FAVICONS = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " = " +
qualifyColumn(TABLE_FAVICONS, Favicons._ID);
static final String TABLE_HISTORY_JOIN_FAVICONS = TABLE_HISTORY + " LEFT OUTER JOIN " +
TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " = " +
qualifyColumn(TABLE_FAVICONS, Favicons._ID);
static final String TABLE_BOOKMARKS_TMP = TABLE_BOOKMARKS + "_tmp";
static final String TABLE_HISTORY_TMP = TABLE_HISTORY + "_tmp";
static final String TABLE_IMAGES_TMP = Obsolete.TABLE_IMAGES + "_tmp";
private static final String[] mobileIdColumns = new String[] { Bookmarks._ID };
private static final String[] mobileIdSelectionArgs = new String[] { Bookmarks.MOBILE_FOLDER_GUID };
public BrowserDatabaseHelper(Context context, String databasePath) {
super(context, databasePath, null, DATABASE_VERSION);
mContext = context;
}
private void createBookmarksTable(SQLiteDatabase db) {
debug("Creating " + TABLE_BOOKMARKS + " table");
// Android versions older than Froyo ship with an sqlite
// that doesn't support foreign keys.
String foreignKeyOnParent = null;
if (Build.VERSION.SDK_INT >= 8) {
foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
}
db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Bookmarks.TITLE + " TEXT," +
Bookmarks.URL + " TEXT," +
Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
Bookmarks.PARENT + " INTEGER," +
Bookmarks.POSITION + " INTEGER NOT NULL," +
Bookmarks.KEYWORD + " TEXT," +
Bookmarks.DESCRIPTION + " TEXT," +
Bookmarks.TAGS + " TEXT," +
Bookmarks.DATE_CREATED + " INTEGER," +
Bookmarks.DATE_MODIFIED + " INTEGER," +
Bookmarks.GUID + " TEXT NOT NULL," +
Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
(foreignKeyOnParent != null ? foreignKeyOnParent : "") +
");");
db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.URL + ")");
db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.GUID + ")");
db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.DATE_MODIFIED + ")");
}
private void createBookmarksTableOn13(SQLiteDatabase db) {
debug("Creating " + TABLE_BOOKMARKS + " table");
// Android versions older than Froyo ship with an sqlite
// that doesn't support foreign keys.
String foreignKeyOnParent = null;
if (Build.VERSION.SDK_INT >= 8) {
foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
}
db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Bookmarks.TITLE + " TEXT," +
Bookmarks.URL + " TEXT," +
Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
Bookmarks.PARENT + " INTEGER," +
Bookmarks.POSITION + " INTEGER NOT NULL," +
Bookmarks.KEYWORD + " TEXT," +
Bookmarks.DESCRIPTION + " TEXT," +
Bookmarks.TAGS + " TEXT," +
Bookmarks.FAVICON_ID + " INTEGER," +
Bookmarks.DATE_CREATED + " INTEGER," +
Bookmarks.DATE_MODIFIED + " INTEGER," +
Bookmarks.GUID + " TEXT NOT NULL," +
Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
(foreignKeyOnParent != null ? foreignKeyOnParent : "") +
");");
db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.URL + ")");
db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.GUID + ")");
db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.DATE_MODIFIED + ")");
}
private void createHistoryTable(SQLiteDatabase db) {
debug("Creating " + TABLE_HISTORY + " table");
db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
History.TITLE + " TEXT," +
History.URL + " TEXT NOT NULL," +
History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
History.DATE_LAST_VISITED + " INTEGER," +
History.DATE_CREATED + " INTEGER," +
History.DATE_MODIFIED + " INTEGER," +
History.GUID + " TEXT NOT NULL," +
History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
");");
db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
+ History.URL + ")");
db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
+ History.GUID + ")");
db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
+ History.DATE_MODIFIED + ")");
db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
+ History.DATE_LAST_VISITED + ")");
}
private void createHistoryTableOn13(SQLiteDatabase db) {
debug("Creating " + TABLE_HISTORY + " table");
db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
History.TITLE + " TEXT," +
History.URL + " TEXT NOT NULL," +
History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
History.FAVICON_ID + " INTEGER," +
History.DATE_LAST_VISITED + " INTEGER," +
History.DATE_CREATED + " INTEGER," +
History.DATE_MODIFIED + " INTEGER," +
History.GUID + " TEXT NOT NULL," +
History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
");");
db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
+ History.URL + ")");
db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
+ History.GUID + ")");
db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
+ History.DATE_MODIFIED + ")");
db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
+ History.DATE_LAST_VISITED + ")");
}
private void createImagesTable(SQLiteDatabase db) {
debug("Creating " + Obsolete.TABLE_IMAGES + " table");
db.execSQL("CREATE TABLE " + Obsolete.TABLE_IMAGES + " (" +
Obsolete.Images._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Obsolete.Images.URL + " TEXT UNIQUE NOT NULL," +
Obsolete.Images.FAVICON + " BLOB," +
Obsolete.Images.FAVICON_URL + " TEXT," +
Obsolete.Images.THUMBNAIL + " BLOB," +
Obsolete.Images.DATE_CREATED + " INTEGER," +
Obsolete.Images.DATE_MODIFIED + " INTEGER," +
Obsolete.Images.GUID + " TEXT NOT NULL," +
Obsolete.Images.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
");");
db.execSQL("CREATE INDEX images_url_index ON " + Obsolete.TABLE_IMAGES + "("
+ Obsolete.Images.URL + ")");
db.execSQL("CREATE UNIQUE INDEX images_guid_index ON " + Obsolete.TABLE_IMAGES + "("
+ Obsolete.Images.GUID + ")");
db.execSQL("CREATE INDEX images_modified_index ON " + Obsolete.TABLE_IMAGES + "("
+ Obsolete.Images.DATE_MODIFIED + ")");
}
private void createFaviconsTable(SQLiteDatabase db) {
debug("Creating " + TABLE_FAVICONS + " table");
db.execSQL("CREATE TABLE " + TABLE_FAVICONS + " (" +
Favicons._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Favicons.URL + " TEXT UNIQUE," +
Favicons.DATA + " BLOB," +
Favicons.DATE_CREATED + " INTEGER," +
Favicons.DATE_MODIFIED + " INTEGER" +
");");
db.execSQL("CREATE INDEX favicons_url_index ON " + TABLE_FAVICONS + "("
+ Favicons.URL + ")");
db.execSQL("CREATE INDEX favicons_modified_index ON " + TABLE_FAVICONS + "("
+ Favicons.DATE_MODIFIED + ")");
}
private void createThumbnailsTable(SQLiteDatabase db) {
debug("Creating " + TABLE_THUMBNAILS + " table");
db.execSQL("CREATE TABLE " + TABLE_THUMBNAILS + " (" +
Thumbnails._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Thumbnails.URL + " TEXT UNIQUE," +
Thumbnails.DATA + " BLOB" +
");");
db.execSQL("CREATE INDEX thumbnails_url_index ON " + TABLE_THUMBNAILS + "("
+ Thumbnails.URL + ")");
}
private void createBookmarksWithImagesView(SQLiteDatabase db) {
debug("Creating " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " AS " +
"SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
Obsolete.TABLE_BOOKMARKS_JOIN_IMAGES);
}
private void createBookmarksWithFaviconsView(SQLiteDatabase db) {
debug("Creating " + VIEW_BOOKMARKS_WITH_FAVICONS + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_FAVICONS + " AS " +
"SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Bookmarks.FAVICON +
", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Bookmarks.FAVICON_URL +
" FROM " + TABLE_BOOKMARKS_JOIN_FAVICONS);
}
private void createHistoryWithImagesView(SQLiteDatabase db) {
debug("Creating " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " AS " +
"SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
Obsolete.TABLE_HISTORY_JOIN_IMAGES);
}
private void createHistoryWithFaviconsView(SQLiteDatabase db) {
debug("Creating " + VIEW_HISTORY_WITH_FAVICONS + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_HISTORY_WITH_FAVICONS + " AS " +
"SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + History.FAVICON +
", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + History.FAVICON_URL +
" FROM " + TABLE_HISTORY_JOIN_FAVICONS);
}
private void createCombinedWithImagesView(SQLiteDatabase db) {
debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritze bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
" ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
}
private void createCombinedWithImagesViewOn9(SQLiteDatabase db) {
debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DISPLAY + ", " +
Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritze bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
" ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
}
private void createCombinedWithImagesViewOn10(SQLiteDatabase db) {
debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DISPLAY + ", " +
Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritze bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
" ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
}
private void createCombinedWithImagesViewOn11(SQLiteDatabase db) {
debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DISPLAY + ", " +
Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritze bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
// Only use DISPLAY_READER if the matching bookmark entry inside reading
// list folder is not marked as deleted.
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
" THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
" ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
}
private void createCombinedViewOn12(SQLiteDatabase db) {
debug("Creating " + VIEW_COMBINED + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DISPLAY + ", " +
Combined.DATE_LAST_VISITED +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritze bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
// Only use DISPLAY_READER if the matching bookmark entry inside reading
// list folder is not marked as deleted.
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
" THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
")");
debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
" SELECT *, " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
" FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
" ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
}
private void createCombinedViewOn13(SQLiteDatabase db) {
debug("Creating " + VIEW_COMBINED + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DISPLAY + ", " +
Combined.DATE_LAST_VISITED + ", " +
Combined.FAVICON_ID +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritize bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
// Only use DISPLAY_READER if the matching bookmark entry inside reading
// list folder is not marked as deleted.
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
" THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
")");
debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
" SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
" FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
" ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
}
private void createCombinedViewOn16(SQLiteDatabase db) {
debug("Creating " + VIEW_COMBINED + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
" SELECT " + Combined.BOOKMARK_ID + ", " +
Combined.HISTORY_ID + ", " +
// We need to return an _id column because CursorAdapter requires it for its
// default implementation for the getItemId() method. However, since
// we're not using this feature in the parts of the UI using this view,
// we can just use 0 for all rows.
"0 AS " + Combined._ID + ", " +
Combined.URL + ", " +
Combined.TITLE + ", " +
Combined.VISITS + ", " +
Combined.DISPLAY + ", " +
Combined.DATE_LAST_VISITED + ", " +
Combined.FAVICON_ID +
" FROM (" +
// Bookmarks without history.
" SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
"-1 AS " + Combined.HISTORY_ID + ", " +
"-1 AS " + Combined.VISITS + ", " +
"-1 AS " + Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
" FROM " + TABLE_BOOKMARKS +
" WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
// Ignore pinned bookmarks.
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " <> " + Bookmarks.FIXED_PINNED_LIST_ID + " AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
" NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
" UNION ALL" +
// History with and without bookmark.
" SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
// Give pinned bookmarks a NULL ID so that they're not treated as bookmarks. We can't
// completely ignore them here because they're joined with history entries we care about.
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
Bookmarks.FIXED_PINNED_LIST_ID + " THEN NULL ELSE " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " END " +
"ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
// Prioritize bookmark titles over history titles, since the user may have
// customized the title for a bookmark.
"COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
// Only use DISPLAY_READER if the matching bookmark entry inside reading
// list folder is not marked as deleted.
"CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
" THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
" FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
" ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
" WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
")");
debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
" SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
" FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
" ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
}
@Override
public void onCreate(SQLiteDatabase db) {
debug("Creating browser.db: " + db.getPath());
createBookmarksTableOn13(db);
createHistoryTableOn13(db);
createFaviconsTable(db);
createThumbnailsTable(db);
createBookmarksWithFaviconsView(db);
createHistoryWithFaviconsView(db);
createCombinedViewOn16(db);
createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
R.string.bookmarks_folder_places, 0);
createOrUpdateAllSpecialFolders(db);
int offset = createDefaultBookmarks(db, 0);
// We'd like to create distribution bookmarks before our own default bookmarks,
// but we won't necessarily have loaded the distribution yet. Oh well.
enqueueDistributionBookmarkLoad(db, offset);
createReadingListTable(db);
}
private String getLocalizedProperty(JSONObject bookmark, String property, Locale locale) throws JSONException {
// Try the full locale
String fullLocale = property + "." + locale.toString();
if (bookmark.has(fullLocale)) {
return bookmark.getString(fullLocale);
}
// Try without a variant
if (!TextUtils.isEmpty(locale.getVariant())) {
String noVariant = fullLocale.substring(0, fullLocale.lastIndexOf("_"));
if (bookmark.has(noVariant)) {
return bookmark.getString(noVariant);
}
}
// Try just the language
String lang = property + "." + locale.getLanguage();
if (bookmark.has(lang)) {
return bookmark.getString(lang);
}
// Default to the non-localized property name
return bookmark.getString(property);
}
// We can't rely on the distribution file having been loaded,
// so we delegate that work to the distribution handler.
private void enqueueDistributionBookmarkLoad(final SQLiteDatabase db, final int start) {
final Distribution distribution = Distribution.getInstance(mContext);
distribution.addOnDistributionReadyCallback(new Runnable() {
@Override
public void run() {
Log.d(LOGTAG, "Running post-distribution task: bookmarks.");
if (!distribution.exists()) {
return;
}
// This runnable is always executed on the background thread,
// thanks to Distribution's guarantees.
// Yes, the user might have added a bookmark in the interim,
// in which case `start` will be wrong. Sync will need to fix
// up the indices.
createDistributionBookmarks(distribution, db, start);
}
});
}
/**
* Fetch distribution bookmarks and insert them into the database.
*
* @param db the destination database.
* @param start the initial index at which to insert.
* @return the number of inserted bookmarks.
*/
private int createDistributionBookmarks(Distribution distribution, SQLiteDatabase db, int start) {
JSONArray bookmarks = distribution.getBookmarks();
if (bookmarks == null) {
return 0;
}
Locale locale = Locale.getDefault();
int pos = start;
Integer mobileFolderId = getMobileFolderId(db);
if (mobileFolderId == null) {
Log.e(LOGTAG, "Error creating distribution bookmarks: mobileFolderId is null.");
return 0;
}
for (int i = 0; i < bookmarks.length(); i++) {
try {
final JSONObject bookmark = bookmarks.getJSONObject(i);
String title = getLocalizedProperty(bookmark, "title", locale);
final String url = getLocalizedProperty(bookmark, "url", locale);
createBookmark(db, title, url, pos, mobileFolderId);
if (bookmark.has("pinned")) {
try {
// Create a fake bookmark in the hidden pinned folder to pin bookmark
// to about:home top sites. Pass pos as the pinned position to pin
// sites in the order that bookmarks are specified in bookmarks.json.
if (bookmark.getBoolean("pinned")) {
createBookmark(db, title, url, pos, Bookmarks.FIXED_PINNED_LIST_ID);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Error pinning bookmark to top sites.", e);
}
}
pos++;
// Return early if there is no icon for this bookmark.
if (!bookmark.has("icon")) {
continue;
}
try {
final String iconData = bookmark.getString("icon");
final Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconData);
if (icon != null) {
createFavicon(db, url, icon);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Error creating distribution bookmark icon.", e);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Error creating distribution bookmark.", e);
}
}
return pos - start;
}
private void createReadingListTable(SQLiteDatabase db) {
debug("Creating " + TABLE_READING_LIST + " table");
db.execSQL("CREATE TABLE " + TABLE_READING_LIST + "(" +
ReadingListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
ReadingListItems.URL + " TEXT NOT NULL, " +
ReadingListItems.TITLE + " TEXT, " +
ReadingListItems.EXCERPT + " TEXT, " +
ReadingListItems.READ + " TINYINT DEFAULT 0, " +
ReadingListItems.IS_DELETED + " TINYINT DEFAULT 0, " +
ReadingListItems.GUID + " TEXT UNIQUE NOT NULL, " +
ReadingListItems.DATE_MODIFIED + " INTEGER NOT NULL, " +
ReadingListItems.DATE_CREATED + " INTEGER NOT NULL, " +
ReadingListItems.LENGTH + " INTEGER DEFAULT 0 ); ");
db.execSQL("CREATE INDEX reading_list_url ON " + TABLE_READING_LIST + "("
+ ReadingListItems.URL + ")");
db.execSQL("CREATE UNIQUE INDEX reading_list_guid ON " + TABLE_READING_LIST + "("
+ ReadingListItems.GUID + ")");
}
/**
* Insert default bookmarks into the database.
*
* @param db the destination database.
* @param start the initial index at which to insert.
* @return the number of inserted bookmarks.
*/
private int createDefaultBookmarks(SQLiteDatabase db, int start) {
Class<?> stringsClass = R.string.class;
Field[] fields = stringsClass.getFields();
Pattern p = Pattern.compile("^bookmarkdefaults_title_");
Integer mobileFolderId = getMobileFolderId(db);
if (mobileFolderId == null) {
Log.e(LOGTAG, "Error creating default bookmarks: mobileFolderId is null");
return 0;
}
int pos = start;
for (int i = 0; i < fields.length; i++) {
final String name = fields[i].getName();
Matcher m = p.matcher(name);
if (!m.find()) {
continue;
}
try {
int titleid = fields[i].getInt(null);
String title = mContext.getString(titleid);
Field urlField = stringsClass.getField(name.replace("_title_", "_url_"));
int urlId = urlField.getInt(null);
final String url = mContext.getString(urlId);
createBookmark(db, title, url, pos, mobileFolderId);
// Create icons in a separate thread to avoid blocking about:home on startup.
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
SQLiteDatabase db = getWritableDatabase();
Bitmap icon = getDefaultFaviconFromPath(name);
if (icon == null) {
icon = getDefaultFaviconFromDrawable(name);
}
if (icon != null) {
createFavicon(db, url, icon);
}
}
});
pos++;
} catch (java.lang.IllegalAccessException ex) {
Log.e(LOGTAG, "Can't create bookmark " + name, ex);
} catch (java.lang.NoSuchFieldException ex) {
Log.e(LOGTAG, "Can't create bookmark " + name, ex);
}
}
return pos - start;
}
private void createBookmark(SQLiteDatabase db, String title, String url, int pos, int parent) {
ContentValues bookmarkValues = new ContentValues();
bookmarkValues.put(Bookmarks.PARENT, parent);
long now = System.currentTimeMillis();
bookmarkValues.put(Bookmarks.DATE_CREATED, now);
bookmarkValues.put(Bookmarks.DATE_MODIFIED, now);
bookmarkValues.put(Bookmarks.TITLE, title);
bookmarkValues.put(Bookmarks.URL, url);
bookmarkValues.put(Bookmarks.GUID, Utils.generateGuid());
bookmarkValues.put(Bookmarks.POSITION, pos);
db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, bookmarkValues);
}
private void createFavicon(SQLiteDatabase db, String url, Bitmap icon) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ContentValues iconValues = new ContentValues();
iconValues.put(Favicons.PAGE_URL, url);
byte[] data = null;
if (icon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
data = stream.toByteArray();
} else {
Log.w(LOGTAG, "Favicon compression failed.");
}
iconValues.put(Favicons.DATA, data);
insertFavicon(db, iconValues);
}
private Bitmap getDefaultFaviconFromPath(String name) {
Class<?> stringClass = R.string.class;
try {
// Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
Field faviconField = stringClass.getField(name.replace("_title_", "_favicon_"));
if (faviconField == null) {
return null;
}
int faviconId = faviconField.getInt(null);
String path = mContext.getString(faviconId);
String apkPath = mContext.getPackageResourcePath();
File apkFile = new File(apkPath);
String bitmapPath = "jar:jar:" + apkFile.toURI() + "!/" + AppConstants.OMNIJAR_NAME + "!/" + path;
return GeckoJarReader.getBitmap(mContext.getResources(), bitmapPath);
} catch (java.lang.IllegalAccessException ex) {
Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
} catch (java.lang.NoSuchFieldException ex) {
// If the field does not exist, that means we intend to load via a drawable
}
return null;
}
private Bitmap getDefaultFaviconFromDrawable(String name) {
Class<?> drawablesClass = R.drawable.class;
try {
// Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
Field faviconField = drawablesClass.getField(name.replace("_title_", "_favicon_"));
if (faviconField == null) {
return null;
}
int faviconId = faviconField.getInt(null);
return BitmapUtils.decodeResource(mContext, faviconId);
} catch (java.lang.IllegalAccessException ex) {
Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
} catch (java.lang.NoSuchFieldException ex) {
// If the field does not exist, that means we intend to load via a file path
}
return null;
}
private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
createOrUpdateSpecialFolder(db, Bookmarks.MOBILE_FOLDER_GUID,
R.string.bookmarks_folder_mobile, 0);
createOrUpdateSpecialFolder(db, Bookmarks.TOOLBAR_FOLDER_GUID,
R.string.bookmarks_folder_toolbar, 1);
createOrUpdateSpecialFolder(db, Bookmarks.MENU_FOLDER_GUID,
R.string.bookmarks_folder_menu, 2);
createOrUpdateSpecialFolder(db, Bookmarks.TAGS_FOLDER_GUID,
R.string.bookmarks_folder_tags, 3);
createOrUpdateSpecialFolder(db, Bookmarks.UNFILED_FOLDER_GUID,
R.string.bookmarks_folder_unfiled, 4);
createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
R.string.bookmarks_folder_reading_list, 5);
createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
R.string.bookmarks_folder_pinned, 6);
}
private void createOrUpdateSpecialFolder(SQLiteDatabase db,
String guid, int titleId, int position) {
ContentValues values = new ContentValues();
values.put(Bookmarks.GUID, guid);
values.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
values.put(Bookmarks.POSITION, position);
if (guid.equals(Bookmarks.PLACES_FOLDER_GUID))
values.put(Bookmarks._ID, Bookmarks.FIXED_ROOT_ID);
else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID))
values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID);
else if (guid.equals(Bookmarks.PINNED_FOLDER_GUID))
values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID);
// Set the parent to 0, which sync assumes is the root
values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
String title = mContext.getResources().getString(titleId);
values.put(Bookmarks.TITLE, title);
long now = System.currentTimeMillis();
values.put(Bookmarks.DATE_CREATED, now);
values.put(Bookmarks.DATE_MODIFIED, now);
int updated = db.update(TABLE_BOOKMARKS, values,
Bookmarks.GUID + " = ?",
new String[] { guid });
if (updated == 0) {
db.insert(TABLE_BOOKMARKS, Bookmarks.GUID, values);
debug("Inserted special folder: " + guid);
} else {
debug("Updated special folder: " + guid);
}
}
private boolean isSpecialFolder(ContentValues values) {
String guid = values.getAsString(Bookmarks.GUID);
if (guid == null)
return false;
return guid.equals(Bookmarks.MOBILE_FOLDER_GUID) ||
guid.equals(Bookmarks.MENU_FOLDER_GUID) ||
guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID) ||
guid.equals(Bookmarks.UNFILED_FOLDER_GUID) ||
guid.equals(Bookmarks.TAGS_FOLDER_GUID);
}
private void migrateBookmarkFolder(SQLiteDatabase db, int folderId,
BookmarkMigrator migrator) {
Cursor c = null;
debug("Migrating bookmark folder with id = " + folderId);
String selection = Bookmarks.PARENT + " = " + folderId;
String[] selectionArgs = null;
boolean isRootFolder = (folderId == Bookmarks.FIXED_ROOT_ID);
// If we're loading the root folder, we have to account for
// any previously created special folder that was created without
// setting a parent id (e.g. mobile folder) and making sure we're
// not adding any infinite recursion as root's parent is root itself.
if (isRootFolder) {
selection = Bookmarks.GUID + " != ?" + " AND (" +
selection + " OR " + Bookmarks.PARENT + " = NULL)";
selectionArgs = new String[] { Bookmarks.PLACES_FOLDER_GUID };
}
List<Integer> subFolders = new ArrayList<Integer>();
List<ContentValues> invalidSpecialEntries = new ArrayList<ContentValues>();
try {
c = db.query(TABLE_BOOKMARKS_TMP,
null,
selection,
selectionArgs,
null, null, null);
// The key point here is that bookmarks should be added in
// parent order to avoid any problems with the foreign key
// in Bookmarks.PARENT.
while (c.moveToNext()) {
ContentValues values = new ContentValues();
// We're using a null projection in the query which
// means we're getting all columns from the table.
// It's safe to simply transform the row into the
// values to be inserted on the new table.
DatabaseUtils.cursorRowToContentValues(c, values);
boolean isSpecialFolder = isSpecialFolder(values);
// The mobile folder used to be created with PARENT = NULL.
// We want fix that here.
if (values.getAsLong(Bookmarks.PARENT) == null && isSpecialFolder)
values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
if (isRootFolder && !isSpecialFolder) {
invalidSpecialEntries.add(values);
continue;
}
if (migrator != null)
migrator.updateForNewTable(values);
debug("Migrating bookmark: " + values.getAsString(Bookmarks.TITLE));
db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
Integer type = values.getAsInteger(Bookmarks.TYPE);
if (type != null && type == Bookmarks.TYPE_FOLDER)
subFolders.add(values.getAsInteger(Bookmarks._ID));
}
} finally {
if (c != null)
c.close();
}
// At this point is safe to assume that the mobile folder is
// in the new table given that we've always created it on
// database creation time.
final int nInvalidSpecialEntries = invalidSpecialEntries.size();
if (nInvalidSpecialEntries > 0) {
Integer mobileFolderId = getMobileFolderId(db);
if (mobileFolderId == null) {
Log.e(LOGTAG, "Error migrating invalid special folder entries: mobile folder id is null");
return;
}
debug("Found " + nInvalidSpecialEntries + " invalid special folder entries");
for (int i = 0; i < nInvalidSpecialEntries; i++) {
ContentValues values = invalidSpecialEntries.get(i);
values.put(Bookmarks.PARENT, mobileFolderId);
db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
}
}
final int nSubFolders = subFolders.size();
for (int i = 0; i < nSubFolders; i++) {
int subFolderId = subFolders.get(i);
migrateBookmarkFolder(db, subFolderId, migrator);
}
}
private void migrateBookmarksTable(SQLiteDatabase db) {
migrateBookmarksTable(db, null);
}
private void migrateBookmarksTable(SQLiteDatabase db, BookmarkMigrator migrator) {
debug("Renaming bookmarks table to " + TABLE_BOOKMARKS_TMP);
db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS +
" RENAME TO " + TABLE_BOOKMARKS_TMP);
debug("Dropping views and indexes related to " + TABLE_BOOKMARKS);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
db.execSQL("DROP INDEX IF EXISTS bookmarks_url_index");
db.execSQL("DROP INDEX IF EXISTS bookmarks_type_deleted_index");
db.execSQL("DROP INDEX IF EXISTS bookmarks_guid_index");
db.execSQL("DROP INDEX IF EXISTS bookmarks_modified_index");
createBookmarksTable(db);
createBookmarksWithImagesView(db);
createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
R.string.bookmarks_folder_places, 0);
migrateBookmarkFolder(db, Bookmarks.FIXED_ROOT_ID, migrator);
// Ensure all special folders exist and have the
// right folder hierarchy.
createOrUpdateAllSpecialFolders(db);
debug("Dropping bookmarks temporary table");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS_TMP);
}
private void migrateHistoryTable(SQLiteDatabase db) {
debug("Renaming history table to " + TABLE_HISTORY_TMP);
db.execSQL("ALTER TABLE " + TABLE_HISTORY +
" RENAME TO " + TABLE_HISTORY_TMP);
debug("Dropping views and indexes related to " + TABLE_HISTORY);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP INDEX IF EXISTS history_url_index");
db.execSQL("DROP INDEX IF EXISTS history_guid_index");
db.execSQL("DROP INDEX IF EXISTS history_modified_index");
db.execSQL("DROP INDEX IF EXISTS history_visited_index");
createHistoryTable(db);
createHistoryWithImagesView(db);
createCombinedWithImagesView(db);
db.execSQL("INSERT INTO " + TABLE_HISTORY + " SELECT * FROM " + TABLE_HISTORY_TMP);
debug("Dropping history temporary table");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY_TMP);
}
private void migrateImagesTable(SQLiteDatabase db) {
debug("Renaming images table to " + TABLE_IMAGES_TMP);
db.execSQL("ALTER TABLE " + Obsolete.TABLE_IMAGES +
" RENAME TO " + TABLE_IMAGES_TMP);
debug("Dropping views and indexes related to " + Obsolete.TABLE_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP INDEX IF EXISTS images_url_index");
db.execSQL("DROP INDEX IF EXISTS images_guid_index");
db.execSQL("DROP INDEX IF EXISTS images_modified_index");
createImagesTable(db);
createHistoryWithImagesView(db);
createCombinedWithImagesView(db);
db.execSQL("INSERT INTO " + Obsolete.TABLE_IMAGES + " SELECT * FROM " + TABLE_IMAGES_TMP);
debug("Dropping images temporary table");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES_TMP);
}
private void upgradeDatabaseFrom1to2(SQLiteDatabase db) {
migrateBookmarksTable(db);
}
private void upgradeDatabaseFrom2to3(SQLiteDatabase db) {
debug("Dropping view: " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
createBookmarksWithImagesView(db);
debug("Dropping view: " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
createHistoryWithImagesView(db);
}
private void upgradeDatabaseFrom3to4(SQLiteDatabase db) {
migrateBookmarksTable(db, new BookmarkMigrator3to4());
}
private void upgradeDatabaseFrom4to5(SQLiteDatabase db) {
createCombinedWithImagesView(db);
}
private void upgradeDatabaseFrom5to6(SQLiteDatabase db) {
debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
createCombinedWithImagesView(db);
}
private void upgradeDatabaseFrom6to7(SQLiteDatabase db) {
debug("Removing history visits with NULL GUIDs");
db.execSQL("DELETE FROM " + TABLE_HISTORY + " WHERE " + History.GUID + " IS NULL");
debug("Update images with NULL GUIDs");
String[] columns = new String[] { Obsolete.Images._ID };
Cursor cursor = null;
try {
cursor = db.query(Obsolete.TABLE_IMAGES, columns, Obsolete.Images.GUID + " IS NULL", null, null ,null, null, null);
ContentValues values = new ContentValues();
if (cursor.moveToFirst()) {
do {
values.put(Obsolete.Images.GUID, Utils.generateGuid());
db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images._ID + " = ?", new String[] {
cursor.getString(cursor.getColumnIndexOrThrow(Obsolete.Images._ID))
});
} while (cursor.moveToNext());
}
} finally {
if (cursor != null)
cursor.close();
}
migrateBookmarksTable(db);
migrateHistoryTable(db);
migrateImagesTable(db);
}
private void upgradeDatabaseFrom7to8(SQLiteDatabase db) {
debug("Combining history entries with the same URL");
final String TABLE_DUPES = "duped_urls";
final String TOTAL = "total";
final String LATEST = "latest";
final String WINNER = "winner";
db.execSQL("CREATE TEMP TABLE " + TABLE_DUPES + " AS" +
" SELECT " + History.URL + ", " +
"SUM(" + History.VISITS + ") AS " + TOTAL + ", " +
"MAX(" + History.DATE_MODIFIED + ") AS " + LATEST + ", " +
"MAX(" + History._ID + ") AS " + WINNER +
" FROM " + TABLE_HISTORY +
" GROUP BY " + History.URL +
" HAVING count(" + History.URL + ") > 1");
db.execSQL("CREATE UNIQUE INDEX " + TABLE_DUPES + "_url_index ON " +
TABLE_DUPES + " (" + History.URL + ")");
final String fromClause = " FROM " + TABLE_DUPES + " WHERE " +
qualifyColumn(TABLE_DUPES, History.URL) + " = " +
qualifyColumn(TABLE_HISTORY, History.URL);
db.execSQL("UPDATE " + TABLE_HISTORY +
" SET " + History.VISITS + " = (SELECT " + TOTAL + fromClause + "), " +
History.DATE_MODIFIED + " = (SELECT " + LATEST + fromClause + "), " +
History.IS_DELETED + " = " +
"(" + History._ID + " <> (SELECT " + WINNER + fromClause + "))" +
" WHERE " + History.URL + " IN (SELECT " + History.URL + " FROM " + TABLE_DUPES + ")");
db.execSQL("DROP TABLE " + TABLE_DUPES);
}
private void upgradeDatabaseFrom8to9(SQLiteDatabase db) {
createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
R.string.bookmarks_folder_reading_list, 5);
debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
createCombinedWithImagesViewOn9(db);
}
private void upgradeDatabaseFrom9to10(SQLiteDatabase db) {
debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
createCombinedWithImagesViewOn10(db);
}
private void upgradeDatabaseFrom10to11(SQLiteDatabase db) {
debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
+ Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
createCombinedWithImagesViewOn11(db);
}
private void upgradeDatabaseFrom11to12(SQLiteDatabase db) {
debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
createCombinedViewOn12(db);
}
private void upgradeDatabaseFrom12to13(SQLiteDatabase db) {
// Update images table with favicon URLs
SQLiteDatabase faviconsDb = null;
Cursor c = null;
try {
final String FAVICON_TABLE = "favicon_urls";
final String FAVICON_URL = "favicon_url";
final String FAVICON_PAGE = "page_url";
String dbPath = mContext.getDatabasePath(Obsolete.FAVICON_DB).getPath();
faviconsDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
String[] columns = new String[] { FAVICON_URL, FAVICON_PAGE };
c = faviconsDb.query(FAVICON_TABLE, columns, null, null, null, null, null, null);
int faviconIndex = c.getColumnIndexOrThrow(FAVICON_URL);
int pageIndex = c.getColumnIndexOrThrow(FAVICON_PAGE);
while (c.moveToNext()) {
ContentValues values = new ContentValues(1);
String faviconUrl = c.getString(faviconIndex);
String pageUrl = c.getString(pageIndex);
values.put(FAVICON_URL, faviconUrl);
db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images.URL + " = ?", new String[] { pageUrl });
}
} catch (SQLException e) {
// If we can't read from the database for some reason, we won't
// be able to import the favicon URLs. This isn't a fatal
// error, so continue the upgrade.
Log.e(LOGTAG, "Exception importing from " + Obsolete.FAVICON_DB, e);
} finally {
if (c != null)
c.close();
if (faviconsDb != null)
faviconsDb.close();
}
createFaviconsTable(db);
// Import favicons into the favicons table
db.execSQL("ALTER TABLE " + TABLE_HISTORY
+ " ADD COLUMN " + History.FAVICON_ID + " INTEGER");
db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS
+ " ADD COLUMN " + Bookmarks.FAVICON_ID + " INTEGER");
try {
c = db.query(Obsolete.TABLE_IMAGES,
new String[] {
Obsolete.Images.URL,
Obsolete.Images.FAVICON_URL,
Obsolete.Images.FAVICON,
Obsolete.Images.DATE_MODIFIED,
Obsolete.Images.DATE_CREATED
},
Obsolete.Images.FAVICON + " IS NOT NULL",
null, null, null, null);
while (c.moveToNext()) {
long faviconId = -1;
int faviconUrlIndex = c.getColumnIndexOrThrow(Obsolete.Images.FAVICON_URL);
String faviconUrl = null;
if (!c.isNull(faviconUrlIndex)) {
faviconUrl = c.getString(faviconUrlIndex);
Cursor c2 = null;
try {
c2 = db.query(TABLE_FAVICONS,
new String[] { Favicons._ID },
Favicons.URL + " = ?",
new String[] { faviconUrl },
null, null, null);
if (c2.moveToFirst()) {
faviconId = c2.getLong(c2.getColumnIndexOrThrow(Favicons._ID));
}
} finally {
if (c2 != null)
c2.close();
}
}
if (faviconId == -1) {
ContentValues values = new ContentValues(4);
values.put(Favicons.URL, faviconUrl);
values.put(Favicons.DATA, c.getBlob(c.getColumnIndexOrThrow(Obsolete.Images.FAVICON)));
values.put(Favicons.DATE_MODIFIED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_MODIFIED)));
values.put(Favicons.DATE_CREATED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_CREATED)));
faviconId = db.insert(TABLE_FAVICONS, null, values);
}
ContentValues values = new ContentValues(1);
values.put(FaviconColumns.FAVICON_ID, faviconId);
db.update(TABLE_HISTORY, values, History.URL + " = ?",
new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
db.update(TABLE_BOOKMARKS, values, Bookmarks.URL + " = ?",
new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
}
} finally {
if (c != null)
c.close();
}
createThumbnailsTable(db);
// Import thumbnails into the thumbnails table
db.execSQL("INSERT INTO " + TABLE_THUMBNAILS + " ("
+ Thumbnails.URL + ", "
+ Thumbnails.DATA + ") "
+ "SELECT " + Obsolete.Images.URL + ", " + Obsolete.Images.THUMBNAIL
+ " FROM " + Obsolete.TABLE_IMAGES
+ " WHERE " + Obsolete.Images.THUMBNAIL + " IS NOT NULL");
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
createBookmarksWithFaviconsView(db);
createHistoryWithFaviconsView(db);
createCombinedViewOn13(db);
db.execSQL("DROP TABLE IF EXISTS " + Obsolete.TABLE_IMAGES);
}
private void upgradeDatabaseFrom13to14(SQLiteDatabase db) {
createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
R.string.bookmarks_folder_pinned, 6);
}
private void upgradeDatabaseFrom14to15(SQLiteDatabase db) {
Cursor c = null;
try {
// Get all the pinned bookmarks
c = db.query(TABLE_BOOKMARKS,
new String[] { Bookmarks._ID, Bookmarks.URL },
Bookmarks.PARENT + " = ?",
new String[] { Integer.toString(Bookmarks.FIXED_PINNED_LIST_ID) },
null, null, null);
while (c.moveToNext()) {
// Check if this URL can be parsed as a URI with a valid scheme.
String url = c.getString(c.getColumnIndexOrThrow(Bookmarks.URL));
if (Uri.parse(url).getScheme() != null) {
continue;
}
// If it can't, update the URL to be an encoded "user-entered" value.
ContentValues values = new ContentValues(1);
String newUrl = Uri.fromParts("user-entered", url, null).toString();
values.put(Bookmarks.URL, newUrl);
db.update(TABLE_BOOKMARKS, values, Bookmarks._ID + " = ?",
new String[] { Integer.toString(c.getInt(c.getColumnIndexOrThrow(Bookmarks._ID))) });
}
} finally {
if (c != null) {
c.close();
}
}
}
private void upgradeDatabaseFrom15to16(SQLiteDatabase db) {
db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
createCombinedViewOn16(db);
}
private void upgradeDatabaseFrom16to17(SQLiteDatabase db) {
// Purge any 0-byte favicons/thumbnails
try {
db.execSQL("DELETE FROM " + TABLE_FAVICONS +
" WHERE length(" + Favicons.DATA + ") = 0");
db.execSQL("DELETE FROM " + TABLE_THUMBNAILS +
" WHERE length(" + Thumbnails.DATA + ") = 0");
} catch (SQLException e) {
Log.e(LOGTAG, "Error purging invalid favicons or thumbnails", e);
}
}
/*
* Moves reading list items from 'bookmarks' table to 'reading_list' table. Uses the
* same item GUID.
*/
private void upgradeDatabaseFrom17to18(SQLiteDatabase db) {
debug("Moving reading list items from 'bookmarks' table to 'reading_list' table");
final String selection = Bookmarks.PARENT + " = ? AND " + Bookmarks.IS_DELETED + " = ? ";
final String[] selectionArgs = { String.valueOf(Bookmarks.FIXED_READING_LIST_ID), "0" };
final String[] projection = { Bookmarks._ID,
Bookmarks.GUID,
Bookmarks.URL,
Bookmarks.DATE_MODIFIED,
Bookmarks.DATE_CREATED,
Bookmarks.TITLE };
Cursor cursor = null;
try {
// Start transaction
db.beginTransaction();
// Create 'reading_list' table
createReadingListTable(db);
// Get all the reading list items from bookmarks table
cursor = db.query(TABLE_BOOKMARKS, projection, selection, selectionArgs,
null, null, null);
// Insert reading list items into reading_list table
while (cursor.moveToNext()) {
debug(DatabaseUtils.dumpCurrentRowToString(cursor));
ContentValues values = new ContentValues();
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.URL, values, ReadingListItems.URL);
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.GUID, values, ReadingListItems.GUID);
DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.TITLE, values, ReadingListItems.TITLE);
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_CREATED, values, ReadingListItems.DATE_CREATED);
DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_MODIFIED, values, ReadingListItems.DATE_MODIFIED);
db.insertOrThrow(TABLE_READING_LIST, null, values);
}
// Delete reading list items from bookmarks table
db.delete(TABLE_BOOKMARKS,
Bookmarks.PARENT + " = ? ",
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
// Delete reading list special folder
db.delete(TABLE_BOOKMARKS,
Bookmarks._ID + " = ? ",
new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
// Done
db.setTransactionSuccessful();
} catch (SQLException e) {
Log.e(LOGTAG, "Error migrating reading list items", e);
} finally {
if (cursor != null) {
cursor.close();
}
db.endTransaction();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
debug("Upgrading browser.db: " + db.getPath() + " from " +
oldVersion + " to " + newVersion);
// 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:
upgradeDatabaseFrom1to2(db);
break;
case 3:
upgradeDatabaseFrom2to3(db);
break;
case 4:
upgradeDatabaseFrom3to4(db);
break;
case 5:
upgradeDatabaseFrom4to5(db);
break;
case 6:
upgradeDatabaseFrom5to6(db);
break;
case 7:
upgradeDatabaseFrom6to7(db);
break;
case 8:
upgradeDatabaseFrom7to8(db);
break;
case 9:
upgradeDatabaseFrom8to9(db);
break;
case 10:
upgradeDatabaseFrom9to10(db);
break;
case 11:
upgradeDatabaseFrom10to11(db);
break;
case 12:
upgradeDatabaseFrom11to12(db);
break;
case 13:
upgradeDatabaseFrom12to13(db);
break;
case 14:
upgradeDatabaseFrom13to14(db);
break;
case 15:
upgradeDatabaseFrom14to15(db);
break;
case 16:
upgradeDatabaseFrom15to16(db);
break;
case 17:
upgradeDatabaseFrom16to17(db);
break;
case 18:
upgradeDatabaseFrom17to18(db);
break;
}
}
// If an upgrade after 12->13 fails, the entire upgrade is rolled
// back, but we can't undo the deletion of favicon_urls.db if we
// delete this in step 13; therefore, we wait until all steps are
// complete before removing it.
if (oldVersion < 13 && newVersion >= 13
&& mContext.getDatabasePath(Obsolete.FAVICON_DB).exists()
&& !mContext.deleteDatabase(Obsolete.FAVICON_DB)) {
throw new SQLException("Could not delete " + Obsolete.FAVICON_DB);
}
}
@Override
public void onOpen(SQLiteDatabase db) {
debug("Opening browser.db: " + db.getPath());
Cursor cursor = null;
try {
cursor = db.rawQuery("PRAGMA foreign_keys=ON", null);
} finally {
if (cursor != null)
cursor.close();
}
cursor = null;
try {
cursor = db.rawQuery("PRAGMA synchronous=NORMAL", null);
} finally {
if (cursor != null)
cursor.close();
}
// From Honeycomb on, it's possible to run several db
// commands in parallel using multiple connections.
if (Build.VERSION.SDK_INT >= 11) {
// Modern Android allows WAL to be enabled through a mode flag.
if (Build.VERSION.SDK_INT < 16) {
db.enableWriteAheadLogging();
// This does nothing on 16+.
db.setLockingEnabled(false);
}
} 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();
}
}
}
static final String qualifyColumn(String table, String column) {
return DBUtils.qualifyColumn(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);
}
}
private Integer getMobileFolderId(SQLiteDatabase db) {
Cursor c = null;
try {
c = db.query(TABLE_BOOKMARKS,
mobileIdColumns,
Bookmarks.GUID + " = ?",
mobileIdSelectionArgs,
null, null, null);
if (c == null || !c.moveToFirst())
return null;
return c.getInt(c.getColumnIndex(Bookmarks._ID));
} finally {
if (c != null)
c.close();
}
}
private long insertFavicon(SQLiteDatabase db, ContentValues values) {
// This method is a dupicate of BrowserProvider.insertFavicon.
// If changes are needed, please update both
String faviconUrl = values.getAsString(Favicons.URL);
String pageUrl = null;
long faviconId;
trace("Inserting favicon for URL: " + faviconUrl);
DBUtils.stripEmptyByteArray(values, Favicons.DATA);
// Extract the page URL from the ContentValues
if (values.containsKey(Favicons.PAGE_URL)) {
pageUrl = values.getAsString(Favicons.PAGE_URL);
values.remove(Favicons.PAGE_URL);
}
// If no URL is provided, insert using the default one.
if (TextUtils.isEmpty(faviconUrl) && !TextUtils.isEmpty(pageUrl)) {
values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl));
}
long now = System.currentTimeMillis();
values.put(Favicons.DATE_CREATED, now);
values.put(Favicons.DATE_MODIFIED, now);
faviconId = db.insertOrThrow(TABLE_FAVICONS, null, values);
if (pageUrl != null) {
ContentValues updateValues = new ContentValues(1);
updateValues.put(FaviconColumns.FAVICON_ID, faviconId);
db.update(TABLE_HISTORY,
updateValues,
History.URL + " = ?",
new String[] { pageUrl });
db.update(TABLE_BOOKMARKS,
updateValues,
Bookmarks.URL + " = ?",
new String[] { pageUrl });
}
return faviconId;
}
private interface BookmarkMigrator {
public void updateForNewTable(ContentValues bookmark);
}
private class BookmarkMigrator3to4 implements BookmarkMigrator {
@Override
public void updateForNewTable(ContentValues bookmark) {
Integer isFolder = bookmark.getAsInteger("folder");
if (isFolder == null || isFolder != 1) {
bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_BOOKMARK);
} else {
bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
}
bookmark.remove("folder");
}
}
}