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