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