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                             String url = c.getString(0);
    522                             if (TextUtils.isEmpty(url))
    523                                 continue; // We require a valid URL
    524                             ContentValues values = new ContentValues();
    525                             values.put(Bookmarks.URL, url);
    526                             values.put(Bookmarks.TITLE, c.getString(1));
    527                             values.put(Bookmarks.DATE_CREATED, c.getInt(4));
    528                             values.put(Bookmarks.POSITION, 0);
    529                             values.put(Bookmarks.PARENT, FIXED_ID_ROOT);
    530                             ContentValues imageValues = new ContentValues();
    531                             imageValues.put(Images.URL, url);
    532                             imageValues.put(Images.FAVICON, c.getBlob(2));
    533                             imageValues.put(Images.TOUCH_ICON, c.getBlob(3));
    534                             db.insert(TABLE_IMAGES, Images.THUMBNAIL, imageValues);
    535                             db.insert(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
    536                         }
    537                         c.close();
    538                     }
    539                     // Import history
    540                     c = oldDb.query(table,
    541                             new String[] {
    542                             BookmarkColumns.URL, // 0
    543                             BookmarkColumns.TITLE, // 1
    544                             BookmarkColumns.VISITS, // 2
    545                             BookmarkColumns.DATE, // 3
    546                             BookmarkColumns.CREATED, // 4
    547                             }, BookmarkColumns.VISITS + " > 0 OR "
    548                             + BookmarkColumns.BOOKMARK + " = 0",
    549                             null, null, null, null);
    550                     if (c != null) {
    551                         while (c.moveToNext()) {
    552                             ContentValues values = new ContentValues();
    553                             String url = c.getString(0);
    554                             if (TextUtils.isEmpty(url))
    555                                 continue; // We require a valid URL
    556                             values.put(History.URL, url);
    557                             values.put(History.TITLE, c.getString(1));
    558                             values.put(History.VISITS, c.getInt(2));
    559                             values.put(History.DATE_LAST_VISITED, c.getLong(3));
    560                             values.put(History.DATE_CREATED, c.getLong(4));
    561                             db.insert(TABLE_HISTORY, History.FAVICON, values);
    562                         }
    563                         c.close();
    564                     }
    565                     // Wipe the old DB, in case the delete fails.
    566                     oldDb.delete(table, null, null);
    567                 } finally {
    568                     if (c != null) c.close();
    569                     oldDb.close();
    570                     helper.close();
    571                 }
    572                 if (!oldDbFile.delete()) {
    573                     oldDbFile.deleteOnExit();
    574                 }
    575                 return true;
    576             }
    577             return false;
    578         }
    579 
    580         void createAccountsView(SQLiteDatabase db) {
    581             db.execSQL("CREATE VIEW IF NOT EXISTS v_accounts AS "
    582                     + "SELECT NULL AS " + Accounts.ACCOUNT_NAME
    583                     + ", NULL AS " + Accounts.ACCOUNT_TYPE
    584                     + ", " + FIXED_ID_ROOT + " AS " + Accounts.ROOT_ID
    585                     + " UNION ALL SELECT " + Accounts.ACCOUNT_NAME
    586                     + ", " + Accounts.ACCOUNT_TYPE + ", "
    587                     + Bookmarks._ID + " AS " + Accounts.ROOT_ID
    588                     + " FROM " + TABLE_BOOKMARKS + " WHERE "
    589                     + ChromeSyncColumns.SERVER_UNIQUE + " = \""
    590                     + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "\" AND "
    591                     + Bookmarks.IS_DELETED + " = 0");
    592         }
    593 
    594         @Override
    595         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    596             if (oldVersion < 32) {
    597                 createOmniboxSuggestions(db);
    598             }
    599             if (oldVersion < 31) {
    600                 createThumbnails(db);
    601             }
    602             if (oldVersion < 30) {
    603                 db.execSQL("DROP VIEW IF EXISTS " + VIEW_SNAPSHOTS_COMBINED);
    604                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
    605             }
    606             if (oldVersion < 28) {
    607                 enableSync(db);
    608             }
    609             if (oldVersion < 27) {
    610                 createAccountsView(db);
    611             }
    612             if (oldVersion < 26) {
    613                 db.execSQL("DROP VIEW IF EXISTS combined");
    614             }
    615             if (oldVersion < 25) {
    616                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
    617                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
    618                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
    619                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
    620                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SETTINGS);
    621                 mSyncHelper.onAccountsChanged(db, new Account[] {}); // remove all sync info
    622                 onCreate(db);
    623             }
    624         }
    625 
    626         public void onOpen(SQLiteDatabase db) {
    627             db.enableWriteAheadLogging();
    628             mSyncHelper.onDatabaseOpened(db);
    629         }
    630 
    631         private void createDefaultBookmarks(SQLiteDatabase db) {
    632             ContentValues values = new ContentValues();
    633             // TODO figure out how to deal with localization for the defaults
    634 
    635             // Bookmarks folder
    636             values.put(Bookmarks._ID, FIXED_ID_ROOT);
    637             values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
    638             values.put(Bookmarks.TITLE, "Bookmarks");
    639             values.putNull(Bookmarks.PARENT);
    640             values.put(Bookmarks.POSITION, 0);
    641             values.put(Bookmarks.IS_FOLDER, true);
    642             values.put(Bookmarks.DIRTY, true);
    643             db.insertOrThrow(TABLE_BOOKMARKS, null, values);
    644 
    645             addDefaultBookmarks(db, FIXED_ID_ROOT);
    646         }
    647 
    648         private void addDefaultBookmarks(SQLiteDatabase db, long parentId) {
    649             Resources res = getContext().getResources();
    650             final CharSequence[] bookmarks = res.getTextArray(
    651                     R.array.bookmarks);
    652             int size = bookmarks.length;
    653             TypedArray preloads = res.obtainTypedArray(R.array.bookmark_preloads);
    654             try {
    655                 String parent = Long.toString(parentId);
    656                 String now = Long.toString(System.currentTimeMillis());
    657                 for (int i = 0; i < size; i = i + 2) {
    658                     CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
    659                             bookmarks[i + 1]);
    660                     db.execSQL("INSERT INTO bookmarks (" +
    661                             Bookmarks.TITLE + ", " +
    662                             Bookmarks.URL + ", " +
    663                             Bookmarks.IS_FOLDER + "," +
    664                             Bookmarks.PARENT + "," +
    665                             Bookmarks.POSITION + "," +
    666                             Bookmarks.DATE_CREATED +
    667                         ") VALUES (" +
    668                             "'" + bookmarks[i] + "', " +
    669                             "'" + bookmarkDestination + "', " +
    670                             "0," +
    671                             parent + "," +
    672                             Integer.toString(i) + "," +
    673                             now +
    674                             ");");
    675 
    676                     int faviconId = preloads.getResourceId(i, 0);
    677                     int thumbId = preloads.getResourceId(i + 1, 0);
    678                     byte[] thumb = null, favicon = null;
    679                     try {
    680                         thumb = readRaw(res, thumbId);
    681                     } catch (IOException e) {
    682                     }
    683                     try {
    684                         favicon = readRaw(res, faviconId);
    685                     } catch (IOException e) {
    686                     }
    687                     if (thumb != null || favicon != null) {
    688                         ContentValues imageValues = new ContentValues();
    689                         imageValues.put(Images.URL, bookmarkDestination.toString());
    690                         if (favicon != null) {
    691                             imageValues.put(Images.FAVICON, favicon);
    692                         }
    693                         if (thumb != null) {
    694                             imageValues.put(Images.THUMBNAIL, thumb);
    695                         }
    696                         db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
    697                     }
    698                 }
    699             } catch (ArrayIndexOutOfBoundsException e) {
    700             }
    701         }
    702 
    703         private byte[] readRaw(Resources res, int id) throws IOException {
    704             if (id == 0) {
    705                 return null;
    706             }
    707             InputStream is = res.openRawResource(id);
    708             try {
    709                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
    710                 byte[] buf = new byte[4096];
    711                 int read;
    712                 while ((read = is.read(buf)) > 0) {
    713                     bos.write(buf, 0, read);
    714                 }
    715                 bos.flush();
    716                 return bos.toByteArray();
    717             } finally {
    718                 is.close();
    719             }
    720         }
    721 
    722         // XXX: This is a major hack to remove our dependency on gsf constants and
    723         // its content provider. http://b/issue?id=2425179
    724         private String getClientId(ContentResolver cr) {
    725             String ret = "android-google";
    726             Cursor c = null;
    727             try {
    728                 c = cr.query(Uri.parse("content://com.google.settings/partner"),
    729                         new String[] { "value" }, "name='client_id'", null, null);
    730                 if (c != null && c.moveToNext()) {
    731                     ret = c.getString(0);
    732                 }
    733             } catch (RuntimeException ex) {
    734                 // fall through to return the default
    735             } finally {
    736                 if (c != null) {
    737                     c.close();
    738                 }
    739             }
    740             return ret;
    741         }
    742 
    743         private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
    744             StringBuffer sb = new StringBuffer();
    745             int lastCharLoc = 0;
    746 
    747             final String client_id = getClientId(context.getContentResolver());
    748 
    749             for (int i = 0; i < srcString.length(); ++i) {
    750                 char c = srcString.charAt(i);
    751                 if (c == '{') {
    752                     sb.append(srcString.subSequence(lastCharLoc, i));
    753                     lastCharLoc = i;
    754               inner:
    755                     for (int j = i; j < srcString.length(); ++j) {
    756                         char k = srcString.charAt(j);
    757                         if (k == '}') {
    758                             String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
    759                             if (propertyKeyValue.equals("CLIENT_ID")) {
    760                                 sb.append(client_id);
    761                             } else {
    762                                 sb.append("unknown");
    763                             }
    764                             lastCharLoc = j + 1;
    765                             i = j;
    766                             break inner;
    767                         }
    768                     }
    769                 }
    770             }
    771             if (srcString.length() - lastCharLoc > 0) {
    772                 // Put on the tail, if there is one
    773                 sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
    774             }
    775             return sb;
    776         }
    777     }
    778 
    779     @Override
    780     public SQLiteOpenHelper getDatabaseHelper(Context context) {
    781         synchronized (this) {
    782             if (mOpenHelper == null) {
    783                 mOpenHelper = new DatabaseHelper(context);
    784             }
    785             return mOpenHelper;
    786         }
    787     }
    788 
    789     @Override
    790     public boolean isCallerSyncAdapter(Uri uri) {
    791         return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
    792     }
    793 
    794     @VisibleForTesting
    795     public void setWidgetObserver(ContentObserver obs) {
    796         mWidgetObserver = obs;
    797     }
    798 
    799     void refreshWidgets() {
    800         mUpdateWidgets = true;
    801     }
    802 
    803     @Override
    804     protected void onEndTransaction(boolean callerIsSyncAdapter) {
    805         super.onEndTransaction(callerIsSyncAdapter);
    806         if (mUpdateWidgets) {
    807             if (mWidgetObserver == null) {
    808                 BookmarkThumbnailWidgetProvider.refreshWidgets(getContext());
    809             } else {
    810                 mWidgetObserver.dispatchChange(false);
    811             }
    812             mUpdateWidgets = false;
    813         }
    814         mSyncToNetwork = true;
    815     }
    816 
    817     @Override
    818     public String getType(Uri uri) {
    819         final int match = URI_MATCHER.match(uri);
    820         switch (match) {
    821             case LEGACY:
    822             case BOOKMARKS:
    823                 return Bookmarks.CONTENT_TYPE;
    824             case LEGACY_ID:
    825             case BOOKMARKS_ID:
    826                 return Bookmarks.CONTENT_ITEM_TYPE;
    827             case HISTORY:
    828                 return History.CONTENT_TYPE;
    829             case HISTORY_ID:
    830                 return History.CONTENT_ITEM_TYPE;
    831             case SEARCHES:
    832                 return Searches.CONTENT_TYPE;
    833             case SEARCHES_ID:
    834                 return Searches.CONTENT_ITEM_TYPE;
    835         }
    836         return null;
    837     }
    838 
    839     boolean isNullAccount(String account) {
    840         if (account == null) return true;
    841         account = account.trim();
    842         return account.length() == 0 || account.equals("null");
    843     }
    844 
    845     Object[] getSelectionWithAccounts(Uri uri, String selection, String[] selectionArgs) {
    846         // Look for account info
    847         String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
    848         String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
    849         boolean hasAccounts = false;
    850         if (accountType != null && accountName != null) {
    851             if (!isNullAccount(accountType) && !isNullAccount(accountName)) {
    852                 selection = DatabaseUtils.concatenateWhere(selection,
    853                         Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? ");
    854                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    855                         new String[] { accountType, accountName });
    856                 hasAccounts = true;
    857             } else {
    858                 selection = DatabaseUtils.concatenateWhere(selection,
    859                         Bookmarks.ACCOUNT_NAME + " IS NULL AND " +
    860                         Bookmarks.ACCOUNT_TYPE + " IS NULL");
    861             }
    862         }
    863         return new Object[] { selection, selectionArgs, hasAccounts };
    864     }
    865 
    866     @Override
    867     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    868             String sortOrder) {
    869         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    870         final int match = URI_MATCHER.match(uri);
    871         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    872         String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
    873         String groupBy = uri.getQueryParameter(PARAM_GROUP_BY);
    874         switch (match) {
    875             case ACCOUNTS: {
    876                 qb.setTables(VIEW_ACCOUNTS);
    877                 qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
    878                 String allowEmpty = uri.getQueryParameter(PARAM_ALLOW_EMPTY_ACCOUNTS);
    879                 if ("false".equals(allowEmpty)) {
    880                     selection = DatabaseUtils.concatenateWhere(selection,
    881                             SQL_WHERE_ACCOUNT_HAS_BOOKMARKS);
    882                 }
    883                 if (sortOrder == null) {
    884                     sortOrder = DEFAULT_SORT_ACCOUNTS;
    885                 }
    886                 break;
    887             }
    888 
    889             case BOOKMARKS_FOLDER_ID:
    890             case BOOKMARKS_ID:
    891             case BOOKMARKS: {
    892                 // Only show deleted bookmarks if requested to do so
    893                 if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) {
    894                     selection = DatabaseUtils.concatenateWhere(
    895                             Bookmarks.IS_DELETED + "=0", selection);
    896                 }
    897 
    898                 if (match == BOOKMARKS_ID) {
    899                     // Tack on the ID of the specific bookmark requested
    900                     selection = DatabaseUtils.concatenateWhere(selection,
    901                             TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?");
    902                     selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    903                             new String[] { Long.toString(ContentUris.parseId(uri)) });
    904                 } else if (match == BOOKMARKS_FOLDER_ID) {
    905                     // Tack on the ID of the specific folder requested
    906                     selection = DatabaseUtils.concatenateWhere(selection,
    907                             TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?");
    908                     selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    909                             new String[] { Long.toString(ContentUris.parseId(uri)) });
    910                 }
    911 
    912                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
    913                 selection = (String) withAccount[0];
    914                 selectionArgs = (String[]) withAccount[1];
    915                 boolean hasAccounts = (Boolean) withAccount[2];
    916 
    917                 // Set a default sort order if one isn't specified
    918                 if (TextUtils.isEmpty(sortOrder)) {
    919                     if (hasAccounts) {
    920                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
    921                     } else {
    922                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
    923                     }
    924                 }
    925 
    926                 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
    927                 qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
    928                 break;
    929             }
    930 
    931             case BOOKMARKS_FOLDER: {
    932                 // Look for an account
    933                 boolean useAccount = false;
    934                 String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
    935                 String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
    936                 if (!isNullAccount(accountType) && !isNullAccount(accountName)) {
    937                     useAccount = true;
    938                 }
    939 
    940                 qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
    941                 String[] args;
    942                 String query;
    943                 // Set a default sort order if one isn't specified
    944                 if (TextUtils.isEmpty(sortOrder)) {
    945                     if (useAccount) {
    946                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER_SYNC;
    947                     } else {
    948                         sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
    949                     }
    950                 }
    951                 if (!useAccount) {
    952                     qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
    953                     String where = Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0";
    954                     where = DatabaseUtils.concatenateWhere(where, selection);
    955                     args = new String[] { Long.toString(FIXED_ID_ROOT) };
    956                     if (selectionArgs != null) {
    957                         args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
    958                     }
    959                     query = qb.buildQuery(projection, where, null, null, sortOrder, null);
    960                 } else {
    961                     qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
    962                     String where = Bookmarks.ACCOUNT_TYPE + "=? AND " +
    963                             Bookmarks.ACCOUNT_NAME + "=? " +
    964                             "AND parent = " +
    965                             "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " +
    966                             ChromeSyncColumns.SERVER_UNIQUE + "=" +
    967                             "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " +
    968                             "AND account_type = ? AND account_name = ?) " +
    969                             "AND " + Bookmarks.IS_DELETED + "=0";
    970                     where = DatabaseUtils.concatenateWhere(where, selection);
    971                     String bookmarksBarQuery = qb.buildQuery(projection,
    972                             where, null, null, null, null);
    973                     args = new String[] {accountType, accountName,
    974                             accountType, accountName};
    975                     if (selectionArgs != null) {
    976                         args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
    977                     }
    978 
    979                     where = Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" +
    980                             " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?";
    981                     where = DatabaseUtils.concatenateWhere(where, selection);
    982                     qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
    983                     String otherBookmarksQuery = qb.buildQuery(projection,
    984                             where, null, null, null, null);
    985 
    986                     query = qb.buildUnionQuery(
    987                             new String[] { bookmarksBarQuery, otherBookmarksQuery },
    988                             sortOrder, limit);
    989 
    990                     args = DatabaseUtils.appendSelectionArgs(args, new String[] {
    991                             accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS,
    992                             });
    993                     if (selectionArgs != null) {
    994                         args = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
    995                     }
    996                 }
    997 
    998                 Cursor cursor = db.rawQuery(query, args);
    999                 if (cursor != null) {
   1000                     cursor.setNotificationUri(getContext().getContentResolver(),
   1001                             BrowserContract.AUTHORITY_URI);
   1002                 }
   1003                 return cursor;
   1004             }
   1005 
   1006             case BOOKMARKS_DEFAULT_FOLDER_ID: {
   1007                 String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
   1008                 String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
   1009                 long id = queryDefaultFolderId(accountName, accountType);
   1010                 MatrixCursor c = new MatrixCursor(new String[] {Bookmarks._ID});
   1011                 c.newRow().add(id);
   1012                 return c;
   1013             }
   1014 
   1015             case BOOKMARKS_SUGGESTIONS: {
   1016                 return doSuggestQuery(selection, selectionArgs, limit);
   1017             }
   1018 
   1019             case HISTORY_ID: {
   1020                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
   1021                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1022                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1023                 // fall through
   1024             }
   1025             case HISTORY: {
   1026                 filterSearchClient(selectionArgs);
   1027                 if (sortOrder == null) {
   1028                     sortOrder = DEFAULT_SORT_HISTORY;
   1029                 }
   1030                 qb.setProjectionMap(HISTORY_PROJECTION_MAP);
   1031                 qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
   1032                 break;
   1033             }
   1034 
   1035             case SEARCHES_ID: {
   1036                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
   1037                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1038                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1039                 // fall through
   1040             }
   1041             case SEARCHES: {
   1042                 qb.setTables(TABLE_SEARCHES);
   1043                 qb.setProjectionMap(SEARCHES_PROJECTION_MAP);
   1044                 break;
   1045             }
   1046 
   1047             case SYNCSTATE: {
   1048                 return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder);
   1049             }
   1050 
   1051             case SYNCSTATE_ID: {
   1052                 selection = appendAccountToSelection(uri, selection);
   1053                 String selectionWithId =
   1054                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
   1055                         + (selection == null ? "" : " AND (" + selection + ")");
   1056                 return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
   1057             }
   1058 
   1059             case IMAGES: {
   1060                 qb.setTables(TABLE_IMAGES);
   1061                 qb.setProjectionMap(IMAGES_PROJECTION_MAP);
   1062                 break;
   1063             }
   1064 
   1065             case LEGACY_ID:
   1066             case COMBINED_ID: {
   1067                 selection = DatabaseUtils.concatenateWhere(
   1068                         selection, Combined._ID + " = CAST(? AS INTEGER)");
   1069                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1070                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1071                 // fall through
   1072             }
   1073             case LEGACY:
   1074             case COMBINED: {
   1075                 if ((match == LEGACY || match == LEGACY_ID)
   1076                         && projection == null) {
   1077                     projection = Browser.HISTORY_PROJECTION;
   1078                 }
   1079                 String[] args = createCombinedQuery(uri, projection, qb);
   1080                 if (selectionArgs == null) {
   1081                     selectionArgs = args;
   1082                 } else {
   1083                     selectionArgs = DatabaseUtils.appendSelectionArgs(args, selectionArgs);
   1084                 }
   1085                 break;
   1086             }
   1087 
   1088             case SETTINGS: {
   1089                 qb.setTables(TABLE_SETTINGS);
   1090                 qb.setProjectionMap(SETTINGS_PROJECTION_MAP);
   1091                 break;
   1092             }
   1093 
   1094             case THUMBNAILS_ID: {
   1095                 selection = DatabaseUtils.concatenateWhere(
   1096                         selection, Thumbnails._ID + " = ?");
   1097                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1098                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1099                 // fall through
   1100             }
   1101             case THUMBNAILS: {
   1102                 qb.setTables(TABLE_THUMBNAILS);
   1103                 break;
   1104             }
   1105 
   1106             case OMNIBOX_SUGGESTIONS: {
   1107                 qb.setTables(VIEW_OMNIBOX_SUGGESTIONS);
   1108                 break;
   1109             }
   1110 
   1111             default: {
   1112                 throw new UnsupportedOperationException("Unknown URL " + uri.toString());
   1113             }
   1114         }
   1115 
   1116         Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy,
   1117                 null, sortOrder, limit);
   1118         cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
   1119         return cursor;
   1120     }
   1121 
   1122     private Cursor doSuggestQuery(String selection, String[] selectionArgs, String limit) {
   1123         if (TextUtils.isEmpty(selectionArgs[0])) {
   1124             selection = ZERO_QUERY_SUGGEST_SELECTION;
   1125             selectionArgs = null;
   1126         } else {
   1127             String like = selectionArgs[0] + "%";
   1128             if (selectionArgs[0].startsWith("http")
   1129                     || selectionArgs[0].startsWith("file")) {
   1130                 selectionArgs[0] = like;
   1131             } else {
   1132                 selectionArgs = new String[6];
   1133                 selectionArgs[0] = "http://" + like;
   1134                 selectionArgs[1] = "http://www." + like;
   1135                 selectionArgs[2] = "https://" + like;
   1136                 selectionArgs[3] = "https://www." + like;
   1137                 // To match against titles.
   1138                 selectionArgs[4] = like;
   1139                 selectionArgs[5] = like;
   1140                 selection = SUGGEST_SELECTION;
   1141             }
   1142             selection = DatabaseUtils.concatenateWhere(selection,
   1143                     Bookmarks.IS_DELETED + "=0 AND " + Bookmarks.IS_FOLDER + "=0");
   1144 
   1145         }
   1146         Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_BOOKMARKS_JOIN_HISTORY,
   1147                 SUGGEST_PROJECTION, selection, selectionArgs, null, null,
   1148                 SUGGEST_ORDER_BY, null);
   1149 
   1150         return new SuggestionsCursor(c);
   1151     }
   1152 
   1153     private String[] createCombinedQuery(
   1154             Uri uri, String[] projection, SQLiteQueryBuilder qb) {
   1155         String[] args = null;
   1156         StringBuilder whereBuilder = new StringBuilder(128);
   1157         whereBuilder.append(Bookmarks.IS_DELETED);
   1158         whereBuilder.append(" = 0");
   1159         // Look for account info
   1160         Object[] withAccount = getSelectionWithAccounts(uri, null, null);
   1161         String selection = (String) withAccount[0];
   1162         String[] selectionArgs = (String[]) withAccount[1];
   1163         if (selection != null) {
   1164             whereBuilder.append(" AND " + selection);
   1165             if (selectionArgs != null) {
   1166                 // We use the selection twice, hence we need to duplicate the args
   1167                 args = new String[selectionArgs.length * 2];
   1168                 System.arraycopy(selectionArgs, 0, args, 0, selectionArgs.length);
   1169                 System.arraycopy(selectionArgs, 0, args, selectionArgs.length,
   1170                         selectionArgs.length);
   1171             }
   1172         }
   1173         String where = whereBuilder.toString();
   1174         // Build the bookmark subquery for history union subquery
   1175         qb.setTables(TABLE_BOOKMARKS);
   1176         String subQuery = qb.buildQuery(null, where, null, null, null, null);
   1177         // Build the history union subquery
   1178         qb.setTables(String.format(FORMAT_COMBINED_JOIN_SUBQUERY_JOIN_IMAGES, subQuery));
   1179         qb.setProjectionMap(COMBINED_HISTORY_PROJECTION_MAP);
   1180         String historySubQuery = qb.buildQuery(null,
   1181                 null, null, null, null, null);
   1182         // Build the bookmark union subquery
   1183         qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
   1184         qb.setProjectionMap(COMBINED_BOOKMARK_PROJECTION_MAP);
   1185         where += String.format(" AND %s NOT IN (SELECT %s FROM %s)",
   1186                 Combined.URL, History.URL, TABLE_HISTORY);
   1187         String bookmarksSubQuery = qb.buildQuery(null, where,
   1188                 null, null, null, null);
   1189         // Put it all together
   1190         String query = qb.buildUnionQuery(
   1191                 new String[] {historySubQuery, bookmarksSubQuery},
   1192                 null, null);
   1193         qb.setTables("(" + query + ")");
   1194         qb.setProjectionMap(null);
   1195         return args;
   1196     }
   1197 
   1198     int deleteBookmarks(String selection, String[] selectionArgs,
   1199             boolean callerIsSyncAdapter) {
   1200         //TODO cascade deletes down from folders
   1201         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1202         if (callerIsSyncAdapter) {
   1203             return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
   1204         }
   1205         ContentValues values = new ContentValues();
   1206         values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
   1207         values.put(Bookmarks.IS_DELETED, 1);
   1208         return updateBookmarksInTransaction(values, selection, selectionArgs,
   1209                 callerIsSyncAdapter);
   1210     }
   1211 
   1212     @Override
   1213     public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
   1214             boolean callerIsSyncAdapter) {
   1215         final int match = URI_MATCHER.match(uri);
   1216         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1217         int deleted = 0;
   1218         switch (match) {
   1219             case BOOKMARKS_ID: {
   1220                 selection = DatabaseUtils.concatenateWhere(selection,
   1221                         TABLE_BOOKMARKS + "._id=?");
   1222                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1223                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1224                 // fall through
   1225             }
   1226             case BOOKMARKS: {
   1227                 // Look for account info
   1228                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
   1229                 selection = (String) withAccount[0];
   1230                 selectionArgs = (String[]) withAccount[1];
   1231                 deleted = deleteBookmarks(selection, selectionArgs, callerIsSyncAdapter);
   1232                 pruneImages();
   1233                 if (deleted > 0) {
   1234                     refreshWidgets();
   1235                 }
   1236                 break;
   1237             }
   1238 
   1239             case HISTORY_ID: {
   1240                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
   1241                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1242                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1243                 // fall through
   1244             }
   1245             case HISTORY: {
   1246                 filterSearchClient(selectionArgs);
   1247                 deleted = db.delete(TABLE_HISTORY, selection, selectionArgs);
   1248                 pruneImages();
   1249                 break;
   1250             }
   1251 
   1252             case SEARCHES_ID: {
   1253                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
   1254                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1255                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1256                 // fall through
   1257             }
   1258             case SEARCHES: {
   1259                 deleted = db.delete(TABLE_SEARCHES, selection, selectionArgs);
   1260                 break;
   1261             }
   1262 
   1263             case SYNCSTATE: {
   1264                 deleted = mSyncHelper.delete(db, selection, selectionArgs);
   1265                 break;
   1266             }
   1267             case SYNCSTATE_ID: {
   1268                 String selectionWithId =
   1269                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
   1270                         + (selection == null ? "" : " AND (" + selection + ")");
   1271                 deleted = mSyncHelper.delete(db, selectionWithId, selectionArgs);
   1272                 break;
   1273             }
   1274             case LEGACY_ID: {
   1275                 selection = DatabaseUtils.concatenateWhere(
   1276                         selection, Combined._ID + " = CAST(? AS INTEGER)");
   1277                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1278                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1279                 // fall through
   1280             }
   1281             case LEGACY: {
   1282                 String[] projection = new String[] { Combined._ID,
   1283                         Combined.IS_BOOKMARK, Combined.URL };
   1284                 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
   1285                 String[] args = createCombinedQuery(uri, projection, qb);
   1286                 if (selectionArgs == null) {
   1287                     selectionArgs = args;
   1288                 } else {
   1289                     selectionArgs = DatabaseUtils.appendSelectionArgs(
   1290                             args, selectionArgs);
   1291                 }
   1292                 Cursor c = qb.query(db, projection, selection, selectionArgs,
   1293                         null, null, null);
   1294                 while (c.moveToNext()) {
   1295                     long id = c.getLong(0);
   1296                     boolean isBookmark = c.getInt(1) != 0;
   1297                     String url = c.getString(2);
   1298                     if (isBookmark) {
   1299                         deleted += deleteBookmarks(Bookmarks._ID + "=?",
   1300                                 new String[] { Long.toString(id) },
   1301                                 callerIsSyncAdapter);
   1302                         db.delete(TABLE_HISTORY, History.URL + "=?",
   1303                                 new String[] { url });
   1304                     } else {
   1305                         deleted += db.delete(TABLE_HISTORY,
   1306                                 Bookmarks._ID + "=?",
   1307                                 new String[] { Long.toString(id) });
   1308                     }
   1309                 }
   1310                 c.close();
   1311                 break;
   1312             }
   1313             case THUMBNAILS_ID: {
   1314                 selection = DatabaseUtils.concatenateWhere(
   1315                         selection, Thumbnails._ID + " = ?");
   1316                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1317                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1318                 // fall through
   1319             }
   1320             case THUMBNAILS: {
   1321                 deleted = db.delete(TABLE_THUMBNAILS, selection, selectionArgs);
   1322                 break;
   1323             }
   1324             default: {
   1325                 throw new UnsupportedOperationException("Unknown delete URI " + uri);
   1326             }
   1327         }
   1328         if (deleted > 0) {
   1329             postNotifyUri(uri);
   1330             if (shouldNotifyLegacy(uri)) {
   1331                 postNotifyUri(LEGACY_AUTHORITY_URI);
   1332             }
   1333         }
   1334         return deleted;
   1335     }
   1336 
   1337     long queryDefaultFolderId(String accountName, String accountType) {
   1338         if (!isNullAccount(accountName) && !isNullAccount(accountType)) {
   1339             final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
   1340             Cursor c = db.query(TABLE_BOOKMARKS, new String[] { Bookmarks._ID },
   1341                     ChromeSyncColumns.SERVER_UNIQUE + " = ?" +
   1342                     " AND account_type = ? AND account_name = ?",
   1343                     new String[] { ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR,
   1344                     accountType, accountName }, null, null, null);
   1345             try {
   1346                 if (c.moveToFirst()) {
   1347                     return c.getLong(0);
   1348                 }
   1349             } finally {
   1350                 c.close();
   1351             }
   1352         }
   1353         return FIXED_ID_ROOT;
   1354     }
   1355 
   1356     @Override
   1357     public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
   1358         int match = URI_MATCHER.match(uri);
   1359         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1360         long id = -1;
   1361         if (match == LEGACY) {
   1362             // Intercept and route to the correct table
   1363             Integer bookmark = values.getAsInteger(BookmarkColumns.BOOKMARK);
   1364             values.remove(BookmarkColumns.BOOKMARK);
   1365             if (bookmark == null || bookmark == 0) {
   1366                 match = HISTORY;
   1367             } else {
   1368                 match = BOOKMARKS;
   1369                 values.remove(BookmarkColumns.DATE);
   1370                 values.remove(BookmarkColumns.VISITS);
   1371                 values.remove(BookmarkColumns.USER_ENTERED);
   1372                 values.put(Bookmarks.IS_FOLDER, 0);
   1373             }
   1374         }
   1375         switch (match) {
   1376             case BOOKMARKS: {
   1377                 // Mark rows dirty if they're not coming from a sync adapter
   1378                 if (!callerIsSyncAdapter) {
   1379                     long now = System.currentTimeMillis();
   1380                     values.put(Bookmarks.DATE_CREATED, now);
   1381                     values.put(Bookmarks.DATE_MODIFIED, now);
   1382                     values.put(Bookmarks.DIRTY, 1);
   1383 
   1384                     boolean hasAccounts = values.containsKey(Bookmarks.ACCOUNT_TYPE)
   1385                             || values.containsKey(Bookmarks.ACCOUNT_NAME);
   1386                     String accountType = values
   1387                             .getAsString(Bookmarks.ACCOUNT_TYPE);
   1388                     String accountName = values
   1389                             .getAsString(Bookmarks.ACCOUNT_NAME);
   1390                     boolean hasParent = values.containsKey(Bookmarks.PARENT);
   1391                     if (hasParent && hasAccounts) {
   1392                         // Let's make sure it's valid
   1393                         long parentId = values.getAsLong(Bookmarks.PARENT);
   1394                         hasParent = isValidParent(
   1395                                 accountType, accountName, parentId);
   1396                     } else if (hasParent && !hasAccounts) {
   1397                         long parentId = values.getAsLong(Bookmarks.PARENT);
   1398                         hasParent = setParentValues(parentId, values);
   1399                     }
   1400 
   1401                     // If no parent is set default to the "Bookmarks Bar" folder
   1402                     if (!hasParent) {
   1403                         values.put(Bookmarks.PARENT,
   1404                                 queryDefaultFolderId(accountName, accountType));
   1405                     }
   1406                 }
   1407 
   1408                 // If no position is requested put the bookmark at the beginning of the list
   1409                 if (!values.containsKey(Bookmarks.POSITION)) {
   1410                     values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
   1411                 }
   1412 
   1413                 // Extract out the image values so they can be inserted into the images table
   1414                 String url = values.getAsString(Bookmarks.URL);
   1415                 ContentValues imageValues = extractImageValues(values, url);
   1416                 Boolean isFolder = values.getAsBoolean(Bookmarks.IS_FOLDER);
   1417                 if ((isFolder == null || !isFolder)
   1418                         && imageValues != null && !TextUtils.isEmpty(url)) {
   1419                     int count = db.update(TABLE_IMAGES, imageValues, Images.URL + "=?",
   1420                             new String[] { url });
   1421                     if (count == 0) {
   1422                         db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
   1423                     }
   1424                 }
   1425 
   1426                 id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
   1427                 refreshWidgets();
   1428                 break;
   1429             }
   1430 
   1431             case HISTORY: {
   1432                 // If no created time is specified set it to now
   1433                 if (!values.containsKey(History.DATE_CREATED)) {
   1434                     values.put(History.DATE_CREATED, System.currentTimeMillis());
   1435                 }
   1436                 String url = values.getAsString(History.URL);
   1437                 url = filterSearchClient(url);
   1438                 values.put(History.URL, url);
   1439 
   1440                 // Extract out the image values so they can be inserted into the images table
   1441                 ContentValues imageValues = extractImageValues(values,
   1442                         values.getAsString(History.URL));
   1443                 if (imageValues != null) {
   1444                     db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
   1445                 }
   1446 
   1447                 id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
   1448                 break;
   1449             }
   1450 
   1451             case SEARCHES: {
   1452                 id = insertSearchesInTransaction(db, values);
   1453                 break;
   1454             }
   1455 
   1456             case SYNCSTATE: {
   1457                 id = mSyncHelper.insert(db, values);
   1458                 break;
   1459             }
   1460 
   1461             case SETTINGS: {
   1462                 id = 0;
   1463                 insertSettingsInTransaction(db, values);
   1464                 break;
   1465             }
   1466 
   1467             case THUMBNAILS: {
   1468                 id = db.replaceOrThrow(TABLE_THUMBNAILS, null, values);
   1469                 break;
   1470             }
   1471 
   1472             default: {
   1473                 throw new UnsupportedOperationException("Unknown insert URI " + uri);
   1474             }
   1475         }
   1476 
   1477         if (id >= 0) {
   1478             postNotifyUri(uri);
   1479             if (shouldNotifyLegacy(uri)) {
   1480                 postNotifyUri(LEGACY_AUTHORITY_URI);
   1481             }
   1482             return ContentUris.withAppendedId(uri, id);
   1483         } else {
   1484             return null;
   1485         }
   1486     }
   1487 
   1488     private String[] getAccountNameAndType(long id) {
   1489         if (id <= 0) {
   1490             return null;
   1491         }
   1492         Uri uri = ContentUris.withAppendedId(Bookmarks.CONTENT_URI, id);
   1493         Cursor c = query(uri,
   1494                 new String[] { Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_TYPE },
   1495                 null, null, null);
   1496         try {
   1497             if (c.moveToFirst()) {
   1498                 String parentName = c.getString(0);
   1499                 String parentType = c.getString(1);
   1500                 return new String[] { parentName, parentType };
   1501             }
   1502             return null;
   1503         } finally {
   1504             c.close();
   1505         }
   1506     }
   1507 
   1508     private boolean setParentValues(long parentId, ContentValues values) {
   1509         String[] parent = getAccountNameAndType(parentId);
   1510         if (parent == null) {
   1511             return false;
   1512         }
   1513         values.put(Bookmarks.ACCOUNT_NAME, parent[0]);
   1514         values.put(Bookmarks.ACCOUNT_TYPE, parent[1]);
   1515         return true;
   1516     }
   1517 
   1518     private boolean isValidParent(String accountType, String accountName,
   1519             long parentId) {
   1520         String[] parent = getAccountNameAndType(parentId);
   1521         if (parent != null
   1522                 && TextUtils.equals(accountName, parent[0])
   1523                 && TextUtils.equals(accountType, parent[1])) {
   1524             return true;
   1525         }
   1526         return false;
   1527     }
   1528 
   1529     private void filterSearchClient(String[] selectionArgs) {
   1530         if (selectionArgs != null) {
   1531             for (int i = 0; i < selectionArgs.length; i++) {
   1532                 selectionArgs[i] = filterSearchClient(selectionArgs[i]);
   1533             }
   1534         }
   1535     }
   1536 
   1537     // Filters out the client= param for search urls
   1538     private String filterSearchClient(String url) {
   1539         // remove "client" before updating it to the history so that it wont
   1540         // show up in the auto-complete list.
   1541         int index = url.indexOf("client=");
   1542         if (index > 0 && url.contains(".google.")) {
   1543             int end = url.indexOf('&', index);
   1544             if (end > 0) {
   1545                 url = url.substring(0, index)
   1546                         .concat(url.substring(end + 1));
   1547             } else {
   1548                 // the url.charAt(index-1) should be either '?' or '&'
   1549                 url = url.substring(0, index-1);
   1550             }
   1551         }
   1552         return url;
   1553     }
   1554 
   1555     /**
   1556      * Searches are unique, so perform an UPSERT manually since SQLite doesn't support them.
   1557      */
   1558     private long insertSearchesInTransaction(SQLiteDatabase db, ContentValues values) {
   1559         String search = values.getAsString(Searches.SEARCH);
   1560         if (TextUtils.isEmpty(search)) {
   1561             throw new IllegalArgumentException("Must include the SEARCH field");
   1562         }
   1563         Cursor cursor = null;
   1564         try {
   1565             cursor = db.query(TABLE_SEARCHES, new String[] { Searches._ID },
   1566                     Searches.SEARCH + "=?", new String[] { search }, null, null, null);
   1567             if (cursor.moveToNext()) {
   1568                 long id = cursor.getLong(0);
   1569                 db.update(TABLE_SEARCHES, values, Searches._ID + "=?",
   1570                         new String[] { Long.toString(id) });
   1571                 return id;
   1572             } else {
   1573                 return db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
   1574             }
   1575         } finally {
   1576             if (cursor != null) cursor.close();
   1577         }
   1578     }
   1579 
   1580     /**
   1581      * Settings are unique, so perform an UPSERT manually since SQLite doesn't support them.
   1582      */
   1583     private long insertSettingsInTransaction(SQLiteDatabase db, ContentValues values) {
   1584         String key = values.getAsString(Settings.KEY);
   1585         if (TextUtils.isEmpty(key)) {
   1586             throw new IllegalArgumentException("Must include the KEY field");
   1587         }
   1588         String[] keyArray = new String[] { key };
   1589         Cursor cursor = null;
   1590         try {
   1591             cursor = db.query(TABLE_SETTINGS, new String[] { Settings.KEY },
   1592                     Settings.KEY + "=?", keyArray, null, null, null);
   1593             if (cursor.moveToNext()) {
   1594                 long id = cursor.getLong(0);
   1595                 db.update(TABLE_SETTINGS, values, Settings.KEY + "=?", keyArray);
   1596                 return id;
   1597             } else {
   1598                 return db.insertOrThrow(TABLE_SETTINGS, Settings.VALUE, values);
   1599             }
   1600         } finally {
   1601             if (cursor != null) cursor.close();
   1602         }
   1603     }
   1604 
   1605     @Override
   1606     public int updateInTransaction(Uri uri, ContentValues values, String selection,
   1607             String[] selectionArgs, boolean callerIsSyncAdapter) {
   1608         int match = URI_MATCHER.match(uri);
   1609         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1610         if (match == LEGACY || match == LEGACY_ID) {
   1611             // Intercept and route to the correct table
   1612             Integer bookmark = values.getAsInteger(BookmarkColumns.BOOKMARK);
   1613             values.remove(BookmarkColumns.BOOKMARK);
   1614             if (bookmark == null || bookmark == 0) {
   1615                 if (match == LEGACY) {
   1616                     match = HISTORY;
   1617                 } else {
   1618                     match = HISTORY_ID;
   1619                 }
   1620             } else {
   1621                 if (match == LEGACY) {
   1622                     match = BOOKMARKS;
   1623                 } else {
   1624                     match = BOOKMARKS_ID;
   1625                 }
   1626                 values.remove(BookmarkColumns.DATE);
   1627                 values.remove(BookmarkColumns.VISITS);
   1628                 values.remove(BookmarkColumns.USER_ENTERED);
   1629             }
   1630         }
   1631         int modified = 0;
   1632         switch (match) {
   1633             case BOOKMARKS_ID: {
   1634                 selection = DatabaseUtils.concatenateWhere(selection,
   1635                         TABLE_BOOKMARKS + "._id=?");
   1636                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1637                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1638                 // fall through
   1639             }
   1640             case BOOKMARKS: {
   1641                 Object[] withAccount = getSelectionWithAccounts(uri, selection, selectionArgs);
   1642                 selection = (String) withAccount[0];
   1643                 selectionArgs = (String[]) withAccount[1];
   1644                 modified = updateBookmarksInTransaction(values, selection, selectionArgs,
   1645                         callerIsSyncAdapter);
   1646                 if (modified > 0) {
   1647                     refreshWidgets();
   1648                 }
   1649                 break;
   1650             }
   1651 
   1652             case HISTORY_ID: {
   1653                 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
   1654                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
   1655                         new String[] { Long.toString(ContentUris.parseId(uri)) });
   1656                 // fall through
   1657             }
   1658             case HISTORY: {
   1659                 modified = updateHistoryInTransaction(values, selection, selectionArgs);
   1660                 break;
   1661             }
   1662 
   1663             case SYNCSTATE: {
   1664                 modified = mSyncHelper.update(mDb, values,
   1665                         appendAccountToSelection(uri, selection), selectionArgs);
   1666                 break;
   1667             }
   1668 
   1669             case SYNCSTATE_ID: {
   1670                 selection = appendAccountToSelection(uri, selection);
   1671                 String selectionWithId =
   1672                         (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
   1673                         + (selection == null ? "" : " AND (" + selection + ")");
   1674                 modified = mSyncHelper.update(mDb, values,
   1675                         selectionWithId, selectionArgs);
   1676                 break;
   1677             }
   1678 
   1679             case IMAGES: {
   1680                 String url = values.getAsString(Images.URL);
   1681                 if (TextUtils.isEmpty(url)) {
   1682                     throw new IllegalArgumentException("Images.URL is required");
   1683                 }
   1684                 if (!shouldUpdateImages(db, url, values)) {
   1685                     return 0;
   1686                 }
   1687                 int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
   1688                         new String[] { url });
   1689                 if (count == 0) {
   1690                     db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
   1691                     count = 1;
   1692                 }
   1693                 // Only favicon is exposed in the public API. If we updated
   1694                 // the thumbnail or touch icon don't bother notifying the
   1695                 // legacy authority since it can't read it anyway.
   1696                 boolean updatedLegacy = false;
   1697                 if (getUrlCount(db, TABLE_BOOKMARKS, url) > 0) {
   1698                     postNotifyUri(Bookmarks.CONTENT_URI);
   1699                     updatedLegacy = values.containsKey(Images.FAVICON);
   1700                     refreshWidgets();
   1701                 }
   1702                 if (getUrlCount(db, TABLE_HISTORY, url) > 0) {
   1703                     postNotifyUri(History.CONTENT_URI);
   1704                     updatedLegacy = values.containsKey(Images.FAVICON);
   1705                 }
   1706                 if (pruneImages() > 0 || updatedLegacy) {
   1707                     postNotifyUri(LEGACY_AUTHORITY_URI);
   1708                 }
   1709                 // Even though we may be calling notifyUri on Bookmarks, don't
   1710                 // sync to network as images aren't synced. Otherwise this
   1711                 // unnecessarily triggers a bookmark sync.
   1712                 mSyncToNetwork = false;
   1713                 return count;
   1714             }
   1715 
   1716             case SEARCHES: {
   1717                 modified = db.update(TABLE_SEARCHES, values, selection, selectionArgs);
   1718                 break;
   1719             }
   1720 
   1721             case ACCOUNTS: {
   1722                 Account[] accounts = AccountManager.get(getContext()).getAccounts();
   1723                 mSyncHelper.onAccountsChanged(mDb, accounts);
   1724                 break;
   1725             }
   1726 
   1727             case THUMBNAILS: {
   1728                 modified = db.update(TABLE_THUMBNAILS, values,
   1729                         selection, selectionArgs);
   1730                 break;
   1731             }
   1732 
   1733             default: {
   1734                 throw new UnsupportedOperationException("Unknown update URI " + uri);
   1735             }
   1736         }
   1737         pruneImages();
   1738         if (modified > 0) {
   1739             postNotifyUri(uri);
   1740             if (shouldNotifyLegacy(uri)) {
   1741                 postNotifyUri(LEGACY_AUTHORITY_URI);
   1742             }
   1743         }
   1744         return modified;
   1745     }
   1746 
   1747     // We want to avoid sending out more URI notifications than we have to
   1748     // Thus, we check to see if the images we are about to store are already there
   1749     // This is used because things like a site's favion or touch icon is rarely
   1750     // changed, but the browser tries to update it every time the page loads.
   1751     // Without this, we will always send out 3 URI notifications per page load.
   1752     // With this, that drops to 0 or 1, depending on if the thumbnail changed.
   1753     private boolean shouldUpdateImages(
   1754             SQLiteDatabase db, String url, ContentValues values) {
   1755         final String[] projection = new String[] {
   1756                 Images.FAVICON,
   1757                 Images.THUMBNAIL,
   1758                 Images.TOUCH_ICON,
   1759         };
   1760         Cursor cursor = db.query(TABLE_IMAGES, projection, Images.URL + "=?",
   1761                 new String[] { url }, null, null, null);
   1762         byte[] nfavicon = values.getAsByteArray(Images.FAVICON);
   1763         byte[] nthumb = values.getAsByteArray(Images.THUMBNAIL);
   1764         byte[] ntouch = values.getAsByteArray(Images.TOUCH_ICON);
   1765         byte[] cfavicon = null;
   1766         byte[] cthumb = null;
   1767         byte[] ctouch = null;
   1768         try {
   1769             if (cursor.getCount() <= 0) {
   1770                 return nfavicon != null || nthumb != null || ntouch != null;
   1771             }
   1772             while (cursor.moveToNext()) {
   1773                 if (nfavicon != null) {
   1774                     cfavicon = cursor.getBlob(0);
   1775                     if (!Arrays.equals(nfavicon, cfavicon)) {
   1776                         return true;
   1777                     }
   1778                 }
   1779                 if (nthumb != null) {
   1780                     cthumb = cursor.getBlob(1);
   1781                     if (!Arrays.equals(nthumb, cthumb)) {
   1782                         return true;
   1783                     }
   1784                 }
   1785                 if (ntouch != null) {
   1786                     ctouch = cursor.getBlob(2);
   1787                     if (!Arrays.equals(ntouch, ctouch)) {
   1788                         return true;
   1789                     }
   1790                 }
   1791             }
   1792         } finally {
   1793             cursor.close();
   1794         }
   1795         return false;
   1796     }
   1797 
   1798     int getUrlCount(SQLiteDatabase db, String table, String url) {
   1799         Cursor c = db.query(table, new String[] { "COUNT(*)" },
   1800                 "url = ?", new String[] { url }, null, null, null);
   1801         try {
   1802             int count = 0;
   1803             if (c.moveToFirst()) {
   1804                 count = c.getInt(0);
   1805             }
   1806             return count;
   1807         } finally {
   1808             c.close();
   1809         }
   1810     }
   1811 
   1812     /**
   1813      * Does a query to find the matching bookmarks and updates each one with the provided values.
   1814      */
   1815     int updateBookmarksInTransaction(ContentValues values, String selection,
   1816             String[] selectionArgs, boolean callerIsSyncAdapter) {
   1817         int count = 0;
   1818         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1819         final String[] bookmarksProjection = new String[] {
   1820                 Bookmarks._ID, // 0
   1821                 Bookmarks.VERSION, // 1
   1822                 Bookmarks.URL, // 2
   1823                 Bookmarks.TITLE, // 3
   1824                 Bookmarks.IS_FOLDER, // 4
   1825                 Bookmarks.ACCOUNT_NAME, // 5
   1826                 Bookmarks.ACCOUNT_TYPE, // 6
   1827         };
   1828         Cursor cursor = db.query(TABLE_BOOKMARKS, bookmarksProjection,
   1829                 selection, selectionArgs, null, null, null);
   1830         boolean updatingParent = values.containsKey(Bookmarks.PARENT);
   1831         String parentAccountName = null;
   1832         String parentAccountType = null;
   1833         if (updatingParent) {
   1834             long parent = values.getAsLong(Bookmarks.PARENT);
   1835             Cursor c = db.query(TABLE_BOOKMARKS, new String[] {
   1836                     Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_TYPE},
   1837                     "_id = ?", new String[] { Long.toString(parent) },
   1838                     null, null, null);
   1839             if (c.moveToFirst()) {
   1840                 parentAccountName = c.getString(0);
   1841                 parentAccountType = c.getString(1);
   1842             }
   1843             c.close();
   1844         } else if (values.containsKey(Bookmarks.ACCOUNT_NAME)
   1845                 || values.containsKey(Bookmarks.ACCOUNT_TYPE)) {
   1846             // TODO: Implement if needed (no one needs this yet)
   1847         }
   1848         try {
   1849             String[] args = new String[1];
   1850             // Mark the bookmark dirty if the caller isn't a sync adapter
   1851             if (!callerIsSyncAdapter) {
   1852                 values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
   1853                 values.put(Bookmarks.DIRTY, 1);
   1854             }
   1855 
   1856             boolean updatingUrl = values.containsKey(Bookmarks.URL);
   1857             String url = null;
   1858             if (updatingUrl) {
   1859                 url = values.getAsString(Bookmarks.URL);
   1860             }
   1861             ContentValues imageValues = extractImageValues(values, url);
   1862 
   1863             while (cursor.moveToNext()) {
   1864                 long id = cursor.getLong(0);
   1865                 args[0] = Long.toString(id);
   1866                 String accountName = cursor.getString(5);
   1867                 String accountType = cursor.getString(6);
   1868                 // If we are updating the parent and either the account name or
   1869                 // type do not match that of the new parent
   1870                 if (updatingParent
   1871                         && (!TextUtils.equals(accountName, parentAccountName)
   1872                         || !TextUtils.equals(accountType, parentAccountType))) {
   1873                     // Parent is a different account
   1874                     // First, insert a new bookmark/folder with the new account
   1875                     // Then, if this is a folder, reparent all it's children
   1876                     // Finally, delete the old bookmark/folder
   1877                     ContentValues newValues = valuesFromCursor(cursor);
   1878                     newValues.putAll(values);
   1879                     newValues.remove(Bookmarks._ID);
   1880                     newValues.remove(Bookmarks.VERSION);
   1881                     newValues.put(Bookmarks.ACCOUNT_NAME, parentAccountName);
   1882                     newValues.put(Bookmarks.ACCOUNT_TYPE, parentAccountType);
   1883                     Uri insertUri = insertInTransaction(Bookmarks.CONTENT_URI,
   1884                             newValues, callerIsSyncAdapter);
   1885                     long newId = ContentUris.parseId(insertUri);
   1886                     if (cursor.getInt(4) != 0) {
   1887                         // This is a folder, reparent
   1888                         ContentValues updateChildren = new ContentValues(1);
   1889                         updateChildren.put(Bookmarks.PARENT, newId);
   1890                         count += updateBookmarksInTransaction(updateChildren,
   1891                                 Bookmarks.PARENT + "=?", new String[] {
   1892                                 Long.toString(id)}, callerIsSyncAdapter);
   1893                     }
   1894                     // Now, delete the old one
   1895                     Uri uri = ContentUris.withAppendedId(Bookmarks.CONTENT_URI, id);
   1896                     deleteInTransaction(uri, null, null, callerIsSyncAdapter);
   1897                     count += 1;
   1898                 } else {
   1899                     if (!callerIsSyncAdapter) {
   1900                         // increase the local version for non-sync changes
   1901                         values.put(Bookmarks.VERSION, cursor.getLong(1) + 1);
   1902                     }
   1903                     count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
   1904                 }
   1905 
   1906                 // Update the images over in their table
   1907                 if (imageValues != null) {
   1908                     if (!updatingUrl) {
   1909                         url = cursor.getString(2);
   1910                         imageValues.put(Images.URL, url);
   1911                     }
   1912 
   1913                     if (!TextUtils.isEmpty(url)) {
   1914                         args[0] = url;
   1915                         if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
   1916                             db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
   1917                         }
   1918                     }
   1919                 }
   1920             }
   1921         } finally {
   1922             if (cursor != null) cursor.close();
   1923         }
   1924         return count;
   1925     }
   1926 
   1927     ContentValues valuesFromCursor(Cursor c) {
   1928         int count = c.getColumnCount();
   1929         ContentValues values = new ContentValues(count);
   1930         String[] colNames = c.getColumnNames();
   1931         for (int i = 0; i < count; i++) {
   1932             switch (c.getType(i)) {
   1933             case Cursor.FIELD_TYPE_BLOB:
   1934                 values.put(colNames[i], c.getBlob(i));
   1935                 break;
   1936             case Cursor.FIELD_TYPE_FLOAT:
   1937                 values.put(colNames[i], c.getFloat(i));
   1938                 break;
   1939             case Cursor.FIELD_TYPE_INTEGER:
   1940                 values.put(colNames[i], c.getLong(i));
   1941                 break;
   1942             case Cursor.FIELD_TYPE_STRING:
   1943                 values.put(colNames[i], c.getString(i));
   1944                 break;
   1945             }
   1946         }
   1947         return values;
   1948     }
   1949 
   1950     /**
   1951      * Does a query to find the matching bookmarks and updates each one with the provided values.
   1952      */
   1953     int updateHistoryInTransaction(ContentValues values, String selection, String[] selectionArgs) {
   1954         int count = 0;
   1955         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   1956         filterSearchClient(selectionArgs);
   1957         Cursor cursor = query(History.CONTENT_URI,
   1958                 new String[] { History._ID, History.URL },
   1959                 selection, selectionArgs, null);
   1960         try {
   1961             String[] args = new String[1];
   1962 
   1963             boolean updatingUrl = values.containsKey(History.URL);
   1964             String url = null;
   1965             if (updatingUrl) {
   1966                 url = filterSearchClient(values.getAsString(History.URL));
   1967                 values.put(History.URL, url);
   1968             }
   1969             ContentValues imageValues = extractImageValues(values, url);
   1970 
   1971             while (cursor.moveToNext()) {
   1972                 args[0] = cursor.getString(0);
   1973                 count += db.update(TABLE_HISTORY, values, "_id=?", args);
   1974 
   1975                 // Update the images over in their table
   1976                 if (imageValues != null) {
   1977                     if (!updatingUrl) {
   1978                         url = cursor.getString(1);
   1979                         imageValues.put(Images.URL, url);
   1980                     }
   1981                     args[0] = url;
   1982                     if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
   1983                         db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
   1984                     }
   1985                 }
   1986             }
   1987         } finally {
   1988             if (cursor != null) cursor.close();
   1989         }
   1990         return count;
   1991     }
   1992 
   1993     String appendAccountToSelection(Uri uri, String selection) {
   1994         final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
   1995         final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE);
   1996 
   1997         final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
   1998         if (partialUri) {
   1999             // Throw when either account is incomplete
   2000             throw new IllegalArgumentException(
   2001                     "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri);
   2002         }
   2003 
   2004         // Accounts are valid by only checking one parameter, since we've
   2005         // already ruled out partial accounts.
   2006         final boolean validAccount = !TextUtils.isEmpty(accountName);
   2007         if (validAccount) {
   2008             StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "="
   2009                     + DatabaseUtils.sqlEscapeString(accountName) + " AND "
   2010                     + RawContacts.ACCOUNT_TYPE + "="
   2011                     + DatabaseUtils.sqlEscapeString(accountType));
   2012             if (!TextUtils.isEmpty(selection)) {
   2013                 selectionSb.append(" AND (");
   2014                 selectionSb.append(selection);
   2015                 selectionSb.append(')');
   2016             }
   2017             return selectionSb.toString();
   2018         } else {
   2019             return selection;
   2020         }
   2021     }
   2022 
   2023     ContentValues extractImageValues(ContentValues values, String url) {
   2024         ContentValues imageValues = null;
   2025         // favicon
   2026         if (values.containsKey(Bookmarks.FAVICON)) {
   2027             imageValues = new ContentValues();
   2028             imageValues.put(Images.FAVICON, values.getAsByteArray(Bookmarks.FAVICON));
   2029             values.remove(Bookmarks.FAVICON);
   2030         }
   2031 
   2032         // thumbnail
   2033         if (values.containsKey(Bookmarks.THUMBNAIL)) {
   2034             if (imageValues == null) {
   2035                 imageValues = new ContentValues();
   2036             }
   2037             imageValues.put(Images.THUMBNAIL, values.getAsByteArray(Bookmarks.THUMBNAIL));
   2038             values.remove(Bookmarks.THUMBNAIL);
   2039         }
   2040 
   2041         // touch icon
   2042         if (values.containsKey(Bookmarks.TOUCH_ICON)) {
   2043             if (imageValues == null) {
   2044                 imageValues = new ContentValues();
   2045             }
   2046             imageValues.put(Images.TOUCH_ICON, values.getAsByteArray(Bookmarks.TOUCH_ICON));
   2047             values.remove(Bookmarks.TOUCH_ICON);
   2048         }
   2049 
   2050         if (imageValues != null) {
   2051             imageValues.put(Images.URL,  url);
   2052         }
   2053         return imageValues;
   2054     }
   2055 
   2056     int pruneImages() {
   2057         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   2058         return db.delete(TABLE_IMAGES, IMAGE_PRUNE, null);
   2059     }
   2060 
   2061     boolean shouldNotifyLegacy(Uri uri) {
   2062         if (uri.getPathSegments().contains("history")
   2063                 || uri.getPathSegments().contains("bookmarks")
   2064                 || uri.getPathSegments().contains("searches")) {
   2065             return true;
   2066         }
   2067         return false;
   2068     }
   2069 
   2070     @Override
   2071     protected boolean syncToNetwork(Uri uri) {
   2072         if (BrowserContract.AUTHORITY.equals(uri.getAuthority())
   2073                 && uri.getPathSegments().contains("bookmarks")) {
   2074             return mSyncToNetwork;
   2075         }
   2076         if (LEGACY_AUTHORITY.equals(uri.getAuthority())) {
   2077             // Allow for 3rd party sync adapters
   2078             return true;
   2079         }
   2080         return false;
   2081     }
   2082 
   2083     static class SuggestionsCursor extends AbstractCursor {
   2084         private static final int ID_INDEX = 0;
   2085         private static final int URL_INDEX = 1;
   2086         private static final int TITLE_INDEX = 2;
   2087         private static final int ICON_INDEX = 3;
   2088         private static final int LAST_ACCESS_TIME_INDEX = 4;
   2089         // shared suggestion array index, make sure to match COLUMNS
   2090         private static final int SUGGEST_COLUMN_INTENT_ACTION_ID = 1;
   2091         private static final int SUGGEST_COLUMN_INTENT_DATA_ID = 2;
   2092         private static final int SUGGEST_COLUMN_TEXT_1_ID = 3;
   2093         private static final int SUGGEST_COLUMN_TEXT_2_TEXT_ID = 4;
   2094         private static final int SUGGEST_COLUMN_TEXT_2_URL_ID = 5;
   2095         private static final int SUGGEST_COLUMN_ICON_1_ID = 6;
   2096         private static final int SUGGEST_COLUMN_LAST_ACCESS_HINT_ID = 7;
   2097 
   2098         // shared suggestion columns
   2099         private static final String[] COLUMNS = new String[] {
   2100                 BaseColumns._ID,
   2101                 SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
   2102                 SearchManager.SUGGEST_COLUMN_INTENT_DATA,
   2103                 SearchManager.SUGGEST_COLUMN_TEXT_1,
   2104                 SearchManager.SUGGEST_COLUMN_TEXT_2,
   2105                 SearchManager.SUGGEST_COLUMN_TEXT_2_URL,
   2106                 SearchManager.SUGGEST_COLUMN_ICON_1,
   2107                 SearchManager.SUGGEST_COLUMN_LAST_ACCESS_HINT};
   2108 
   2109         private final Cursor mSource;
   2110 
   2111         public SuggestionsCursor(Cursor cursor) {
   2112             mSource = cursor;
   2113         }
   2114 
   2115         @Override
   2116         public String[] getColumnNames() {
   2117             return COLUMNS;
   2118         }
   2119 
   2120         @Override
   2121         public String getString(int columnIndex) {
   2122             switch (columnIndex) {
   2123             case ID_INDEX:
   2124                 return mSource.getString(columnIndex);
   2125             case SUGGEST_COLUMN_INTENT_ACTION_ID:
   2126                 return Intent.ACTION_VIEW;
   2127             case SUGGEST_COLUMN_INTENT_DATA_ID:
   2128                 return mSource.getString(URL_INDEX);
   2129             case SUGGEST_COLUMN_TEXT_2_TEXT_ID:
   2130             case SUGGEST_COLUMN_TEXT_2_URL_ID:
   2131                 return UrlUtils.stripUrl(mSource.getString(URL_INDEX));
   2132             case SUGGEST_COLUMN_TEXT_1_ID:
   2133                 return mSource.getString(TITLE_INDEX);
   2134             case SUGGEST_COLUMN_ICON_1_ID:
   2135                 return mSource.getString(ICON_INDEX);
   2136             case SUGGEST_COLUMN_LAST_ACCESS_HINT_ID:
   2137                 return mSource.getString(LAST_ACCESS_TIME_INDEX);
   2138             }
   2139             return null;
   2140         }
   2141 
   2142         @Override
   2143         public int getCount() {
   2144             return mSource.getCount();
   2145         }
   2146 
   2147         @Override
   2148         public double getDouble(int column) {
   2149             throw new UnsupportedOperationException();
   2150         }
   2151 
   2152         @Override
   2153         public float getFloat(int column) {
   2154             throw new UnsupportedOperationException();
   2155         }
   2156 
   2157         @Override
   2158         public int getInt(int column) {
   2159             throw new UnsupportedOperationException();
   2160         }
   2161 
   2162         @Override
   2163         public long getLong(int column) {
   2164             switch (column) {
   2165             case ID_INDEX:
   2166                 return mSource.getLong(ID_INDEX);
   2167             case SUGGEST_COLUMN_LAST_ACCESS_HINT_ID:
   2168                 return mSource.getLong(LAST_ACCESS_TIME_INDEX);
   2169             }
   2170             throw new UnsupportedOperationException();
   2171         }
   2172 
   2173         @Override
   2174         public short getShort(int column) {
   2175             throw new UnsupportedOperationException();
   2176         }
   2177 
   2178         @Override
   2179         public boolean isNull(int column) {
   2180             return mSource.isNull(column);
   2181         }
   2182 
   2183         @Override
   2184         public boolean onMove(int oldPosition, int newPosition) {
   2185             return mSource.moveToPosition(newPosition);
   2186         }
   2187     }
   2188 
   2189     // ---------------------------------------------------
   2190     //  SQL below, be warned
   2191     // ---------------------------------------------------
   2192 
   2193     private static final String SQL_CREATE_VIEW_OMNIBOX_SUGGESTIONS =
   2194             "CREATE VIEW IF NOT EXISTS v_omnibox_suggestions "
   2195             + " AS "
   2196             + "  SELECT _id, url, title, 1 AS bookmark, 0 AS visits, 0 AS date"
   2197             + "  FROM bookmarks "
   2198             + "  WHERE deleted = 0 AND folder = 0 "
   2199             + "  UNION ALL "
   2200             + "  SELECT _id, url, title, 0 AS bookmark, visits, date "
   2201             + "  FROM history "
   2202             + "  WHERE url NOT IN (SELECT url FROM bookmarks"
   2203             + "    WHERE deleted = 0 AND folder = 0) "
   2204             + "  ORDER BY bookmark DESC, visits DESC, date DESC ";
   2205 
   2206     private static final String SQL_WHERE_ACCOUNT_HAS_BOOKMARKS =
   2207             "0 < ( "
   2208             + "SELECT count(*) "
   2209             + "FROM bookmarks "
   2210             + "WHERE deleted = 0 AND folder = 0 "
   2211             + "  AND ( "
   2212             + "    v_accounts.account_name = bookmarks.account_name "
   2213             + "    OR (v_accounts.account_name IS NULL AND bookmarks.account_name IS NULL) "
   2214             + "  ) "
   2215             + "  AND ( "
   2216             + "    v_accounts.account_type = bookmarks.account_type "
   2217             + "    OR (v_accounts.account_type IS NULL AND bookmarks.account_type IS NULL) "
   2218             + "  ) "
   2219             + ")";
   2220 }
   2221