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