mobile/android/base/db/BrowserDatabaseHelper.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.db;
     8 import java.io.ByteArrayOutputStream;
     9 import java.io.File;
    10 import java.lang.reflect.Field;
    11 import java.util.ArrayList;
    12 import java.util.List;
    13 import java.util.Locale;
    14 import java.util.regex.Matcher;
    15 import java.util.regex.Pattern;
    17 import org.json.JSONArray;
    18 import org.json.JSONException;
    19 import org.json.JSONObject;
    20 import org.mozilla.gecko.AppConstants;
    21 import org.mozilla.gecko.Distribution;
    22 import org.mozilla.gecko.R;
    23 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
    24 import org.mozilla.gecko.db.BrowserContract.Combined;
    25 import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
    26 import org.mozilla.gecko.db.BrowserContract.Favicons;
    27 import org.mozilla.gecko.db.BrowserContract.History;
    28 import org.mozilla.gecko.db.BrowserContract.Obsolete;
    29 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
    30 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
    31 import org.mozilla.gecko.gfx.BitmapUtils;
    32 import org.mozilla.gecko.sync.Utils;
    33 import org.mozilla.gecko.util.GeckoJarReader;
    34 import org.mozilla.gecko.util.ThreadUtils;
    36 import android.content.ContentValues;
    37 import android.content.Context;
    38 import android.database.Cursor;
    39 import android.database.DatabaseUtils;
    40 import android.database.SQLException;
    41 import android.database.sqlite.SQLiteDatabase;
    42 import android.database.sqlite.SQLiteOpenHelper;
    43 import android.graphics.Bitmap;
    44 import android.net.Uri;
    45 import android.os.Build;
    46 import android.text.TextUtils;
    47 import android.util.Log;
    50 final class BrowserDatabaseHelper extends SQLiteOpenHelper {
    52     private static final String LOGTAG = "GeckoBrowserDBHelper";
    53     public static final int DATABASE_VERSION = 18;
    54     public static final String DATABASE_NAME = "browser.db";
    56     final protected Context mContext;
    58     static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
    59     static final String TABLE_HISTORY = History.TABLE_NAME;
    60     static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
    61     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
    62     static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
    64     static final String VIEW_COMBINED = Combined.VIEW_NAME;
    65     static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
    66     static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
    67     static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
    69     static final String TABLE_BOOKMARKS_JOIN_FAVICONS = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
    70             TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " = " +
    71             qualifyColumn(TABLE_FAVICONS, Favicons._ID);
    73     static final String TABLE_HISTORY_JOIN_FAVICONS = TABLE_HISTORY + " LEFT OUTER JOIN " +
    74             TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " = " +
    75             qualifyColumn(TABLE_FAVICONS, Favicons._ID);
    77     static final String TABLE_BOOKMARKS_TMP = TABLE_BOOKMARKS + "_tmp";
    78     static final String TABLE_HISTORY_TMP = TABLE_HISTORY + "_tmp";
    79     static final String TABLE_IMAGES_TMP = Obsolete.TABLE_IMAGES + "_tmp";
    81     private static final String[] mobileIdColumns = new String[] { Bookmarks._ID };
    82     private static final String[] mobileIdSelectionArgs = new String[] { Bookmarks.MOBILE_FOLDER_GUID };
    84     public BrowserDatabaseHelper(Context context, String databasePath) {
    85         super(context, databasePath, null, DATABASE_VERSION);
    86         mContext = context;
    87     }
    89     private void createBookmarksTable(SQLiteDatabase db) {
    90         debug("Creating " + TABLE_BOOKMARKS + " table");
    92         // Android versions older than Froyo ship with an sqlite
    93         // that doesn't support foreign keys.
    94         String foreignKeyOnParent = null;
    95         if (Build.VERSION.SDK_INT >= 8) {
    96             foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
    97                 ") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
    98         }
   100         db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
   101                 Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   102                 Bookmarks.TITLE + " TEXT," +
   103                 Bookmarks.URL + " TEXT," +
   104                 Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
   105                 Bookmarks.PARENT + " INTEGER," +
   106                 Bookmarks.POSITION + " INTEGER NOT NULL," +
   107                 Bookmarks.KEYWORD + " TEXT," +
   108                 Bookmarks.DESCRIPTION + " TEXT," +
   109                 Bookmarks.TAGS + " TEXT," +
   110                 Bookmarks.DATE_CREATED + " INTEGER," +
   111                 Bookmarks.DATE_MODIFIED + " INTEGER," +
   112                 Bookmarks.GUID + " TEXT NOT NULL," +
   113                 Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
   114                 (foreignKeyOnParent != null ? foreignKeyOnParent : "") +
   115                 ");");
   117         db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
   118                 + Bookmarks.URL + ")");
   119         db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
   120                 + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
   121         db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
   122                 + Bookmarks.GUID + ")");
   123         db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
   124                 + Bookmarks.DATE_MODIFIED + ")");
   125     }
   127     private void createBookmarksTableOn13(SQLiteDatabase db) {
   128         debug("Creating " + TABLE_BOOKMARKS + " table");
   130         // Android versions older than Froyo ship with an sqlite
   131         // that doesn't support foreign keys.
   132         String foreignKeyOnParent = null;
   133         if (Build.VERSION.SDK_INT >= 8) {
   134             foreignKeyOnParent = ", FOREIGN KEY (" + Bookmarks.PARENT +
   135                 ") REFERENCES " + TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")";
   136         }
   138         db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
   139                 Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   140                 Bookmarks.TITLE + " TEXT," +
   141                 Bookmarks.URL + " TEXT," +
   142                 Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
   143                 Bookmarks.PARENT + " INTEGER," +
   144                 Bookmarks.POSITION + " INTEGER NOT NULL," +
   145                 Bookmarks.KEYWORD + " TEXT," +
   146                 Bookmarks.DESCRIPTION + " TEXT," +
   147                 Bookmarks.TAGS + " TEXT," +
   148                 Bookmarks.FAVICON_ID + " INTEGER," +
   149                 Bookmarks.DATE_CREATED + " INTEGER," +
   150                 Bookmarks.DATE_MODIFIED + " INTEGER," +
   151                 Bookmarks.GUID + " TEXT NOT NULL," +
   152                 Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
   153                 (foreignKeyOnParent != null ? foreignKeyOnParent : "") +
   154                 ");");
   156         db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
   157                 + Bookmarks.URL + ")");
   158         db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
   159                 + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
   160         db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
   161                 + Bookmarks.GUID + ")");
   162         db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
   163                 + Bookmarks.DATE_MODIFIED + ")");
   164     }
   166     private void createHistoryTable(SQLiteDatabase db) {
   167         debug("Creating " + TABLE_HISTORY + " table");
   168         db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
   169                 History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   170                 History.TITLE + " TEXT," +
   171                 History.URL + " TEXT NOT NULL," +
   172                 History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
   173                 History.DATE_LAST_VISITED + " INTEGER," +
   174                 History.DATE_CREATED + " INTEGER," +
   175                 History.DATE_MODIFIED + " INTEGER," +
   176                 History.GUID + " TEXT NOT NULL," +
   177                 History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
   178                 ");");
   180         db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
   181                 + History.URL + ")");
   182         db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
   183                 + History.GUID + ")");
   184         db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
   185                 + History.DATE_MODIFIED + ")");
   186         db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
   187                 + History.DATE_LAST_VISITED + ")");
   188     }
   190     private void createHistoryTableOn13(SQLiteDatabase db) {
   191         debug("Creating " + TABLE_HISTORY + " table");
   192         db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
   193                 History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   194                 History.TITLE + " TEXT," +
   195                 History.URL + " TEXT NOT NULL," +
   196                 History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
   197                 History.FAVICON_ID + " INTEGER," +
   198                 History.DATE_LAST_VISITED + " INTEGER," +
   199                 History.DATE_CREATED + " INTEGER," +
   200                 History.DATE_MODIFIED + " INTEGER," +
   201                 History.GUID + " TEXT NOT NULL," +
   202                 History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
   203                 ");");
   205         db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + "("
   206                 + History.URL + ")");
   207         db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + "("
   208                 + History.GUID + ")");
   209         db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + "("
   210                 + History.DATE_MODIFIED + ")");
   211         db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + "("
   212                 + History.DATE_LAST_VISITED + ")");
   213     }
   215     private void createImagesTable(SQLiteDatabase db) {
   216         debug("Creating " + Obsolete.TABLE_IMAGES + " table");
   217         db.execSQL("CREATE TABLE " + Obsolete.TABLE_IMAGES + " (" +
   218                 Obsolete.Images._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   219                 Obsolete.Images.URL + " TEXT UNIQUE NOT NULL," +
   220                 Obsolete.Images.FAVICON + " BLOB," +
   221                 Obsolete.Images.FAVICON_URL + " TEXT," +
   222                 Obsolete.Images.THUMBNAIL + " BLOB," +
   223                 Obsolete.Images.DATE_CREATED + " INTEGER," +
   224                 Obsolete.Images.DATE_MODIFIED + " INTEGER," +
   225                 Obsolete.Images.GUID + " TEXT NOT NULL," +
   226                 Obsolete.Images.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
   227                 ");");
   229         db.execSQL("CREATE INDEX images_url_index ON " + Obsolete.TABLE_IMAGES + "("
   230                 + Obsolete.Images.URL + ")");
   231         db.execSQL("CREATE UNIQUE INDEX images_guid_index ON " + Obsolete.TABLE_IMAGES + "("
   232                 + Obsolete.Images.GUID + ")");
   233         db.execSQL("CREATE INDEX images_modified_index ON " + Obsolete.TABLE_IMAGES + "("
   234                 + Obsolete.Images.DATE_MODIFIED + ")");
   235     }
   237     private void createFaviconsTable(SQLiteDatabase db) {
   238         debug("Creating " + TABLE_FAVICONS + " table");
   239         db.execSQL("CREATE TABLE " + TABLE_FAVICONS + " (" +
   240                 Favicons._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   241                 Favicons.URL + " TEXT UNIQUE," +
   242                 Favicons.DATA + " BLOB," +
   243                 Favicons.DATE_CREATED + " INTEGER," +
   244                 Favicons.DATE_MODIFIED + " INTEGER" +
   245                 ");");
   247         db.execSQL("CREATE INDEX favicons_url_index ON " + TABLE_FAVICONS + "("
   248                 + Favicons.URL + ")");
   249         db.execSQL("CREATE INDEX favicons_modified_index ON " + TABLE_FAVICONS + "("
   250                 + Favicons.DATE_MODIFIED + ")");
   251     }
   253     private void createThumbnailsTable(SQLiteDatabase db) {
   254         debug("Creating " + TABLE_THUMBNAILS + " table");
   255         db.execSQL("CREATE TABLE " + TABLE_THUMBNAILS + " (" +
   256                 Thumbnails._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
   257                 Thumbnails.URL + " TEXT UNIQUE," +
   258                 Thumbnails.DATA + " BLOB" +
   259                 ");");
   261         db.execSQL("CREATE INDEX thumbnails_url_index ON " + TABLE_THUMBNAILS + "("
   262                 + Thumbnails.URL + ")");
   263     }
   265     private void createBookmarksWithImagesView(SQLiteDatabase db) {
   266         debug("Creating " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " view");
   268         db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES + " AS " +
   269                 "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
   270                 ", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
   271                 Obsolete.TABLE_BOOKMARKS_JOIN_IMAGES);
   272     }
   274     private void createBookmarksWithFaviconsView(SQLiteDatabase db) {
   275         debug("Creating " + VIEW_BOOKMARKS_WITH_FAVICONS + " view");
   277         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_FAVICONS + " AS " +
   278                 "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
   279                 ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Bookmarks.FAVICON +
   280                 ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Bookmarks.FAVICON_URL +
   281                 " FROM " + TABLE_BOOKMARKS_JOIN_FAVICONS);
   282     }
   284     private void createHistoryWithImagesView(SQLiteDatabase db) {
   285         debug("Creating " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " view");
   287         db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES + " AS " +
   288                 "SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
   289                 ", " + Obsolete.Images.FAVICON + ", " + Obsolete.Images.THUMBNAIL + " FROM " +
   290                 Obsolete.TABLE_HISTORY_JOIN_IMAGES);
   291     }
   293     private void createHistoryWithFaviconsView(SQLiteDatabase db) {
   294         debug("Creating " + VIEW_HISTORY_WITH_FAVICONS + " view");
   296         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_HISTORY_WITH_FAVICONS + " AS " +
   297                 "SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
   298                 ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + History.FAVICON +
   299                 ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + History.FAVICON_URL +
   300                 " FROM " + TABLE_HISTORY_JOIN_FAVICONS);
   301     }
   303     private void createCombinedWithImagesView(SQLiteDatabase db) {
   304         debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
   306         db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
   307                 " SELECT " + Combined.BOOKMARK_ID + ", " +
   308                              Combined.HISTORY_ID + ", " +
   309                              // We need to return an _id column because CursorAdapter requires it for its
   310                              // default implementation for the getItemId() method. However, since
   311                              // we're not using this feature in the parts of the UI using this view,
   312                              // we can just use 0 for all rows.
   313                              "0 AS " + Combined._ID + ", " +
   314                              Combined.URL + ", " +
   315                              Combined.TITLE + ", " +
   316                              Combined.VISITS + ", " +
   317                              Combined.DATE_LAST_VISITED + ", " +
   318                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
   319                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
   320                 " FROM (" +
   321                     // Bookmarks without history.
   322                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   323                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
   324                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
   325                                  "-1 AS " + Combined.HISTORY_ID + ", " +
   326                                  "-1 AS " + Combined.VISITS + ", " +
   327                                  "-1 AS " + Combined.DATE_LAST_VISITED +
   328                     " FROM " + TABLE_BOOKMARKS +
   329                     " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
   330                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
   331                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
   332                                     " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
   333                     " UNION ALL" +
   334                     // History with and without bookmark.
   335                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   336                                  qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
   337                                  // Prioritze bookmark titles over history titles, since the user may have
   338                                  // customized the title for a bookmark.
   339                                  "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
   340                                                qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
   341                                  qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
   342                                  qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
   343                                  qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
   344                     " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
   345                         " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
   346                     " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
   347                                 qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
   348                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
   349                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
   350                 ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
   351                     " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
   352     }
   354     private void createCombinedWithImagesViewOn9(SQLiteDatabase db) {
   355         debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
   357         db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
   358                 " SELECT " + Combined.BOOKMARK_ID + ", " +
   359                              Combined.HISTORY_ID + ", " +
   360                              // We need to return an _id column because CursorAdapter requires it for its
   361                              // default implementation for the getItemId() method. However, since
   362                              // we're not using this feature in the parts of the UI using this view,
   363                              // we can just use 0 for all rows.
   364                              "0 AS " + Combined._ID + ", " +
   365                              Combined.URL + ", " +
   366                              Combined.TITLE + ", " +
   367                              Combined.VISITS + ", " +
   368                              Combined.DISPLAY + ", " +
   369                              Combined.DATE_LAST_VISITED + ", " +
   370                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
   371                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
   372                 " FROM (" +
   373                     // Bookmarks without history.
   374                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   375                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
   376                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
   377                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   378                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   379                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   380                                  "-1 AS " + Combined.HISTORY_ID + ", " +
   381                                  "-1 AS " + Combined.VISITS + ", " +
   382                                  "-1 AS " + Combined.DATE_LAST_VISITED +
   383                     " FROM " + TABLE_BOOKMARKS +
   384                     " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
   385                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
   386                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
   387                                     " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
   388                     " UNION ALL" +
   389                     // History with and without bookmark.
   390                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   391                                  qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
   392                                  // Prioritze bookmark titles over history titles, since the user may have
   393                                  // customized the title for a bookmark.
   394                                  "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
   395                                                qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
   396                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   397                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   398                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   399                                  qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
   400                                  qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
   401                                  qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
   402                     " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
   403                         " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
   404                     " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
   405                                 qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
   406                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
   407                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
   408                 ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
   409                     " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
   410     }
   412     private void createCombinedWithImagesViewOn10(SQLiteDatabase db) {
   413         debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
   415         db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
   416                 " SELECT " + Combined.BOOKMARK_ID + ", " +
   417                              Combined.HISTORY_ID + ", " +
   418                              // We need to return an _id column because CursorAdapter requires it for its
   419                              // default implementation for the getItemId() method. However, since
   420                              // we're not using this feature in the parts of the UI using this view,
   421                              // we can just use 0 for all rows.
   422                              "0 AS " + Combined._ID + ", " +
   423                              Combined.URL + ", " +
   424                              Combined.TITLE + ", " +
   425                              Combined.VISITS + ", " +
   426                              Combined.DISPLAY + ", " +
   427                              Combined.DATE_LAST_VISITED + ", " +
   428                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
   429                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
   430                 " FROM (" +
   431                     // Bookmarks without history.
   432                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   433                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
   434                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
   435                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   436                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   437                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   438                                  "-1 AS " + Combined.HISTORY_ID + ", " +
   439                                  "-1 AS " + Combined.VISITS + ", " +
   440                                  "-1 AS " + Combined.DATE_LAST_VISITED +
   441                     " FROM " + TABLE_BOOKMARKS +
   442                     " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
   443                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
   444                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
   445                                     " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
   446                     " UNION ALL" +
   447                     // History with and without bookmark.
   448                     " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
   449                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
   450                                  qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
   451                                  // Prioritze bookmark titles over history titles, since the user may have
   452                                  // customized the title for a bookmark.
   453                                  "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
   454                                                qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
   455                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   456                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   457                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   458                                  qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
   459                                  qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
   460                                  qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
   461                     " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
   462                         " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
   463                     " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
   464                                 qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
   465                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
   466                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ")" +
   467                 ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
   468                     " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
   469     }
   471     private void createCombinedWithImagesViewOn11(SQLiteDatabase db) {
   472         debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
   474         db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
   475                 " SELECT " + Combined.BOOKMARK_ID + ", " +
   476                              Combined.HISTORY_ID + ", " +
   477                              // We need to return an _id column because CursorAdapter requires it for its
   478                              // default implementation for the getItemId() method. However, since
   479                              // we're not using this feature in the parts of the UI using this view,
   480                              // we can just use 0 for all rows.
   481                              "0 AS " + Combined._ID + ", " +
   482                              Combined.URL + ", " +
   483                              Combined.TITLE + ", " +
   484                              Combined.VISITS + ", " +
   485                              Combined.DISPLAY + ", " +
   486                              Combined.DATE_LAST_VISITED + ", " +
   487                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
   488                              qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
   489                 " FROM (" +
   490                     // Bookmarks without history.
   491                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   492                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
   493                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
   494                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   495                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   496                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   497                                  "-1 AS " + Combined.HISTORY_ID + ", " +
   498                                  "-1 AS " + Combined.VISITS + ", " +
   499                                  "-1 AS " + Combined.DATE_LAST_VISITED +
   500                     " FROM " + TABLE_BOOKMARKS +
   501                     " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
   502                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
   503                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
   504                                     " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
   505                     " UNION ALL" +
   506                     // History with and without bookmark.
   507                     " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
   508                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
   509                                  qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
   510                                  // Prioritze bookmark titles over history titles, since the user may have
   511                                  // customized the title for a bookmark.
   512                                  "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
   513                                                qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
   514                                  // Only use DISPLAY_READER if the matching bookmark entry inside reading
   515                                  // list folder is not marked as deleted.
   516                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
   517                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
   518                                     " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
   519                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   520                                  qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
   521                                  qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
   522                                  qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
   523                     " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
   524                         " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
   525                     " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
   526                                 qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
   527                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
   528                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
   529                 ") LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
   530                     " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
   531     }
   533     private void createCombinedViewOn12(SQLiteDatabase db) {
   534         debug("Creating " + VIEW_COMBINED + " view");
   536         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
   537                 " SELECT " + Combined.BOOKMARK_ID + ", " +
   538                              Combined.HISTORY_ID + ", " +
   539                              // We need to return an _id column because CursorAdapter requires it for its
   540                              // default implementation for the getItemId() method. However, since
   541                              // we're not using this feature in the parts of the UI using this view,
   542                              // we can just use 0 for all rows.
   543                              "0 AS " + Combined._ID + ", " +
   544                              Combined.URL + ", " +
   545                              Combined.TITLE + ", " +
   546                              Combined.VISITS + ", " +
   547                              Combined.DISPLAY + ", " +
   548                              Combined.DATE_LAST_VISITED +
   549                 " FROM (" +
   550                     // Bookmarks without history.
   551                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   552                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
   553                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
   554                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   555                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   556                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   557                                  "-1 AS " + Combined.HISTORY_ID + ", " +
   558                                  "-1 AS " + Combined.VISITS + ", " +
   559                                  "-1 AS " + Combined.DATE_LAST_VISITED +
   560                     " FROM " + TABLE_BOOKMARKS +
   561                     " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
   562                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
   563                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
   564                                     " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
   565                     " UNION ALL" +
   566                     // History with and without bookmark.
   567                     " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
   568                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
   569                                  qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
   570                                  // Prioritze bookmark titles over history titles, since the user may have
   571                                  // customized the title for a bookmark.
   572                                  "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
   573                                                qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
   574                                  // Only use DISPLAY_READER if the matching bookmark entry inside reading
   575                                  // list folder is not marked as deleted.
   576                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
   577                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
   578                                     " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
   579                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   580                                  qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
   581                                  qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
   582                                  qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED +
   583                     " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
   584                         " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
   585                     " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
   586                                 qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
   587                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
   588                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
   589                 ")");
   591         debug("Creating " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " view");
   593         db.execSQL("CREATE VIEW IF NOT EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES + " AS" +
   594                 " SELECT *, " +
   595                     qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.FAVICON) + " AS " + Combined.FAVICON + ", " +
   596                     qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.THUMBNAIL) + " AS " + Obsolete.Combined.THUMBNAIL +
   597                 " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + Obsolete.TABLE_IMAGES +
   598                     " ON " + Combined.URL + " = " + qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL));
   599     }
   601     private void createCombinedViewOn13(SQLiteDatabase db) {
   602         debug("Creating " + VIEW_COMBINED + " view");
   604         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
   605                 " SELECT " + Combined.BOOKMARK_ID + ", " +
   606                              Combined.HISTORY_ID + ", " +
   607                              // We need to return an _id column because CursorAdapter requires it for its
   608                              // default implementation for the getItemId() method. However, since
   609                              // we're not using this feature in the parts of the UI using this view,
   610                              // we can just use 0 for all rows.
   611                              "0 AS " + Combined._ID + ", " +
   612                              Combined.URL + ", " +
   613                              Combined.TITLE + ", " +
   614                              Combined.VISITS + ", " +
   615                              Combined.DISPLAY + ", " +
   616                              Combined.DATE_LAST_VISITED + ", " +
   617                              Combined.FAVICON_ID +
   618                 " FROM (" +
   619                     // Bookmarks without history.
   620                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   621                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
   622                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
   623                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   624                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   625                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   626                                  "-1 AS " + Combined.HISTORY_ID + ", " +
   627                                  "-1 AS " + Combined.VISITS + ", " +
   628                                  "-1 AS " + Combined.DATE_LAST_VISITED + ", " +
   629                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
   630                     " FROM " + TABLE_BOOKMARKS +
   631                     " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
   632                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
   633                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
   634                                     " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
   635                     " UNION ALL" +
   636                     // History with and without bookmark.
   637                     " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
   638                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +  " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
   639                                  qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
   640                                  // Prioritize bookmark titles over history titles, since the user may have
   641                                  // customized the title for a bookmark.
   642                                  "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
   643                                                qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
   644                                  // Only use DISPLAY_READER if the matching bookmark entry inside reading
   645                                  // list folder is not marked as deleted.
   646                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
   647                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
   648                                     " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
   649                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   650                                  qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
   651                                  qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
   652                                  qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
   653                                  qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
   654                     " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
   655                         " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
   656                     " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
   657                                 qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
   658                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
   659                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
   660                 ")");
   662         debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
   664         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
   665                 " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
   666                     qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
   667                     qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
   668                 " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
   669                     " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
   670     }
   672     private void createCombinedViewOn16(SQLiteDatabase db) {
   673         debug("Creating " + VIEW_COMBINED + " view");
   675         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
   676                 " SELECT " + Combined.BOOKMARK_ID + ", " +
   677                              Combined.HISTORY_ID + ", " +
   678                              // We need to return an _id column because CursorAdapter requires it for its
   679                              // default implementation for the getItemId() method. However, since
   680                              // we're not using this feature in the parts of the UI using this view,
   681                              // we can just use 0 for all rows.
   682                              "0 AS " + Combined._ID + ", " +
   683                              Combined.URL + ", " +
   684                              Combined.TITLE + ", " +
   685                              Combined.VISITS + ", " +
   686                              Combined.DISPLAY + ", " +
   687                              Combined.DATE_LAST_VISITED + ", " +
   688                              Combined.FAVICON_ID +
   689                 " FROM (" +
   690                     // Bookmarks without history.
   691                     " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
   692                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
   693                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
   694                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   695                                     Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " +
   696                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   697                                  "-1 AS " + Combined.HISTORY_ID + ", " +
   698                                  "-1 AS " + Combined.VISITS + ", " +
   699                                  "-1 AS " + Combined.DATE_LAST_VISITED + ", " +
   700                                  qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
   701                     " FROM " + TABLE_BOOKMARKS +
   702                     " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
   703                                 // Ignore pinned bookmarks.
   704                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT)  + " <> " + Bookmarks.FIXED_PINNED_LIST_ID + " AND " +
   705                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED)  + " = 0 AND " +
   706                                 qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
   707                                     " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
   708                     " UNION ALL" +
   709                     // History with and without bookmark.
   710                     " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " +
   711                                     // Give pinned bookmarks a NULL ID so that they're not treated as bookmarks. We can't
   712                                     // completely ignore them here because they're joined with history entries we care about.
   713                                     "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " +
   714                                     Bookmarks.FIXED_PINNED_LIST_ID + " THEN NULL ELSE " +
   715                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " END " +
   716                                  "ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " +
   717                                  qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " +
   718                                  // Prioritize bookmark titles over history titles, since the user may have
   719                                  // customized the title for a bookmark.
   720                                  "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
   721                                                qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " +
   722                                  // Only use DISPLAY_READER if the matching bookmark entry inside reading
   723                                  // list folder is not marked as deleted.
   724                                  "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " +
   725                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID +
   726                                     " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " +
   727                                     Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " +
   728                                  qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " +
   729                                  qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " +
   730                                  qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + ", " +
   731                                  qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
   732                     " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
   733                         " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
   734                     " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " +
   735                                 qualifyColumn(TABLE_HISTORY, History.IS_DELETED)  + " = 0 AND (" +
   736                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
   737                                     qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE)  + " = " + Bookmarks.TYPE_BOOKMARK + ") " +
   738                 ")");
   740         debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
   742         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
   743                 " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
   744                     qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
   745                     qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
   746                 " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
   747                     " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
   748     }
   750     @Override
   751     public void onCreate(SQLiteDatabase db) {
   752         debug("Creating browser.db: " + db.getPath());
   754         createBookmarksTableOn13(db);
   755         createHistoryTableOn13(db);
   756         createFaviconsTable(db);
   757         createThumbnailsTable(db);
   759         createBookmarksWithFaviconsView(db);
   760         createHistoryWithFaviconsView(db);
   761         createCombinedViewOn16(db);
   763         createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
   764             R.string.bookmarks_folder_places, 0);
   766         createOrUpdateAllSpecialFolders(db);
   768         // Create distribution bookmarks before our own default bookmarks
   769         int pos = createDistributionBookmarks(db);
   770         createDefaultBookmarks(db, pos);
   772         createReadingListTable(db);
   773     }
   775     private String getLocalizedProperty(JSONObject bookmark, String property, Locale locale) throws JSONException {
   776         // Try the full locale
   777         String fullLocale = property + "." + locale.toString();
   778         if (bookmark.has(fullLocale)) {
   779             return bookmark.getString(fullLocale);
   780         }
   781         // Try without a variant
   782         if (!TextUtils.isEmpty(locale.getVariant())) {
   783             String noVariant = fullLocale.substring(0, fullLocale.lastIndexOf("_"));
   784             if (bookmark.has(noVariant)) {
   785                 return bookmark.getString(noVariant);
   786             }
   787         }
   788         // Try just the language
   789         String lang = property + "." + locale.getLanguage();
   790         if (bookmark.has(lang)) {
   791             return bookmark.getString(lang);
   792         }
   793         // Default to the non-localized property name
   794         return bookmark.getString(property);
   795     }
   797     // Returns the number of bookmarks inserted in the db
   798     private int createDistributionBookmarks(SQLiteDatabase db) {
   799         JSONArray bookmarks = Distribution.getBookmarks(mContext);
   800         if (bookmarks == null) {
   801             return 0;
   802         }
   804         Locale locale = Locale.getDefault();
   805         int pos = 0;
   806         Integer mobileFolderId = getMobileFolderId(db);
   807         if (mobileFolderId == null) {
   808             Log.e(LOGTAG, "Error creating distribution bookmarks: mobileFolderId is null");
   809             return 0;
   810         }
   812         for (int i = 0; i < bookmarks.length(); i++) {
   813             try {
   814                 final JSONObject bookmark = bookmarks.getJSONObject(i);
   816                 String title = getLocalizedProperty(bookmark, "title", locale);
   817                 final String url = getLocalizedProperty(bookmark, "url", locale);
   818                 createBookmark(db, title, url, pos, mobileFolderId);
   820                 if (bookmark.has("pinned")) {
   821                     try {
   822                         // Create a fake bookmark in the hidden pinned folder to pin bookmark
   823                         // to about:home top sites. Pass pos as the pinned position to pin
   824                         // sites in the order that bookmarks are specified in bookmarks.json.
   825                         if (bookmark.getBoolean("pinned")) {
   826                             createBookmark(db, title, url, pos, Bookmarks.FIXED_PINNED_LIST_ID);
   827                         }
   828                     } catch (JSONException e) {
   829                         Log.e(LOGTAG, "Error pinning bookmark to top sites", e);
   830                     }
   831                 }
   833                 pos++;
   835                 // return early if there is no icon for this bookmark
   836                 if (!bookmark.has("icon")) {
   837                     continue;
   838                 }
   840                 // create icons in a separate thread to avoid blocking about:home on startup
   841                 ThreadUtils.postToBackgroundThread(new Runnable() {
   842                     @Override
   843                     public void run() {
   844                         SQLiteDatabase db = getWritableDatabase();
   845                         try {
   846                             String iconData = bookmark.getString("icon");
   847                             Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconData);
   848                             if (icon != null) {
   849                                 createFavicon(db, url, icon);
   850                             }
   851                         } catch (JSONException e) {
   852                             Log.e(LOGTAG, "Error creating distribution bookmark icon", e);
   853                         }
   854                     }
   855                 });
   856             } catch (JSONException e) {
   857                 Log.e(LOGTAG, "Error creating distribution bookmark", e);
   858             }
   859         }
   860         return pos;
   861     }
   863     private void createReadingListTable(SQLiteDatabase db) {
   864         debug("Creating " + TABLE_READING_LIST + " table");
   866         db.execSQL("CREATE TABLE " + TABLE_READING_LIST + "(" +
   867                     ReadingListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
   868                     ReadingListItems.URL + " TEXT NOT NULL, " +
   869                     ReadingListItems.TITLE + " TEXT, " +
   870                     ReadingListItems.EXCERPT + " TEXT, " +
   871                     ReadingListItems.READ + " TINYINT DEFAULT 0, " +
   872                     ReadingListItems.IS_DELETED + " TINYINT DEFAULT 0, " +
   873                     ReadingListItems.GUID + " TEXT UNIQUE NOT NULL, " +
   874                     ReadingListItems.DATE_MODIFIED + " INTEGER NOT NULL, " +
   875                     ReadingListItems.DATE_CREATED  + " INTEGER NOT NULL, " +
   876                     ReadingListItems.LENGTH + " INTEGER DEFAULT 0 ); ");
   878         db.execSQL("CREATE INDEX reading_list_url ON " + TABLE_READING_LIST + "("
   879                 + ReadingListItems.URL + ")");
   880         db.execSQL("CREATE UNIQUE INDEX reading_list_guid ON " + TABLE_READING_LIST + "("
   881                 + ReadingListItems.GUID + ")");
   882     }
   884     // Inserts default bookmarks, starting at a specified position
   885     private void createDefaultBookmarks(SQLiteDatabase db, int pos) {
   886         Class<?> stringsClass = R.string.class;
   887         Field[] fields = stringsClass.getFields();
   888         Pattern p = Pattern.compile("^bookmarkdefaults_title_");
   890         Integer mobileFolderId = getMobileFolderId(db);
   891         if (mobileFolderId == null) {
   892             Log.e(LOGTAG, "Error creating default bookmarks: mobileFolderId is null");
   893             return;
   894         }
   896         for (int i = 0; i < fields.length; i++) {
   897             final String name = fields[i].getName();
   898             Matcher m = p.matcher(name);
   899             if (!m.find()) {
   900                 continue;
   901             }
   902             try {
   903                 int titleid = fields[i].getInt(null);
   904                 String title = mContext.getString(titleid);
   906                 Field urlField = stringsClass.getField(name.replace("_title_", "_url_"));
   907                 int urlId = urlField.getInt(null);
   908                 final String url = mContext.getString(urlId);
   909                 createBookmark(db, title, url, pos, mobileFolderId);
   911                 // create icons in a separate thread to avoid blocking about:home on startup
   912                 ThreadUtils.postToBackgroundThread(new Runnable() {
   913                     @Override
   914                     public void run() {
   915                         SQLiteDatabase db = getWritableDatabase();
   916                         Bitmap icon = getDefaultFaviconFromPath(name);
   917                         if (icon == null) {
   918                             icon = getDefaultFaviconFromDrawable(name);
   919                         }
   920                         if (icon != null) {
   921                             createFavicon(db, url, icon);
   922                         }
   923                     }
   924                 });
   925                 pos++;
   926             } catch (java.lang.IllegalAccessException ex) {
   927                 Log.e(LOGTAG, "Can't create bookmark " + name, ex);
   928             } catch (java.lang.NoSuchFieldException ex) {
   929                 Log.e(LOGTAG, "Can't create bookmark " + name, ex);
   930             }
   931         }
   932     }
   934     private void createBookmark(SQLiteDatabase db, String title, String url, int pos, int parent) {
   935         ContentValues bookmarkValues = new ContentValues();
   936         bookmarkValues.put(Bookmarks.PARENT, parent);
   938         long now = System.currentTimeMillis();
   939         bookmarkValues.put(Bookmarks.DATE_CREATED, now);
   940         bookmarkValues.put(Bookmarks.DATE_MODIFIED, now);
   942         bookmarkValues.put(Bookmarks.TITLE, title);
   943         bookmarkValues.put(Bookmarks.URL, url);
   944         bookmarkValues.put(Bookmarks.GUID, Utils.generateGuid());
   945         bookmarkValues.put(Bookmarks.POSITION, pos);
   946         db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, bookmarkValues);
   947     }
   949     private void createFavicon(SQLiteDatabase db, String url, Bitmap icon) {
   950         ByteArrayOutputStream stream = new ByteArrayOutputStream();
   952         ContentValues iconValues = new ContentValues();
   953         iconValues.put(Favicons.PAGE_URL, url);
   955         byte[] data = null;
   956         if (icon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
   957             data = stream.toByteArray();
   958         } else {
   959             Log.w(LOGTAG, "Favicon compression failed.");
   960         }
   961         iconValues.put(Favicons.DATA, data);
   963         insertFavicon(db, iconValues);
   964     }
   966     private Bitmap getDefaultFaviconFromPath(String name) {
   967         Class<?> stringClass = R.string.class;
   968         try {
   969             // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
   970             Field faviconField = stringClass.getField(name.replace("_title_", "_favicon_"));
   971             if (faviconField == null) {
   972                 return null;
   973             }
   974             int faviconId = faviconField.getInt(null);
   975             String path = mContext.getString(faviconId);
   977             String apkPath = mContext.getPackageResourcePath();
   978             File apkFile = new File(apkPath);
   979             String bitmapPath = "jar:jar:" + apkFile.toURI() + "!/" + AppConstants.OMNIJAR_NAME + "!/" + path;
   980             return GeckoJarReader.getBitmap(mContext.getResources(), bitmapPath);
   981         } catch (java.lang.IllegalAccessException ex) {
   982             Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
   983         } catch (java.lang.NoSuchFieldException ex) {
   984             // If the field does not exist, that means we intend to load via a drawable
   985         }
   986         return null;
   987     }
   989     private Bitmap getDefaultFaviconFromDrawable(String name) {
   990         Class<?> drawablesClass = R.drawable.class;
   991         try {
   992             // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
   993             Field faviconField = drawablesClass.getField(name.replace("_title_", "_favicon_"));
   994             if (faviconField == null) {
   995                 return null;
   996             }
   997             int faviconId = faviconField.getInt(null);
   998             return BitmapUtils.decodeResource(mContext, faviconId);
   999         } catch (java.lang.IllegalAccessException ex) {
  1000             Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
  1001         } catch (java.lang.NoSuchFieldException ex) {
  1002             // If the field does not exist, that means we intend to load via a file path
  1004         return null;
  1007     private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
  1008         createOrUpdateSpecialFolder(db, Bookmarks.MOBILE_FOLDER_GUID,
  1009             R.string.bookmarks_folder_mobile, 0);
  1010         createOrUpdateSpecialFolder(db, Bookmarks.TOOLBAR_FOLDER_GUID,
  1011             R.string.bookmarks_folder_toolbar, 1);
  1012         createOrUpdateSpecialFolder(db, Bookmarks.MENU_FOLDER_GUID,
  1013             R.string.bookmarks_folder_menu, 2);
  1014         createOrUpdateSpecialFolder(db, Bookmarks.TAGS_FOLDER_GUID,
  1015             R.string.bookmarks_folder_tags, 3);
  1016         createOrUpdateSpecialFolder(db, Bookmarks.UNFILED_FOLDER_GUID,
  1017             R.string.bookmarks_folder_unfiled, 4);
  1018         createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
  1019             R.string.bookmarks_folder_reading_list, 5);
  1020         createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
  1021             R.string.bookmarks_folder_pinned, 6);
  1024     private void createOrUpdateSpecialFolder(SQLiteDatabase db,
  1025             String guid, int titleId, int position) {
  1026         ContentValues values = new ContentValues();
  1027         values.put(Bookmarks.GUID, guid);
  1028         values.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
  1029         values.put(Bookmarks.POSITION, position);
  1031         if (guid.equals(Bookmarks.PLACES_FOLDER_GUID))
  1032             values.put(Bookmarks._ID, Bookmarks.FIXED_ROOT_ID);
  1033         else if (guid.equals(Bookmarks.READING_LIST_FOLDER_GUID))
  1034             values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID);
  1035         else if (guid.equals(Bookmarks.PINNED_FOLDER_GUID))
  1036             values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID);
  1038         // Set the parent to 0, which sync assumes is the root
  1039         values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
  1041         String title = mContext.getResources().getString(titleId);
  1042         values.put(Bookmarks.TITLE, title);
  1044         long now = System.currentTimeMillis();
  1045         values.put(Bookmarks.DATE_CREATED, now);
  1046         values.put(Bookmarks.DATE_MODIFIED, now);
  1048         int updated = db.update(TABLE_BOOKMARKS, values,
  1049                                 Bookmarks.GUID + " = ?",
  1050                                 new String[] { guid });
  1052         if (updated == 0) {
  1053             db.insert(TABLE_BOOKMARKS, Bookmarks.GUID, values);
  1054             debug("Inserted special folder: " + guid);
  1055         } else {
  1056             debug("Updated special folder: " + guid);
  1060     private boolean isSpecialFolder(ContentValues values) {
  1061         String guid = values.getAsString(Bookmarks.GUID);
  1062         if (guid == null)
  1063             return false;
  1065         return guid.equals(Bookmarks.MOBILE_FOLDER_GUID) ||
  1066                guid.equals(Bookmarks.MENU_FOLDER_GUID) ||
  1067                guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID) ||
  1068                guid.equals(Bookmarks.UNFILED_FOLDER_GUID) ||
  1069                guid.equals(Bookmarks.TAGS_FOLDER_GUID);
  1072     private void migrateBookmarkFolder(SQLiteDatabase db, int folderId,
  1073             BookmarkMigrator migrator) {
  1074         Cursor c = null;
  1076         debug("Migrating bookmark folder with id = " + folderId);
  1078         String selection = Bookmarks.PARENT + " = " + folderId;
  1079         String[] selectionArgs = null;
  1081         boolean isRootFolder = (folderId == Bookmarks.FIXED_ROOT_ID);
  1083         // If we're loading the root folder, we have to account for
  1084         // any previously created special folder that was created without
  1085         // setting a parent id (e.g. mobile folder) and making sure we're
  1086         // not adding any infinite recursion as root's parent is root itself.
  1087         if (isRootFolder) {
  1088             selection = Bookmarks.GUID + " != ?" + " AND (" +
  1089                         selection + " OR " + Bookmarks.PARENT + " = NULL)";
  1090             selectionArgs = new String[] { Bookmarks.PLACES_FOLDER_GUID };
  1093         List<Integer> subFolders = new ArrayList<Integer>();
  1094         List<ContentValues> invalidSpecialEntries = new ArrayList<ContentValues>();
  1096         try {
  1097             c = db.query(TABLE_BOOKMARKS_TMP,
  1098                          null,
  1099                          selection,
  1100                          selectionArgs,
  1101                          null, null, null);
  1103             // The key point here is that bookmarks should be added in
  1104             // parent order to avoid any problems with the foreign key
  1105             // in Bookmarks.PARENT.
  1106             while (c.moveToNext()) {
  1107                 ContentValues values = new ContentValues();
  1109                 // We're using a null projection in the query which
  1110                 // means we're getting all columns from the table.
  1111                 // It's safe to simply transform the row into the
  1112                 // values to be inserted on the new table.
  1113                 DatabaseUtils.cursorRowToContentValues(c, values);
  1115                 boolean isSpecialFolder = isSpecialFolder(values);
  1117                 // The mobile folder used to be created with PARENT = NULL.
  1118                 // We want fix that here.
  1119                 if (values.getAsLong(Bookmarks.PARENT) == null && isSpecialFolder)
  1120                     values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
  1122                 if (isRootFolder && !isSpecialFolder) {
  1123                     invalidSpecialEntries.add(values);
  1124                     continue;
  1127                 if (migrator != null)
  1128                     migrator.updateForNewTable(values);
  1130                 debug("Migrating bookmark: " + values.getAsString(Bookmarks.TITLE));
  1131                 db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
  1133                 Integer type = values.getAsInteger(Bookmarks.TYPE);
  1134                 if (type != null && type == Bookmarks.TYPE_FOLDER)
  1135                     subFolders.add(values.getAsInteger(Bookmarks._ID));
  1137         } finally {
  1138             if (c != null)
  1139                 c.close();
  1142         // At this point is safe to assume that the mobile folder is
  1143         // in the new table given that we've always created it on
  1144         // database creation time.
  1145         final int nInvalidSpecialEntries = invalidSpecialEntries.size();
  1146         if (nInvalidSpecialEntries > 0) {
  1147             Integer mobileFolderId = getMobileFolderId(db);
  1148             if (mobileFolderId == null) {
  1149                 Log.e(LOGTAG, "Error migrating invalid special folder entries: mobile folder id is null");
  1150                 return;
  1153             debug("Found " + nInvalidSpecialEntries + " invalid special folder entries");
  1154             for (int i = 0; i < nInvalidSpecialEntries; i++) {
  1155                 ContentValues values = invalidSpecialEntries.get(i);
  1156                 values.put(Bookmarks.PARENT, mobileFolderId);
  1158                 db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
  1162         final int nSubFolders = subFolders.size();
  1163         for (int i = 0; i < nSubFolders; i++) {
  1164             int subFolderId = subFolders.get(i);
  1165             migrateBookmarkFolder(db, subFolderId, migrator);
  1169     private void migrateBookmarksTable(SQLiteDatabase db) {
  1170         migrateBookmarksTable(db, null);
  1173     private void migrateBookmarksTable(SQLiteDatabase db, BookmarkMigrator migrator) {
  1174         debug("Renaming bookmarks table to " + TABLE_BOOKMARKS_TMP);
  1175         db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS +
  1176                    " RENAME TO " + TABLE_BOOKMARKS_TMP);
  1178         debug("Dropping views and indexes related to " + TABLE_BOOKMARKS);
  1179         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
  1181         db.execSQL("DROP INDEX IF EXISTS bookmarks_url_index");
  1182         db.execSQL("DROP INDEX IF EXISTS bookmarks_type_deleted_index");
  1183         db.execSQL("DROP INDEX IF EXISTS bookmarks_guid_index");
  1184         db.execSQL("DROP INDEX IF EXISTS bookmarks_modified_index");
  1186         createBookmarksTable(db);
  1187         createBookmarksWithImagesView(db);
  1189         createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
  1190             R.string.bookmarks_folder_places, 0);
  1192         migrateBookmarkFolder(db, Bookmarks.FIXED_ROOT_ID, migrator);
  1194         // Ensure all special folders exist and have the
  1195         // right folder hierarchy.
  1196         createOrUpdateAllSpecialFolders(db);
  1198         debug("Dropping bookmarks temporary table");
  1199         db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS_TMP);
  1203     private void migrateHistoryTable(SQLiteDatabase db) {
  1204         debug("Renaming history table to " + TABLE_HISTORY_TMP);
  1205         db.execSQL("ALTER TABLE " + TABLE_HISTORY +
  1206                    " RENAME TO " + TABLE_HISTORY_TMP);
  1208         debug("Dropping views and indexes related to " + TABLE_HISTORY);
  1209         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
  1210         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1212         db.execSQL("DROP INDEX IF EXISTS history_url_index");
  1213         db.execSQL("DROP INDEX IF EXISTS history_guid_index");
  1214         db.execSQL("DROP INDEX IF EXISTS history_modified_index");
  1215         db.execSQL("DROP INDEX IF EXISTS history_visited_index");
  1217         createHistoryTable(db);
  1218         createHistoryWithImagesView(db);
  1219         createCombinedWithImagesView(db);
  1221         db.execSQL("INSERT INTO " + TABLE_HISTORY + " SELECT * FROM " + TABLE_HISTORY_TMP);
  1223         debug("Dropping history temporary table");
  1224         db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY_TMP);
  1227     private void migrateImagesTable(SQLiteDatabase db) {
  1228         debug("Renaming images table to " + TABLE_IMAGES_TMP);
  1229         db.execSQL("ALTER TABLE " + Obsolete.TABLE_IMAGES +
  1230                    " RENAME TO " + TABLE_IMAGES_TMP);
  1232         debug("Dropping views and indexes related to " + Obsolete.TABLE_IMAGES);
  1233         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
  1234         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1236         db.execSQL("DROP INDEX IF EXISTS images_url_index");
  1237         db.execSQL("DROP INDEX IF EXISTS images_guid_index");
  1238         db.execSQL("DROP INDEX IF EXISTS images_modified_index");
  1240         createImagesTable(db);
  1241         createHistoryWithImagesView(db);
  1242         createCombinedWithImagesView(db);
  1244         db.execSQL("INSERT INTO " + Obsolete.TABLE_IMAGES + " SELECT * FROM " + TABLE_IMAGES_TMP);
  1246         debug("Dropping images temporary table");
  1247         db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES_TMP);
  1250     private void upgradeDatabaseFrom1to2(SQLiteDatabase db) {
  1251         migrateBookmarksTable(db);
  1254     private void upgradeDatabaseFrom2to3(SQLiteDatabase db) {
  1255         debug("Dropping view: " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
  1256         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
  1258         createBookmarksWithImagesView(db);
  1260         debug("Dropping view: " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
  1261         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
  1263         createHistoryWithImagesView(db);
  1266     private void upgradeDatabaseFrom3to4(SQLiteDatabase db) {
  1267         migrateBookmarksTable(db, new BookmarkMigrator3to4());
  1270     private void upgradeDatabaseFrom4to5(SQLiteDatabase db) {
  1271         createCombinedWithImagesView(db);
  1274     private void upgradeDatabaseFrom5to6(SQLiteDatabase db) {
  1275         debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1276         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1278         createCombinedWithImagesView(db);
  1281     private void upgradeDatabaseFrom6to7(SQLiteDatabase db) {
  1282         debug("Removing history visits with NULL GUIDs");
  1283         db.execSQL("DELETE FROM " + TABLE_HISTORY + " WHERE " + History.GUID + " IS NULL");
  1285         debug("Update images with NULL GUIDs");
  1286         String[] columns = new String[] { Obsolete.Images._ID };
  1287         Cursor cursor = null;
  1288         try {
  1289           cursor = db.query(Obsolete.TABLE_IMAGES, columns, Obsolete.Images.GUID + " IS NULL", null, null ,null, null, null);
  1290           ContentValues values = new ContentValues();
  1291           if (cursor.moveToFirst()) {
  1292               do {
  1293                   values.put(Obsolete.Images.GUID, Utils.generateGuid());
  1294                   db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images._ID + " = ?", new String[] {
  1295                     cursor.getString(cursor.getColumnIndexOrThrow(Obsolete.Images._ID))
  1296                   });
  1297               } while (cursor.moveToNext());
  1299         } finally {
  1300           if (cursor != null)
  1301             cursor.close();
  1304         migrateBookmarksTable(db);
  1305         migrateHistoryTable(db);
  1306         migrateImagesTable(db);
  1309     private void upgradeDatabaseFrom7to8(SQLiteDatabase db) {
  1310         debug("Combining history entries with the same URL");
  1312         final String TABLE_DUPES = "duped_urls";
  1313         final String TOTAL = "total";
  1314         final String LATEST = "latest";
  1315         final String WINNER = "winner";
  1317         db.execSQL("CREATE TEMP TABLE " + TABLE_DUPES + " AS" +
  1318                   " SELECT " + History.URL + ", " +
  1319                               "SUM(" + History.VISITS + ") AS " + TOTAL + ", " +
  1320                               "MAX(" + History.DATE_MODIFIED + ") AS " + LATEST + ", " +
  1321                               "MAX(" + History._ID + ") AS " + WINNER +
  1322                   " FROM " + TABLE_HISTORY +
  1323                   " GROUP BY " + History.URL +
  1324                   " HAVING count(" + History.URL + ") > 1");
  1326         db.execSQL("CREATE UNIQUE INDEX " + TABLE_DUPES + "_url_index ON " +
  1327                    TABLE_DUPES + " (" + History.URL + ")");
  1329         final String fromClause = " FROM " + TABLE_DUPES + " WHERE " +
  1330                                   qualifyColumn(TABLE_DUPES, History.URL) + " = " +
  1331                                   qualifyColumn(TABLE_HISTORY, History.URL);
  1333         db.execSQL("UPDATE " + TABLE_HISTORY +
  1334                   " SET " + History.VISITS + " = (SELECT " + TOTAL + fromClause + "), " +
  1335                             History.DATE_MODIFIED + " = (SELECT " + LATEST + fromClause + "), " +
  1336                             History.IS_DELETED + " = " +
  1337                                 "(" + History._ID + " <> (SELECT " + WINNER + fromClause + "))" +
  1338                   " WHERE " + History.URL + " IN (SELECT " + History.URL + " FROM " + TABLE_DUPES + ")");
  1340         db.execSQL("DROP TABLE " + TABLE_DUPES);
  1343     private void upgradeDatabaseFrom8to9(SQLiteDatabase db) {
  1344         createOrUpdateSpecialFolder(db, Bookmarks.READING_LIST_FOLDER_GUID,
  1345             R.string.bookmarks_folder_reading_list, 5);
  1347         debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1348         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1350         createCombinedWithImagesViewOn9(db);
  1353     private void upgradeDatabaseFrom9to10(SQLiteDatabase db) {
  1354         debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1355         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1357         createCombinedWithImagesViewOn10(db);
  1360     private void upgradeDatabaseFrom10to11(SQLiteDatabase db) {
  1361         debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1362         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1364         db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
  1365                 + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
  1367         createCombinedWithImagesViewOn11(db);
  1370     private void upgradeDatabaseFrom11to12(SQLiteDatabase db) {
  1371         debug("Dropping view: " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1372         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1374         createCombinedViewOn12(db);
  1377     private void upgradeDatabaseFrom12to13(SQLiteDatabase db) {
  1378         // Update images table with favicon URLs
  1379         SQLiteDatabase faviconsDb = null;
  1380         Cursor c = null;
  1381         try {
  1382             final String FAVICON_TABLE = "favicon_urls";
  1383             final String FAVICON_URL = "favicon_url";
  1384             final String FAVICON_PAGE = "page_url";
  1386             String dbPath = mContext.getDatabasePath(Obsolete.FAVICON_DB).getPath();
  1387             faviconsDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
  1388             String[] columns = new String[] { FAVICON_URL, FAVICON_PAGE };
  1389             c = faviconsDb.query(FAVICON_TABLE, columns, null, null, null, null, null, null);
  1390             int faviconIndex = c.getColumnIndexOrThrow(FAVICON_URL);
  1391             int pageIndex = c.getColumnIndexOrThrow(FAVICON_PAGE);
  1392             while (c.moveToNext()) {
  1393                 ContentValues values = new ContentValues(1);
  1394                 String faviconUrl = c.getString(faviconIndex);
  1395                 String pageUrl = c.getString(pageIndex);
  1396                 values.put(FAVICON_URL, faviconUrl);
  1397                 db.update(Obsolete.TABLE_IMAGES, values, Obsolete.Images.URL + " = ?", new String[] { pageUrl });
  1399         } catch (SQLException e) {
  1400             // If we can't read from the database for some reason, we won't
  1401             // be able to import the favicon URLs. This isn't a fatal
  1402             // error, so continue the upgrade.
  1403             Log.e(LOGTAG, "Exception importing from " + Obsolete.FAVICON_DB, e);
  1404         } finally {
  1405             if (c != null)
  1406                 c.close();
  1407             if (faviconsDb != null)
  1408                 faviconsDb.close();
  1411         createFaviconsTable(db);
  1413         // Import favicons into the favicons table
  1414         db.execSQL("ALTER TABLE " + TABLE_HISTORY
  1415                 + " ADD COLUMN " + History.FAVICON_ID + " INTEGER");
  1416         db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS
  1417                 + " ADD COLUMN " + Bookmarks.FAVICON_ID + " INTEGER");
  1419         try {
  1420             c = db.query(Obsolete.TABLE_IMAGES,
  1421                     new String[] {
  1422                         Obsolete.Images.URL,
  1423                         Obsolete.Images.FAVICON_URL,
  1424                         Obsolete.Images.FAVICON,
  1425                         Obsolete.Images.DATE_MODIFIED,
  1426                         Obsolete.Images.DATE_CREATED
  1427                     },
  1428                     Obsolete.Images.FAVICON + " IS NOT NULL",
  1429                     null, null, null, null);
  1431             while (c.moveToNext()) {
  1432                 long faviconId = -1;
  1433                 int faviconUrlIndex = c.getColumnIndexOrThrow(Obsolete.Images.FAVICON_URL);
  1434                 String faviconUrl = null;
  1435                 if (!c.isNull(faviconUrlIndex)) {
  1436                     faviconUrl = c.getString(faviconUrlIndex);
  1437                     Cursor c2 = null;
  1438                     try {
  1439                         c2 = db.query(TABLE_FAVICONS,
  1440                                 new String[] { Favicons._ID },
  1441                                 Favicons.URL + " = ?",
  1442                                 new String[] { faviconUrl },
  1443                                 null, null, null);
  1444                         if (c2.moveToFirst()) {
  1445                             faviconId = c2.getLong(c2.getColumnIndexOrThrow(Favicons._ID));
  1447                     } finally {
  1448                         if (c2 != null)
  1449                             c2.close();
  1453                 if (faviconId == -1) {
  1454                     ContentValues values = new ContentValues(4);
  1455                     values.put(Favicons.URL, faviconUrl);
  1456                     values.put(Favicons.DATA, c.getBlob(c.getColumnIndexOrThrow(Obsolete.Images.FAVICON)));
  1457                     values.put(Favicons.DATE_MODIFIED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_MODIFIED)));
  1458                     values.put(Favicons.DATE_CREATED, c.getLong(c.getColumnIndexOrThrow(Obsolete.Images.DATE_CREATED)));
  1459                     faviconId = db.insert(TABLE_FAVICONS, null, values);
  1462                 ContentValues values = new ContentValues(1);
  1463                 values.put(FaviconColumns.FAVICON_ID, faviconId);
  1464                 db.update(TABLE_HISTORY, values, History.URL + " = ?",
  1465                         new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
  1466                 db.update(TABLE_BOOKMARKS, values, Bookmarks.URL + " = ?",
  1467                         new String[] { c.getString(c.getColumnIndexOrThrow(Obsolete.Images.URL)) });
  1469         } finally {
  1470             if (c != null)
  1471                 c.close();
  1474         createThumbnailsTable(db);
  1476         // Import thumbnails into the thumbnails table
  1477         db.execSQL("INSERT INTO " + TABLE_THUMBNAILS + " ("
  1478                 + Thumbnails.URL + ", "
  1479                 + Thumbnails.DATA + ") "
  1480                 + "SELECT " + Obsolete.Images.URL + ", " + Obsolete.Images.THUMBNAIL
  1481                 + " FROM " + Obsolete.TABLE_IMAGES
  1482                 + " WHERE " + Obsolete.Images.THUMBNAIL + " IS NOT NULL");
  1484         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_BOOKMARKS_WITH_IMAGES);
  1485         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_HISTORY_WITH_IMAGES);
  1486         db.execSQL("DROP VIEW IF EXISTS " + Obsolete.VIEW_COMBINED_WITH_IMAGES);
  1487         db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
  1489         createBookmarksWithFaviconsView(db);
  1490         createHistoryWithFaviconsView(db);
  1491         createCombinedViewOn13(db);
  1493         db.execSQL("DROP TABLE IF EXISTS " + Obsolete.TABLE_IMAGES);
  1496     private void upgradeDatabaseFrom13to14(SQLiteDatabase db) {
  1497         createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
  1498             R.string.bookmarks_folder_pinned, 6);
  1501     private void upgradeDatabaseFrom14to15(SQLiteDatabase db) {
  1502         Cursor c = null;
  1503         try {
  1504             // Get all the pinned bookmarks
  1505             c = db.query(TABLE_BOOKMARKS,
  1506                          new String[] { Bookmarks._ID, Bookmarks.URL },
  1507                          Bookmarks.PARENT + " = ?",
  1508                          new String[] { Integer.toString(Bookmarks.FIXED_PINNED_LIST_ID) },
  1509                          null, null, null);
  1511             while (c.moveToNext()) {
  1512                 // Check if this URL can be parsed as a URI with a valid scheme.
  1513                 String url = c.getString(c.getColumnIndexOrThrow(Bookmarks.URL));
  1514                 if (Uri.parse(url).getScheme() != null) {
  1515                     continue;
  1518                 // If it can't, update the URL to be an encoded "user-entered" value.
  1519                 ContentValues values = new ContentValues(1);
  1520                 String newUrl = Uri.fromParts("user-entered", url, null).toString();
  1521                 values.put(Bookmarks.URL, newUrl);
  1522                 db.update(TABLE_BOOKMARKS, values, Bookmarks._ID + " = ?",
  1523                           new String[] { Integer.toString(c.getInt(c.getColumnIndexOrThrow(Bookmarks._ID))) });
  1525         } finally {
  1526             if (c != null) {
  1527                 c.close();
  1532     private void upgradeDatabaseFrom15to16(SQLiteDatabase db) {
  1533         db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
  1534         db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
  1536         createCombinedViewOn16(db);
  1539     private void upgradeDatabaseFrom16to17(SQLiteDatabase db) {
  1540         // Purge any 0-byte favicons/thumbnails
  1541         try {
  1542             db.execSQL("DELETE FROM " + TABLE_FAVICONS +
  1543                     " WHERE length(" + Favicons.DATA + ") = 0");
  1544             db.execSQL("DELETE FROM " + TABLE_THUMBNAILS +
  1545                     " WHERE length(" + Thumbnails.DATA + ") = 0");
  1546         } catch (SQLException e) {
  1547             Log.e(LOGTAG, "Error purging invalid favicons or thumbnails", e);
  1551     /*
  1552      * Moves reading list items from 'bookmarks' table to 'reading_list' table. Uses the
  1553      * same item GUID.
  1554      */
  1555     private void upgradeDatabaseFrom17to18(SQLiteDatabase db) {
  1556         debug("Moving reading list items from 'bookmarks' table to 'reading_list' table");
  1558         final String selection = Bookmarks.PARENT + " = ? AND " + Bookmarks.IS_DELETED + " = ? ";
  1559         final String[] selectionArgs = { String.valueOf(Bookmarks.FIXED_READING_LIST_ID), "0" };
  1560         final String[] projection = {   Bookmarks._ID,
  1561                                         Bookmarks.GUID,
  1562                                         Bookmarks.URL,
  1563                                         Bookmarks.DATE_MODIFIED,
  1564                                         Bookmarks.DATE_CREATED,
  1565                                         Bookmarks.TITLE };
  1566         Cursor cursor = null;
  1567         try {
  1568             // Start transaction
  1569             db.beginTransaction();
  1571             // Create 'reading_list' table
  1572             createReadingListTable(db);
  1574             // Get all the reading list items from bookmarks table
  1575             cursor = db.query(TABLE_BOOKMARKS, projection, selection, selectionArgs,
  1576                          null, null, null);
  1578             // Insert reading list items into reading_list table
  1579             while (cursor.moveToNext()) {
  1580                 debug(DatabaseUtils.dumpCurrentRowToString(cursor));
  1581                 ContentValues values = new ContentValues();
  1582                 DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.URL, values, ReadingListItems.URL);
  1583                 DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.GUID, values, ReadingListItems.GUID);
  1584                 DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.TITLE, values, ReadingListItems.TITLE);
  1585                 DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_CREATED, values, ReadingListItems.DATE_CREATED);
  1586                 DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_MODIFIED, values, ReadingListItems.DATE_MODIFIED);
  1588                 db.insertOrThrow(TABLE_READING_LIST, null, values);
  1591             // Delete reading list items from bookmarks table
  1592             db.delete(TABLE_BOOKMARKS,
  1593                       Bookmarks.PARENT + " = ? ",
  1594                       new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
  1596             // Delete reading list special folder
  1597             db.delete(TABLE_BOOKMARKS,
  1598                       Bookmarks._ID + " = ? ",
  1599                       new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
  1600             // Done
  1601             db.setTransactionSuccessful();
  1603         } catch (SQLException e) {
  1604             Log.e(LOGTAG, "Error migrating reading list items", e);
  1605         } finally {
  1606             if (cursor != null) {
  1607                 cursor.close();
  1609             db.endTransaction();
  1613     @Override
  1614     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  1615         debug("Upgrading browser.db: " + db.getPath() + " from " +
  1616                 oldVersion + " to " + newVersion);
  1618         // We have to do incremental upgrades until we reach the current
  1619         // database schema version.
  1620         for (int v = oldVersion + 1; v <= newVersion; v++) {
  1621             switch(v) {
  1622                 case 2:
  1623                     upgradeDatabaseFrom1to2(db);
  1624                     break;
  1626                 case 3:
  1627                     upgradeDatabaseFrom2to3(db);
  1628                     break;
  1630                 case 4:
  1631                     upgradeDatabaseFrom3to4(db);
  1632                     break;
  1634                 case 5:
  1635                     upgradeDatabaseFrom4to5(db);
  1636                     break;
  1638                 case 6:
  1639                     upgradeDatabaseFrom5to6(db);
  1640                     break;
  1642                 case 7:
  1643                     upgradeDatabaseFrom6to7(db);
  1644                     break;
  1646                 case 8:
  1647                     upgradeDatabaseFrom7to8(db);
  1648                     break;
  1650                 case 9:
  1651                     upgradeDatabaseFrom8to9(db);
  1652                     break;
  1654                 case 10:
  1655                     upgradeDatabaseFrom9to10(db);
  1656                     break;
  1658                 case 11:
  1659                     upgradeDatabaseFrom10to11(db);
  1660                     break;
  1662                 case 12:
  1663                     upgradeDatabaseFrom11to12(db);
  1664                     break;
  1666                 case 13:
  1667                     upgradeDatabaseFrom12to13(db);
  1668                     break;
  1670                 case 14:
  1671                     upgradeDatabaseFrom13to14(db);
  1672                     break;
  1674                 case 15:
  1675                     upgradeDatabaseFrom14to15(db);
  1676                     break;
  1678                 case 16:
  1679                     upgradeDatabaseFrom15to16(db);
  1680                     break;
  1682                 case 17:
  1683                     upgradeDatabaseFrom16to17(db);
  1684                     break;
  1686                 case 18:
  1687                     upgradeDatabaseFrom17to18(db);
  1688                     break;
  1692         // If an upgrade after 12->13 fails, the entire upgrade is rolled
  1693         // back, but we can't undo the deletion of favicon_urls.db if we
  1694         // delete this in step 13; therefore, we wait until all steps are
  1695         // complete before removing it.
  1696         if (oldVersion < 13 && newVersion >= 13
  1697                             && mContext.getDatabasePath(Obsolete.FAVICON_DB).exists()
  1698                             && !mContext.deleteDatabase(Obsolete.FAVICON_DB)) {
  1699             throw new SQLException("Could not delete " + Obsolete.FAVICON_DB);
  1703     @Override
  1704     public void onOpen(SQLiteDatabase db) {
  1705         debug("Opening browser.db: " + db.getPath());
  1707         Cursor cursor = null;
  1708         try {
  1709             cursor = db.rawQuery("PRAGMA foreign_keys=ON", null);
  1710         } finally {
  1711             if (cursor != null)
  1712                 cursor.close();
  1714         cursor = null;
  1715         try {
  1716             cursor = db.rawQuery("PRAGMA synchronous=NORMAL", null);
  1717         } finally {
  1718             if (cursor != null)
  1719                 cursor.close();
  1722         // From Honeycomb on, it's possible to run several db
  1723         // commands in parallel using multiple connections.
  1724         if (Build.VERSION.SDK_INT >= 11) {
  1725             db.enableWriteAheadLogging();
  1726             db.setLockingEnabled(false);
  1727         } else {
  1728             // Pre-Honeycomb, we can do some lesser optimizations.
  1729             cursor = null;
  1730             try {
  1731                 cursor = db.rawQuery("PRAGMA journal_mode=PERSIST", null);
  1732             } finally {
  1733                 if (cursor != null)
  1734                     cursor.close();
  1739     static final String qualifyColumn(String table, String column) {
  1740         return DBUtils.qualifyColumn(table, column);
  1743     // Calculate these once, at initialization. isLoggable is too expensive to
  1744     // have in-line in each log call.
  1745     private static boolean logDebug   = Log.isLoggable(LOGTAG, Log.DEBUG);
  1746     private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
  1747     protected static void trace(String message) {
  1748         if (logVerbose) {
  1749             Log.v(LOGTAG, message);
  1753     protected static void debug(String message) {
  1754         if (logDebug) {
  1755             Log.d(LOGTAG, message);
  1759     private Integer getMobileFolderId(SQLiteDatabase db) {
  1760         Cursor c = null;
  1762         try {
  1763             c = db.query(TABLE_BOOKMARKS,
  1764                          mobileIdColumns,
  1765                          Bookmarks.GUID + " = ?",
  1766                          mobileIdSelectionArgs,
  1767                          null, null, null);
  1769             if (c == null || !c.moveToFirst())
  1770                 return null;
  1772             return c.getInt(c.getColumnIndex(Bookmarks._ID));
  1773         } finally {
  1774             if (c != null)
  1775                 c.close();
  1779     private long insertFavicon(SQLiteDatabase db, ContentValues values) {
  1780         // This method is a dupicate of BrowserProvider.insertFavicon.
  1781         // If changes are needed, please update both
  1782         String faviconUrl = values.getAsString(Favicons.URL);
  1783         String pageUrl = null;
  1784         long faviconId;
  1786         trace("Inserting favicon for URL: " + faviconUrl);
  1788         DBUtils.stripEmptyByteArray(values, Favicons.DATA);
  1790         // Extract the page URL from the ContentValues
  1791         if (values.containsKey(Favicons.PAGE_URL)) {
  1792             pageUrl = values.getAsString(Favicons.PAGE_URL);
  1793             values.remove(Favicons.PAGE_URL);
  1796         // If no URL is provided, insert using the default one.
  1797         if (TextUtils.isEmpty(faviconUrl) && !TextUtils.isEmpty(pageUrl)) {
  1798             values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl));
  1801         long now = System.currentTimeMillis();
  1802         values.put(Favicons.DATE_CREATED, now);
  1803         values.put(Favicons.DATE_MODIFIED, now);
  1804         faviconId = db.insertOrThrow(TABLE_FAVICONS, null, values);
  1806         if (pageUrl != null) {
  1807             ContentValues updateValues = new ContentValues(1);
  1808             updateValues.put(FaviconColumns.FAVICON_ID, faviconId);
  1809             db.update(TABLE_HISTORY,
  1810                       updateValues,
  1811                       History.URL + " = ?",
  1812                       new String[] { pageUrl });
  1813             db.update(TABLE_BOOKMARKS,
  1814                       updateValues,
  1815                       Bookmarks.URL + " = ?",
  1816                       new String[] { pageUrl });
  1819         return faviconId;
  1822     private interface BookmarkMigrator {
  1823         public void updateForNewTable(ContentValues bookmark);
  1826     private class BookmarkMigrator3to4 implements BookmarkMigrator {
  1827         @Override
  1828         public void updateForNewTable(ContentValues bookmark) {
  1829             Integer isFolder = bookmark.getAsInteger("folder");
  1830             if (isFolder == null || isFolder != 1) {
  1831                 bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_BOOKMARK);
  1832             } else {
  1833                 bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
  1836             bookmark.remove("folder");

mercurial