gt:                         db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
context: /*
   * Copyright (C) 2010 he Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.browser.provider;
  
  import android.accounts.Account;
  import android.accounts.AccountManager;
  import android.app.SearchManager;
  import android.content.ContentResolver;
  import android.content.ContentUris;
  import android.content.ContentValues;
  import android.content.Context;
  import android.content.Intent;
  import android.content.UriMatcher;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.database.AbstractCursor;
  import android.database.ContentObserver;
  import android.database.Cursor;
  import android.database.DatabaseUtils;
  import android.database.MatrixCursor;
  import android.database.sqlite.SQLiteDatabase;
  import android.database.sqlite.SQLiteOpenHelper;
  import android.database.sqlite.SQLiteQueryBuilder;
  import android.net.Uri;
  import android.provider.BaseColumns;
  import android.provider.Browser;
  import android.provider.Browser.BookmarkColumns;
  import android.provider.BrowserContract;
  import android.provider.BrowserContract.Accounts;
  import android.provider.BrowserContract.Bookmarks;
  import android.provider.BrowserContract.ChromeSyncColumns;
  import android.provider.BrowserContract.Combined;
  import android.provider.BrowserContract.History;
  import android.provider.BrowserContract.Images;
  import android.provider.BrowserContract.Searches;
  import android.provider.BrowserContract.Settings;
  import android.provider.BrowserContract.SyncState;
  import android.provider.ContactsContract.RawContacts;
  import android.provider.SyncStateContract;
  import android.text.TextUtils;
  
  import com.android.browser.R;
  import com.android.browser.UrlUtils;
  import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
  import com.android.common.content.SyncStateContentProviderHelper;
  import com.google.common.annotations.VisibleForTesting;
  
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  
  public class BrowserProvider2 extends SQLiteContentProvider {
  
      public static final String PARAM_GROUP_BY = "groupBy";
      public static final String PARAM_ALLOW_EMPTY_ACCOUNTS = "allowEmptyAccounts";
  
      public static final String LEGACY_AUTHORITY = "browser";
      static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder()
              .authority(LEGACY_AUTHORITY).scheme("content").build();
  
      public static interface Thumbnails {
          public static final Uri CONTENT_URI = Uri.withAppendedPath(
                  BrowserContract.AUTHORITY_URI, "thumbnails");
          public static final String _ID = "_id";
          public static final String THUMBNAIL = "thumbnail";
      }
  
      public static interface OmniboxSuggestions {
          public static final Uri CONTENT_URI = Uri.withAppendedPath(
                  BrowserContract.AUTHORITY_URI, "omnibox_suggestions");
          public static final String _ID = "_id";
          public static final String URL = "url";
          public static final String TITLE = "title";
          public static final String IS_BOOKMARK = "bookmark";
      }
  
      static final String TABLE_BOOKMARKS = "bookmarks";
      static final String TABLE_HISTORY = "history";
      static final String TABLE_IMAGES = "images";
      static final String TABLE_SEARCHES = "searches";
      static final String TABLE_SYNC_STATE = "syncstate";
      static final String TABLE_SETTINGS = "settings";
      static final String TABLE_SNAPSHOTS = "snapshots";
      static final String TABLE_THUMBNAILS = "thumbnails";
  
      static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " +
              "ON bookmarks.url = images." + Images.URL;
      static final String TABLE_HISTORY_JOIN_IMAGES = "history LEFT OUTER JOIN images " +
              "ON history.url = images." + Images.URL;
  
      static final String VIEW_ACCOUNTS = "v_accounts";
      static final String VIEW_SNAPSHOTS_COMBINED = "v_snapshots_combined";
      static final String VIEW_OMNIBOX_SUGGESTIONS = "v_omnibox_suggestions";
  
      static final String FORMAT_COMBINED_JOIN_SUBQUERY_JOIN_IMAGES =
              "history LEFT OUTER JOIN (%s) bookmarks " +
              "ON history.url = bookmarks.url LEFT OUTER JOIN images " +
              "ON history.url = images.url_key";
  
      static final String DEFAULT_SORT_HISTORY = History.DATE_LAST_VISITED + " DESC";
      static final String DEFAULT_SORT_ACCOUNTS =
              Accounts.ACCOUNT_NAME + " IS NOT NULL DESC, "
              + Accounts.ACCOUNT_NAME + " ASC";
  
      private static final String TABLE_BOOKMARKS_JOIN_HISTORY =
          "history LEFT OUTER JOIN bookmarks ON history.url = bookmarks.url";
  
      private static final String[] SUGGEST_PROJECTION = new String[] {
              qualifyColumn(TABLE_HISTORY, History._ID),
              qualifyColumn(TABLE_HISTORY, History.URL),
              bookmarkOrHistoryColumn(Combined.TITLE),
              bookmarkOrHistoryLiteral(Combined.URL,
                      Integer.toString(R.drawable.ic_bookmark_off_holo_dark),
                      Integer.toString(R.drawable.ic_history_holo_dark)),
              qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED)};
  
      private static final String SUGGEST_SELECTION =
              "history.url LIKE ? OR history.url LIKE ? OR history.url LIKE ? OR history.url LIKE ?"
              + " OR history.title LIKE ? OR bookmarks.title LIKE ?";
  
      private static final String ZERO_QUERY_SUGGEST_SELECTION =
              TABLE_HISTORY + "." + History.DATE_LAST_VISITED + " != 0";
  
      private static final String IMAGE_PRUNE =
              "url_key NOT IN (SELECT url FROM bookmarks " +
              "WHERE url IS NOT NULL AND deleted == 0) AND url_key NOT IN " +
              "(SELECT url FROM history WHERE url IS NOT NULL)";
  
      static final int THUMBNAILS = 10;
      static final int THUMBNAILS_ID = 11;
      static final int OMNIBOX_SUGGESTIONS = 20;
  
      static final int BOOKMARKS = 1000;
      static final int BOOKMARKS_ID = 1001;
      static final int BOOKMARKS_FOLDER = 1002;
      static final int BOOKMARKS_FOLDER_ID = 1003;
      static final int BOOKMARKS_SUGGESTIONS = 1004;
      static final int BOOKMARKS_DEFAULT_FOLDER_ID = 1005;
  
      static final int HISTORY = 2000;
      static final int HISTORY_ID = 2001;
  
      static final int SEARCHES = 3000;
      static final int SEARCHES_ID = 3001;
  
      static final int SYNCSTATE = 4000;
      static final int SYNCSTATE_ID = 4001;
  
      static final int IMAGES = 5000;
  
      static final int COMBINED = 6000;
      static final int COMBINED_ID = 6001;
  
      static final int ACCOUNTS = 7000;
  
      static final int SETTINGS = 8000;
  
      static final int LEGACY = 9000;
      static final int LEGACY_ID = 9001;
  
      public static final long FIXED_ID_ROOT = 1;
  
      // Default sort order for unsync'd bookmarks
      static final String DEFAULT_BOOKMARKS_SORT_ORDER =
              Bookmarks.IS_FOLDER + " DESC, position ASC, _id ASC";
  
      // Default sort order for sync'd bookmarks
      static final String DEFAULT_BOOKMARKS_SORT_ORDER_SYNC = "position ASC, _id ASC";
  
      static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
  
      static final HashMap<String, String> ACCOUNTS_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP =
              new HashMap<String, String>();
      static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> IMAGES_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> COMBINED_HISTORY_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> COMBINED_BOOKMARK_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> SEARCHES_PROJECTION_MAP = new HashMap<String, String>();
      static final HashMap<String, String> SETTINGS_PROJECTION_MAP = new HashMap<String, String>();
  
      static {
          final UriMatcher matcher = URI_MATCHER;
          final String authority = BrowserContract.AUTHORITY;
          matcher.addURI(authority, "accounts", ACCOUNTS);
          matcher.addURI(authority, "bookmarks", BOOKMARKS);
          matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
          matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
          matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
          matcher.addURI(authority, "bookmarks/folder/id", BOOKMARKS_DEFAULT_FOLDER_ID);
          matcher.addURI(authority,
                  SearchManager.SUGGEST_URI_PATH_QUERY,
                  BOOKMARKS_SUGGESTIONS);
          matcher.addURI(authority,
                  "bookmarks/" + SearchManager.SUGGEST_URI_PATH_QUERY,
                  BOOKMARKS_SUGGESTIONS);
          matcher.addURI(authority, "history", HISTORY);
          matcher.addURI(authority, "history/#", HISTORY_ID);
          matcher.addURI(authority, "searches", SEARCHES);
          matcher.addURI(authority, "searches/#", SEARCHES_ID);
          matcher.addURI(authority, "syncstate", SYNCSTATE);
          matcher.addURI(authority, "syncstate/#", SYNCSTATE_ID);
          matcher.addURI(authority, "images", IMAGES);
          matcher.addURI(authority, "combined", COMBINED);
          matcher.addURI(authority, "combined/#", COMBINED_ID);
          matcher.addURI(authority, "settings", SETTINGS);
          matcher.addURI(authority, "thumbnails", THUMBNAILS);
          matcher.addURI(authority, "thumbnails/#", THUMBNAILS_ID);
          matcher.addURI(authority, "omnibox_suggestions", OMNIBOX_SUGGESTIONS);
  
          // Legacy
          matcher.addURI(LEGACY_AUTHORITY, "searches", SEARCHES);
          matcher.addURI(LEGACY_AUTHORITY, "searches/#", SEARCHES_ID);
          matcher.addURI(LEGACY_AUTHORITY, "bookmarks", LEGACY);
          matcher.addURI(LEGACY_AUTHORITY, "bookmarks/#", LEGACY_ID);
          matcher.addURI(LEGACY_AUTHORITY,
                  SearchManager.SUGGEST_URI_PATH_QUERY,
                  BOOKMARKS_SUGGESTIONS);
          matcher.addURI(LEGACY_AUTHORITY,
                  "bookmarks/" + SearchManager.SUGGEST_URI_PATH_QUERY,
                  BOOKMARKS_SUGGESTIONS);
  
          // Projection maps
          HashMap<String, String> map;
  
          // Accounts
          map = ACCOUNTS_PROJECTION_MAP;
          map.put(Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_TYPE);
          map.put(Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_NAME);
          map.put(Accounts.ROOT_ID, Accounts.ROOT_ID);
  
          // Bookmarks
          map = BOOKMARKS_PROJECTION_MAP;
          map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
          map.put(Bookmarks.TITLE, Bookmarks.TITLE);
          map.put(Bookmarks.URL, Bookmarks.URL);
          map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
          map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
          map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
          map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
          map.put(Bookmarks.PARENT, Bookmarks.PARENT);
          map.put(Bookmarks.POSITION, Bookmarks.POSITION);
          map.put(Bookmarks.INSERT_AFTER, Bookmarks.INSERT_AFTER);
          map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
          map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME);
          map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE);
          map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID);
          map.put(Bookmarks.VERSION, Bookmarks.VERSION);
          map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
          map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
          map.put(Bookmarks.DIRTY, Bookmarks.DIRTY);
          map.put(Bookmarks.SYNC1, Bookmarks.SYNC1);
          map.put(Bookmarks.SYNC2, Bookmarks.SYNC2);
          map.put(Bookmarks.SYNC3, Bookmarks.SYNC3);
          map.put(Bookmarks.SYNC4, Bookmarks.SYNC4);
          map.put(Bookmarks.SYNC5, Bookmarks.SYNC5);
          map.put(Bookmarks.PARENT_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
                  " FROM " + TABLE_BOOKMARKS + " A WHERE " +
                  "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.PARENT +
                  ") AS " + Bookmarks.PARENT_SOURCE_ID);
          map.put(Bookmarks.INSERT_AFTER_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
                  " FROM " + TABLE_BOOKMARKS + " A WHERE " +
                  "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.INSERT_AFTER +
                  ") AS " + Bookmarks.INSERT_AFTER_SOURCE_ID);
          map.put(Bookmarks.TYPE, "CASE "
                  + " WHEN " + Bookmarks.IS_FOLDER + "=0 THEN "
                      + Bookmarks.BOOKMARK_TYPE_BOOKMARK
                  + " WHEN " + ChromeSyncColumns.SERVER_UNIQUE + "='"
                      + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' THEN "
                      + Bookmarks.BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER
                  + " WHEN " + ChromeSyncColumns.SERVER_UNIQUE + "='"
                      + ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS + "' THEN "
                      + Bookmarks.BOOKMARK_TYPE_OTHER_FOLDER
                  + " ELSE " + Bookmarks.BOOKMARK_TYPE_FOLDER
                  + " END AS " + Bookmarks.TYPE);
  
          // Other bookmarks
          OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP);
          OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION,
                  Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION);
  
          // History
          map = HISTORY_PROJECTION_MAP;
          map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
          map.put(History.TITLE, History.TITLE);
          map.put(History.URL, History.URL);
          map.put(History.FAVICON, History.FAVICON);
          map.put(History.THUMBNAIL, History.THUMBNAIL);
          map.put(History.TOUCH_ICON, History.TOUCH_ICON);
          map.put(History.DATE_CREATED, History.DATE_CREATED);
          map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
          map.put(History.VISITS, History.VISITS);
          map.put(History.USER_ENTERED, History.USER_ENTERED);
  
          // Sync state
          map = SYNC_STATE_PROJECTION_MAP;
          map.put(SyncState._ID, SyncState._ID);
          map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME);
          map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE);
          map.put(SyncState.DATA, SyncState.DATA);
  
          // Images
          map = IMAGES_PROJECTION_MAP;
          map.put(Images.URL, Images.URL);
          map.put(Images.FAVICON, Images.FAVICON);
          map.put(Images.THUMBNAIL, Images.THUMBNAIL);
          map.put(Images.TOUCH_ICON, Images.TOUCH_ICON);
  
          // Combined history half
          map = COMBINED_HISTORY_PROJECTION_MAP;
          map.put(Combined._ID, bookmarkOrHistoryColumn(Combined._ID));
          map.put(Combined.TITLE, bookmarkOrHistoryColumn(Combined.TITLE));
          map.put(Combined.URL, qualifyColumn(TABLE_HISTORY, Combined.URL));
          map.put(Combined.DATE_CREATED, qualifyColumn(TABLE_HISTORY, Combined.DATE_CREATED));
          map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED);
          map.put(Combined.IS_BOOKMARK, "CASE WHEN " +
                  TABLE_BOOKMARKS + "." + Bookmarks._ID +
                  " IS NOT NULL THEN 1 ELSE 0 END AS " + Combined.IS_BOOKMARK);
          map.put(Combined.VISITS, Combined.VISITS);
          map.put(Combined.FAVICON, Combined.FAVICON);
          map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
          map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
          map.put(Combined.USER_ENTERED, "NULL AS " + Combined.USER_ENTERED);
  
          // Combined bookmark half
          map = COMBINED_BOOKMARK_PROJECTION_MAP;
          map.put(Combined._ID, Combined._ID);
          map.put(Combined.TITLE, Combined.TITLE);
          map.put(Combined.URL, Combined.URL);
          map.put(Combined.DATE_CREATED, Combined.DATE_CREATED);
          map.put(Combined.DATE_LAST_VISITED, "NULL AS " + Combined.DATE_LAST_VISITED);
          map.put(Combined.IS_BOOKMARK, "1 AS " + Combined.IS_BOOKMARK);
          map.put(Combined.VISITS, "0 AS " + Combined.VISITS);
          map.put(Combined.FAVICON, Combined.FAVICON);
          map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
          map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
          map.put(Combined.USER_ENTERED, "NULL AS " + Combined.USER_ENTERED);
  
          // Searches
          map = SEARCHES_PROJECTION_MAP;
          map.put(Searches._ID, Searches._ID);
          map.put(Searches.SEARCH, Searches.SEARCH);
          map.put(Searches.DATE, Searches.DATE);
  
          // Settings
          map = SETTINGS_PROJECTION_MAP;
          map.put(Settings.KEY, Settings.KEY);
          map.put(Settings.VALUE, Settings.VALUE);
      }
  
      static final String bookmarkOrHistoryColumn(String column) {
          return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN " +
                  "bookmarks." + column + " ELSE history." + column + " END AS " + column;
      }
  
      static final String bookmarkOrHistoryLiteral(String column, String bookmarkValue,
              String historyValue) {
          return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN \"" + bookmarkValue +
                  "\" ELSE \"" + historyValue + "\" END";
      }
  
      static final String qualifyColumn(String table, String column) {
          return table + "." + column + " AS " + column;
      }
  
      DatabaseHelper mOpenHelper;
      SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
      // This is so provider tests can intercept widget updating
      ContentObserver mWidgetObserver = null;
      boolean mUpdateWidgets = false;
      boolean mSyncToNetwork = true;
  
      final class DatabaseHelper extends SQLiteOpenHelper {
          static final String DATABASE_NAME = "browser2.db";
          static final int DATABASE_VERSION = 32;
          public DatabaseHelper(Context context) {
              super(context, DATABASE_NAME, null, DATABASE_VERSION);
              setWriteAheadLoggingEnabled(true);
          }
  
          @Override
          public void onCreate(SQLiteDatabase db) {
              db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
                      Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                      Bookmarks.TITLE + " TEXT," +
                      Bookmarks.URL + " TEXT," +
                      Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
                      Bookmarks.PARENT + " INTEGER," +
                      Bookmarks.POSITION + " INTEGER NOT NULL," +
                      Bookmarks.INSERT_AFTER + " INTEGER," +
                      Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," +
                      Bookmarks.ACCOUNT_NAME + " TEXT," +
                      Bookmarks.ACCOUNT_TYPE + " TEXT," +
                      Bookmarks.SOURCE_ID + " TEXT," +
                      Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," +
                      Bookmarks.DATE_CREATED + " INTEGER," +
                      Bookmarks.DATE_MODIFIED + " INTEGER," +
                      Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
                      Bookmarks.SYNC1 + " TEXT," +
                      Bookmarks.SYNC2 + " TEXT," +
                      Bookmarks.SYNC3 + " TEXT," +
                      Bookmarks.SYNC4 + " TEXT," +
                      Bookmarks.SYNC5 + " TEXT" +
                      ");");
  
              // TODO indices
  
              db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
                      History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                      History.TITLE + " TEXT," +
                      History.URL + " TEXT NOT NULL," +
                      History.DATE_CREATED + " INTEGER," +
                      History.DATE_LAST_VISITED + " INTEGER," +
                      History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
                      History.USER_ENTERED + " INTEGER" +
                      ");");
  
              db.execSQL("CREATE TABLE " + TABLE_IMAGES + " (" +
                      Images.URL + " TEXT UNIQUE NOT NULL," +
                      Images.FAVICON + " BLOB," +
                      Images.THUMBNAIL + " BLOB," +
                      Images.TOUCH_ICON + " BLOB" +
                      ");");
              db.execSQL("CREATE INDEX imagesUrlIndex ON " + TABLE_IMAGES +
                      "(" + Images.URL + ")");
  
              db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
                      Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                      Searches.SEARCH + " TEXT," +
                      Searches.DATE + " LONG" +
                      ");");
  
              db.execSQL("CREATE TABLE " + TABLE_SETTINGS + " (" +
                      Settings.KEY + " TEXT PRIMARY KEY," +
                      Settings.VALUE + " TEXT NOT NULL" +
                      ");");
  
              createAccountsView(db);
              createThumbnails(db);
  
              mSyncHelper.createDatabase(db);
  
              if (!importFromBrowserProvider(db)) {
                  createDefaultBookmarks(db);
              }
  
              enableSync(db);
              createOmniboxSuggestions(db);
          }
  
          void createOmniboxSuggestions(SQLiteDatabase db) {
              db.execSQL(SQL_CREATE_VIEW_OMNIBOX_SUGGESTIONS);
          }
  
          void createThumbnails(SQLiteDatabase db) {
              db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_THUMBNAILS + " (" +
                      Thumbnails._ID + " INTEGER PRIMARY KEY," +
                      Thumbnails.THUMBNAIL + " BLOB NOT NULL" +
                      ");");
          }
  
          void enableSync(SQLiteDatabase db) {
              ContentValues values = new ContentValues();
              values.put(Settings.KEY, Settings.KEY_SYNC_ENABLED);
              values.put(Settings.VALUE, 1);
              insertSettingsInTransaction(db, values);
              // Enable bookmark sync on all accounts
              AccountManager am = (AccountManager) getContext().getSystemService(
                      Context.ACCOUNT_SERVICE);
              if (am == null) {
                  return;
              }
              Account[] accounts = am.getAccountsByType("com.google");
              if (accounts == null || accounts.length == 0) {
                  return;
              }
              for (Account account : accounts) {
                  if (ContentResolver.getIsSyncable(
                          account, BrowserContract.AUTHORITY) == 0) {
                      // Account wasn't syncable, enable it
                      ContentResolver.setIsSyncable(
                              account, BrowserContract.AUTHORITY, 1);
                      ContentResolver.setSyncAutomatically(
                              account, BrowserContract.AUTHORITY, true);
                  }
              }
          }
  
          boolean importFromBrowserProvider(SQLiteDatabase db) {
              Context context = getContext();
              File oldDbFile = context.getDatabasePath(BrowserProvider.sDatabaseName);
              if (oldDbFile.exists()) {
                  BrowserProvider.DatabaseHelper helper =
                          new BrowserProvider.DatabaseHelper(context);
                  SQLiteDatabase oldDb = helper.getWritableDatabase();
                  Cursor c = null;
                  try {
                      String table = BrowserProvider.TABLE_NAMES[BrowserProvider.URI_MATCH_BOOKMARKS];
                      // Import bookmarks
                      c = oldDb.query(table,
                              new String[] {
                              BookmarkColumns.URL, // 0
                              BookmarkColumns.TITLE, // 1
                              BookmarkColumns.FAVICON, // 2
                              BookmarkColumns.TOUCH_ICON, // 3
                              BookmarkColumns.CREATED, // 4
                              }, BookmarkColumns.BOOKMARK + "!=0", null,
                              null, null, null);
                      if (c != null) {
                          while (c.moveToNext()) {
                              String url = c.getString(0);
                              if (TextUtils.isEmpty(url))
                                  continue; // We require a valid URL
                              ContentValues values = new ContentValues();
                              values.put(Bookmarks.URL, url);
                              values.put(Bookmarks.TITLE, c.getString(1));
                              values.put(Bookmarks.DATE_CREATED, c.getInt(4));
                              values.put(Bookmarks.POSITION, 0);
                              values.put(Bookmarks.PARENT, FIXED_ID_ROOT);
                              ContentValues imageValues = new ContentValues();
                              imageValues.put(Images.URL, url);
                              imageValues.put(Images.FAVICON, c.getBlob(2));
                              imageValues.put(Images.TOUCH_ICON, c.getBlob(3));
                              db.insert(TABLE_IMAGES, Images.THUMBNAIL, imageValues);
                              db.insert(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
                          }
                          c.close();
                      }
                      // Import history
                      c = oldDb.query(table,
                              new String[] {
                              BookmarkColumns.URL, // 0
                              BookmarkColumns.TITLE, // 1
                              BookmarkColumns.VISITS, // 2
                              BookmarkColumns.DATE, // 3
                              BookmarkColumns.CREATED, // 4
                              }, BookmarkColumns.VISITS + " > 0 OR "
                              + BookmarkColumns.BOOKMARK + " = 0",
                              null, null, null, null);
                      if (c != null) {
                          while (c.moveToNext()) {
                              ContentValues values = new ContentValues();
                              String url = c.getString(0);
                              if (TextUtils.isEmpty(url))
                                  continue; // We require a valid URL
                              values.put(History.URL, url);
                              values.put(History.TITLE, c.getString(1));
                              values.put(History.VISITS, c.getInt(2));
                              values.put(History.DATE_LAST_VISITED, c.getLong(3));
                              values.put(History.DATE_CREATED, c.getLong(4));
                              db.insert(TABLE_HISTORY, History.FAVICON, values);
                          }
                          c.close();
                      }
                      // Wipe the old DB, in case the delete fails.
                      oldDb.delete(table, null, null);
                  } finally {
                      if (c != null) c.close();
                      oldDb.close();
                      helper.close();
                  }
                  if (!oldDbFile.delete()) {
                      oldDbFile.deleteOnExit();
                  }
                  return true;
              }
              return false;
          }
  
          void createAccountsView(SQLiteDatabase db) {
              db.execSQL("CREATE VIEW IF NOT EXISTS v_accounts AS "
                      + "SELECT NULL AS " + Accounts.ACCOUNT_NAME
                      + ", NULL AS " + Accounts.ACCOUNT_TYPE
                      + ", " + FIXED_ID_ROOT + " AS " + Accounts.ROOT_ID
                      + " UNION ALL SELECT " + Accounts.ACCOUNT_NAME
                      + ", " + Accounts.ACCOUNT_TYPE + ", "
                      + Bookmarks._ID + " AS " + Accounts.ROOT_ID
                      + " FROM " + TABLE_BOOKMARKS + " WHERE "
                      + ChromeSyncColumns.SERVER_UNIQUE + " = \""
                      + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "\" AND "
                      + Bookmarks.IS_DELETED + " = 0");
          }
  
          @Override
          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
              if (oldVersion < 32) {
                  createOmniboxSuggestions(db);
              }
              if (oldVersion < 31) {
                  createThumbnails(db);
              }
              if (oldVersion < 30) {
                  db.execSQL("DROP VIEW IF EXISTS " + VIEW_SNAPSHOTS_COMBINED);
                  db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
              }
              if (oldVersion < 28) {
                  enableSync(db);
              }
              if (oldVersion < 27) {
                  createAccountsView(db);
              }
              if (oldVersion < 26) {
                  db.execSQL("DROP VIEW IF EXISTS combined");
              }
              if (oldVersion < 25) {
                  db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
                  db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
                  db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
                  db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
                  db.execSQL("DROP TABLE IF EXISTS " + TABLE_SETTINGS);
                  mSyncHelper.onAccountsChanged(db, new Account[] {}); // remove all sync info
                  onCreate(db);
              }
          }
  
          public void onOpen(SQLiteDatabase db) {
              mSyncHelper.onDatabaseOpened(db);
          }
  
          private void createDefaultBookmarks(SQLiteDatabase db) {
              ContentValues values = new ContentValues();
              // TODO figure out how to deal with localization for the defaults
  
              // Bookmarks folder
              values.put(Bookmarks._ID, FIXED_ID_ROOT);
              values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
              values.put(Bookmarks.TITLE, "Bookmarks");
              values.put(Bookmarks.URL, "");
              values.putNull(Bookmarks.PARENT);
              values.put(Bookmarks.POSITION, 0);
              values.put(Bookmarks.IS_FOLDER, true);
              values.put(Bookmarks.DIRTY, true);
              db.insertOrThrow(TABLE_BOOKMARKS, null, values);
  
              addDefaultBookmarks(db, FIXED_ID_ROOT);
          }
  
          private void addDefaultBookmarks(SQLiteDatabase db, long parentId) {
              Resources res = getContext().getResources();
              final CharSequence[] bookmarks = res.getTextArray(
                      R.array.bookmarks);
              int size = bookmarks.length;
              TypedArray preloads = res.obtainTypedArray(R.array.bookmark_preloads);
              try {
                  String parent = Long.toString(parentId);
                  String now = Long.toString(System.currentTimeMillis());
                  for (int i = 0; i < size; i = i + 2) {
                      CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
                              bookmarks[i + 1]);
                      db.execSQL("INSERT INTO bookmarks (" +
                              Bookmarks.TITLE + ", " +
                              Bookmarks.URL + ", " +
                              Bookmarks.IS_FOLDER + "," +
                              Bookmarks.PARENT + "," +
                              Bookmarks.POSITION + "," +
                              Bookmarks.DATE_CREATED +
                          ") VALUES (" +
                              "'" + bookmarks[i] + "', " +
                              "'" + bookmarkDestination + "', " +
                              "0," +
                              parent + "," +
                              Integer.toString(i) + "," +
                              now +
                              ");");
  
                      int faviconId = preloads.getResourceId(i, 0);
                      int thumbId = preloads.getResourceId(i + 1, 0);
                      byte[] thumb = null, favicon = null;
                      try {
                          thumb = readRaw(res, thumbId);
                      } catch (IOException e) {
                      }
                      try {
                          favicon = readRaw(res, faviconId);
                      } catch (IOException e) {
                      }
                      if (thumb != null || favicon != null) {
                          ContentValues imageValues = new ContentValues();
                          imageValues.put(Images.URL, bookmarkDestination.toString());
                          if (favicon != null) {
                              imageValues.put(Images.FAVICON, favicon);
                          }
                          if (thumb != null) {
                              imageValues.put(Images.THUMBNAIL, thumb);
                          }
                          db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
                      }
                  }
              } catch (ArrayIndexOutOfBoundsException e) {
              } finally {
                  preloads.recycle();
              }
          }
  
          private byte[] readRaw(Resources res, int id) throws IOException {
              if (id == 0) {
                  return null;
              }
              InputStream is = res.openRawResource(id);
              try {
                  ByteArrayOutputStream bos = new ByteArrayOutputStream();
                  byte[] buf = new byte[4096];
                  int read;
                  while ((read = is.read(buf)) > 0) {
                      bos.write(buf, 0, read);
                  }
                  bos.flush();
                  return bos.toByteArray();
              } finally {
                  is.close();
              }
          }
  
          // XXX: This is a major hack to remove our dependency on gsf constants and
          // its content provider. http://b/issue?id=2425179
          private String getClientId(ContentResolver cr) {
              String ret = "android-google";
              Cursor c = null;
              try {
                  c = cr.query(Uri.parse("content://com.google.settings/partner"),
                          new String[] { "value" }, "name='client_id'", null, null);
                  if (c != null && c.moveToNext()) {
                      ret = c.getString(0);
                  }
              } catch (RuntimeException ex) {
                  // fall through to return the default
              } finally {
                  if (c != null) {
                      c.close();
                  }
              }
              return ret;
          }
  
          private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
              StringBuffer sb = new StringBuffer();
              int lastCharLoc = 0;
  
              final String client_id = getClientId(context.getContentResolver());
  
              for (int i = 0; i < srcString.length(); ++i) {
                  char c = srcString.charAt(i);
                  if (c == '{') {
                      sb.append(srcString.subSequence(lastCharLoc, i));
                      lastCharLoc = i;
                inner:
                      for (int j = i; j < srcString.length(); ++j) {
                          char k = srcString.charAt(j);
                          if (k == '}') {
                              String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
                              if (propertyKeyValue.equals("CLIENT_ID")) {
                                  sb.append(client_id);
                              } else {
                                  sb.append("unknown");
                              }
                              lastCharLoc = j + 1;
                              i = j;
                              break inner;
                          }
                      }
                  }
              }
              if (srcString.length() - lastCharLoc > 0) {
                  // Put on the tail, if there is one
                  sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
              }
              return sb;
          }
      }
  
      @Override
      public SQLiteOpenHelper getDatabaseHelper(Context context) {
          synchronized (this) {
              if (mOpenHelper == null) {
                  mOpenHelper = new DatabaseHelper(context);
              }
              return mOpenHelper;
          }
      }
  
      @Override
      public boolean isCallerSyncAdapter(Uri uri) {
          return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
      }
  
      @VisibleForTesting
      public void setWidgetObserver(ContentObserver obs) {
          mWidgetObserver = obs;
      }
  
      void refreshWidgets() {
          mUpdateWidgets = true;
      }
  
      @Override
      protected void onEndTransaction(boolean callerIsSyncAdapter) {
          super.onEndTransaction(callerIsSyncAdapter);
          if (mUpdateWidgets) {
              if (mWidgetObserver == null) {
                  BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
              } else {
                  mWidgetObserver.dispatchChange(false);
              }
              mUpdateWidgets = false;
          }
          mSyncToNetwork = true;
      }
  
      @Override
      public String getType(Uri uri) {
          final int match = URI_MATCHER.match(uri);
          switch (match) {
              case LEGACY:
              case BOOKMARKS:
                  return Bookmarks.CONTENT_TYPE;
              case LEGACY_ID:
              case BOOKMARKS_ID:
                  return Bookmarks.CONTENT_ITEM_TYPE;
              case HISTORY:
                  return History.CONTENT_TYPE;
              case HISTORY_ID:
                  return History.CONTENT_ITEM_TYPE;
              case SEARCHES:
                  return Searches.CONTENT_TYPE;
              case SEARCHES_ID:
                  return Searches.CONTENT_ITEM_TYPE;
          }
          return null;
      }
  
      boolean isNullAccount(String account) {
          if (account == null) return true;
          account = account.trim();
          return account.length() == 0 || account.equals("null");
      }
  
      Object[] getSelectionWithAccounts(Uri uri, String selection, String[] selectionArgs) {
          // Look for account info
          String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
          String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
          boolean hasAccounts = false;
          if (accountType != null && accountName != null) {
              if (!isNullAccount(accountType) && !isNullAccount(accountName)) {
                  selection = DatabaseUtils.concatenateWhere(selection,
                          Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? ");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { accountType, accountName });
                  hasAccounts = true;
              } else {
                  selection = DatabaseUtils.concatenateWhere(selection,
                          Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
                          Bookmarks.ACCOUNT_TYPE + " IS NULL");
              }
          }
          return new Object[] { selection, selectionArgs, hasAccounts };
      }
  
      @Override
      public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
              String sortOrder) {
          SQLiteDatabase db = mOpenHelper.getReadableDatabase();
          final int match = URI_MATCHER.match(uri);
          SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
          String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
          String groupBy = uri.getQueryParameter(PARAM_GROUP_BY);
          switch (match) {
              case ACCOUNTS: {
                  qb.setTables(VIEW_ACCOUNTS);
                  qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
                  String allowEmpty = uri.getQueryParameter(PARAM_ALLOW_EMPTY_ACCOUNTS);
                  if ("false".equals(allowEmpty)) {
                      selection = DatabaseUtils.concatenateWhere(selection,
                              SQL_WHERE_ACCOUNT_HAS_BOOKMARKS);
                  }
                  if (sortOrder == null) {
                      sortOrder = DEFAULT_SORT_ACCOUNTS;
                  }
                  break;
              }
  
              case BOOKMARKS_FOLDER_ID:
              case BOOKMARKS_ID:
              case BOOKMARKS: {
                  // Only show deleted bookmarks if requested to do so
                  if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) {
                      selection = DatabaseUtils.concatenateWhere(
                              Bookmarks.IS_DELETED + "=0", selection);
                  }
  
                  if (match == BOOKMARKS_ID) {
                      // Tack on the ID of the specific bookmark requested
                      selection = DatabaseUtils.concatenateWhere(selection,
                              TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?");
                      selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                              new String[] { Long.toString(ContentUris.parseId(uri)) });
                  } else if (match == BOOKMARKS_FOLDER_ID) {
                      // Tack on the ID of the specific folder requested
                      selection = DatabaseUtils.concatenateWhere(selection,
                              TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?");
                      selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                              new String[] { Long.toString(ContentUris.parseId(uri)) });
                  }
  
                  Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
                  selection = (String) withAccount[0];
                  selectionArgs = (String[]) withAccount[1];
                  boolean hasAccounts = (Boolean) withAccount[2];
  
                  // Set a default sort order if one isn't specified
                  if (TextUtils.isEmpty(sortOrder)) {
                      if (hasAccounts) {
                          sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
                      } else {
                          sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
                      }
                  }
  
                  qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
                  qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
                  break;
              }
  
              case BOOKMARKS_FOLDER: {
                  // Look for an account
                  boolean useAccount = false;
                  String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
                  String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
                  if (!isNullAccount(accountType) && !isNullAccount(accountName)) {
                      useAccount = true;
                  }
  
                  qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
                  String[] args;
                  String query;
                  // Set a default sort order if one isn't specified
                  if (TextUtils.isEmpty(sortOrder)) {
                      if (useAccount) {
                          sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
                      } else {
                          sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
                      }
                  }
                  if (!useAccount) {
                      qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
                      String where = Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0";
                      where = DatabaseUtils.concatenateWhere(where, selection);
                      args = new String[] { Long.toString(FIXED_ID_ROOT) };
                      if (selectionArgs != null) {
                          args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
                      }
                      query = qb.buildQuery(projection, where, null, null, sortOrder, null);
                  } else {
                      qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
                      String where = Bookmarks.ACCOUNT_TYPE + "=? AND " +
                              Bookmarks.ACCOUNT_NAME + "=? " +
                              "AND parent = " +
                              "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " +
                              ChromeSyncColumns.SERVER_UNIQUE + "=" +
                              "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " +
                              "AND account_type = ? AND account_name = ?) " +
                              "AND " + Bookmarks.IS_DELETED + "=0";
                      where = DatabaseUtils.concatenateWhere(where, selection);
                      String bookmarksBarQuery = qb.buildQuery(projection,
                              where, null, null, null, null);
                      args = new String[] {accountType, accountName,
                              accountType, accountName};
                      if (selectionArgs != null) {
                          args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
                      }
  
                      where = Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" +
                              " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?";
                      where = DatabaseUtils.concatenateWhere(where, selection);
                      qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
                      String otherBookmarksQuery = qb.buildQuery(projection,
                              where, null, null, null, null);
  
                      query = qb.buildUnionQuery(
                              new String[] { bookmarksBarQuery, otherBookmarksQuery },
                              sortOrder, limit);
  
                      args = DatabaseUtils.appendSelectionArgs(args, new String[] {
                              accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS,
                              });
                      if (selectionArgs != null) {
                          args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
                      }
                  }
  
                  Cursor cursor = db.rawQuery(query, args);
                  if (cursor != null) {
                      cursor.setNotificationUri(getContext().getContentResolver(),
                              BrowserContract.AUTHORITY_URI);
                  }
                  return cursor;
              }
  
              case BOOKMARKS_DEFAULT_FOLDER_ID: {
                  String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
                  String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
                  long id = queryDefaultFolderId(accountName, accountType);
                  MatrixCursor c = new MatrixCursor(new String[] {Bookmarks._ID});
                  c.newRow().add(id);
                  return c;
              }
  
              case BOOKMARKS_SUGGESTIONS: {
                  return doSuggestQuery(selection, selectionArgs, limit);
              }
  
              case HISTORY_ID: {
                  selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case HISTORY: {
                  filterSearchClient(selectionArgs);
                  if (sortOrder == null) {
                      sortOrder = DEFAULT_SORT_HISTORY;
                  }
                  qb.setProjectionMap(HISTORY_PROJECTION_MAP);
                  qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
                  break;
              }
  
              case SEARCHES_ID: {
                  selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case SEARCHES: {
                  qb.setTables(TABLE_SEARCHES);
                  qb.setProjectionMap(SEARCHES_PROJECTION_MAP);
                  break;
              }
  
              case SYNCSTATE: {
                  return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder);
              }
  
              case SYNCSTATE_ID: {
                  selection = appendAccountToSelection(uri, selection);
                  String selectionWithId =
                          (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                          + (selection == null ? "" : " AND (" + selection + ")");
                  return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
              }
  
              case IMAGES: {
                  qb.setTables(TABLE_IMAGES);
                  qb.setProjectionMap(IMAGES_PROJECTION_MAP);
                  break;
              }
  
              case LEGACY_ID:
              case COMBINED_ID: {
                  selection = DatabaseUtils.concatenateWhere(
                          selection, Combined._ID + " = CAST(? AS INTEGER)");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case LEGACY:
              case COMBINED: {
                  if ((match == LEGACY || match == LEGACY_ID)
                          && projection == null) {
                      projection = Browser.HISTORY_PROJECTION;
                  }
                  String[] args = createCombinedQuery(uri, projection, qb);
                  if (selectionArgs == null) {
                      selectionArgs = args;
                  } else {
                      selectionArgs = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
                  }
                  break;
              }
  
              case SETTINGS: {
                  qb.setTables(TABLE_SETTINGS);
                  qb.setProjectionMap(SETTINGS_PROJECTION_MAP);
                  break;
              }
  
              case THUMBNAILS_ID: {
                  selection = DatabaseUtils.concatenateWhere(
                          selection, Thumbnails._ID + " = ?");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case THUMBNAILS: {
                  qb.setTables(TABLE_THUMBNAILS);
                  break;
              }
  
              case OMNIBOX_SUGGESTIONS: {
                  qb.setTables(VIEW_OMNIBOX_SUGGESTIONS);
                  break;
              }
  
              default: {
                  throw new UnsupportedOperationException("Unknown URL " + uri.toString());
              }
          }
  
          Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy,
                  null, sortOrder, limit);
          cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
          return cursor;
      }
  
      private Cursor doSuggestQuery(String selection, String[] selectionArgs, String limit) {
          if (TextUtils.isEmpty(selectionArgs[0])) {
              selection = ZERO_QUERY_SUGGEST_SELECTION;
              selectionArgs = null;
          } else {
              String like = selectionArgs[0] + "%";
              if (selectionArgs[0].startsWith("http")
                      || selectionArgs[0].startsWith("file")) {
                  selectionArgs[0] = like;
              } else {
                  selectionArgs = new String[6];
                  selectionArgs[0] = "http://" + like;
                  selectionArgs[1] = "http://www." + like;
                  selectionArgs[2] = "https://" + like;
                  selectionArgs[3] = "https://www." + like;
                  // To match against titles.
                  selectionArgs[4] = like;
                  selectionArgs[5] = like;
                  selection = SUGGEST_SELECTION;
              }
              selection = DatabaseUtils.concatenateWhere(selection,
                      Bookmarks.IS_DELETED + "=0 AND " + Bookmarks.IS_FOLDER + "=0");
  
          }
          Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_BOOKMARKS_JOIN_HISTORY,
                  SUGGEST_PROJECTION, selection, selectionArgs, null, null,
                  null, null);
  
          return new SuggestionsCursor(c);
      }
  
      private String[] createCombinedQuery(
              Uri uri, String[] projection, SQLiteQueryBuilder qb) {
          String[] args = null;
          StringBuilder whereBuilder = new StringBuilder(128);
          whereBuilder.append(Bookmarks.IS_DELETED);
          whereBuilder.append(" = 0");
          // Look for account info
          Object[] withAccount = getSelectionWithAccounts(uri, null, null);
          String selection = (String) withAccount[0];
          String[] selectionArgs = (String[]) withAccount[1];
          if (selection != null) {
              whereBuilder.append(" AND " + selection);
              if (selectionArgs != null) {
                  // We use the selection twice, hence we need to duplicate the args
                  args = new String[selectionArgs.length * 2];
                  System.arraycopy(selectionArgs, 0, args, 0, selectionArgs.length);
                  System.arraycopy(selectionArgs, 0, args, selectionArgs.length,
                          selectionArgs.length);
              }
          }
          String where = whereBuilder.toString();
          // Build the bookmark subquery for history union subquery
          qb.setTables(TABLE_BOOKMARKS);
          String subQuery = qb.buildQuery(null, where, null, null, null, null);
          // Build the history union subquery
          qb.setTables(String.format(FORMAT_COMBINED_JOIN_SUBQUERY_JOIN_IMAGES, subQuery));
          qb.setProjectionMap(COMBINED_HISTORY_PROJECTION_MAP);
          String historySubQuery = qb.buildQuery(null,
                  null, null, null, null, null);
          // Build the bookmark union subquery
          qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
          qb.setProjectionMap(COMBINED_BOOKMARK_PROJECTION_MAP);
          where += String.format(" AND %s NOT IN (SELECT %s FROM %s)",
                  Combined.URL, History.URL, TABLE_HISTORY);
          String bookmarksSubQuery = qb.buildQuery(null, where,
                  null, null, null, null);
          // Put it all together
          String query = qb.buildUnionQuery(
                  new String[] {historySubQuery, bookmarksSubQuery},
                  null, null);
          qb.setTables("(" + query + ")");
          qb.setProjectionMap(null);
          return args;
      }
  
      int deleteBookmarks(String selection, String[] selectionArgs,
              boolean callerIsSyncAdapter) {
          final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
          if (callerIsSyncAdapter) {
              return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
          }
  
          Object[] appendedBookmarks = appendBookmarksIfFolder(selection, selectionArgs);
          selection = (String) appendedBookmarks[0];
          selectionArgs = (String[]) appendedBookmarks[1];
  
          ContentValues values = new ContentValues();
          values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
          values.put(Bookmarks.IS_DELETED, 1);
          return updateBookmarksInTransaction(values, selection, selectionArgs,
                  callerIsSyncAdapter);
      }
  
      private Object[] appendBookmarksIfFolder(String selection, String[] selectionArgs) {
          final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
          final String[] bookmarksProjection = new String[] {
                  Bookmarks._ID, // 0
                  Bookmarks.IS_FOLDER // 1
          };
          StringBuilder newSelection = new StringBuilder(selection);
          List<String> newSelectionArgs = new ArrayList<String>();
  
          Cursor cursor = null;
          try {
              cursor = db.query(TABLE_BOOKMARKS, bookmarksProjection,
                      selection, selectionArgs, null, null, null);
              if (cursor != null) {
                  while (cursor.moveToNext()) {
                      String id = Long.toString(cursor.getLong(0));
                      newSelectionArgs.add(id);
                      if (cursor.getInt(1) != 0) {
                          // collect bookmarks in this folder
                          Object[] bookmarks = appendBookmarksIfFolder(
                                  Bookmarks.PARENT + "=?", new String[] { id });
                          String[] bookmarkIds = (String[]) bookmarks[1];
                          if (bookmarkIds.length > 0) {
                              newSelection.append(" OR " + TABLE_BOOKMARKS + "._id IN (");
                              for (String bookmarkId : bookmarkIds) {
                                  newSelection.append("?,");
                                  newSelectionArgs.add(bookmarkId);
                              }
                              newSelection.deleteCharAt(newSelection.length() - 1);
                              newSelection.append(")");
                          }
                      }
                  }
              }
          } finally {
              if (cursor != null) {
                  cursor.close();
              }
          }
  
          return new Object[] {
                  newSelection.toString(),
                  newSelectionArgs.toArray(new String[newSelectionArgs.size()])
          };
      }
  
      @Override
      public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
              boolean callerIsSyncAdapter) {
          final int match = URI_MATCHER.match(uri);
          final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
          int deleted = 0;
          switch (match) {
              case BOOKMARKS_ID: {
                  selection = DatabaseUtils.concatenateWhere(selection,
                          TABLE_BOOKMARKS + "._id=?");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case BOOKMARKS: {
                  // Look for account info
                  Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
                  selection = (String) withAccount[0];
                  selectionArgs = (String[]) withAccount[1];
                  deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
                  pruneImages();
                  if (deleted > 0) {
                      refreshWidgets();
                  }
                  break;
              }
  
              case HISTORY_ID: {
                  selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case HISTORY: {
                  filterSearchClient(selectionArgs);
                  deleted = db.delete(TABLE_HISTORY, selection, selectionArgs);
                  pruneImages();
                  break;
              }
  
              case SEARCHES_ID: {
                  selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case SEARCHES: {
                  deleted = db.delete(TABLE_SEARCHES, selection, selectionArgs);
                  break;
              }
  
              case SYNCSTATE: {
                  deleted = mSyncHelper.delete(db, selection, selectionArgs);
                  break;
              }
              case SYNCSTATE_ID: {
                  String selectionWithId =
                          (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
                          + (selection == null ? "" : " AND (" + selection + ")");
                  deleted = mSyncHelper.delete(db, selectionWithId, selectionArgs);
                  break;
              }
              case LEGACY_ID: {
                  selection = DatabaseUtils.concatenateWhere(
                          selection, Combined._ID + " = CAST(? AS INTEGER)");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case LEGACY: {
                  String[] projection = new String[] { Combined._ID,
                          Combined.IS_BOOKMARK, Combined.URL };
                  SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
                  String[] args = createCombinedQuery(uri, projection, qb);
                  if (selectionArgs == null) {
                      selectionArgs = args;
                  } else {
                      selectionArgs = DatabaseUtils.appendSelectionArgs(
                              args, selectionArgs);
                  }
                  Cursor c = qb.query(db, projection, selection, selectionArgs,
                          null, null, null);
                  while (c.moveToNext()) {
                      long id = c.getLong(0);
                      boolean isBookmark = c.getInt(1) != 0;
                      String url = c.getString(2);
                      if (isBookmark) {
                          deleted += deleteBookmarks(Bookmarks._ID + "=?",
                                  new String[] { Long.toString(id) },
                                  callerIsSyncAdapter);
                          db.delete(TABLE_HISTORY, History.URL + "=?",
                                  new String[] { url });
                      } else {
                          deleted += db.delete(TABLE_HISTORY,
                                  Bookmarks._ID + "=?",
                                  new String[] { Long.toString(id) });
                      }
                  }
                  c.close();
                  break;
              }
              case THUMBNAILS_ID: {
                  selection = DatabaseUtils.concatenateWhere(
                          selection, Thumbnails._ID + " = ?");
                  selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                          new String[] { Long.toString(ContentUris.parseId(uri)) });
                  // fall through
              }
              case THUMBNAILS: {
                  deleted = db.delete(TABLE_THUMBNAILS, selection, selectionArgs);
                  break;
              }
              default: {
                  throw new UnsupportedOperationException("Unknown delete URI " + uri);
              }
          }
          if (deleted > 0) {
              postNotifyUri(uri);
              if (shouldNotifyLegacy(uri)) {
                  postNotifyUri(LEGACY_AUTHORITY_URI);
              }
          }
          return deleted;
      }
  
      long queryDefaultFolderId(String accountName, String accountType) {
          if (!isNullAccount(accountName) && !isNullAccount(accountType)) {
              final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
              Cursor c = db.query(TABLE_BOOKMARKS, new String[] { Bookmarks._ID },
                      ChromeSyncColumns.SERVER_UNIQUE + " = ?" +
                      " AND account_type = ? AND account_name = ?",
                      new String[] { ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR,
                      accountType, accountName }, null, null, null);
              try {
                  if (c.moveToFirst()) {
                      return c.getLong(0);
                  }
              } finally {
                  c.close();
              }
          }
          return FIXED_ID_ROOT;
      }
  
      @Override
      public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
          int match = URI_MATCHER.match(uri);
          final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
          long id = -1;
          if (match == LEGACY) {
              // Intercept and route to the correct table
              Integer bookmark = values.getAsInteger(BookmarkColumns.BOOKMARK);
              values.remove(BookmarkColumns.BOOKMARK);
              if (bookmark == null || bookmark == 0) {
                  match = HISTORY;
              } else {
                  match = BOOKMARKS;
                  values.remove(BookmarkColumns.DATE);
                  values.remove(BookmarkColumns.VISITS);
                  values.remove(BookmarkColumns.USER_ENTERED);
                  values.put(Bookmarks.IS_FOLDER, 0);
              }
          }
          switch (match) {
              case BOOKMARKS: {
                  // Mark rows dirty if they're not coming from a sync adapter
                  if (!callerIsSyncAdapter) {
                      long now = System.currentTimeMillis();
                      values.put(Bookmarks.DATE_CREATED, now);
                      values.put(Bookmarks.DATE_MODIFIED, now);
                      values.put(Bookmarks.DIRTY, 1);
  
                      boolean hasAccounts = values.containsKey(Bookmarks.ACCOUNT_TYPE)
                              || values.containsKey(Bookmarks.ACCOUNT_NAME);
                      String accountType = values
                              .getAsString(Bookmarks.ACCOUNT_TYPE);
                      String accountName = values
                              .getAsString(Bookmarks.ACCOUNT_NAME);
                      boolean hasParent = values.containsKey(Bookmarks.PARENT);
                      if (hasParent && hasAccounts) {
                          // Let's make sure it's valid
                          long parentId = values.getAsLong(Bookmarks.PARENT);
                          hasParent = isValidParent(
                                  accountType, accountName, parentId);
                      } else if (hasParent && !hasAccounts) {
                          long parentId = values.getAsLong(Bookmarks.PARENT);
                          hasParent = setParentValues(parentId, values);
                      }
  
                      // If no parent is set default to the "Bookmarks Bar" folder
                      if (!hasParent) {
                          values.put(Bookmarks.PARENT,
                                  queryDefaultFolderId(accountName, accountType));
                      }
                  }
  
                  // If no position is requested put the bookmark at the beginning of the list
                  if (!values.containsKey(Bookmarks.POSITION)) {
                      values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
                  }
  
                  // Extract out the image values so they can be inserted into the images table
                  String url = values.getAsString(Bookmarks.URL);
                  ContentValues imageValues = extractImageValues(values, url);
                  Boolean isFolder = values.getAsBoolean(Bookmarks.IS_FOLDER);
                  if ((isFolder == null || !isFolder)
                          && imageValues != null && !TextUtils.isEmpty(url)) {
                      int count = db.update(TABLE_IMAGES, imageValues, Images.URL + "=?",
                              new String[] { url });
                      if (count == 0) {
  