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