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