BrowserProvider2.java revision 6e073f306f7da0f985646e460fd47f47187ab144
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 com.android.browser.R; 20import com.android.internal.content.SyncStateContentProviderHelper; 21 22import android.content.ContentResolver; 23import android.content.ContentUris; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.UriMatcher; 27import android.database.Cursor; 28import android.database.DatabaseUtils; 29import android.database.sqlite.SQLiteDatabase; 30import android.database.sqlite.SQLiteOpenHelper; 31import android.database.sqlite.SQLiteQueryBuilder; 32import android.net.Uri; 33import android.provider.BrowserContract; 34import android.provider.BrowserContract.Accounts; 35import android.provider.BrowserContract.Bookmarks; 36import android.provider.BrowserContract.ChromeSyncColumns; 37import android.provider.BrowserContract.Combined; 38import android.provider.BrowserContract.History; 39import android.provider.BrowserContract.Images; 40import android.provider.BrowserContract.Searches; 41import android.provider.BrowserContract.SyncState; 42import android.provider.ContactsContract.RawContacts; 43import android.provider.SyncStateContract; 44import android.text.TextUtils; 45 46import java.util.HashMap; 47 48public class BrowserProvider2 extends SQLiteContentProvider { 49 50 static final String LEGACY_AUTHORITY = "browser"; 51 static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder().authority(LEGACY_AUTHORITY).build(); 52 53 static final String TABLE_BOOKMARKS = "bookmarks"; 54 static final String TABLE_HISTORY = "history"; 55 static final String TABLE_IMAGES = "images"; 56 static final String TABLE_SEARCHES = "searches"; 57 static final String TABLE_SYNC_STATE = "syncstate"; 58 static final String VIEW_COMBINED = "combined"; 59 60 static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " + 61 "ON bookmarks.url = images." + Images.URL; 62 static final String TABLE_HISTORY_JOIN_IMAGES = "history LEFT OUTER JOIN images " + 63 "ON history.url = images." + Images.URL; 64 65 static final String DEFAULT_SORT_HISTORY = History.DATE_LAST_VISITED + " DESC"; 66 67 static final int BOOKMARKS = 1000; 68 static final int BOOKMARKS_ID = 1001; 69 static final int BOOKMARKS_FOLDER = 1002; 70 static final int BOOKMARKS_FOLDER_ID = 1003; 71 72 static final int HISTORY = 2000; 73 static final int HISTORY_ID = 2001; 74 75 static final int SEARCHES = 3000; 76 static final int SEARCHES_ID = 3001; 77 78 static final int SYNCSTATE = 4000; 79 static final int SYNCSTATE_ID = 4001; 80 81 static final int IMAGES = 5000; 82 83 static final int COMBINED = 6000; 84 static final int COMBINED_ID = 6001; 85 86 static final int ACCOUNTS = 7000; 87 88 static final long FIXED_ID_CHROME_ROOT = 1; 89 static final long FIXED_ID_BOOKMARKS = 2; 90 static final long FIXED_ID_BOOKMARKS_BAR = 3; 91 static final long FIXED_ID_OTHER_BOOKMARKS = 4; 92 93 static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC"; 94 95 static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 96 97 static final HashMap<String, String> ACCOUNTS_PROJECTION_MAP = new HashMap<String, String>(); 98 static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>(); 99 static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP = 100 new HashMap<String, String>(); 101 static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>(); 102 static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>(); 103 static final HashMap<String, String> IMAGES_PROJECTION_MAP = new HashMap<String, String>(); 104 static final HashMap<String, String> COMBINED_PROJECTION_MAP = new HashMap<String, String>(); 105 106 static { 107 final UriMatcher matcher = URI_MATCHER; 108 final String authority = BrowserContract.AUTHORITY; 109 matcher.addURI(authority, "accounts", ACCOUNTS); 110 matcher.addURI(authority, "bookmarks", BOOKMARKS); 111 matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID); 112 matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER); 113 matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID); 114 matcher.addURI(authority, "history", HISTORY); 115 matcher.addURI(authority, "history/#", HISTORY_ID); 116 matcher.addURI(authority, "searches", SEARCHES); 117 matcher.addURI(authority, "searches/#", SEARCHES_ID); 118 matcher.addURI(authority, "syncstate", SYNCSTATE); 119 matcher.addURI(authority, "syncstate/#", SYNCSTATE_ID); 120 matcher.addURI(authority, "images", IMAGES); 121 matcher.addURI(authority, "combined", COMBINED); 122 matcher.addURI(authority, "combined/#", COMBINED_ID); 123 124 // Projection maps 125 HashMap<String, String> map; 126 127 // Accounts 128 map = ACCOUNTS_PROJECTION_MAP; 129 map.put(Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_TYPE); 130 map.put(Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_NAME); 131 132 // Bookmarks 133 map = BOOKMARKS_PROJECTION_MAP; 134 map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID)); 135 map.put(Bookmarks.TITLE, Bookmarks.TITLE); 136 map.put(Bookmarks.URL, Bookmarks.URL); 137 map.put(Bookmarks.FAVICON, Bookmarks.FAVICON); 138 map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL); 139 map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON); 140 map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER); 141 map.put(Bookmarks.PARENT, Bookmarks.PARENT); 142 map.put(Bookmarks.POSITION, Bookmarks.POSITION); 143 map.put(Bookmarks.INSERT_AFTER, Bookmarks.INSERT_AFTER); 144 map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED); 145 map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME); 146 map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE); 147 map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID); 148 map.put(Bookmarks.VERSION, Bookmarks.VERSION); 149 map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED); 150 map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED); 151 map.put(Bookmarks.DIRTY, Bookmarks.DIRTY); 152 map.put(Bookmarks.SYNC1, Bookmarks.SYNC1); 153 map.put(Bookmarks.SYNC2, Bookmarks.SYNC2); 154 map.put(Bookmarks.SYNC3, Bookmarks.SYNC3); 155 map.put(Bookmarks.SYNC4, Bookmarks.SYNC4); 156 map.put(Bookmarks.SYNC5, Bookmarks.SYNC5); 157 map.put(Bookmarks.PARENT_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID + 158 " FROM " + TABLE_BOOKMARKS + " A WHERE " + 159 "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.PARENT + 160 ") AS " + Bookmarks.PARENT_SOURCE_ID); 161 map.put(Bookmarks.INSERT_AFTER_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID + 162 " FROM " + TABLE_BOOKMARKS + " A WHERE " + 163 "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.INSERT_AFTER + 164 ") AS " + Bookmarks.INSERT_AFTER_SOURCE_ID); 165 166 // Other bookmarks 167 OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP); 168 OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION, 169 Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION); 170 171 // History 172 map = HISTORY_PROJECTION_MAP; 173 map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID)); 174 map.put(History.TITLE, History.TITLE); 175 map.put(History.URL, History.URL); 176 map.put(History.FAVICON, History.FAVICON); 177 map.put(History.THUMBNAIL, History.THUMBNAIL); 178 map.put(History.TOUCH_ICON, History.TOUCH_ICON); 179 map.put(History.DATE_CREATED, History.DATE_CREATED); 180 map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED); 181 map.put(History.VISITS, History.VISITS); 182 map.put(History.USER_ENTERED, History.USER_ENTERED); 183 184 // Sync state 185 map = SYNC_STATE_PROJECTION_MAP; 186 map.put(SyncState._ID, SyncState._ID); 187 map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME); 188 map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE); 189 map.put(SyncState.DATA, SyncState.DATA); 190 191 // Images 192 map = IMAGES_PROJECTION_MAP; 193 map.put(Images.URL, Images.URL); 194 map.put(Images.FAVICON, Images.FAVICON); 195 map.put(Images.THUMBNAIL, Images.THUMBNAIL); 196 map.put(Images.TOUCH_ICON, Images.TOUCH_ICON); 197 198 // Combined history half 199 map = COMBINED_PROJECTION_MAP; 200 map.put(Combined._ID, Combined._ID); 201 map.put(Combined.TITLE, Combined.TITLE); 202 map.put(Combined.URL, Combined.URL); 203 map.put(Combined.DATE_CREATED, Combined.DATE_CREATED); 204 map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED); 205 map.put(Combined.IS_BOOKMARK, Combined.IS_BOOKMARK); 206 map.put(Combined.VISITS, Combined.VISITS); 207 map.put(Combined.FAVICON, Combined.FAVICON); 208 map.put(Combined.THUMBNAIL, Combined.THUMBNAIL); 209 map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON); 210 map.put(Combined.USER_ENTERED, Combined.USER_ENTERED); 211 } 212 213 static final String bookmarkOrHistoryColumn(String column) { 214 return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN " + 215 "bookmarks." + column + " ELSE history." + column + " END AS " + column; 216 } 217 218 static final String qualifyColumn(String table, String column) { 219 return table + "." + column + " AS " + column; 220 } 221 222 DatabaseHelper mOpenHelper; 223 SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper(); 224 225 final class DatabaseHelper extends SQLiteOpenHelper { 226 static final String DATABASE_NAME = "browser2.db"; 227 static final int DATABASE_VERSION = 22; 228 public DatabaseHelper(Context context) { 229 super(context, DATABASE_NAME, null, DATABASE_VERSION); 230 } 231 232 @Override 233 public void onCreate(SQLiteDatabase db) { 234 db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" + 235 Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 236 Bookmarks.TITLE + " TEXT," + 237 Bookmarks.URL + " TEXT," + 238 Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," + 239 Bookmarks.PARENT + " INTEGER," + 240 Bookmarks.POSITION + " INTEGER NOT NULL," + 241 Bookmarks.INSERT_AFTER + " INTEGER," + 242 Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," + 243 Bookmarks.ACCOUNT_NAME + " TEXT," + 244 Bookmarks.ACCOUNT_TYPE + " TEXT," + 245 Bookmarks.SOURCE_ID + " TEXT," + 246 Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," + 247 Bookmarks.DATE_CREATED + " INTEGER," + 248 Bookmarks.DATE_MODIFIED + " INTEGER," + 249 Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," + 250 Bookmarks.SYNC1 + " TEXT," + 251 Bookmarks.SYNC2 + " TEXT," + 252 Bookmarks.SYNC3 + " TEXT," + 253 Bookmarks.SYNC4 + " TEXT," + 254 Bookmarks.SYNC5 + " TEXT" + 255 ");"); 256 257 // TODO indices 258 259 createDefaultBookmarks(db); 260 261 db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" + 262 History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 263 History.TITLE + " TEXT," + 264 History.URL + " TEXT NOT NULL," + 265 History.DATE_CREATED + " INTEGER," + 266 History.DATE_LAST_VISITED + " INTEGER," + 267 History.VISITS + " INTEGER NOT NULL DEFAULT 0," + 268 History.USER_ENTERED + " INTEGER" + 269 ");"); 270 271 db.execSQL("CREATE TABLE " + TABLE_IMAGES + " (" + 272 Images.URL + " TEXT UNIQUE NOT NULL," + 273 Images.FAVICON + " BLOB," + 274 Images.THUMBNAIL + " BLOB," + 275 Images.TOUCH_ICON + " BLOB" + 276 ");"); 277 db.execSQL("CREATE INDEX imagesUrlIndex ON " + TABLE_IMAGES + 278 "(" + Images.URL + ")"); 279 280 db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" + 281 Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 282 Searches.SEARCH + " TEXT," + 283 Searches.DATE + " LONG" + 284 ");"); 285 286 db.execSQL("CREATE VIEW " + VIEW_COMBINED + " AS " + 287 "SELECT " + 288 bookmarkOrHistoryColumn(Combined._ID) + ", " + 289 bookmarkOrHistoryColumn(Combined.TITLE) + ", " + 290 qualifyColumn(TABLE_HISTORY, Combined.URL) + ", " + 291 qualifyColumn(TABLE_HISTORY, Combined.DATE_CREATED) + ", " + 292 Combined.DATE_LAST_VISITED + ", " + 293 "CASE WHEN bookmarks._id IS NOT NULL THEN 1 ELSE 0 END AS " + Combined.IS_BOOKMARK + ", " + 294 Combined.VISITS + ", " + 295 Combined.FAVICON + ", " + 296 Combined.THUMBNAIL + ", " + 297 Combined.TOUCH_ICON + ", " + 298 "NULL AS " + Combined.USER_ENTERED + " "+ 299 "FROM history LEFT OUTER JOIN bookmarks ON history.url = bookmarks.url LEFT OUTER JOIN images ON history.url = images.url_key " + 300 301 "UNION ALL " + 302 303 "SELECT " + 304 Combined._ID + ", " + 305 Combined.TITLE + ", " + 306 Combined.URL + ", " + 307 Combined.DATE_CREATED + ", " + 308 "NULL AS " + Combined.DATE_LAST_VISITED + ", "+ 309 "1 AS " + Combined.IS_BOOKMARK + ", " + 310 "0 AS " + Combined.VISITS + ", "+ 311 Combined.FAVICON + ", " + 312 Combined.THUMBNAIL + ", " + 313 Combined.TOUCH_ICON + ", " + 314 "NULL AS " + Combined.USER_ENTERED + " "+ 315 "FROM bookmarks LEFT OUTER JOIN images ON bookmarks.url = images.url_key WHERE url NOT IN (SELECT url FROM history)"); 316 317 mSyncHelper.createDatabase(db); 318 } 319 320 @Override 321 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 322 // TODO write upgrade logic 323 db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS); 324 db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY); 325 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES); 326 db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES); 327 db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED); 328 onCreate(db); 329 } 330 331 @Override 332 public void onOpen(SQLiteDatabase db) { 333 mSyncHelper.onDatabaseOpened(db); 334 } 335 336 337 private void createDefaultBookmarks(SQLiteDatabase db) { 338 ContentValues values = new ContentValues(); 339 // TODO figure out how to deal with localization for the defaults 340 341 // Chrome sync root folder 342 values.put(Bookmarks._ID, FIXED_ID_CHROME_ROOT); 343 values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT); 344 values.put(Bookmarks.TITLE, "Google Chrome"); 345 values.put(Bookmarks.PARENT, 0); 346 values.put(Bookmarks.POSITION, 0); 347 values.put(Bookmarks.IS_FOLDER, true); 348 values.put(Bookmarks.DIRTY, true); 349 db.insertOrThrow(TABLE_BOOKMARKS, null, values); 350 351 // Bookmarks folder 352 values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS); 353 values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS); 354 values.put(Bookmarks.TITLE, "Bookmarks"); 355 values.put(Bookmarks.PARENT, FIXED_ID_CHROME_ROOT); 356 values.put(Bookmarks.POSITION, 0); 357 values.put(Bookmarks.IS_FOLDER, true); 358 values.put(Bookmarks.DIRTY, true); 359 db.insertOrThrow(TABLE_BOOKMARKS, null, values); 360 361 // Bookmarks Bar folder 362 values.clear(); 363 values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS_BAR); 364 values.put(ChromeSyncColumns.SERVER_UNIQUE, 365 ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR); 366 values.put(Bookmarks.TITLE, "Bookmarks Bar"); 367 values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS); 368 values.put(Bookmarks.POSITION, 0); 369 values.put(Bookmarks.IS_FOLDER, true); 370 values.put(Bookmarks.DIRTY, true); 371 db.insertOrThrow(TABLE_BOOKMARKS, null, values); 372 373 // Other Bookmarks folder 374 values.clear(); 375 values.put(Bookmarks._ID, FIXED_ID_OTHER_BOOKMARKS); 376 values.put(ChromeSyncColumns.SERVER_UNIQUE, 377 ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS); 378 values.put(Bookmarks.TITLE, "Other Bookmarks"); 379 values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS); 380 values.put(Bookmarks.POSITION, 1000); 381 values.put(Bookmarks.IS_FOLDER, true); 382 values.put(Bookmarks.DIRTY, true); 383 db.insertOrThrow(TABLE_BOOKMARKS, null, values); 384 385 addDefaultBookmarks(db, FIXED_ID_BOOKMARKS_BAR); 386 387 // TODO remove this testing code 388 db.execSQL("INSERT INTO bookmarks (" + 389 Bookmarks.TITLE + ", " + 390 Bookmarks.URL + ", " + 391 Bookmarks.IS_FOLDER + "," + 392 Bookmarks.PARENT + "," + 393 Bookmarks.POSITION + 394 ") VALUES (" + 395 "'Google Reader', " + 396 "'http://reader.google.com', " + 397 "0," + 398 Long.toString(FIXED_ID_OTHER_BOOKMARKS) + "," + 399 0 + 400 ");"); 401 } 402 403 private void addDefaultBookmarks(SQLiteDatabase db, long parentId) { 404 final CharSequence[] bookmarks = getContext().getResources().getTextArray( 405 R.array.bookmarks); 406 int size = bookmarks.length; 407 try { 408 String parent = Long.toString(parentId); 409 String now = Long.toString(System.currentTimeMillis()); 410 for (int i = 0; i < size; i = i + 2) { 411 CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(), 412 bookmarks[i + 1]); 413 db.execSQL("INSERT INTO bookmarks (" + 414 Bookmarks.TITLE + ", " + 415 Bookmarks.URL + ", " + 416 Bookmarks.IS_FOLDER + "," + 417 Bookmarks.PARENT + "," + 418 Bookmarks.POSITION + "," + 419 Bookmarks.DATE_CREATED + 420 ") VALUES (" + 421 "'" + bookmarks[i] + "', " + 422 "'" + bookmarkDestination + "', " + 423 "0," + 424 parent + "," + 425 Integer.toString(i) + "," + 426 now + 427 ");"); 428 } 429 } catch (ArrayIndexOutOfBoundsException e) { 430 } 431 } 432 433 // XXX: This is a major hack to remove our dependency on gsf constants and 434 // its content provider. http://b/issue?id=2425179 435 private String getClientId(ContentResolver cr) { 436 String ret = "android-google"; 437 Cursor c = null; 438 try { 439 c = cr.query(Uri.parse("content://com.google.settings/partner"), 440 new String[] { "value" }, "name='client_id'", null, null); 441 if (c != null && c.moveToNext()) { 442 ret = c.getString(0); 443 } 444 } catch (RuntimeException ex) { 445 // fall through to return the default 446 } finally { 447 if (c != null) { 448 c.close(); 449 } 450 } 451 return ret; 452 } 453 454 private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) { 455 StringBuffer sb = new StringBuffer(); 456 int lastCharLoc = 0; 457 458 final String client_id = getClientId(context.getContentResolver()); 459 460 for (int i = 0; i < srcString.length(); ++i) { 461 char c = srcString.charAt(i); 462 if (c == '{') { 463 sb.append(srcString.subSequence(lastCharLoc, i)); 464 lastCharLoc = i; 465 inner: 466 for (int j = i; j < srcString.length(); ++j) { 467 char k = srcString.charAt(j); 468 if (k == '}') { 469 String propertyKeyValue = srcString.subSequence(i + 1, j).toString(); 470 if (propertyKeyValue.equals("CLIENT_ID")) { 471 sb.append(client_id); 472 } else { 473 sb.append("unknown"); 474 } 475 lastCharLoc = j + 1; 476 i = j; 477 break inner; 478 } 479 } 480 } 481 } 482 if (srcString.length() - lastCharLoc > 0) { 483 // Put on the tail, if there is one 484 sb.append(srcString.subSequence(lastCharLoc, srcString.length())); 485 } 486 return sb; 487 } 488 } 489 490 @Override 491 public SQLiteOpenHelper getDatabaseHelper(Context context) { 492 synchronized (this) { 493 if (mOpenHelper == null) { 494 mOpenHelper = new DatabaseHelper(context); 495 } 496 return mOpenHelper; 497 } 498 } 499 500 @Override 501 public boolean isCallerSyncAdapter(Uri uri) { 502 return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false); 503 } 504 505 @Override 506 public void notifyChange(boolean callerIsSyncAdapter) { 507 ContentResolver resolver = getContext().getContentResolver(); 508 resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter); 509 resolver.notifyChange(LEGACY_AUTHORITY_URI, null, !callerIsSyncAdapter); 510 } 511 512 @Override 513 public String getType(Uri uri) { 514 final int match = URI_MATCHER.match(uri); 515 switch (match) { 516 case BOOKMARKS: 517 return Bookmarks.CONTENT_TYPE; 518 case BOOKMARKS_ID: 519 return Bookmarks.CONTENT_ITEM_TYPE; 520 case HISTORY: 521 return History.CONTENT_TYPE; 522 case HISTORY_ID: 523 return History.CONTENT_ITEM_TYPE; 524 case SEARCHES: 525 return Searches.CONTENT_TYPE; 526 case SEARCHES_ID: 527 return Searches.CONTENT_ITEM_TYPE; 528// case SUGGEST: 529// return SearchManager.SUGGEST_MIME_TYPE; 530 } 531 return null; 532 } 533 534 @Override 535 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 536 String sortOrder) { 537 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 538 final int match = URI_MATCHER.match(uri); 539 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 540 String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT); 541 switch (match) { 542 case ACCOUNTS: { 543 qb.setTables(TABLE_BOOKMARKS); 544 qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP); 545 qb.setDistinct(true); 546 break; 547 } 548 549 case BOOKMARKS_FOLDER_ID: 550 case BOOKMARKS_ID: 551 case BOOKMARKS: { 552 // Only show deleted bookmarks if requested to do so 553 if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) { 554 selection = DatabaseUtils.concatenateWhere( 555 Bookmarks.IS_DELETED + "=0", selection); 556 } 557 558 if (match == BOOKMARKS_ID) { 559 // Tack on the ID of the specific bookmark requested 560 selection = DatabaseUtils.concatenateWhere(selection, 561 TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?"); 562 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 563 new String[] { Long.toString(ContentUris.parseId(uri)) }); 564 } else if (match == BOOKMARKS_FOLDER_ID) { 565 // Tack on the ID of the specific folder requested 566 selection = DatabaseUtils.concatenateWhere(selection, 567 TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?"); 568 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 569 new String[] { Long.toString(ContentUris.parseId(uri)) }); 570 } 571 572 // Look for account info 573 String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE); 574 String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME); 575 if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) { 576 selection = DatabaseUtils.concatenateWhere(selection, 577 Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? "); 578 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 579 new String[] { accountType, accountName }); 580 } 581 582 // Set a default sort order if one isn't specified 583 if (TextUtils.isEmpty(sortOrder)) { 584 sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER; 585 } 586 587 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); 588 qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES); 589 break; 590 } 591 592 case BOOKMARKS_FOLDER: { 593 // Don't allow selections to be applied to the default folder 594 if (!TextUtils.isEmpty(selection) || selectionArgs != null) { 595 throw new UnsupportedOperationException( 596 "selections aren't supported on this URI"); 597 } 598 599 // Look for an account 600 boolean useAccount = false; 601 String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE); 602 String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME); 603 if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) { 604 useAccount = true; 605 } 606 607 qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES); 608 String bookmarksBarQuery; 609 String otherBookmarksQuery; 610 String[] args; 611 if (!useAccount) { 612 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); 613 bookmarksBarQuery = qb.buildQuery(projection, 614 Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0", 615 null, null, null, null, null); 616 617 qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP); 618 otherBookmarksQuery = qb.buildQuery(projection, 619 Bookmarks._ID + "=?", 620 null, null, null, null, null); 621 622 args = new String[] { Long.toString(FIXED_ID_BOOKMARKS_BAR), 623 Long.toString(FIXED_ID_OTHER_BOOKMARKS) }; 624 } else { 625 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); 626 bookmarksBarQuery = qb.buildQuery(projection, 627 Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? " + 628 "AND parent = " + 629 "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " + 630 ChromeSyncColumns.SERVER_UNIQUE + "=" + 631 "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " + 632 "AND account_type = ? AND account_name = ?) " + 633 "AND " + Bookmarks.IS_DELETED + "=0", 634 null, null, null, null, null); 635 636 qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP); 637 otherBookmarksQuery = qb.buildQuery(projection, 638 Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" + 639 " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?", 640 null, null, null, null, null); 641 642 args = new String[] { 643 accountType, accountName, accountType, accountName, 644 accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS, 645 }; 646 } 647 648 String query = qb.buildUnionQuery( 649 new String[] { bookmarksBarQuery, otherBookmarksQuery }, 650 DEFAULT_BOOKMARKS_SORT_ORDER, limit); 651 return db.rawQuery(query, args); 652 } 653 654 case HISTORY_ID: { 655 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?"); 656 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 657 new String[] { Long.toString(ContentUris.parseId(uri)) }); 658 // fall through 659 } 660 case HISTORY: { 661 if (sortOrder == null) { 662 sortOrder = DEFAULT_SORT_HISTORY; 663 } 664 qb.setProjectionMap(HISTORY_PROJECTION_MAP); 665 qb.setTables(TABLE_HISTORY_JOIN_IMAGES); 666 break; 667 } 668 669 case SYNCSTATE: { 670 return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder); 671 } 672 673 case SYNCSTATE_ID: { 674 selection = appendAccountToSelection(uri, selection); 675 String selectionWithId = 676 (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ") 677 + (selection == null ? "" : " AND (" + selection + ")"); 678 return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder); 679 } 680 681 case IMAGES: { 682 qb.setTables(TABLE_IMAGES); 683 qb.setProjectionMap(IMAGES_PROJECTION_MAP); 684 break; 685 } 686 687 case COMBINED_ID: { 688 selection = DatabaseUtils.concatenateWhere(selection, VIEW_COMBINED + "._id=?"); 689 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 690 new String[] { Long.toString(ContentUris.parseId(uri)) }); 691 // fall through 692 } 693 case COMBINED: { 694 qb.setTables(VIEW_COMBINED); 695 qb.setProjectionMap(COMBINED_PROJECTION_MAP); 696 break; 697 } 698 699 default: { 700 throw new UnsupportedOperationException("Unknown URL " + uri.toString()); 701 } 702 } 703 704 Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, 705 limit); 706 cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI); 707 return cursor; 708 } 709 710 @Override 711 public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs, 712 boolean callerIsSyncAdapter) { 713 final int match = URI_MATCHER.match(uri); 714 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 715 switch (match) { 716 case BOOKMARKS_ID: 717 case BOOKMARKS: { 718 //TODO cascade deletes down from folders 719 if (!callerIsSyncAdapter) { 720 // If the caller isn't a sync adapter just go through and update all the 721 // bookmarks to have the deleted flag set. 722 ContentValues values = new ContentValues(); 723 values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis()); 724 values.put(Bookmarks.IS_DELETED, 1); 725 return updateInTransaction(uri, values, selection, selectionArgs, 726 callerIsSyncAdapter); 727 } else { 728 // Sync adapters are allowed to actually delete things 729 if (match == BOOKMARKS_ID) { 730 selection = DatabaseUtils.concatenateWhere(selection, 731 TABLE_BOOKMARKS + "._id=?"); 732 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 733 new String[] { Long.toString(ContentUris.parseId(uri)) }); 734 } 735 return db.delete(TABLE_BOOKMARKS, selection, selectionArgs); 736 } 737 } 738 739 case HISTORY_ID: { 740 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?"); 741 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 742 new String[] { Long.toString(ContentUris.parseId(uri)) }); 743 // fall through 744 } 745 case HISTORY: { 746 return db.delete(TABLE_HISTORY, selection, selectionArgs); 747 } 748 749 case SEARCHES_ID: { 750 selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?"); 751 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 752 new String[] { Long.toString(ContentUris.parseId(uri)) }); 753 // fall through 754 } 755 case SEARCHES: { 756 return db.delete(TABLE_SEARCHES, selection, selectionArgs); 757 } 758 759 case SYNCSTATE: { 760 return mSyncHelper.delete(db, selection, selectionArgs); 761 } 762 case SYNCSTATE_ID: { 763 String selectionWithId = 764 (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ") 765 + (selection == null ? "" : " AND (" + selection + ")"); 766 return mSyncHelper.delete(db, selectionWithId, selectionArgs); 767 } 768 } 769 throw new UnsupportedOperationException("Unknown update URI " + uri); 770 } 771 772 @Override 773 public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) { 774 final int match = URI_MATCHER.match(uri); 775 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 776 long id = -1; 777 switch (match) { 778 case BOOKMARKS: { 779 // Mark rows dirty if they're not coming from a sync adapater 780 if (!callerIsSyncAdapter) { 781 long now = System.currentTimeMillis(); 782 values.put(Bookmarks.DATE_CREATED, now); 783 values.put(Bookmarks.DATE_MODIFIED, now); 784 values.put(Bookmarks.DIRTY, 1); 785 786 // If no parent is set default to the "Bookmarks Bar" folder 787 if (!values.containsKey(Bookmarks.PARENT)) { 788 values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS_BAR); 789 } 790 } 791 792 // If no position is requested put the bookmark at the beginning of the list 793 if (!values.containsKey(Bookmarks.POSITION)) { 794 values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE)); 795 } 796 797 // Extract out the image values so they can be inserted into the images table 798 String url = values.getAsString(Bookmarks.URL); 799 ContentValues imageValues = extractImageValues(values, url); 800 boolean isFolder = (values.getAsInteger(Bookmarks.IS_FOLDER) != 0); 801 if (!isFolder && imageValues != null && !TextUtils.isEmpty(url)) { 802 db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues); 803 } 804 805 id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values); 806 break; 807 } 808 809 case HISTORY: { 810 // If no created time is specified set it to now 811 if (!values.containsKey(History.DATE_CREATED)) { 812 values.put(History.DATE_CREATED, System.currentTimeMillis()); 813 } 814 815 // Extract out the image values so they can be inserted into the images table 816 ContentValues imageValues = extractImageValues(values, 817 values.getAsString(History.URL)); 818 if (imageValues != null) { 819 db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues); 820 } 821 822 id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values); 823 break; 824 } 825 826 case SEARCHES: { 827 id = insertSearchesInTransaction(db, values); 828 break; 829 } 830 831 case SYNCSTATE: { 832 id = mSyncHelper.insert(db, values); 833 break; 834 } 835 836 default: { 837 throw new UnsupportedOperationException("Unknown insert URI " + uri); 838 } 839 } 840 841 if (id >= 0) { 842 return ContentUris.withAppendedId(uri, id); 843 } else { 844 return null; 845 } 846 } 847 848 /** 849 * Searches are unique, so perform an UPSERT manually since SQLite doesn't support them. 850 */ 851 private long insertSearchesInTransaction(SQLiteDatabase db, ContentValues values) { 852 String search = values.getAsString(Searches.SEARCH); 853 if (TextUtils.isEmpty(search)) { 854 throw new IllegalArgumentException("Must include the SEARCH field"); 855 } 856 Cursor cursor = null; 857 try { 858 cursor = db.query(TABLE_SEARCHES, new String[] { Searches._ID }, 859 Searches.SEARCH + "=?", new String[] { search }, null, null, null); 860 if (cursor.moveToNext()) { 861 long id = cursor.getLong(0); 862 db.update(TABLE_SEARCHES, values, Searches._ID + "=?", 863 new String[] { Long.toString(id) }); 864 return id; 865 } else { 866 return db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values); 867 } 868 } finally { 869 if (cursor != null) cursor.close(); 870 } 871 } 872 873 @Override 874 public int updateInTransaction(Uri uri, ContentValues values, String selection, 875 String[] selectionArgs, boolean callerIsSyncAdapter) { 876 final int match = URI_MATCHER.match(uri); 877 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 878 switch (match) { 879 case BOOKMARKS_ID: { 880 selection = DatabaseUtils.concatenateWhere(selection, 881 TABLE_BOOKMARKS + "._id=?"); 882 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 883 new String[] { Long.toString(ContentUris.parseId(uri)) }); 884 // fall through 885 } 886 case BOOKMARKS: { 887 return updateBookmarksInTransaction(values, selection, selectionArgs, 888 callerIsSyncAdapter); 889 } 890 891 case HISTORY_ID: { 892 selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?"); 893 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 894 new String[] { Long.toString(ContentUris.parseId(uri)) }); 895 // fall through 896 } 897 case HISTORY: { 898 return updateHistoryInTransaction(values, selection, selectionArgs); 899 } 900 901 case SYNCSTATE: { 902 return mSyncHelper.update(mDb, values, 903 appendAccountToSelection(uri, selection), selectionArgs); 904 } 905 906 case SYNCSTATE_ID: { 907 selection = appendAccountToSelection(uri, selection); 908 String selectionWithId = 909 (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ") 910 + (selection == null ? "" : " AND (" + selection + ")"); 911 return mSyncHelper.update(mDb, values, 912 selectionWithId, selectionArgs); 913 } 914 915 case IMAGES: { 916 String url = values.getAsString(Images.URL); 917 if (TextUtils.isEmpty(url)) { 918 throw new IllegalArgumentException("Images.URL is required"); 919 } 920 int count = db.update(TABLE_IMAGES, values, Images.URL + "=?", 921 new String[] { url }); 922 if (count == 0) { 923 db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values); 924 count = 1; 925 } 926 return count; 927 } 928 } 929 throw new UnsupportedOperationException("Unknown update URI " + uri); 930 } 931 932 /** 933 * Does a query to find the matching bookmarks and updates each one with the provided values. 934 */ 935 int updateBookmarksInTransaction(ContentValues values, String selection, 936 String[] selectionArgs, boolean callerIsSyncAdapter) { 937 int count = 0; 938 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 939 Cursor cursor = query(Bookmarks.CONTENT_URI, 940 new String[] { Bookmarks._ID, Bookmarks.VERSION, Bookmarks.URL }, 941 selection, selectionArgs, null); 942 try { 943 String[] args = new String[1]; 944 // Mark the bookmark dirty if the caller isn't a sync adapter 945 if (!callerIsSyncAdapter) { 946 values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis()); 947 values.put(Bookmarks.DIRTY, 1); 948 } 949 950 boolean updatingUrl = values.containsKey(Bookmarks.URL); 951 String url = null; 952 if (updatingUrl) { 953 url = values.getAsString(Bookmarks.URL); 954 } 955 ContentValues imageValues = extractImageValues(values, url); 956 957 while (cursor.moveToNext()) { 958 args[0] = cursor.getString(0); 959 if (!callerIsSyncAdapter) { 960 // increase the local version for non-sync changes 961 values.put(Bookmarks.VERSION, cursor.getLong(1) + 1); 962 } 963 count += db.update(TABLE_BOOKMARKS, values, "_id=?", args); 964 965 // Update the images over in their table 966 if (imageValues != null) { 967 if (!updatingUrl) { 968 url = cursor.getString(2); 969 imageValues.put(Images.URL, url); 970 } 971 972 if (!TextUtils.isEmpty(url)) { 973 args[0] = url; 974 if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) { 975 db.insert(TABLE_IMAGES, Images.FAVICON, imageValues); 976 } 977 } 978 } 979 } 980 } finally { 981 if (cursor != null) cursor.close(); 982 } 983 return count; 984 } 985 986 /** 987 * Does a query to find the matching bookmarks and updates each one with the provided values. 988 */ 989 int updateHistoryInTransaction(ContentValues values, String selection, String[] selectionArgs) { 990 int count = 0; 991 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 992 Cursor cursor = query(History.CONTENT_URI, 993 new String[] { History._ID, History.URL }, 994 selection, selectionArgs, null); 995 try { 996 String[] args = new String[1]; 997 998 boolean updatingUrl = values.containsKey(History.URL); 999 String url = null; 1000 if (updatingUrl) { 1001 url = values.getAsString(History.URL); 1002 } 1003 ContentValues imageValues = extractImageValues(values, url); 1004 1005 while (cursor.moveToNext()) { 1006 args[0] = cursor.getString(0); 1007 count += db.update(TABLE_HISTORY, values, "_id=?", args); 1008 1009 // Update the images over in their table 1010 if (imageValues != null) { 1011 if (!updatingUrl) { 1012 url = cursor.getString(1); 1013 imageValues.put(Images.URL, url); 1014 } 1015 args[0] = url; 1016 if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) { 1017 db.insert(TABLE_IMAGES, Images.FAVICON, imageValues); 1018 } 1019 } 1020 } 1021 } finally { 1022 if (cursor != null) cursor.close(); 1023 } 1024 return count; 1025 } 1026 1027 String appendAccountToSelection(Uri uri, String selection) { 1028 final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME); 1029 final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE); 1030 1031 final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType); 1032 if (partialUri) { 1033 // Throw when either account is incomplete 1034 throw new IllegalArgumentException( 1035 "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri); 1036 } 1037 1038 // Accounts are valid by only checking one parameter, since we've 1039 // already ruled out partial accounts. 1040 final boolean validAccount = !TextUtils.isEmpty(accountName); 1041 if (validAccount) { 1042 StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "=" 1043 + DatabaseUtils.sqlEscapeString(accountName) + " AND " 1044 + RawContacts.ACCOUNT_TYPE + "=" 1045 + DatabaseUtils.sqlEscapeString(accountType)); 1046 if (!TextUtils.isEmpty(selection)) { 1047 selectionSb.append(" AND ("); 1048 selectionSb.append(selection); 1049 selectionSb.append(')'); 1050 } 1051 return selectionSb.toString(); 1052 } else { 1053 return selection; 1054 } 1055 } 1056 1057 ContentValues extractImageValues(ContentValues values, String url) { 1058 ContentValues imageValues = null; 1059 // favicon 1060 if (values.containsKey(Bookmarks.FAVICON)) { 1061 imageValues = new ContentValues(); 1062 imageValues.put(Images.FAVICON, values.getAsByteArray(Bookmarks.FAVICON)); 1063 values.remove(Bookmarks.FAVICON); 1064 } 1065 1066 // thumbnail 1067 if (values.containsKey(Bookmarks.THUMBNAIL)) { 1068 if (imageValues == null) { 1069 imageValues = new ContentValues(); 1070 } 1071 imageValues.put(Images.THUMBNAIL, values.getAsByteArray(Bookmarks.THUMBNAIL)); 1072 values.remove(Bookmarks.THUMBNAIL); 1073 } 1074 1075 // touch icon 1076 if (values.containsKey(Bookmarks.TOUCH_ICON)) { 1077 if (imageValues == null) { 1078 imageValues = new ContentValues(); 1079 } 1080 imageValues.put(Images.TOUCH_ICON, values.getAsByteArray(Bookmarks.TOUCH_ICON)); 1081 values.remove(Bookmarks.TOUCH_ICON); 1082 } 1083 1084 if (imageValues != null) { 1085 imageValues.put(Images.URL, url); 1086 } 1087 return imageValues; 1088 } 1089} 1090