Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2010 he Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.browser.provider;
     18 
     19 import android.accounts.Account;
     20 import android.accounts.AccountManager;
     21 import android.app.SearchManager;
     22 import android.content.ContentResolver;
     23 import android.content.ContentUris;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.UriMatcher;
     28 import android.content.res.Resources;
     29 import android.content.res.TypedArray;
     30 import android.database.AbstractCursor;
     31 import android.database.ContentObserver;
     32 import android.database.Cursor;
     33 import android.database.DatabaseUtils;
     34 import android.database.MatrixCursor;
     35 import android.database.sqlite.SQLiteDatabase;
     36 import android.database.sqlite.SQLiteOpenHelper;
     37 import android.database.sqlite.SQLiteQueryBuilder;
     38 import android.net.Uri;
     39 import android.provider.BaseColumns;
     40 import android.provider.Browser;
     41 import android.provider.Browser.BookmarkColumns;
     42 import android.provider.BrowserContract;
     43 import android.provider.BrowserContract.Accounts;
     44 import android.provider.BrowserContract.Bookmarks;
     45 import android.provider.BrowserContract.ChromeSyncColumns;
     46 import android.provider.BrowserContract.Combined;
     47 import android.provider.BrowserContract.History;
     48 import android.provider.BrowserContract.Images;
     49 import android.provider.BrowserContract.Searches;
     50 import android.provider.BrowserContract.Settings;
     51 import android.provider.BrowserContract.SyncState;
     52 import android.provider.ContactsContract.RawContacts;
     53 import android.provider.SyncStateContract;
     54 import android.text.TextUtils;
     55 
     56 import com.android.browser.R;
     57 import com.android.browser.UrlUtils;
     58 import com.android.browser.widget.BookmarkThumbnailWidgetProvider;
     59 import com.android.common.content.SyncStateContentProviderHelper;
     60 import com.google.common.annotations.VisibleForTesting;
     61 
     62 import java.io.ByteArrayOutputStream;
     63 import java.io.File;
     64 import java.io.IOException;
     65 import java.io.InputStream;
     66 import java.util.Arrays;
     67 import java.util.HashMap;
     68 
     69 public class BrowserProvider2 extends SQLiteContentProvider {
     70 
     71     public static final String PARAM_GROUP_BY = "groupBy";
     72     public static final String PARAM_ALLOW_EMPTY_ACCOUNTS = "allowEmptyAccounts";
     73 
     74     public static final String LEGACY_AUTHORITY = "browser";
     75     static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder()
     76             .authority(LEGACY_AUTHORITY).scheme("content").build();
     77 
     78     public static interface Thumbnails {
     79         public static final Uri CONTENT_URI = Uri.withAppendedPath(
     80                 BrowserContract.AUTHORITY_URI, "thumbnails");
     81         public static final String _ID = "_id";
     82         public static final String THUMBNAIL = "thumbnail";
     83     }
     84 
     85     public static interface OmniboxSuggestions {
     86         public static final Uri CONTENT_URI = Uri.withAppendedPath(
     87                 BrowserContract.AUTHORITY_URI, "omnibox_suggestions");
     88         public static final String _ID = "_id";
     89         public static final String URL = "url";
     90         public static final String TITLE = "title";
     91         public static final String IS_BOOKMARK = "bookmark";
     92     }
     93 
     94     static final String TABLE_BOOKMARKS = "bookmarks";
     95     static final String TABLE_HISTORY = "history";
     96     static final String TABLE_IMAGES = "images";
     97     static final String TABLE_SEARCHES = "searches";
     98     static final String TABLE_SYNC_STATE = "syncstate";
     99     static final String TABLE_SETTINGS = "settings";
    100     static final String TABLE_SNAPSHOTS = "snapshots";
    101     static final String TABLE_THUMBNAILS = "thumbnails";
    102 
    103     static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " +
    104             "ON bookmarks.url = images." + Images.URL;
    105     static final String TABLE_HISTORY_JOIN_IMAGES = "history LEFT OUTER JOIN images " +
    106             "ON history.url = images." + Images.URL;
    107 
    108     static final String VIEW_ACCOUNTS = "v_accounts";
    109     static final String VIEW_SNAPSHOTS_COMBINED = "v_snapshots_combined";
    110     static final String VIEW_OMNIBOX_SUGGESTIONS = "v_omnibox_suggestions";
    111 
    112     static final String FORMAT_COMBINED_JOIN_SUBQUERY_JOIN_IMAGES =
    113             "history LEFT OUTER JOIN (%s) bookmarks " +
    114             "ON history.url = bookmarks.url LEFT OUTER JOIN images " +
    115             "ON history.url = images.url_key";
    116 
    117     static final String DEFAULT_SORT_HISTORY = History.DATE_LAST_VISITED + " DESC";
    118     static final String DEFAULT_SORT_ACCOUNTS =
    119             Accounts.ACCOUNT_NAME + " IS NOT NULL DESC, "
    120             + Accounts.ACCOUNT_NAME + " ASC";
    121 
    122     private static final String TABLE_BOOKMARKS_JOIN_HISTORY =
    123         "history LEFT OUTER JOIN bookmarks ON history.url = bookmarks.url";
    124 
    125     private static final String[] SUGGEST_PROJECTION = new String[] {
    126             qualifyColumn(TABLE_HISTORY, History._ID),
    127             qualifyColumn(TABLE_HISTORY, History.URL),
    128             bookmarkOrHistoryColumn(Combined.TITLE),
    129             bookmarkOrHistoryLiteral(Combined.URL,
    130                     Integer.toString(R.drawable.ic_bookmark_off_holo_dark),
    131                     Integer.toString(R.drawable.ic_history_holo_dark)),
    132             qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED)};
    133 
    134     private static final String SUGGEST_SELECTION =
    135             "history.url LIKE ? OR history.url LIKE ? OR history.url LIKE ? OR history.url LIKE ?"
    136             + " OR history.title LIKE ? OR bookmarks.title LIKE ?";
    137 
    138     private static final String ZERO_QUERY_SUGGEST_SELECTION =
    139             TABLE_HISTORY + "." + History.DATE_LAST_VISITED + " != 0";
    140 
    141     private static final String SUGGEST_ORDER_BY =
    142             TABLE_HISTORY + "." + History.DATE_LAST_VISITED + " DESC";
    143 
    144     private static final String IMAGE_PRUNE =
    145             "url_key NOT IN (SELECT url FROM bookmarks " +
    146             "WHERE url IS NOT NULL AND deleted == 0) AND url_key NOT IN " +
    147             "(SELECT url FROM history WHERE url IS NOT NULL)";
    148 
    149     static final int THUMBNAILS = 10;
    150     static final int THUMBNAILS_ID = 11;
    151     static final int OMNIBOX_SUGGESTIONS = 20;
    152 
    153     static final int BOOKMARKS = 1000;
    154     static final int BOOKMARKS_ID = 1001;
    155     static final int BOOKMARKS_FOLDER = 1002;
    156     static final int BOOKMARKS_FOLDER_ID = 1003;
    157     static final int BOOKMARKS_SUGGESTIONS = 1004;
    158     static final int BOOKMARKS_DEFAULT_FOLDER_ID = 1005;
    159 
    160     static final int HISTORY = 2000;
    161     static final int HISTORY_ID = 2001;
    162 
    163     static final int SEARCHES = 3000;
    164     static final int SEARCHES_ID = 3001;
    165 
    166     static final int SYNCSTATE = 4000;
    167     static final int SYNCSTATE_ID = 4001;
    168 
    169     static final int IMAGES = 5000;
    170 
    171     static final int COMBINED = 6000;
    172     static final int COMBINED_ID = 6001;
    173 
    174     static final int ACCOUNTS = 7000;
    175 
    176     static final int SETTINGS = 8000;
    177 
    178     static final int LEGACY = 9000;
    179     static final int LEGACY_ID = 9001;
    180 
    181     public static final long FIXED_ID_ROOT = 1;
    182 
    183     // Default sort order for unsync'd bookmarks
    184     static final String DEFAULT_BOOKMARKS_SORT_ORDER =
    185             Bookmarks.IS_FOLDER + " DESC, position ASC, _id ASC";
    186 
    187     // Default sort order for sync'd bookmarks
    188     static final String DEFAULT_BOOKMARKS_SORT_ORDER_SYNC = "position ASC, _id ASC";
    189 
    190     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    191 
    192     static final HashMap<String, String> ACCOUNTS_PROJECTION_MAP = new HashMap<String, String>();
    193     static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
    194     static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP =
    195             new HashMap<String, String>();
    196     static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>();
    197     static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
    198     static final HashMap<String, String> IMAGES_PROJECTION_MAP = new HashMap<String, String>();
    199     static final HashMap<String, String> COMBINED_HISTORY_PROJECTION_MAP = new HashMap<String, String>();
    200     static final HashMap<String, String> COMBINED_BOOKMARK_PROJECTION_MAP = new HashMap<String, String>();
    201     static final HashMap<String, String> SEARCHES_PROJECTION_MAP = new HashMap<String, String>();
    202     static final HashMap<String, String> SETTINGS_PROJECTION_MAP = new HashMap<String, String>();
    203 
    204     static {
    205         final UriMatcher matcher = URI_MATCHER;
    206         final String authority = BrowserContract.AUTHORITY;
    207         matcher.addURI(authority, "accounts", ACCOUNTS);
    208         matcher.addURI(authority, "bookmarks", BOOKMARKS);
    209         matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
    210         matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
    211         matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
    212         matcher.addURI(authority, "bookmarks/folder/id", BOOKMARKS_DEFAULT_FOLDER_ID);
    213         matcher.addURI(authority,
    214                 SearchManager.SUGGEST_URI_PATH_QUERY,
    215                 BOOKMARKS_SUGGESTIONS);
    216         matcher.addURI(authority,
    217                 "bookmarks/" + SearchManager.SUGGEST_URI_PATH_QUERY,
    218                 BOOKMARKS_SUGGESTIONS);
    219         matcher.addURI(authority, "history", HISTORY);
    220         matcher.addURI(authority, "history/#", HISTORY_ID);
    221         matcher.addURI(authority, "searches", SEARCHES);
    222         matcher.addURI(authority, "searches/#", SEARCHES_ID);
    223         matcher.addURI(authority, "syncstate", SYNCSTATE);
    224         matcher.addURI(authority, "syncstate/#", SYNCSTATE_ID);
    225         matcher.addURI(authority, "images", IMAGES);
    226         matcher.addURI(authority, "combined", COMBINED);
    227         matcher.addURI(authority, "combined/#", COMBINED_ID);
    228         matcher.addURI(authority, "settings", SETTINGS);
    229         matcher.addURI(authority, "thumbnails", THUMBNAILS);
    230         matcher.addURI(authority, "thumbnails/#", THUMBNAILS_ID);
    231         matcher.addURI(authority, "omnibox_suggestions", OMNIBOX_SUGGESTIONS);
    232 
    233         // Legacy
    234         matcher.addURI(LEGACY_AUTHORITY, "searches", SEARCHES);
    235         matcher.addURI(LEGACY_AUTHORITY, "searches/#", SEARCHES_ID);
    236         matcher.addURI(LEGACY_AUTHORITY, "bookmarks", LEGACY);
    237         matcher.addURI(LEGACY_AUTHORITY, "bookmarks/#", LEGACY_ID);
    238         matcher.addURI(LEGACY_AUTHORITY,
    239                 SearchManager.SUGGEST_URI_PATH_QUERY,
    240                 BOOKMARKS_SUGGESTIONS);
    241         matcher.addURI(LEGACY_AUTHORITY,
    242                 "bookmarks/" + SearchManager.SUGGEST_URI_PATH_QUERY,
    243                 BOOKMARKS_SUGGESTIONS);
    244 
    245         // Projection maps
    246         HashMap<String, String> map;
    247 
    248         // Accounts
    249         map = ACCOUNTS_PROJECTION_MAP;
    250         map.put(Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_TYPE);
    251         map.put(Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_NAME);
    252         map.put(Accounts.ROOT_ID, Accounts.ROOT_ID);
    253 
    254         // Bookmarks
    255         map = BOOKMARKS_PROJECTION_MAP;
    256         map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
    257         map.put(Bookmarks.TITLE, Bookmarks.TITLE);
    258         map.put(Bookmarks.URL, Bookmarks.URL);
    259         map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
    260         map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
    261         map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
    262         map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
    263         map.put(Bookmarks.PARENT, Bookmarks.PARENT);
    264         map.put(Bookmarks.POSITION, Bookmarks.POSITION);
    265         map.put(Bookmarks.INSERT_AFTER, Bookmarks.INSERT_AFTER);
    266         map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
    267         map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME);
    268         map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE);
    269         map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID);
    270         map.put(Bookmarks.VERSION, Bookmarks.VERSION);
    271         map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
    272         map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
    273         map.put(Bookmarks.DIRTY, Bookmarks.DIRTY);
    274         map.put(Bookmarks.SYNC1, Bookmarks.SYNC1);
    275         map.put(Bookmarks.SYNC2, Bookmarks.SYNC2);
    276         map.put(Bookmarks.SYNC3, Bookmarks.SYNC3);
    277         map.put(Bookmarks.SYNC4, Bookmarks.SYNC4);
    278         map.put(Bookmarks.SYNC5, Bookmarks.SYNC5);
    279         map.put(Bookmarks.PARENT_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
    280                 " FROM " + TABLE_BOOKMARKS + " A WHERE " +
    281                 "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.PARENT +
    282                 ") AS " + Bookmarks.PARENT_SOURCE_ID);
    283         map.put(Bookmarks.INSERT_AFTER_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
    284                 " FROM " + TABLE_BOOKMARKS + " A WHERE " +
    285                 "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.INSERT_AFTER +
    286                 ") AS " + Bookmarks.INSERT_AFTER_SOURCE_ID);
    287 
    288         // Other bookmarks
    289         OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP);
    290         OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION,
    291                 Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION);
    292 
    293         // History
    294         map = HISTORY_PROJECTION_MAP;
    295         map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
    296         map.put(History.TITLE, History.TITLE);
    297         map.put(History.URL, History.URL);
    298         map.put(History.FAVICON, History.FAVICON);
    299         map.put(History.THUMBNAIL, History.THUMBNAIL);
    300         map.put(History.TOUCH_ICON, History.TOUCH_ICON);
    301         map.put(History.DATE_CREATED, History.DATE_CREATED);
    302         map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
    303         map.put(History.VISITS, History.VISITS);
    304         map.put(History.USER_ENTERED, History.USER_ENTERED);
    305 
    306         // Sync state
    307         map = SYNC_STATE_PROJECTION_MAP;
    308         map.put(SyncState._ID, SyncState._ID);
    309         map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME);
    310         map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE);
    311         map.put(SyncState.DATA, SyncState.DATA);
    312 
    313         // Images
    314         map = IMAGES_PROJECTION_MAP;
    315         map.put(Images.URL, Images.URL);
    316         map.put(Images.FAVICON, Images.FAVICON);
    317         map.put(Images.THUMBNAIL, Images.THUMBNAIL);
    318         map.put(Images.TOUCH_ICON, Images.TOUCH_ICON);
    319 
    320         // Combined history half
    321         map = COMBINED_HISTORY_PROJECTION_MAP;
    322         map.put(Combined._ID, bookmarkOrHistoryColumn(Combined._ID));
    323         map.put(Combined.TITLE, bookmarkOrHistoryColumn(Combined.TITLE));
    324         map.put(Combined.URL, qualifyColumn(TABLE_HISTORY, Combined.URL));
    325         map.put(Combined.DATE_CREATED, qualifyColumn(TABLE_HISTORY, Combined.DATE_CREATED));
    326         map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED);
    327         map.put(Combined.IS_BOOKMARK, "CASE WHEN " +
    328                 TABLE_BOOKMARKS + "." + Bookmarks._ID +
    329                 " IS NOT NULL THEN 1 ELSE 0 END AS " + Combined.IS_BOOKMARK);
    330         map.put(Combined.VISITS, Combined.VISITS);
    331         map.put(Combined.FAVICON, Combined.FAVICON);
    332         map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
    333         map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
    334         map.put(Combined.USER_ENTERED, "NULL AS " + Combined.USER_ENTERED);
    335 
    336         // Combined bookmark half
    337         map = COMBINED_BOOKMARK_PROJECTION_MAP;
    338         map.put(Combined._ID, Combined._ID);
    339         map.put(Combined.TITLE, Combined.TITLE);
    340         map.put(Combined.URL, Combined.URL);
    341         map.put(Combined.DATE_CREATED, Combined.DATE_CREATED);
    342         map.put(Combined.DATE_LAST_VISITED, "NULL AS " + Combined.DATE_LAST_VISITED);
    343         map.put(Combined.IS_BOOKMARK, "1 AS " + Combined.IS_BOOKMARK);
    344         map.put(Combined.VISITS, "0 AS " + Combined.VISITS);
    345         map.put(Combined.FAVICON, Combined.FAVICON);
    346         map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
    347         map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
    348         map.put(Combined.USER_ENTERED, "NULL AS " + Combined.USER_ENTERED);
    349 
    350         // Searches
    351         map = SEARCHES_PROJECTION_MAP;
    352         map.put(Searches._ID, Searches._ID);
    353         map.put(Searches.SEARCH, Searches.SEARCH);
    354         map.put(Searches.DATE, Searches.DATE);
    355 
    356         // Settings
    357         map = SETTINGS_PROJECTION_MAP;
    358         map.put(Settings.KEY, Settings.KEY);
    359         map.put(Settings.VALUE, Settings.VALUE);
    360     }
    361 
    362     static final String bookmarkOrHistoryColumn(String column) {
    363         return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN " +
    364                 "bookmarks." + column + " ELSE history." + column + " END AS " + column;
    365     }
    366 
    367     static final String bookmarkOrHistoryLiteral(String column, String bookmarkValue,
    368             String historyValue) {
    369         return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN \"" + bookmarkValue +
    370                 "\" ELSE \"" + historyValue + "\" END";
    371     }
    372 
    373     static final String qualifyColumn(String table, String column) {
    374         return table + "." + column + " AS " + column;
    375     }
    376 
    377     DatabaseHelper mOpenHelper;
    378     SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
    379     // This is so provider tests can intercept widget updating
    380     ContentObserver mWidgetObserver = null;
    381     boolean mUpdateWidgets = false;
    382     boolean mSyncToNetwork = true;
    383 
    384     final class DatabaseHelper extends SQLiteOpenHelper {
    385         static final String DATABASE_NAME = "browser2.db";
    386         static final int DATABASE_VERSION = 32;
    387         public DatabaseHelper(Context context) {
    388             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    389         }
    390 
    391         @Override
    392         public void onCreate(SQLiteDatabase db) {
    393             db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
    394                     Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
    395                     Bookmarks.TITLE + " TEXT," +
    396                     Bookmarks.URL + " TEXT," +
    397                     Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
    398                     Bookmarks.PARENT + " INTEGER," +
    399                     Bookmarks.POSITION + " INTEGER NOT NULL," +
    400                     Bookmarks.INSERT_AFTER + " INTEGER," +
    401                     Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," +
    402                     Bookmarks.ACCOUNT_NAME + " TEXT," +
    403                     Bookmarks.ACCOUNT_TYPE + " TEXT," +
    404                     Bookmarks.SOURCE_ID + " TEXT," +
    405                     Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," +
    406                     Bookmarks.DATE_CREATED + " INTEGER," +
    407                     Bookmarks.DATE_MODIFIED + " INTEGER," +
    408                     Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
    409                     Bookmarks.SYNC1 + " TEXT," +
    410                     Bookmarks.SYNC2 + " TEXT," +
    411                     Bookmarks.SYNC3 + " TEXT," +
    412                     Bookmarks.SYNC4 + " TEXT," +
    413                     Bookmarks.SYNC5 + " TEXT" +
    414                     ");");
    415 
    416             // TODO indices
    417 
    418             db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
    419                     History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
    420                     History.TITLE + " TEXT," +
    421                     History.URL + " TEXT NOT NULL," +
    422                     History.DATE_CREATED + " INTEGER," +
    423                     History.DATE_LAST_VISITED + " INTEGER," +
    424                     History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
    425                     History.USER_ENTERED + " INTEGER" +
    426                     ");");
    427 
    428             db.execSQL("CREATE TABLE " + TABLE_IMAGES + " (" +
    429                     Images.URL + " TEXT UNIQUE NOT NULL," +
    430                     Images.FAVICON + " BLOB," +
    431                     Images.THUMBNAIL + " BLOB," +
    432                     Images.TOUCH_ICON + " BLOB" +
    433                     ");");
    434             db.execSQL("CREATE INDEX imagesUrlIndex ON " + TABLE_IMAGES +
    435                     "(" + Images.URL + ")");
    436 
    437             db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
    438                     Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
    439                     Searches.SEARCH + " TEXT," +
    440                     Searches.DATE + " LONG" +
    441                     ");");
    442 
    443             db.execSQL("CREATE TABLE " + TABLE_SETTINGS + " (" +
    444                     Settings.KEY + " TEXT PRIMARY KEY," +
    445                     Settings.VALUE + " TEXT NOT NULL" +
    446                     ");");
    447 
    448             createAccountsView(db);
    449             createThumbnails(db);
    450 
    451             mSyncHelper.createDatabase(db);
    452 
    453             if (!importFromBrowserProvider(db)) {
    454                 createDefaultBookmarks(db);
    455             }
    456 
    457             enableSync(db);
    458             createOmniboxSuggestions(db);
    459         }
    460 
    461         void createOmniboxSuggestions(SQLiteDatabase db) {
    462             db.execSQL(SQL_CREATE_VIEW_OMNIBOX_SUGGESTIONS);
    463         }
    464 
    465         void createThumbnails(SQLiteDatabase db) {
    466             db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_THUMBNAILS + " (" +
    467                     Thumbnails._ID + " INTEGER PRIMARY KEY," +
    468                     Thumbnails.THUMBNAIL + " BLOB NOT NULL" +
    469                     ");");
    470         }
    471 
    472         void enableSync(SQLiteDatabase db) {
    473             ContentValues values = new ContentValues();
    474             values.put(Settings.KEY, Settings.KEY_SYNC_ENABLED);
    475             values.put(Settings.VALUE, 1);
    476             insertSettingsInTransaction(db, values);
    477             // Enable bookmark sync on all accounts
    478             AccountManager am = (AccountManager) getContext().getSystemService(
    479                     Context.ACCOUNT_SERVICE);
    480             if (am == null) {
    481                 return;
    482             }
    483             Account[] accounts = am.getAccountsByType("com.google");
    484             if (accounts == null || accounts.length == 0) {
    485                 return;
    486             }
    487             for (Account account : accounts) {
    488                 if (ContentResolver.getIsSyncable(
    489                         account, BrowserContract.AUTHORITY) == 0) {
    490                     // Account wasn't syncable, enable it
    491                     ContentResolver.setIsSyncable(
    492                             account, BrowserContract.AUTHORITY, 1);
    493                     ContentResolver.setSyncAutomatically(
    494                             account, BrowserContract.AUTHORITY, true);
    495                 }
    496             }
    497         }
    498 
    499         boolean importFromBrowserProvider(SQLiteDatabase db) {
    500             Context context = getContext();
    501             File oldDbFile = context.getDatabasePath(BrowserProvider.sDatabaseName);
    502             if (oldDbFile.exists()) {
    503                 BrowserProvider.DatabaseHelper helper =
    504                         new BrowserProvider.DatabaseHelper(context);
    505                 SQLiteDatabase oldDb = helper.getWritableDatabase();
    506                 Cursor c = null;
    507                 try {
    508                     String table = BrowserProvider.TABLE_NAMES[BrowserProvider.URI_MATCH_BOOKMARKS];
    509                     // Import bookmarks
    510                     c = oldDb.query(table,
    511                             new String[] {
    512                             BookmarkColumns.URL, // 0
    513                             BookmarkColumns.TITLE, // 1
    514                             BookmarkColumns.FAVICON, // 2
    515                             BookmarkColumns.TOUCH_ICON, // 3
    516                             BookmarkColumns.CREATED, // 4
    517                             }, BookmarkColumns.BOOKMARK + "!=0", null,
    518                             null, null, null);
    519                     if (c != null) {
    520                         while (c.moveToNext()) {
    521                             ContentValues values = new ContentValues();
    522                             values.put(Bookmarks.URL, c.getString(0));
    523                             values.put(Bookmarks.TITLE, c.getString(1));
    524                             values.put(Bookmarks.DATE_CREATED, c.getInt(4));
    525                             values.put(Bookmarks.POSITION, 0);
    526                             values.put(Bookmarks.PARENT, FIXED_ID_ROOT);
    527                             ContentValues imageValues = new ContentValues();
    528                             imageValues.put(Images.URL, c.getString(0));
    529                             imageValues.put(Images.FAVICON, c.getBlob(2));
    530                             imageValues.put(Images.TOUCH_ICON, c.getBlob(3));
    531                             db.insertOrThrow(TABLE_IMAGES, Images.THUMBNAIL, imageValues);
    532                             db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
    533                         }
    534                         c.close();
    535                     }
    536                     // Import history
    537                     c = oldDb.query(table,
    538                             new String[] {
    539                             BookmarkColumns.URL, // 0
    540                             BookmarkColumns.TITLE, // 1
    541                             BookmarkColumns.VISITS, // 2
    542                             BookmarkColumns.DATE, // 3
    543                             BookmarkColumns.CREATED, // 4
    544                             }, BookmarkColumns.VISITS + " > 0 OR "
    545                             + BookmarkColumns.BOOKMARK + " = 0",
    546                             null, null, null, null);
    547                     if (c != null) {
    548                         while (c.moveToNext()) {
    549                             ContentValues values = new ContentValues();
    550                             values.put(History.URL, c.getString(0));
    551                             values.put(History.TITLE, c.getString(1));
    552                             values.put(History.VISITS, c.getInt(2));
    553                             values.put(History.DATE_LAST_VISITED, c.getLong(3));
    554                             values.put(History.DATE_CREATED, c.getLong(4));
    555                             db.insertOrThrow(TABLE_HISTORY, History.FAVICON, values);
    556                         }
    557                         c.close();
    558                     }
    559                     // Wipe the old DB, in case the delete fails.
    560                     oldDb.delete(table, null, null);
    561                 } finally {
    562                     if (c != null) c.close();
    563                     oldDb.close();
    564                     helper.close();
    565                 }
    566                 if (!oldDbFile.delete()) {
    567                     oldDbFile.deleteOnExit();
    568                 }
    569                 return true;
    570             }
    571             return false;
    572         }
    573 
    574         void createAccountsView(SQLiteDatabase db) {
    575             db.execSQL("CREATE VIEW IF NOT EXISTS v_accounts AS "
    576                     + "SELECT NULL AS " + Accounts.ACCOUNT_NAME
    577                     + ", NULL AS " + Accounts.ACCOUNT_TYPE
    578                     + ", " + FIXED_ID_ROOT + " AS " + Accounts.ROOT_ID
    579                     + " UNION ALL SELECT " + Accounts.ACCOUNT_NAME
    580                     + ", " + Accounts.ACCOUNT_TYPE + ", "
    581                     + Bookmarks._ID + " AS " + Accounts.ROOT_ID
    582                     + " FROM " + TABLE_BOOKMARKS + " WHERE "
    583                     + ChromeSyncColumns.SERVER_UNIQUE + " = \""
    584                     + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "\" AND "
    585                     + Bookmarks.IS_DELETED + " = 0");
    586         }
    587 
    588         @Override
    589         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    590             if (oldVersion < 32) {
    591                 createOmniboxSuggestions(db);
    592             }
    593             if (oldVersion < 31) {
    594                 createThumbnails(db);
    595             }
    596             if (oldVersion < 30) {
    597                 db.execSQL("DROP VIEW IF EXISTS " + VIEW_SNAPSHOTS_COMBINED);
    598                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
    599             }
    600             if (oldVersion < 28) {
    601                 enableSync(db);
    602             }
    603             if (oldVersion < 27) {
    604                 createAccountsView(db);
    605             }
    606             if (oldVersion < 26) {
    607                 db.execSQL("DROP VIEW IF EXISTS combined");
    608             }
    609             if (oldVersion < 25) {
    610                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
    611                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
    612                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
    613                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
    614                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SETTINGS);
    615                 mSyncHelper.onAccountsChanged(db, new Account[] {}); // remove all sync info
    616                 onCreate(db);
    617             }
    618         }
    619 
    620         public void onOpen(SQLiteDatabase db) {
    621             db.enableWriteAheadLogging();
    622             mSyncHelper.onDatabaseOpened(db);
    623         }
    624 
    625         private void createDefaultBookmarks(SQLiteDatabase db) {
    626             ContentValues values = new ContentValues();
    627             // TODO figure out how to deal with localization for the defaults
    628 
    629             // Bookmarks folder
    630             values.put(Bookmarks._ID, FIXED_ID_ROOT);
    631             values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
    632             values.put(Bookmarks.TITLE, "Bookmarks");
    633             values.putNull(Bookmarks.PARENT);
    634             values.put(Bookmarks.POSITION, 0);
    635             values.put(Bookmarks.IS_FOLDER, true);
    636             values.put(Bookmarks.DIRTY, true);
    637             db.insertOrThrow(TABLE_BOOKMARKS, null, values);
    638 
    639             addDefaultBookmarks(db, FIXED_ID_ROOT);
    640         }
    641 
    642         private void addDefaultBookmarks(SQLiteDatabase db, long parentId) {
    643             Resources res = getContext().getResources();
    644             final CharSequence[] bookmarks = res.getTextArray(
    645                     R.array.bookmarks);
    646             int size = bookmarks.length;
    647             TypedArray preloads = res.obtainTypedArray(R.array.bookmark_preloads);
    648             try {
    649                 String parent = Long.toString(parentId);
    650                 String now = Long.toString(System.currentTimeMillis());
    651                 for (int i = 0; i < size; i = i + 2) {
    652                     CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
    653                             bookmarks[i + 1]);
    654                     db.execSQL("INSERT INTO bookmarks (" +
    655                             Bookmarks.TITLE + ", " +
    656                             Bookmarks.URL + ", " +
    657                             Bookmarks.IS_FOLDER + "," +
    658                             Bookmarks.PARENT + "," +
    659                             Bookmarks.POSITION + "," +
    660                             Bookmarks.DATE_CREATED +
    661                         ") VALUES (" +
    662                             "'" + bookmarks[i] + "', " +
    663                             "'" + bookmarkDestination + "', " +
    664                             "0," +
    665                             parent + "," +
    666                             Integer.toString(i) + "," +
    667                             now +
    668                             ");");
    669 
    670                     int faviconId = preloads.getResourceId(i, 0);
    671                     int thumbId = preloads.getResourceId(i + 1, 0);
    672                     byte[] thumb = null, favicon = null;
    673                     try {
    674                         thumb = readRaw(res, thumbId);
    675                     } catch (IOException e) {
    676                     }
    677                     try {
    678                         favicon = readRaw(res, faviconId);
    679                     } catch (IOException e) {
    680                     }
    681                     if (thumb != null || favicon != null) {
    682                         ContentValues imageValues = new ContentValues();
    683                         imageValues.put(Images.URL, bookmarkDestination.toString());
    684                         if (favicon != null) {
    685                             imageValues.put(Images.FAVICON, favicon);
    686                         }
    687                         if (thumb != null) {
    688                             imageValues.put(Images.THUMBNAIL, thumb);
    689                         }
    690                         db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
    691                     }
    692                 }
    693             } catch (ArrayIndexOutOfBoundsException e) {
    694             }
    695         }
    696 
    697         private byte[] readRaw(Resources res, int id) throws IOException {
    698             if (id == 0) {
    699                 return null;
    700             }
    701             InputStream is = res.openRawResource(id);
    702             try {
    703                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
    704                 byte[] buf = new byte[4096];
    705                 int read;
    706                 while ((read = is.read(buf)) > 0) {
    707                     bos.write(buf, 0, read);
    708                 }
    709                 bos.flush();
    710                 return bos.toByteArray();
    711             } finally {
    712                 is.close();
    713             }
    714         }
    715 
    716         // XXX: This is a major hack to remove our dependency on gsf constants and
    717         // its content provider. http://b/issue?id=2425179
    718         private String getClientId(ContentResolver cr) {
    719             String ret = "android-google";
    720             Cursor c = null;
    721             try {
    722                 c = cr.query(Uri.parse("content://com.google.settings/partner"),
    723                         new String[] { "value" }, "name='client_id'", null, null);
    724                 if (c != null && c.moveToNext()) {
    725                     ret = c.getString(0);
    726                 }
    727             } catch (RuntimeException ex) {
    728                 // fall through to return the default
    729             } finally {
    730                 if (c != null) {
    731                     c.close();
    732                 }
    733             }
    734             return ret;
    735         }
    736 
    737         private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
    738             StringBuffer sb = new StringBuffer();
    739             int lastCharLoc = 0;
    740 
    741             final String client_id = getClientId(context.getContentResolver());
    742 
    743             for (int i = 0; i < srcString.length(); ++i) {
    744                 char c = srcString.charAt(i);
    745                 if (c == '{') {
    746                     sb.append(srcString.subSequence(lastCharLoc, i));
    747                     lastCharLoc = i;
    748               inner:
    749                     for (int j = i; j < srcString.length(); ++j) {
    750                         char k = srcString.charAt(j);
    751                         if (k == '}') {
    752                             String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
    753                             if (propertyKeyValue.equals("CLIENT_ID")) {
    754                                 sb.append(client_id);
    755                             } else {
    756                                 sb.append("unknown");
    757                             }
    758                             lastCharLoc = j + 1;
    759                             i = j;
    760                             break inner;
    761                         }
    762                     }
    763                 }
    764             }
    765             if (srcString.length() - lastCharLoc > 0) {
    766                 // Put on the tail, if there is one
    767                 sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
    768             }
    769             return sb;
    770         }
    771     }
    772 
    773     @Override
    774     public SQLiteOpenHelper getDatabaseHelper(Context context) {
    775         synchronized (this) {
    776             if (mOpenHelper == null) {
    777                 mOpenHelper = new DatabaseHelper(context);
    778             }
    779             return mOpenHelper;
    780         }
    781     }
    782 
    783     @Override
    784     public boolean isCallerSyncAdapter(Uri uri) {
    785         return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
    786     }
    787 
    788     @VisibleForTesting
    789     public void setWidgetObserver(ContentObserver obs) {
    790         mWidgetObserver = obs;
    791     }
    792 
    793     void refreshWidgets() {
    794         mUpdateWidgets = true;
    795     }
    796 
    797     @Override
    798     protected void onEndTransaction(boolean callerIsSyncAdapter) {
    799         super.onEndTransaction(callerIsSyncAdapter);
    800         if (mUpdateWidgets) {
    801             if (mWidgetObserver == null) {
    802                 BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
    803             } else {
    804                 mWidgetObserver.dispatchChange(false);
    805             }
    806             mUpdateWidgets = false;
    807         }
    808         mSyncToNetwork = true;
    809     }
    810 
    811     @Override
    812     public String getType(Uri uri) {
    813         final int match = URI_MATCHER.match(uri);
    814         switch (match) {
    815             case LEGACY:
    816             case BOOKMARKS:
    817                 return Bookmarks.CONTENT_TYPE;
    818             case LEGACY_ID:
    819             case BOOKMARKS_ID:
    820                 return Bookmarks.CONTENT_ITEM_TYPE;
    821             case HISTORY:
    822                 return History.CONTENT_TYPE;
    823             case HISTORY_ID:
    824                 return History.CONTENT_ITEM_TYPE;
    825             case SEARCHES:
    826                 return Searches.CONTENT_TYPE;
    827             case SEARCHES_ID:
    828                 return Searches.CONTENT_ITEM_TYPE;
    829         }
    830         return null;
    831     }
    832 
    833     boolean isNullAccount(String account) {
    834         if (account == null) return true;
    835         account = account.trim();
    836         return account.length() == 0 || account.equals("null");
    837     }
    838 
    839     Object[] getSelectionWithAccounts(Uri uri, String selection, String[] selectionArgs) {
    840         // Look for account info
    841         String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
    842         String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
    843         boolean hasAccounts = false;
    844         if (accountType != null && accountName != null) {
    845             if (!isNullAccount(accountType) && !isNullAccount(accountName)) {
    846                 selection = DatabaseUtils.concatenateWhere(selection,
    847                         Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? ");
    848                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    849                         new String[] { accountType, accountName });
    850                 hasAccounts = true;
    851             } else {
    852                 selection = DatabaseUtils.concatenateWhere(selection,
    853                         Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
    854                         Bookmarks.ACCOUNT_TYPE + " IS NULL");
    855             }
    856         }
    857         return new Object[] { selection, selectionArgs, hasAccounts };
    858     }
    859 
    860     @Override
    861     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    862             String sortOrder) {
    863         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    864         final int match = URI_MATCHER.match(uri);
    865         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    866         String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
    867         String groupBy = uri.getQueryParameter(PARAM_GROUP_BY);
    868         switch (match) {
    869             case ACCOUNTS: {
    870                 qb.setTables(VIEW_ACCOUNTS);
    871                 qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
    872                 String allowEmpty = uri.getQueryParameter(PARAM_ALLOW_EMPTY_ACCOUNTS);
    873                 if ("false".equals(allowEmpty)) {
    874                     selection = DatabaseUtils.concatenateWhere(selection,
    875                             SQL_WHERE_ACCOUNT_HAS_BOOKMARKS);
    876                 }
    877                 if (sortOrder == null) {
    878                     sortOrder = DEFAULT_SORT_ACCOUNTS;
    879                 }
    880                 break;
    881             }
    882 
    883             case BOOKMARKS_FOLDER_ID:
    884             case BOOKMARKS_ID:
    885             case BOOKMARKS: {
    886                 // Only show deleted bookmarks if requested to do so
    887                 if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) {
    888                     selection = DatabaseUtils.concatenateWhere(
    889                             Bookmarks.IS_DELETED + "=0", selection);
    890                 }
    891 
    892                 if (match == BOOKMARKS_ID) {
    893                     // Tack on the ID of the specific bookmark requested
    894                     selection = DatabaseUtils.concatenateWhere(selection,
    895                             TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?");
    896                     selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    897                             new String[] { Long.toString(ContentUris.parseId(uri)) });
    898                 } else if (match == BOOKMARKS_FOLDER_ID) {
    899                     // Tack on the ID of the specific folder requested
    900                     selection = DatabaseUtils.concatenateWhere(selection,
    901                             TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?");
    902                     selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    903                             new String[] { Long.toString(ContentUris.parseId(uri)) });
    904                 }
    905 
    906                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
    907                 selection = (String) withAccount[0];
    908                 selectionArgs = (String[]) withAccount[1];
    909                 boolean hasAccounts = (Boolean) withAccount[2];
    910 
    911                 // Set a default sort order if one isn't specified
    912                 if (TextUtils.isEmpty(sortOrder)) {
    913                     if (hasAccounts) {
    914                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
    915                     } else {
    916                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
    917                     }
    918                 }
    919 
    920                 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
    921                 qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
    922                 break;
    923             }
    924 
    925             case BOOKMARKS_FOLDER: {
    926                 // Look for an account
    927                 boolean useAccount = false;
    928                 String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
    929                 String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
    930                 if (!isNullAccount(accountType) && !isNullAccount(accountName)) {
    931                     useAccount = true;
    932                 }
    933 
    934                 qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
    935                 String[] args;
    936                 String query;
    937                 // Set a default sort order if one isn't specified
    938                 if (TextUtils.isEmpty(sortOrder)) {
    939                     if (useAccount) {
    940                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
    941                     } else {
    942                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
    943                     }
    944                 }
    945                 if (!useAccount) {
    946                     qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
    947                     String where = Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0";
    948                     where = DatabaseUtils.concatenateWhere(where, selection);
    949                     args = new String[] { Long.toString(FIXED_ID_ROOT) };
    950                     if (selectionArgs != null) {
    951                         args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
    952                     }
    953                     query = qb.buildQuery(projection, where, null, null, sortOrder, null);
    954                 } else {
    955                     qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
    956                     String where = Bookmarks.ACCOUNT_TYPE + "=? AND " +
    957                             Bookmarks.ACCOUNT_NAME + "=? " +
    958                             "AND parent = " +
    959                             "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " +
    960                             ChromeSyncColumns.SERVER_UNIQUE + "=" +
    961                             "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " +
    962                             "AND account_type = ? AND account_name = ?) " +
    963                             "AND " + Bookmarks.IS_DELETED + "=0";
    964                     where = DatabaseUtils.concatenateWhere(where, selection);
    965                     String bookmarksBarQuery = qb.buildQuery(projection,
    966                             where, null, null, null, null);
    967                     args = new String[] {accountType, accountName,
    968                             accountType, accountName};
    969                     if (selectionArgs != null) {
    970                         args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
    971                     }
    972 
    973                     where = Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" +
    974                             " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?";
    975                     where = DatabaseUtils.concatenateWhere(where, selection);
    976                     qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
    977                     String otherBookmarksQuery = qb.buildQuery(projection,
    978                             where, null, null, null, null);
    979 
    980                     query = qb.buildUnionQuery(
    981                             new String[] { bookmarksBarQuery, otherBookmarksQuery },
    982                             sortOrder, limit);
    983 
    984                     args = DatabaseUtils.appendSelectionArgs(args, new String[] {
    985                             accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS,
    986                             });
    987                     if (selectionArgs != null) {
    988                         args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
    989                     }
    990                 }
    991 
    992                 Cursor cursor = db.rawQuery(query, args);
    993                 if (cursor != null) {
    994                     cursor.setNotificationUri(getContext().getContentResolver(),
    995                             BrowserContract.AUTHORITY_URI);
    996                 }
    997                 return cursor;
    998             }
    999 
   1000             case BOOKMARKS_DEFAULT_FOLDER_ID: {
   1001                 String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
   1002                 String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
   1003                 long id = queryDefaultFolderId(accountName, accountType);
   1004                 MatrixCursor c = new MatrixCursor(new String[] {Bookmarks._ID});
   1005                 c.newRow().add(id);
   1006                 return c;
   1007             }
   1008 
   1009             case BOOKMARKS_SUGGESTIONS: {
   1010                 return doSuggestQuery(selection, selectionArgs, limit);
   1011             }
   1012 
   1013             case HISTORY_ID: {
   1014                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
   1015                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1016                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1017                 // fall through
   1018             }
   1019             case HISTORY: {
   1020                 filterSearchClient(selectionArgs);
   1021                 if (sortOrder == null) {
   1022                     sortOrder = DEFAULT_SORT_HISTORY;
   1023                 }
   1024                 qb.setProjectionMap(HISTORY_PROJECTION_MAP);
   1025                 qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
   1026                 break;
   1027             }
   1028 
   1029             case SEARCHES_ID: {
   1030                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
   1031                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1032                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1033                 // fall through
   1034             }
   1035             case SEARCHES: {
   1036                 qb.setTables(TABLE_SEARCHES);
   1037                 qb.setProjectionMap(SEARCHES_PROJECTION_MAP);
   1038                 break;
   1039             }
   1040 
   1041             case SYNCSTATE: {
   1042                 return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder);
   1043             }
   1044 
   1045             case SYNCSTATE_ID: {
   1046                 selection = appendAccountToSelection(uri, selection);
   1047                 String selectionWithId =
   1048                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
   1049                         + (selection == null ? "" : " AND (" + selection + ")");
   1050                 return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
   1051             }
   1052 
   1053             case IMAGES: {
   1054                 qb.setTables(TABLE_IMAGES);
   1055                 qb.setProjectionMap(IMAGES_PROJECTION_MAP);
   1056                 break;
   1057             }
   1058 
   1059             case LEGACY_ID:
   1060             case COMBINED_ID: {
   1061                 selection = DatabaseUtils.concatenateWhere(
   1062                         selection, Combined._ID + " = CAST(? AS INTEGER)");
   1063                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1064                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1065                 // fall through
   1066             }
   1067             case LEGACY:
   1068             case COMBINED: {
   1069                 if ((match == LEGACY || match == LEGACY_ID)
   1070                         && projection == null) {
   1071                     projection = Browser.HISTORY_PROJECTION;
   1072                 }
   1073                 String[] args = createCombinedQuery(uri, projection, qb);
   1074                 if (selectionArgs == null) {
   1075                     selectionArgs = args;
   1076                 } else {
   1077                     selectionArgs = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
   1078                 }
   1079                 break;
   1080             }
   1081 
   1082             case SETTINGS: {
   1083                 qb.setTables(TABLE_SETTINGS);
   1084                 qb.setProjectionMap(SETTINGS_PROJECTION_MAP);
   1085                 break;
   1086             }
   1087 
   1088             case THUMBNAILS_ID: {
   1089                 selection = DatabaseUtils.concatenateWhere(
   1090                         selection, Thumbnails._ID + " = ?");
   1091                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1092                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1093                 // fall through
   1094             }
   1095             case THUMBNAILS: {
   1096                 qb.setTables(TABLE_THUMBNAILS);
   1097                 break;
   1098             }
   1099 
   1100             case OMNIBOX_SUGGESTIONS: {
   1101                 qb.setTables(VIEW_OMNIBOX_SUGGESTIONS);
   1102                 break;
   1103             }
   1104 
   1105             default: {
   1106                 throw new UnsupportedOperationException("Unknown URL " + uri.toString());
   1107             }
   1108         }
   1109 
   1110         Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy,
   1111                 null, sortOrder, limit);
   1112         cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
   1113         return cursor;
   1114     }
   1115 
   1116     private Cursor doSuggestQuery(String selection, String[] selectionArgs, String limit) {
   1117         if (TextUtils.isEmpty(selectionArgs[0])) {
   1118             selection = ZERO_QUERY_SUGGEST_SELECTION;
   1119             selectionArgs = null;
   1120         } else {
   1121             String like = selectionArgs[0] + "%";
   1122             if (selectionArgs[0].startsWith("http")
   1123                     || selectionArgs[0].startsWith("file")) {
   1124                 selectionArgs[0] = like;
   1125             } else {
   1126                 selectionArgs = new String[6];
   1127                 selectionArgs[0] = "http://" + like;
   1128                 selectionArgs[1] = "http://www." + like;
   1129                 selectionArgs[2] = "https://" + like;
   1130                 selectionArgs[3] = "https://www." + like;
   1131                 // To match against titles.
   1132                 selectionArgs[4] = like;
   1133                 selectionArgs[5] = like;
   1134                 selection = SUGGEST_SELECTION;
   1135             }
   1136             selection = DatabaseUtils.concatenateWhere(selection,
   1137                     Bookmarks.IS_DELETED + "=0 AND " + Bookmarks.IS_FOLDER + "=0");
   1138 
   1139         }
   1140         Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_BOOKMARKS_JOIN_HISTORY,
   1141                 SUGGEST_PROJECTION, selection, selectionArgs, null, null,
   1142                 SUGGEST_ORDER_BY, null);
   1143 
   1144         return new SuggestionsCursor(c);
   1145     }
   1146 
   1147     private String[] createCombinedQuery(
   1148             Uri uri, String[] projection, SQLiteQueryBuilder qb) {
   1149         String[] args = null;
   1150         StringBuilder whereBuilder = new StringBuilder(128);
   1151         whereBuilder.append(Bookmarks.IS_DELETED);
   1152         whereBuilder.append(" = 0");
   1153         // Look for account info
   1154         Object[] withAccount = getSelectionWithAccounts(uri, null, null);
   1155         String selection = (String) withAccount[0];
   1156         String[] selectionArgs = (String[]) withAccount[1];
   1157         if (selection != null) {
   1158             whereBuilder.append(" AND " + selection);
   1159             if (selectionArgs != null) {
   1160                 // We use the selection twice, hence we need to duplicate the args
   1161                 args = new String[selectionArgs.length * 2];
   1162                 System.arraycopy(selectionArgs, 0, args, 0, selectionArgs.length);
   1163                 System.arraycopy(selectionArgs, 0, args, selectionArgs.length,
   1164                         selectionArgs.length);
   1165             }
   1166         }
   1167         String where = whereBuilder.toString();
   1168         // Build the bookmark subquery for history union subquery
   1169         qb.setTables(TABLE_BOOKMARKS);
   1170         String subQuery = qb.buildQuery(null, where, null, null, null, null);
   1171         // Build the history union subquery
   1172         qb.setTables(String.format(FORMAT_COMBINED_JOIN_SUBQUERY_JOIN_IMAGES, subQuery));
   1173         qb.setProjectionMap(COMBINED_HISTORY_PROJECTION_MAP);
   1174         String historySubQuery = qb.buildQuery(null,
   1175                 null, null, null, null, null);
   1176         // Build the bookmark union subquery
   1177         qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
   1178         qb.setProjectionMap(COMBINED_BOOKMARK_PROJECTION_MAP);
   1179         where += String.format(" AND %s NOT IN (SELECT %s FROM %s)",
   1180                 Combined.URL, History.URL, TABLE_HISTORY);
   1181         String bookmarksSubQuery = qb.buildQuery(null, where,
   1182                 null, null, null, null);
   1183         // Put it all together
   1184         String query = qb.buildUnionQuery(
   1185                 new String[] {historySubQuery, bookmarksSubQuery},
   1186                 null, null);
   1187         qb.setTables("(" + query + ")");
   1188         qb.setProjectionMap(null);
   1189         return args;
   1190     }
   1191 
   1192     int deleteBookmarks(String selection, String[] selectionArgs,
   1193             boolean callerIsSyncAdapter) {
   1194         //TODO cascade deletes down from folders
   1195         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1196         if (callerIsSyncAdapter) {
   1197             return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
   1198         }
   1199         ContentValues values = new ContentValues();
   1200         values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
   1201         values.put(Bookmarks.IS_DELETED, 1);
   1202         return updateBookmarksInTransaction(values, selection, selectionArgs,
   1203                 callerIsSyncAdapter);
   1204     }
   1205 
   1206     @Override
   1207     public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
   1208             boolean callerIsSyncAdapter) {
   1209         final int match = URI_MATCHER.match(uri);
   1210         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1211         int deleted = 0;
   1212         switch (match) {
   1213             case BOOKMARKS_ID: {
   1214                 selection = DatabaseUtils.concatenateWhere(selection,
   1215                         TABLE_BOOKMARKS + "._id=?");
   1216                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1217                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1218                 // fall through
   1219             }
   1220             case BOOKMARKS: {
   1221                 // Look for account info
   1222                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
   1223                 selection = (String) withAccount[0];
   1224                 selectionArgs = (String[]) withAccount[1];
   1225                 deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
   1226                 pruneImages();
   1227                 if (deleted > 0) {
   1228                     refreshWidgets();
   1229                 }
   1230                 break;
   1231             }
   1232 
   1233             case HISTORY_ID: {
   1234                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
   1235                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1236                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1237                 // fall through
   1238             }
   1239             case HISTORY: {
   1240                 filterSearchClient(selectionArgs);
   1241                 deleted = db.delete(TABLE_HISTORY, selection, selectionArgs);
   1242                 pruneImages();
   1243                 break;
   1244             }
   1245 
   1246             case SEARCHES_ID: {
   1247                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
   1248                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1249                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1250                 // fall through
   1251             }
   1252             case SEARCHES: {
   1253                 deleted = db.delete(TABLE_SEARCHES, selection, selectionArgs);
   1254                 break;
   1255             }
   1256 
   1257             case SYNCSTATE: {
   1258                 deleted = mSyncHelper.delete(db, selection, selectionArgs);
   1259                 break;
   1260             }
   1261             case SYNCSTATE_ID: {
   1262                 String selectionWithId =
   1263                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
   1264                         + (selection == null ? "" : " AND (" + selection + ")");
   1265                 deleted = mSyncHelper.delete(db, selectionWithId, selectionArgs);
   1266                 break;
   1267             }
   1268             case LEGACY_ID: {
   1269                 selection = DatabaseUtils.concatenateWhere(
   1270                         selection, Combined._ID + " = CAST(? AS INTEGER)");
   1271                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1272                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1273                 // fall through
   1274             }
   1275             case LEGACY: {
   1276                 String[] projection = new String[] { Combined._ID,
   1277                         Combined.IS_BOOKMARK, Combined.URL };
   1278                 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
   1279                 String[] args = createCombinedQuery(uri, projection, qb);
   1280                 if (selectionArgs == null) {
   1281                     selectionArgs = args;
   1282                 } else {
   1283                     selectionArgs = DatabaseUtils.appendSelectionArgs(
   1284                             args, selectionArgs);
   1285                 }
   1286                 Cursor c = qb.query(db, projection, selection, selectionArgs,
   1287                         null, null, null);
   1288                 while (c.moveToNext()) {
   1289                     long id = c.getLong(0);
   1290                     boolean isBookmark = c.getInt(1) != 0;
   1291                     String url = c.getString(2);
   1292                     if (isBookmark) {
   1293                         deleted += deleteBookmarks(Bookmarks._ID + "=?",
   1294                                 new String[] { Long.toString(id) },
   1295                                 callerIsSyncAdapter);
   1296                         db.delete(TABLE_HISTORY, History.URL + "=?",
   1297                                 new String[] { url });
   1298                     } else {
   1299                         deleted += db.delete(TABLE_HISTORY,
   1300                                 Bookmarks._ID + "=?",
   1301                                 new String[] { Long.toString(id) });
   1302                     }
   1303                 }
   1304                 c.close();
   1305                 break;
   1306             }
   1307             case THUMBNAILS_ID: {
   1308                 selection = DatabaseUtils.concatenateWhere(
   1309                         selection, Thumbnails._ID + " = ?");
   1310                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1311                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1312                 // fall through
   1313             }
   1314             case THUMBNAILS: {
   1315                 deleted = db.delete(TABLE_THUMBNAILS, selection, selectionArgs);
   1316                 break;
   1317             }
   1318             default: {
   1319                 throw new UnsupportedOperationException("Unknown delete URI " + uri);
   1320             }
   1321         }
   1322         if (deleted > 0) {
   1323             postNotifyUri(uri);
   1324             if (shouldNotifyLegacy(uri)) {
   1325                 postNotifyUri(LEGACY_AUTHORITY_URI);
   1326             }
   1327         }
   1328         return deleted;
   1329     }
   1330 
   1331     long queryDefaultFolderId(String accountName, String accountType) {
   1332         if (!isNullAccount(accountName) && !isNullAccount(accountType)) {
   1333             final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
   1334             Cursor c = db.query(TABLE_BOOKMARKS, new String[] { Bookmarks._ID },
   1335                     ChromeSyncColumns.SERVER_UNIQUE + " = ?" +
   1336                     " AND account_type = ? AND account_name = ?",
   1337                     new String[] { ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR,
   1338                     accountType, accountName }, null, null, null);
   1339             try {
   1340                 if (c.moveToFirst()) {
   1341                     return c.getLong(0);
   1342                 }
   1343             } finally {
   1344                 c.close();
   1345             }
   1346         }
   1347         return FIXED_ID_ROOT;
   1348     }
   1349 
   1350     @Override
   1351     public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
   1352         int match = URI_MATCHER.match(uri);
   1353         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1354         long id = -1;
   1355         if (match == LEGACY) {
   1356             // Intercept and route to the correct table
   1357             Integer bookmark = values.getAsInteger(BookmarkColumns.BOOKMARK);
   1358             values.remove(BookmarkColumns.BOOKMARK);
   1359             if (bookmark == null || bookmark == 0) {
   1360                 match = HISTORY;
   1361             } else {
   1362                 match = BOOKMARKS;
   1363                 values.remove(BookmarkColumns.DATE);
   1364                 values.remove(BookmarkColumns.VISITS);
   1365                 values.remove(BookmarkColumns.USER_ENTERED);
   1366                 values.put(Bookmarks.IS_FOLDER, 0);
   1367             }
   1368         }
   1369         switch (match) {
   1370             case BOOKMARKS: {
   1371                 // Mark rows dirty if they're not coming from a sync adapter
   1372                 if (!callerIsSyncAdapter) {
   1373                     long now = System.currentTimeMillis();
   1374                     values.put(Bookmarks.DATE_CREATED, now);
   1375                     values.put(Bookmarks.DATE_MODIFIED, now);
   1376                     values.put(Bookmarks.DIRTY, 1);
   1377 
   1378                     boolean hasAccounts = values.containsKey(Bookmarks.ACCOUNT_TYPE)
   1379                             || values.containsKey(Bookmarks.ACCOUNT_NAME);
   1380                     String accountType = values
   1381                             .getAsString(Bookmarks.ACCOUNT_TYPE);
   1382                     String accountName = values
   1383                             .getAsString(Bookmarks.ACCOUNT_NAME);
   1384                     boolean hasParent = values.containsKey(Bookmarks.PARENT);
   1385                     if (hasParent && hasAccounts) {
   1386                         // Let's make sure it's valid
   1387                         long parentId = values.getAsLong(Bookmarks.PARENT);
   1388                         hasParent = isValidParent(
   1389                                 accountType, accountName, parentId);
   1390                     } else if (hasParent && !hasAccounts) {
   1391                         long parentId = values.getAsLong(Bookmarks.PARENT);
   1392                         hasParent = setParentValues(parentId, values);
   1393                     }
   1394 
   1395                     // If no parent is set default to the "Bookmarks Bar" folder
   1396                     if (!hasParent) {
   1397                         values.put(Bookmarks.PARENT,
   1398                                 queryDefaultFolderId(accountName, accountType));
   1399                     }
   1400                 }
   1401 
   1402                 // If no position is requested put the bookmark at the beginning of the list
   1403                 if (!values.containsKey(Bookmarks.POSITION)) {
   1404                     values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
   1405                 }
   1406 
   1407                 // Extract out the image values so they can be inserted into the images table
   1408                 String url = values.getAsString(Bookmarks.URL);
   1409                 ContentValues imageValues = extractImageValues(values, url);
   1410                 Boolean isFolder = values.getAsBoolean(Bookmarks.IS_FOLDER);
   1411                 if ((isFolder == null || !isFolder)
   1412                         && imageValues != null && !TextUtils.isEmpty(url)) {
   1413                     int count = db.update(TABLE_IMAGES, imageValues, Images.URL + "=?",
   1414                             new String[] { url });
   1415                     if (count == 0) {
   1416                         db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
   1417                     }
   1418                 }
   1419 
   1420                 id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
   1421                 refreshWidgets();
   1422                 break;
   1423             }
   1424 
   1425             case HISTORY: {
   1426                 // If no created time is specified set it to now
   1427                 if (!values.containsKey(History.DATE_CREATED)) {
   1428                     values.put(History.DATE_CREATED, System.currentTimeMillis());
   1429                 }
   1430                 String url = values.getAsString(History.URL);
   1431                 url = filterSearchClient(url);
   1432                 values.put(History.URL, url);
   1433 
   1434                 // Extract out the image values so they can be inserted into the images table
   1435                 ContentValues imageValues = extractImageValues(values,
   1436                         values.getAsString(History.URL));
   1437                 if (imageValues != null) {
   1438                     db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
   1439                 }
   1440 
   1441                 id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
   1442                 break;
   1443             }
   1444 
   1445             case SEARCHES: {
   1446                 id = insertSearchesInTransaction(db, values);
   1447                 break;
   1448             }
   1449 
   1450             case SYNCSTATE: {
   1451                 id = mSyncHelper.insert(db, values);
   1452                 break;
   1453             }
   1454 
   1455             case SETTINGS: {
   1456                 id = 0;
   1457                 insertSettingsInTransaction(db, values);
   1458                 break;
   1459             }
   1460 
   1461             case THUMBNAILS: {
   1462                 id = db.replaceOrThrow(TABLE_THUMBNAILS, null, values);
   1463                 break;
   1464             }
   1465 
   1466             default: {
   1467                 throw new UnsupportedOperationException("Unknown insert URI " + uri);
   1468             }
   1469         }
   1470 
   1471         if (id >= 0) {
   1472             postNotifyUri(uri);
   1473             if (shouldNotifyLegacy(uri)) {
   1474                 postNotifyUri(LEGACY_AUTHORITY_URI);
   1475             }
   1476             return ContentUris.withAppendedId(uri, id);
   1477         } else {
   1478             return null;
   1479         }
   1480     }
   1481 
   1482     private String[] getAccountNameAndType(long id) {
   1483         if (id <= 0) {
   1484             return null;
   1485         }
   1486         Uri uri = ContentUris.withAppendedId(Bookmarks.CONTENT_URI, id);
   1487         Cursor c = query(uri,
   1488                 new String[] { Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_TYPE },
   1489                 null, null, null);
   1490         try {
   1491             if (c.moveToFirst()) {
   1492                 String parentName = c.getString(0);
   1493                 String parentType = c.getString(1);
   1494                 return new String[] { parentName, parentType };
   1495             }
   1496             return null;
   1497         } finally {
   1498             c.close();
   1499         }
   1500     }
   1501 
   1502     private boolean setParentValues(long parentId, ContentValues values) {
   1503         String[] parent = getAccountNameAndType(parentId);
   1504         if (parent == null) {
   1505             return false;
   1506         }
   1507         values.put(Bookmarks.ACCOUNT_NAME, parent[0]);
   1508         values.put(Bookmarks.ACCOUNT_TYPE, parent[1]);
   1509         return true;
   1510     }
   1511 
   1512     private boolean isValidParent(String accountType, String accountName,
   1513             long parentId) {
   1514         String[] parent = getAccountNameAndType(parentId);
   1515         if (parent != null
   1516                 && TextUtils.equals(accountName, parent[0])
   1517                 && TextUtils.equals(accountType, parent[1])) {
   1518             return true;
   1519         }
   1520         return false;
   1521     }
   1522 
   1523     private void filterSearchClient(String[] selectionArgs) {
   1524         if (selectionArgs != null) {
   1525             for (int i = 0; i < selectionArgs.length; i++) {
   1526                 selectionArgs[i] = filterSearchClient(selectionArgs[i]);
   1527             }
   1528         }
   1529     }
   1530 
   1531     // Filters out the client= param for search urls
   1532     private String filterSearchClient(String url) {
   1533         // remove "client" before updating it to the history so that it wont
   1534         // show up in the auto-complete list.
   1535         int index = url.indexOf("client=");
   1536         if (index > 0 && url.contains(".google.")) {
   1537             int end = url.indexOf('&', index);
   1538             if (end > 0) {
   1539                 url = url.substring(0, index)
   1540                         .concat(url.substring(end + 1));
   1541             } else {
   1542                 // the url.charAt(index-1) should be either '?' or '&'
   1543                 url = url.substring(0, index-1);
   1544             }
   1545         }
   1546         return url;
   1547     }
   1548 
   1549     /**
   1550      * Searches are unique, so perform an UPSERT manually since SQLite doesn't support them.
   1551      */
   1552     private long insertSearchesInTransaction(SQLiteDatabase db, ContentValues values) {
   1553         String search = values.getAsString(Searches.SEARCH);
   1554         if (TextUtils.isEmpty(search)) {
   1555             throw new IllegalArgumentException("Must include the SEARCH field");
   1556         }
   1557         Cursor cursor = null;
   1558         try {
   1559             cursor = db.query(TABLE_SEARCHES, new String[] { Searches._ID },
   1560                     Searches.SEARCH + "=?", new String[] { search }, null, null, null);
   1561             if (cursor.moveToNext()) {
   1562                 long id = cursor.getLong(0);
   1563                 db.update(TABLE_SEARCHES, values, Searches._ID + "=?",
   1564                         new String[] { Long.toString(id) });
   1565                 return id;
   1566             } else {
   1567                 return db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
   1568             }
   1569         } finally {
   1570             if (cursor != null) cursor.close();
   1571         }
   1572     }
   1573 
   1574     /**
   1575      * Settings are unique, so perform an UPSERT manually since SQLite doesn't support them.
   1576      */
   1577     private long insertSettingsInTransaction(SQLiteDatabase db, ContentValues values) {
   1578         String key = values.getAsString(Settings.KEY);
   1579         if (TextUtils.isEmpty(key)) {
   1580             throw new IllegalArgumentException("Must include the KEY field");
   1581         }
   1582         String[] keyArray = new String[] { key };
   1583         Cursor cursor = null;
   1584         try {
   1585             cursor = db.query(TABLE_SETTINGS, new String[] { Settings.KEY },
   1586                     Settings.KEY + "=?", keyArray, null, null, null);
   1587             if (cursor.moveToNext()) {
   1588                 long id = cursor.getLong(0);
   1589                 db.update(TABLE_SETTINGS, values, Settings.KEY + "=?", keyArray);
   1590                 return id;
   1591             } else {
   1592                 return db.insertOrThrow(TABLE_SETTINGS, Settings.VALUE, values);
   1593             }
   1594         } finally {
   1595             if (cursor != null) cursor.close();
   1596         }
   1597     }
   1598 
   1599     @Override
   1600     public int updateInTransaction(Uri uri, ContentValues values, String selection,
   1601             String[] selectionArgs, boolean callerIsSyncAdapter) {
   1602         int match = URI_MATCHER.match(uri);
   1603         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1604         if (match == LEGACY || match == LEGACY_ID) {
   1605             // Intercept and route to the correct table
   1606             Integer bookmark = values.getAsInteger(BookmarkColumns.BOOKMARK);
   1607             values.remove(BookmarkColumns.BOOKMARK);
   1608             if (bookmark == null || bookmark == 0) {
   1609                 if (match == LEGACY) {
   1610                     match = HISTORY;
   1611                 } else {
   1612                     match = HISTORY_ID;
   1613                 }
   1614             } else {
   1615                 if (match == LEGACY) {
   1616                     match = BOOKMARKS;
   1617                 } else {
   1618                     match = BOOKMARKS_ID;
   1619                 }
   1620                 values.remove(BookmarkColumns.DATE);
   1621                 values.remove(BookmarkColumns.VISITS);
   1622                 values.remove(BookmarkColumns.USER_ENTERED);
   1623             }
   1624         }
   1625         int modified = 0;
   1626         switch (match) {
   1627             case BOOKMARKS_ID: {
   1628                 selection = DatabaseUtils.concatenateWhere(selection,
   1629                         TABLE_BOOKMARKS + "._id=?");
   1630                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1631                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1632                 // fall through
   1633             }
   1634             case BOOKMARKS: {
   1635                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
   1636                 selection = (String) withAccount[0];
   1637                 selectionArgs = (String[]) withAccount[1];
   1638                 modified = updateBookmarksInTransaction(values, selection, selectionArgs,
   1639                         callerIsSyncAdapter);
   1640                 if (modified > 0) {
   1641                     refreshWidgets();
   1642                 }
   1643                 break;
   1644             }
   1645 
   1646             case HISTORY_ID: {
   1647                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
   1648                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1649                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1650                 // fall through
   1651             }
   1652             case HISTORY: {
   1653                 modified = updateHistoryInTransaction(values, selection, selectionArgs);
   1654                 break;
   1655             }
   1656 
   1657             case SYNCSTATE: {
   1658                 modified = mSyncHelper.update(mDb, values,
   1659                         appendAccountToSelection(uri, selection), selectionArgs);
   1660                 break;
   1661             }
   1662 
   1663             case SYNCSTATE_ID: {
   1664                 selection = appendAccountToSelection(uri, selection);
   1665                 String selectionWithId =
   1666                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
   1667                         + (selection == null ? "" : " AND (" + selection + ")");
   1668                 modified = mSyncHelper.update(mDb, values,
   1669                         selectionWithId, selectionArgs);
   1670                 break;
   1671             }
   1672 
   1673             case IMAGES: {
   1674                 String url = values.getAsString(Images.URL);
   1675                 if (TextUtils.isEmpty(url)) {
   1676                     throw new IllegalArgumentException("Images.URL is required");
   1677                 }
   1678                 if (!shouldUpdateImages(db, url, values)) {
   1679                     return 0;
   1680                 }
   1681                 int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
   1682                         new String[] { url });
   1683                 if (count == 0) {
   1684                     db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
   1685                     count = 1;
   1686                 }
   1687                 // Only favicon is exposed in the public API. If we updated
   1688                 // the thumbnail or touch icon don't bother notifying the
   1689                 // legacy authority since it can't read it anyway.
   1690                 boolean updatedLegacy = false;
   1691                 if (getUrlCount(db, TABLE_BOOKMARKS, url) > 0) {
   1692                     postNotifyUri(Bookmarks.CONTENT_URI);
   1693                     updatedLegacy = values.containsKey(Images.FAVICON);
   1694                     refreshWidgets();
   1695                 }
   1696                 if (getUrlCount(db, TABLE_HISTORY, url) > 0) {
   1697                     postNotifyUri(History.CONTENT_URI);
   1698                     updatedLegacy = values.containsKey(Images.FAVICON);
   1699                 }
   1700                 if (pruneImages() > 0 || updatedLegacy) {
   1701                     postNotifyUri(LEGACY_AUTHORITY_URI);
   1702                 }
   1703                 // Even though we may be calling notifyUri on Bookmarks, don't
   1704                 // sync to network as images aren't synced. Otherwise this
   1705                 // unnecessarily triggers a bookmark sync.
   1706                 mSyncToNetwork = false;
   1707                 return count;
   1708             }
   1709 
   1710             case SEARCHES: {
   1711                 modified = db.update(TABLE_SEARCHES, values, selection, selectionArgs);
   1712                 break;
   1713             }
   1714 
   1715             case ACCOUNTS: {
   1716                 Account[] accounts = AccountManager.get(getContext()).getAccounts();
   1717                 mSyncHelper.onAccountsChanged(mDb, accounts);
   1718                 break;
   1719             }
   1720 
   1721             case THUMBNAILS: {
   1722                 modified = db.update(TABLE_THUMBNAILS, values,
   1723                         selection, selectionArgs);
   1724                 break;
   1725             }
   1726 
   1727             default: {
   1728                 throw new UnsupportedOperationException("Unknown update URI " + uri);
   1729             }
   1730         }
   1731         pruneImages();
   1732         if (modified > 0) {
   1733             postNotifyUri(uri);
   1734             if (shouldNotifyLegacy(uri)) {
   1735                 postNotifyUri(LEGACY_AUTHORITY_URI);
   1736             }
   1737         }
   1738         return modified;
   1739     }
   1740 
   1741     // We want to avoid sending out more URI notifications than we have to
   1742     // Thus, we check to see if the images we are about to store are already there
   1743     // This is used because things like a site's favion or touch icon is rarely
   1744     // changed, but the browser tries to update it every time the page loads.
   1745     // Without this, we will always send out 3 URI notifications per page load.
   1746     // With this, that drops to 0 or 1, depending on if the thumbnail changed.
   1747     private boolean shouldUpdateImages(
   1748             SQLiteDatabase db, String url, ContentValues values) {
   1749         final String[] projection = new String[] {
   1750                 Images.FAVICON,
   1751                 Images.THUMBNAIL,
   1752                 Images.TOUCH_ICON,
   1753         };
   1754         Cursor cursor = db.query(TABLE_IMAGES, projection, Images.URL + "=?",
   1755                 new String[] { url }, null, null, null);
   1756         byte[] nfavicon = values.getAsByteArray(Images.FAVICON);
   1757         byte[] nthumb = values.getAsByteArray(Images.THUMBNAIL);
   1758         byte[] ntouch = values.getAsByteArray(Images.TOUCH_ICON);
   1759         byte[] cfavicon = null;
   1760         byte[] cthumb = null;
   1761         byte[] ctouch = null;
   1762         try {
   1763             if (cursor.getCount() <= 0) {
   1764                 return nfavicon != null || nthumb != null || ntouch != null;
   1765             }
   1766             while (cursor.moveToNext()) {
   1767                 if (nfavicon != null) {
   1768                     cfavicon = cursor.getBlob(0);
   1769                     if (!Arrays.equals(nfavicon, cfavicon)) {
   1770                         return true;
   1771                     }
   1772                 }
   1773                 if (nthumb != null) {
   1774                     cthumb = cursor.getBlob(1);
   1775                     if (!Arrays.equals(nthumb, cthumb)) {
   1776                         return true;
   1777                     }
   1778                 }
   1779                 if (ntouch != null) {
   1780                     ctouch = cursor.getBlob(2);
   1781                     if (!Arrays.equals(ntouch, ctouch)) {
   1782                         return true;
   1783                     }
   1784                 }
   1785             }
   1786         } finally {
   1787             cursor.close();
   1788         }
   1789         return false;
   1790     }
   1791 
   1792     int getUrlCount(SQLiteDatabase db, String table, String url) {
   1793         Cursor c = db.query(table, new String[] { "COUNT(*)" },
   1794                 "url = ?", new String[] { url }, null, null, null);
   1795         try {
   1796             int count = 0;
   1797             if (c.moveToFirst()) {
   1798                 count = c.getInt(0);
   1799             }
   1800             return count;
   1801         } finally {
   1802             c.close();
   1803         }
   1804     }
   1805 
   1806     /**
   1807      * Does a query to find the matching bookmarks and updates each one with the provided values.
   1808      */
   1809     int updateBookmarksInTransaction(ContentValues values, String selection,
   1810             String[] selectionArgs, boolean callerIsSyncAdapter) {
   1811         int count = 0;
   1812         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1813         final String[] bookmarksProjection = new String[] {
   1814                 Bookmarks._ID, // 0
   1815                 Bookmarks.VERSION, // 1
   1816                 Bookmarks.URL, // 2
   1817                 Bookmarks.TITLE, // 3
   1818                 Bookmarks.IS_FOLDER, // 4
   1819                 Bookmarks.ACCOUNT_NAME, // 5
   1820                 Bookmarks.ACCOUNT_TYPE, // 6
   1821         };
   1822         Cursor cursor = db.query(TABLE_BOOKMARKS, bookmarksProjection,
   1823                 selection, selectionArgs, null, null, null);
   1824         boolean updatingParent = values.containsKey(Bookmarks.PARENT);
   1825         String parentAccountName = null;
   1826         String parentAccountType = null;
   1827         if (updatingParent) {
   1828             long parent = values.getAsLong(Bookmarks.PARENT);
   1829             Cursor c = db.query(TABLE_BOOKMARKS, new String[] {
   1830                     Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_TYPE},
   1831                     "_id = ?", new String[] { Long.toString(parent) },
   1832                     null, null, null);
   1833             if (c.moveToFirst()) {
   1834                 parentAccountName = c.getString(0);
   1835                 parentAccountType = c.getString(1);
   1836             }
   1837             c.close();
   1838         } else if (values.containsKey(Bookmarks.ACCOUNT_NAME)
   1839                 || values.containsKey(Bookmarks.ACCOUNT_TYPE)) {
   1840             // TODO: Implement if needed (no one needs this yet)
   1841         }
   1842         try {
   1843             String[] args = new String[1];
   1844             // Mark the bookmark dirty if the caller isn't a sync adapter
   1845             if (!callerIsSyncAdapter) {
   1846                 values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
   1847                 values.put(Bookmarks.DIRTY, 1);
   1848             }
   1849 
   1850             boolean updatingUrl = values.containsKey(Bookmarks.URL);
   1851             String url = null;
   1852             if (updatingUrl) {
   1853                 url = values.getAsString(Bookmarks.URL);
   1854             }
   1855             ContentValues imageValues = extractImageValues(values, url);
   1856 
   1857             while (cursor.moveToNext()) {
   1858                 long id = cursor.getLong(0);
   1859                 args[0] = Long.toString(id);
   1860                 String accountName = cursor.getString(5);
   1861                 String accountType = cursor.getString(6);
   1862                 // If we are updating the parent and either the account name or
   1863                 // type do not match that of the new parent
   1864                 if (updatingParent
   1865                         && (!TextUtils.equals(accountName, parentAccountName)
   1866                         || !TextUtils.equals(accountType, parentAccountType))) {
   1867                     // Parent is a different account
   1868                     // First, insert a new bookmark/folder with the new account
   1869                     // Then, if this is a folder, reparent all it's children
   1870                     // Finally, delete the old bookmark/folder
   1871                     ContentValues newValues = valuesFromCursor(cursor);
   1872                     newValues.putAll(values);
   1873                     newValues.remove(Bookmarks._ID);
   1874                     newValues.remove(Bookmarks.VERSION);
   1875                     newValues.put(Bookmarks.ACCOUNT_NAME, parentAccountName);
   1876                     newValues.put(Bookmarks.ACCOUNT_TYPE, parentAccountType);
   1877                     Uri insertUri = insertInTransaction(Bookmarks.CONTENT_URI,
   1878                             newValues, callerIsSyncAdapter);
   1879                     long newId = ContentUris.parseId(insertUri);
   1880                     if (cursor.getInt(4) != 0) {
   1881                         // This is a folder, reparent
   1882                         ContentValues updateChildren = new ContentValues(1);
   1883                         updateChildren.put(Bookmarks.PARENT, newId);
   1884                         count += updateBookmarksInTransaction(updateChildren,
   1885                                 Bookmarks.PARENT + "=?", new String[] {
   1886                                 Long.toString(id)}, callerIsSyncAdapter);
   1887                     }
   1888                     // Now, delete the old one
   1889                     Uri uri = ContentUris.withAppendedId(Bookmarks.CONTENT_URI, id);
   1890                     deleteInTransaction(uri, null, null, callerIsSyncAdapter);
   1891                     count += 1;
   1892                 } else {
   1893                     if (!callerIsSyncAdapter) {
   1894                         // increase the local version for non-sync changes
   1895                         values.put(Bookmarks.VERSION, cursor.getLong(1) + 1);
   1896                     }
   1897                     count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
   1898                 }
   1899 
   1900                 // Update the images over in their table
   1901                 if (imageValues != null) {
   1902                     if (!updatingUrl) {
   1903                         url = cursor.getString(2);
   1904                         imageValues.put(Images.URL, url);
   1905                     }
   1906 
   1907                     if (!TextUtils.isEmpty(url)) {
   1908                         args[0] = url;
   1909                         if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
   1910                             db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
   1911                         }
   1912                     }
   1913                 }
   1914             }
   1915         } finally {
   1916             if (cursor != null) cursor.close();
   1917         }
   1918         return count;
   1919     }
   1920 
   1921     ContentValues valuesFromCursor(Cursor c) {
   1922         int count = c.getColumnCount();
   1923         ContentValues values = new ContentValues(count);
   1924         String[] colNames = c.getColumnNames();
   1925         for (int i = 0; i < count; i++) {
   1926             switch (c.getType(i)) {
   1927             case Cursor.FIELD_TYPE_BLOB:
   1928                 values.put(colNames[i], c.getBlob(i));
   1929                 break;
   1930             case Cursor.FIELD_TYPE_FLOAT:
   1931                 values.put(colNames[i], c.getFloat(i));
   1932                 break;
   1933             case Cursor.FIELD_TYPE_INTEGER:
   1934                 values.put(colNames[i], c.getLong(i));
   1935                 break;
   1936             case Cursor.FIELD_TYPE_STRING:
   1937                 values.put(colNames[i], c.getString(i));
   1938                 break;
   1939             }
   1940         }
   1941         return values;
   1942     }
   1943 
   1944     /**
   1945      * Does a query to find the matching bookmarks and updates each one with the provided values.
   1946      */
   1947     int updateHistoryInTransaction(ContentValues values, String selection, String[] selectionArgs) {
   1948         int count = 0;
   1949         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1950         filterSearchClient(selectionArgs);
   1951         Cursor cursor = query(History.CONTENT_URI,
   1952                 new String[] { History._ID, History.URL },
   1953                 selection, selectionArgs, null);
   1954         try {
   1955             String[] args = new String[1];
   1956 
   1957             boolean updatingUrl = values.containsKey(History.URL);
   1958             String url = null;
   1959             if (updatingUrl) {
   1960                 url = filterSearchClient(values.getAsString(History.URL));
   1961                 values.put(History.URL, url);
   1962             }
   1963             ContentValues imageValues = extractImageValues(values, url);
   1964 
   1965             while (cursor.moveToNext()) {
   1966                 args[0] = cursor.getString(0);
   1967                 count += db.update(TABLE_HISTORY, values, "_id=?", args);
   1968 
   1969                 // Update the images over in their table
   1970                 if (imageValues != null) {
   1971                     if (!updatingUrl) {
   1972                         url = cursor.getString(1);
   1973                         imageValues.put(Images.URL, url);
   1974                     }
   1975                     args[0] = url;
   1976                     if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
   1977                         db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
   1978                     }
   1979                 }
   1980             }
   1981         } finally {
   1982             if (cursor != null) cursor.close();
   1983         }
   1984         return count;
   1985     }
   1986 
   1987     String appendAccountToSelection(Uri uri, String selection) {
   1988         final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
   1989         final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE);
   1990 
   1991         final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
   1992         if (partialUri) {
   1993             // Throw when either account is incomplete
   1994             throw new IllegalArgumentException(
   1995                     "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri);
   1996         }
   1997 
   1998         // Accounts are valid by only checking one parameter, since we've
   1999         // already ruled out partial accounts.
   2000         final boolean validAccount = !TextUtils.isEmpty(accountName);
   2001         if (validAccount) {
   2002             StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "="
   2003                     + DatabaseUtils.sqlEscapeString(accountName) + " AND "
   2004                     + RawContacts.ACCOUNT_TYPE + "="
   2005                     + DatabaseUtils.sqlEscapeString(accountType));
   2006             if (!TextUtils.isEmpty(selection)) {
   2007                 selectionSb.append(" AND (");
   2008                 selectionSb.append(selection);
   2009                 selectionSb.append(')');
   2010             }
   2011             return selectionSb.toString();
   2012         } else {
   2013             return selection;
   2014         }
   2015     }
   2016 
   2017     ContentValues extractImageValues(ContentValues values, String url) {
   2018         ContentValues imageValues = null;
   2019         // favicon
   2020         if (values.containsKey(Bookmarks.FAVICON)) {
   2021             imageValues = new ContentValues();
   2022             imageValues.put(Images.FAVICON, values.getAsByteArray(Bookmarks.FAVICON));
   2023             values.remove(Bookmarks.FAVICON);
   2024         }
   2025 
   2026         // thumbnail
   2027         if (values.containsKey(Bookmarks.THUMBNAIL)) {
   2028             if (imageValues == null) {
   2029                 imageValues = new ContentValues();
   2030             }
   2031             imageValues.put(Images.THUMBNAIL, values.getAsByteArray(Bookmarks.THUMBNAIL));
   2032             values.remove(Bookmarks.THUMBNAIL);
   2033         }
   2034 
   2035         // touch icon
   2036         if (values.containsKey(Bookmarks.TOUCH_ICON)) {
   2037             if (imageValues == null) {
   2038                 imageValues = new ContentValues();
   2039             }
   2040             imageValues.put(Images.TOUCH_ICON, values.getAsByteArray(Bookmarks.TOUCH_ICON));
   2041             values.remove(Bookmarks.TOUCH_ICON);
   2042         }
   2043 
   2044         if (imageValues != null) {
   2045             imageValues.put(Images.URL,  url);
   2046         }
   2047         return imageValues;
   2048     }
   2049 
   2050     int pruneImages() {
   2051         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   2052         return db.delete(TABLE_IMAGES, IMAGE_PRUNE, null);
   2053     }
   2054 
   2055     boolean shouldNotifyLegacy(Uri uri) {
   2056         if (uri.getPathSegments().contains("history")
   2057                 || uri.getPathSegments().contains("bookmarks")
   2058                 || uri.getPathSegments().contains("searches")) {
   2059             return true;
   2060         }
   2061         return false;
   2062     }
   2063 
   2064     @Override
   2065     protected boolean syncToNetwork(Uri uri) {
   2066         if (BrowserContract.AUTHORITY.equals(uri.getAuthority())
   2067                 && uri.getPathSegments().contains("bookmarks")) {
   2068             return mSyncToNetwork;
   2069         }
   2070         if (LEGACY_AUTHORITY.equals(uri.getAuthority())) {
   2071             // Allow for 3rd party sync adapters
   2072             return true;
   2073         }
   2074         return false;
   2075     }
   2076 
   2077     static class SuggestionsCursor extends AbstractCursor {
   2078         private static final int ID_INDEX = 0;
   2079         private static final int URL_INDEX = 1;
   2080         private static final int TITLE_INDEX = 2;
   2081         private static final int ICON_INDEX = 3;
   2082         private static final int LAST_ACCESS_TIME_INDEX = 4;
   2083         // shared suggestion array index, make sure to match COLUMNS
   2084         private static final int SUGGEST_COLUMN_INTENT_ACTION_ID = 1;
   2085         private static final int SUGGEST_COLUMN_INTENT_DATA_ID = 2;
   2086         private static final int SUGGEST_COLUMN_TEXT_1_ID = 3;
   2087         private static final int SUGGEST_COLUMN_TEXT_2_TEXT_ID = 4;
   2088         private static final int SUGGEST_COLUMN_TEXT_2_URL_ID = 5;
   2089         private static final int SUGGEST_COLUMN_ICON_1_ID = 6;
   2090         private static final int SUGGEST_COLUMN_LAST_ACCESS_HINT_ID = 7;
   2091 
   2092         // shared suggestion columns
   2093         private static final String[] COLUMNS = new String[] {
   2094                 BaseColumns._ID,
   2095                 SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
   2096                 SearchManager.SUGGEST_COLUMN_INTENT_DATA,
   2097                 SearchManager.SUGGEST_COLUMN_TEXT_1,
   2098                 SearchManager.SUGGEST_COLUMN_TEXT_2,
   2099                 SearchManager.SUGGEST_COLUMN_TEXT_2_URL,
   2100                 SearchManager.SUGGEST_COLUMN_ICON_1,
   2101                 SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT};
   2102 
   2103         private final Cursor mSource;
   2104 
   2105         public SuggestionsCursor(Cursor cursor) {
   2106             mSource = cursor;
   2107         }
   2108 
   2109         @Override
   2110         public String[] getColumnNames() {
   2111             return COLUMNS;
   2112         }
   2113 
   2114         @Override
   2115         public String getString(int columnIndex) {
   2116             switch (columnIndex) {
   2117             case ID_INDEX:
   2118                 return mSource.getString(columnIndex);
   2119             case SUGGEST_COLUMN_INTENT_ACTION_ID:
   2120                 return Intent.ACTION_VIEW;
   2121             case SUGGEST_COLUMN_INTENT_DATA_ID:
   2122                 return mSource.getString(URL_INDEX);
   2123             case SUGGEST_COLUMN_TEXT_2_TEXT_ID:
   2124             case SUGGEST_COLUMN_TEXT_2_URL_ID:
   2125                 return UrlUtils.stripUrl(mSource.getString(URL_INDEX));
   2126             case SUGGEST_COLUMN_TEXT_1_ID:
   2127                 return mSource.getString(TITLE_INDEX);
   2128             case SUGGEST_COLUMN_ICON_1_ID:
   2129                 return mSource.getString(ICON_INDEX);
   2130             case SUGGEST_COLUMN_LAST_ACCESS_HINT_ID:
   2131                 return mSource.getString(LAST_ACCESS_TIME_INDEX);
   2132             }
   2133             return null;
   2134         }
   2135 
   2136         @Override
   2137         public int getCount() {
   2138             return mSource.getCount();
   2139         }
   2140 
   2141         @Override
   2142         public double getDouble(int column) {
   2143             throw new UnsupportedOperationException();
   2144         }
   2145 
   2146         @Override
   2147         public float getFloat(int column) {
   2148             throw new UnsupportedOperationException();
   2149         }
   2150 
   2151         @Override
   2152         public int getInt(int column) {
   2153             throw new UnsupportedOperationException();
   2154         }
   2155 
   2156         @Override
   2157         public long getLong(int column) {
   2158             switch (column) {
   2159             case ID_INDEX:
   2160                 return mSource.getLong(ID_INDEX);
   2161             case SUGGEST_COLUMN_LAST_ACCESS_HINT_ID:
   2162                 return mSource.getLong(LAST_ACCESS_TIME_INDEX);
   2163             }
   2164             throw new UnsupportedOperationException();
   2165         }
   2166 
   2167         @Override
   2168         public short getShort(int column) {
   2169             throw new UnsupportedOperationException();
   2170         }
   2171 
   2172         @Override
   2173         public boolean isNull(int column) {
   2174             return mSource.isNull(column);
   2175         }
   2176 
   2177         @Override
   2178         public boolean onMove(int oldPosition, int newPosition) {
   2179             return mSource.moveToPosition(newPosition);
   2180         }
   2181     }
   2182 
   2183     // ---------------------------------------------------
   2184     //  SQL below, be warned
   2185     // ---------------------------------------------------
   2186 
   2187     private static final String SQL_CREATE_VIEW_OMNIBOX_SUGGESTIONS =
   2188             "CREATE VIEW IF NOT EXISTS v_omnibox_suggestions "
   2189             + " AS "
   2190             + "  SELECT _id, url, title, 1 AS bookmark, 0 AS visits, 0 AS date"
   2191             + "  FROM bookmarks "
   2192             + "  WHERE deleted = 0 AND folder = 0 "
   2193             + "  UNION ALL "
   2194             + "  SELECT _id, url, title, 0 AS bookmark, visits, date "
   2195             + "  FROM history "
   2196             + "  WHERE url NOT IN (SELECT url FROM bookmarks"
   2197             + "    WHERE deleted = 0 AND folder = 0) "
   2198             + "  ORDER BY bookmark DESC, visits DESC, date DESC ";
   2199 
   2200     private static final String SQL_WHERE_ACCOUNT_HAS_BOOKMARKS =
   2201             "0 < ( "
   2202             + "SELECT count(*) "
   2203             + "FROM bookmarks "
   2204             + "WHERE deleted = 0 AND folder = 0 "
   2205             + "  AND ( "
   2206             + "    v_accounts.account_name = bookmarks.account_name "
   2207             + "    OR (v_accounts.account_name IS NULL AND bookmarks.account_name IS NULL) "
   2208             + "  ) "
   2209             + "  AND ( "
   2210             + "    v_accounts.account_type = bookmarks.account_type "
   2211             + "    OR (v_accounts.account_type IS NULL AND bookmarks.account_type IS NULL) "
   2212             + "  ) "
   2213             + ")";
   2214 }
   2215