1/* 2 * Copyright (C) 2007 The 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 android.webkit; 18 19import java.util.ArrayList; 20import java.util.HashMap; 21import java.util.Iterator; 22import java.util.Set; 23import java.util.Map.Entry; 24 25import android.content.ContentValues; 26import android.content.Context; 27import android.database.Cursor; 28import android.database.DatabaseUtils; 29import android.database.sqlite.SQLiteDatabase; 30import android.database.sqlite.SQLiteStatement; 31import android.util.Log; 32import android.webkit.CookieManager.Cookie; 33import android.webkit.CacheManager.CacheResult; 34 35public class WebViewDatabase { 36 private static final String DATABASE_FILE = "webview.db"; 37 private static final String CACHE_DATABASE_FILE = "webviewCache.db"; 38 39 // log tag 40 protected static final String LOGTAG = "webviewdatabase"; 41 42 private static final int DATABASE_VERSION = 9; 43 // 2 -> 3 Modified Cache table to allow cache of redirects 44 // 3 -> 4 Added Oma-Downloads table 45 // 4 -> 5 Modified Cache table to support persistent contentLength 46 // 5 -> 4 Removed Oma-Downoads table 47 // 5 -> 6 Add INDEX for cache table 48 // 6 -> 7 Change cache localPath from int to String 49 // 7 -> 8 Move cache to its own db 50 // 8 -> 9 Store both scheme and host when storing passwords 51 private static final int CACHE_DATABASE_VERSION = 1; 52 53 private static WebViewDatabase mInstance = null; 54 55 private static SQLiteDatabase mDatabase = null; 56 private static SQLiteDatabase mCacheDatabase = null; 57 58 // synchronize locks 59 private final Object mCookieLock = new Object(); 60 private final Object mPasswordLock = new Object(); 61 private final Object mFormLock = new Object(); 62 private final Object mHttpAuthLock = new Object(); 63 64 private static final String mTableNames[] = { 65 "cookies", "password", "formurl", "formdata", "httpauth" 66 }; 67 68 // Table ids (they are index to mTableNames) 69 private static final int TABLE_COOKIES_ID = 0; 70 71 private static final int TABLE_PASSWORD_ID = 1; 72 73 private static final int TABLE_FORMURL_ID = 2; 74 75 private static final int TABLE_FORMDATA_ID = 3; 76 77 private static final int TABLE_HTTPAUTH_ID = 4; 78 79 // column id strings for "_id" which can be used by any table 80 private static final String ID_COL = "_id"; 81 82 private static final String[] ID_PROJECTION = new String[] { 83 "_id" 84 }; 85 86 // column id strings for "cookies" table 87 private static final String COOKIES_NAME_COL = "name"; 88 89 private static final String COOKIES_VALUE_COL = "value"; 90 91 private static final String COOKIES_DOMAIN_COL = "domain"; 92 93 private static final String COOKIES_PATH_COL = "path"; 94 95 private static final String COOKIES_EXPIRES_COL = "expires"; 96 97 private static final String COOKIES_SECURE_COL = "secure"; 98 99 // column id strings for "cache" table 100 private static final String CACHE_URL_COL = "url"; 101 102 private static final String CACHE_FILE_PATH_COL = "filepath"; 103 104 private static final String CACHE_LAST_MODIFY_COL = "lastmodify"; 105 106 private static final String CACHE_ETAG_COL = "etag"; 107 108 private static final String CACHE_EXPIRES_COL = "expires"; 109 110 private static final String CACHE_MIMETYPE_COL = "mimetype"; 111 112 private static final String CACHE_ENCODING_COL = "encoding"; 113 114 private static final String CACHE_HTTP_STATUS_COL = "httpstatus"; 115 116 private static final String CACHE_LOCATION_COL = "location"; 117 118 private static final String CACHE_CONTENTLENGTH_COL = "contentlength"; 119 120 // column id strings for "password" table 121 private static final String PASSWORD_HOST_COL = "host"; 122 123 private static final String PASSWORD_USERNAME_COL = "username"; 124 125 private static final String PASSWORD_PASSWORD_COL = "password"; 126 127 // column id strings for "formurl" table 128 private static final String FORMURL_URL_COL = "url"; 129 130 // column id strings for "formdata" table 131 private static final String FORMDATA_URLID_COL = "urlid"; 132 133 private static final String FORMDATA_NAME_COL = "name"; 134 135 private static final String FORMDATA_VALUE_COL = "value"; 136 137 // column id strings for "httpauth" table 138 private static final String HTTPAUTH_HOST_COL = "host"; 139 140 private static final String HTTPAUTH_REALM_COL = "realm"; 141 142 private static final String HTTPAUTH_USERNAME_COL = "username"; 143 144 private static final String HTTPAUTH_PASSWORD_COL = "password"; 145 146 // use InsertHelper to improve insert performance by 40% 147 private static DatabaseUtils.InsertHelper mCacheInserter; 148 private static int mCacheUrlColIndex; 149 private static int mCacheFilePathColIndex; 150 private static int mCacheLastModifyColIndex; 151 private static int mCacheETagColIndex; 152 private static int mCacheExpiresColIndex; 153 private static int mCacheMimeTypeColIndex; 154 private static int mCacheEncodingColIndex; 155 private static int mCacheHttpStatusColIndex; 156 private static int mCacheLocationColIndex; 157 private static int mCacheContentLengthColIndex; 158 159 private static int mCacheTransactionRefcount; 160 161 private WebViewDatabase() { 162 // Singleton only, use getInstance() 163 } 164 165 public static synchronized WebViewDatabase getInstance(Context context) { 166 if (mInstance == null) { 167 mInstance = new WebViewDatabase(); 168 mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null); 169 170 // mDatabase should not be null, 171 // the only case is RequestAPI test has problem to create db 172 if (mDatabase != null && mDatabase.getVersion() != DATABASE_VERSION) { 173 mDatabase.beginTransaction(); 174 try { 175 upgradeDatabase(); 176 mDatabase.setTransactionSuccessful(); 177 } finally { 178 mDatabase.endTransaction(); 179 } 180 } 181 182 if (mDatabase != null) { 183 // use per table Mutex lock, turn off database lock, this 184 // improves performance as database's ReentrantLock is expansive 185 mDatabase.setLockingEnabled(false); 186 } 187 188 mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE, 189 0, null); 190 191 // mCacheDatabase should not be null, 192 // the only case is RequestAPI test has problem to create db 193 if (mCacheDatabase != null 194 && mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) { 195 mCacheDatabase.beginTransaction(); 196 try { 197 upgradeCacheDatabase(); 198 bootstrapCacheDatabase(); 199 mCacheDatabase.setTransactionSuccessful(); 200 } finally { 201 mCacheDatabase.endTransaction(); 202 } 203 // Erase the files from the file system in the 204 // case that the database was updated and the 205 // there were existing cache content 206 CacheManager.removeAllCacheFiles(); 207 } 208 209 if (mCacheDatabase != null) { 210 // use InsertHelper for faster insertion 211 mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase, 212 "cache"); 213 mCacheUrlColIndex = mCacheInserter 214 .getColumnIndex(CACHE_URL_COL); 215 mCacheFilePathColIndex = mCacheInserter 216 .getColumnIndex(CACHE_FILE_PATH_COL); 217 mCacheLastModifyColIndex = mCacheInserter 218 .getColumnIndex(CACHE_LAST_MODIFY_COL); 219 mCacheETagColIndex = mCacheInserter 220 .getColumnIndex(CACHE_ETAG_COL); 221 mCacheExpiresColIndex = mCacheInserter 222 .getColumnIndex(CACHE_EXPIRES_COL); 223 mCacheMimeTypeColIndex = mCacheInserter 224 .getColumnIndex(CACHE_MIMETYPE_COL); 225 mCacheEncodingColIndex = mCacheInserter 226 .getColumnIndex(CACHE_ENCODING_COL); 227 mCacheHttpStatusColIndex = mCacheInserter 228 .getColumnIndex(CACHE_HTTP_STATUS_COL); 229 mCacheLocationColIndex = mCacheInserter 230 .getColumnIndex(CACHE_LOCATION_COL); 231 mCacheContentLengthColIndex = mCacheInserter 232 .getColumnIndex(CACHE_CONTENTLENGTH_COL); 233 } 234 } 235 236 return mInstance; 237 } 238 239 private static void upgradeDatabase() { 240 int oldVersion = mDatabase.getVersion(); 241 if (oldVersion != 0) { 242 Log.i(LOGTAG, "Upgrading database from version " 243 + oldVersion + " to " 244 + DATABASE_VERSION + ", which will destroy old data"); 245 } 246 boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION; 247 if (!justPasswords) { 248 mDatabase.execSQL("DROP TABLE IF EXISTS " 249 + mTableNames[TABLE_COOKIES_ID]); 250 mDatabase.execSQL("DROP TABLE IF EXISTS cache"); 251 mDatabase.execSQL("DROP TABLE IF EXISTS " 252 + mTableNames[TABLE_FORMURL_ID]); 253 mDatabase.execSQL("DROP TABLE IF EXISTS " 254 + mTableNames[TABLE_FORMDATA_ID]); 255 mDatabase.execSQL("DROP TABLE IF EXISTS " 256 + mTableNames[TABLE_HTTPAUTH_ID]); 257 } 258 mDatabase.execSQL("DROP TABLE IF EXISTS " 259 + mTableNames[TABLE_PASSWORD_ID]); 260 261 mDatabase.setVersion(DATABASE_VERSION); 262 263 if (!justPasswords) { 264 // cookies 265 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID] 266 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 267 + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL 268 + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, " 269 + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL 270 + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");"); 271 mDatabase.execSQL("CREATE INDEX cookiesIndex ON " 272 + mTableNames[TABLE_COOKIES_ID] + " (path)"); 273 274 // formurl 275 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] 276 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL 277 + " TEXT" + ");"); 278 279 // formdata 280 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] 281 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 282 + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL 283 + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" 284 + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " 285 + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); 286 287 // httpauth 288 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] 289 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 290 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL 291 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " 292 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" 293 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL + ", " 294 + HTTPAUTH_USERNAME_COL + ") ON CONFLICT REPLACE);"); 295 } 296 // passwords 297 mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID] 298 + " (" + ID_COL + " INTEGER PRIMARY KEY, " 299 + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL 300 + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE (" 301 + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL 302 + ") ON CONFLICT REPLACE);"); 303 } 304 305 private static void upgradeCacheDatabase() { 306 int oldVersion = mCacheDatabase.getVersion(); 307 if (oldVersion != 0) { 308 Log.i(LOGTAG, "Upgrading cache database from version " 309 + oldVersion + " to " 310 + DATABASE_VERSION + ", which will destroy all old data"); 311 } 312 mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache"); 313 mCacheDatabase.setVersion(CACHE_DATABASE_VERSION); 314 } 315 316 private static void bootstrapCacheDatabase() { 317 if (mCacheDatabase != null) { 318 mCacheDatabase.execSQL("CREATE TABLE cache" 319 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL 320 + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, " 321 + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL 322 + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, " 323 + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL 324 + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, " 325 + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL 326 + " INTEGER, " + " UNIQUE (" + CACHE_URL_COL 327 + ") ON CONFLICT REPLACE);"); 328 mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache (" 329 + CACHE_URL_COL + ")"); 330 } 331 } 332 333 private boolean hasEntries(int tableId) { 334 if (mDatabase == null) { 335 return false; 336 } 337 338 Cursor cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION, 339 null, null, null, null, null); 340 boolean ret = cursor.moveToFirst() == true; 341 cursor.close(); 342 return ret; 343 } 344 345 // 346 // cookies functions 347 // 348 349 /** 350 * Get cookies in the format of CookieManager.Cookie inside an ArrayList for 351 * a given domain 352 * 353 * @return ArrayList<Cookie> If nothing is found, return an empty list. 354 */ 355 ArrayList<Cookie> getCookiesForDomain(String domain) { 356 ArrayList<Cookie> list = new ArrayList<Cookie>(); 357 if (domain == null || mDatabase == null) { 358 return list; 359 } 360 361 synchronized (mCookieLock) { 362 final String[] columns = new String[] { 363 ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL, 364 COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL, 365 COOKIES_SECURE_COL 366 }; 367 final String selection = "(" + COOKIES_DOMAIN_COL 368 + " GLOB '*' || ?)"; 369 Cursor cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID], 370 columns, selection, new String[] { domain }, null, null, 371 null); 372 if (cursor.moveToFirst()) { 373 int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL); 374 int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL); 375 int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL); 376 int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL); 377 int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL); 378 int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL); 379 do { 380 Cookie cookie = new Cookie(); 381 cookie.domain = cursor.getString(domainCol); 382 cookie.path = cursor.getString(pathCol); 383 cookie.name = cursor.getString(nameCol); 384 cookie.value = cursor.getString(valueCol); 385 if (cursor.isNull(expiresCol)) { 386 cookie.expires = -1; 387 } else { 388 cookie.expires = cursor.getLong(expiresCol); 389 } 390 cookie.secure = cursor.getShort(secureCol) != 0; 391 cookie.mode = Cookie.MODE_NORMAL; 392 list.add(cookie); 393 } while (cursor.moveToNext()); 394 } 395 cursor.close(); 396 return list; 397 } 398 } 399 400 /** 401 * Delete cookies which matches (domain, path, name). 402 * 403 * @param domain If it is null, nothing happens. 404 * @param path If it is null, all the cookies match (domain) will be 405 * deleted. 406 * @param name If it is null, all the cookies match (domain, path) will be 407 * deleted. 408 */ 409 void deleteCookies(String domain, String path, String name) { 410 if (domain == null || mDatabase == null) { 411 return; 412 } 413 414 synchronized (mCookieLock) { 415 final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND (" 416 + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL 417 + " == ?)"; 418 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where, 419 new String[] { domain, path, name }); 420 } 421 } 422 423 /** 424 * Add a cookie to the database 425 * 426 * @param cookie 427 */ 428 void addCookie(Cookie cookie) { 429 if (cookie.domain == null || cookie.path == null || cookie.name == null 430 || mDatabase == null) { 431 return; 432 } 433 434 synchronized (mCookieLock) { 435 ContentValues cookieVal = new ContentValues(); 436 cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain); 437 cookieVal.put(COOKIES_PATH_COL, cookie.path); 438 cookieVal.put(COOKIES_NAME_COL, cookie.name); 439 cookieVal.put(COOKIES_VALUE_COL, cookie.value); 440 if (cookie.expires != -1) { 441 cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires); 442 } 443 cookieVal.put(COOKIES_SECURE_COL, cookie.secure); 444 mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal); 445 } 446 } 447 448 /** 449 * Whether there is any cookies in the database 450 * 451 * @return TRUE if there is cookie. 452 */ 453 boolean hasCookies() { 454 synchronized (mCookieLock) { 455 return hasEntries(TABLE_COOKIES_ID); 456 } 457 } 458 459 /** 460 * Clear cookie database 461 */ 462 void clearCookies() { 463 if (mDatabase == null) { 464 return; 465 } 466 467 synchronized (mCookieLock) { 468 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null); 469 } 470 } 471 472 /** 473 * Clear session cookies, which means cookie doesn't have EXPIRES. 474 */ 475 void clearSessionCookies() { 476 if (mDatabase == null) { 477 return; 478 } 479 480 final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL"; 481 synchronized (mCookieLock) { 482 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired, 483 null); 484 } 485 } 486 487 /** 488 * Clear expired cookies 489 * 490 * @param now Time for now 491 */ 492 void clearExpiredCookies(long now) { 493 if (mDatabase == null) { 494 return; 495 } 496 497 final String expires = COOKIES_EXPIRES_COL + " <= ?"; 498 synchronized (mCookieLock) { 499 mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires, 500 new String[] { Long.toString(now) }); 501 } 502 } 503 504 // 505 // cache functions, can only be called from WebCoreThread 506 // 507 508 boolean startCacheTransaction() { 509 if (++mCacheTransactionRefcount == 1) { 510 mCacheDatabase.beginTransaction(); 511 return true; 512 } 513 return false; 514 } 515 516 boolean endCacheTransaction() { 517 if (--mCacheTransactionRefcount == 0) { 518 try { 519 mCacheDatabase.setTransactionSuccessful(); 520 } finally { 521 mCacheDatabase.endTransaction(); 522 } 523 return true; 524 } 525 return false; 526 } 527 528 /** 529 * Get a cache item. 530 * 531 * @param url The url 532 * @return CacheResult The CacheManager.CacheResult 533 */ 534 CacheResult getCache(String url) { 535 if (url == null || mCacheDatabase == null) { 536 return null; 537 } 538 539 Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, " 540 + "mimetype, encoding, httpstatus, location, contentlength " 541 + "FROM cache WHERE url = ?", 542 new String[] { url }); 543 544 try { 545 if (cursor.moveToFirst()) { 546 CacheResult ret = new CacheResult(); 547 ret.localPath = cursor.getString(0); 548 ret.lastModified = cursor.getString(1); 549 ret.etag = cursor.getString(2); 550 ret.expires = cursor.getLong(3); 551 ret.mimeType = cursor.getString(4); 552 ret.encoding = cursor.getString(5); 553 ret.httpStatusCode = cursor.getInt(6); 554 ret.location = cursor.getString(7); 555 ret.contentLength = cursor.getLong(8); 556 return ret; 557 } 558 } finally { 559 if (cursor != null) cursor.close(); 560 } 561 return null; 562 } 563 564 /** 565 * Remove a cache item. 566 * 567 * @param url The url 568 */ 569 void removeCache(String url) { 570 if (url == null || mCacheDatabase == null) { 571 return; 572 } 573 574 mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url }); 575 } 576 577 /** 578 * Add or update a cache. CACHE_URL_COL is unique in the table. 579 * 580 * @param url The url 581 * @param c The CacheManager.CacheResult 582 */ 583 void addCache(String url, CacheResult c) { 584 if (url == null || mCacheDatabase == null) { 585 return; 586 } 587 588 mCacheInserter.prepareForInsert(); 589 mCacheInserter.bind(mCacheUrlColIndex, url); 590 mCacheInserter.bind(mCacheFilePathColIndex, c.localPath); 591 mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified); 592 mCacheInserter.bind(mCacheETagColIndex, c.etag); 593 mCacheInserter.bind(mCacheExpiresColIndex, c.expires); 594 mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType); 595 mCacheInserter.bind(mCacheEncodingColIndex, c.encoding); 596 mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode); 597 mCacheInserter.bind(mCacheLocationColIndex, c.location); 598 mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength); 599 mCacheInserter.execute(); 600 } 601 602 /** 603 * Clear cache database 604 */ 605 void clearCache() { 606 if (mCacheDatabase == null) { 607 return; 608 } 609 610 mCacheDatabase.delete("cache", null, null); 611 } 612 613 boolean hasCache() { 614 if (mCacheDatabase == null) { 615 return false; 616 } 617 618 Cursor cursor = mCacheDatabase.query("cache", ID_PROJECTION, 619 null, null, null, null, null); 620 boolean ret = cursor.moveToFirst() == true; 621 cursor.close(); 622 return ret; 623 } 624 625 long getCacheTotalSize() { 626 long size = 0; 627 Cursor cursor = mCacheDatabase.rawQuery( 628 "SELECT SUM(contentlength) as sum FROM cache", null); 629 if (cursor.moveToFirst()) { 630 size = cursor.getLong(0); 631 } 632 cursor.close(); 633 return size; 634 } 635 636 ArrayList<String> trimCache(long amount) { 637 ArrayList<String> pathList = new ArrayList<String>(100); 638 Cursor cursor = mCacheDatabase.rawQuery( 639 "SELECT contentlength, filepath FROM cache ORDER BY expires ASC", 640 null); 641 if (cursor.moveToFirst()) { 642 int batchSize = 100; 643 StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize); 644 pathStr.append("DELETE FROM cache WHERE filepath IN (?"); 645 for (int i = 1; i < batchSize; i++) { 646 pathStr.append(", ?"); 647 } 648 pathStr.append(")"); 649 SQLiteStatement statement = mCacheDatabase.compileStatement(pathStr 650 .toString()); 651 // as bindString() uses 1-based index, initialize index to 1 652 int index = 1; 653 do { 654 long length = cursor.getLong(0); 655 if (length == 0) { 656 continue; 657 } 658 amount -= length; 659 String filePath = cursor.getString(1); 660 statement.bindString(index, filePath); 661 pathList.add(filePath); 662 if (index++ == batchSize) { 663 statement.execute(); 664 statement.clearBindings(); 665 index = 1; 666 } 667 } while (cursor.moveToNext() && amount > 0); 668 if (index > 1) { 669 // there may be old bindings from the previous statement if 670 // index is less than batchSize, which is Ok. 671 statement.execute(); 672 } 673 statement.close(); 674 } 675 cursor.close(); 676 return pathList; 677 } 678 679 // 680 // password functions 681 // 682 683 /** 684 * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique. 685 * 686 * @param schemePlusHost The scheme and host for the password 687 * @param username The username for the password. If it is null, it means 688 * password can't be saved. 689 * @param password The password 690 */ 691 void setUsernamePassword(String schemePlusHost, String username, 692 String password) { 693 if (schemePlusHost == null || mDatabase == null) { 694 return; 695 } 696 697 synchronized (mPasswordLock) { 698 final ContentValues c = new ContentValues(); 699 c.put(PASSWORD_HOST_COL, schemePlusHost); 700 c.put(PASSWORD_USERNAME_COL, username); 701 c.put(PASSWORD_PASSWORD_COL, password); 702 mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL, 703 c); 704 } 705 } 706 707 /** 708 * Retrieve the username and password for a given host 709 * 710 * @param schemePlusHost The scheme and host which passwords applies to 711 * @return String[] if found, String[0] is username, which can be null and 712 * String[1] is password. Return null if it can't find anything. 713 */ 714 String[] getUsernamePassword(String schemePlusHost) { 715 if (schemePlusHost == null || mDatabase == null) { 716 return null; 717 } 718 719 final String[] columns = new String[] { 720 PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL 721 }; 722 final String selection = "(" + PASSWORD_HOST_COL + " == ?)"; 723 synchronized (mPasswordLock) { 724 String[] ret = null; 725 Cursor cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID], 726 columns, selection, new String[] { schemePlusHost }, null, 727 null, null); 728 if (cursor.moveToFirst()) { 729 ret = new String[2]; 730 ret[0] = cursor.getString( 731 cursor.getColumnIndex(PASSWORD_USERNAME_COL)); 732 ret[1] = cursor.getString( 733 cursor.getColumnIndex(PASSWORD_PASSWORD_COL)); 734 } 735 cursor.close(); 736 return ret; 737 } 738 } 739 740 /** 741 * Find out if there are any passwords saved. 742 * 743 * @return TRUE if there is passwords saved 744 */ 745 public boolean hasUsernamePassword() { 746 synchronized (mPasswordLock) { 747 return hasEntries(TABLE_PASSWORD_ID); 748 } 749 } 750 751 /** 752 * Clear password database 753 */ 754 public void clearUsernamePassword() { 755 if (mDatabase == null) { 756 return; 757 } 758 759 synchronized (mPasswordLock) { 760 mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null); 761 } 762 } 763 764 // 765 // http authentication password functions 766 // 767 768 /** 769 * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, 770 * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique. 771 * 772 * @param host The host for the password 773 * @param realm The realm for the password 774 * @param username The username for the password. If it is null, it means 775 * password can't be saved. 776 * @param password The password 777 */ 778 void setHttpAuthUsernamePassword(String host, String realm, String username, 779 String password) { 780 if (host == null || realm == null || mDatabase == null) { 781 return; 782 } 783 784 synchronized (mHttpAuthLock) { 785 final ContentValues c = new ContentValues(); 786 c.put(HTTPAUTH_HOST_COL, host); 787 c.put(HTTPAUTH_REALM_COL, realm); 788 c.put(HTTPAUTH_USERNAME_COL, username); 789 c.put(HTTPAUTH_PASSWORD_COL, password); 790 mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL, 791 c); 792 } 793 } 794 795 /** 796 * Retrieve the HTTP authentication username and password for a given 797 * host+realm pair 798 * 799 * @param host The host the password applies to 800 * @param realm The realm the password applies to 801 * @return String[] if found, String[0] is username, which can be null and 802 * String[1] is password. Return null if it can't find anything. 803 */ 804 String[] getHttpAuthUsernamePassword(String host, String realm) { 805 if (host == null || realm == null || mDatabase == null){ 806 return null; 807 } 808 809 final String[] columns = new String[] { 810 HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL 811 }; 812 final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND (" 813 + HTTPAUTH_REALM_COL + " == ?)"; 814 synchronized (mHttpAuthLock) { 815 String[] ret = null; 816 Cursor cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID], 817 columns, selection, new String[] { host, realm }, null, 818 null, null); 819 if (cursor.moveToFirst()) { 820 ret = new String[2]; 821 ret[0] = cursor.getString( 822 cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)); 823 ret[1] = cursor.getString( 824 cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)); 825 } 826 cursor.close(); 827 return ret; 828 } 829 } 830 831 /** 832 * Find out if there are any HTTP authentication passwords saved. 833 * 834 * @return TRUE if there are passwords saved 835 */ 836 public boolean hasHttpAuthUsernamePassword() { 837 synchronized (mHttpAuthLock) { 838 return hasEntries(TABLE_HTTPAUTH_ID); 839 } 840 } 841 842 /** 843 * Clear HTTP authentication password database 844 */ 845 public void clearHttpAuthUsernamePassword() { 846 if (mDatabase == null) { 847 return; 848 } 849 850 synchronized (mHttpAuthLock) { 851 mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null); 852 } 853 } 854 855 // 856 // form data functions 857 // 858 859 /** 860 * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL, 861 * FORMDATA_VALUE_COL) is unique 862 * 863 * @param url The url of the site 864 * @param formdata The form data in HashMap 865 */ 866 void setFormData(String url, HashMap<String, String> formdata) { 867 if (url == null || formdata == null || mDatabase == null) { 868 return; 869 } 870 871 final String selection = "(" + FORMURL_URL_COL + " == ?)"; 872 synchronized (mFormLock) { 873 long urlid = -1; 874 Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], 875 ID_PROJECTION, selection, new String[] { url }, null, null, 876 null); 877 if (cursor.moveToFirst()) { 878 urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); 879 } else { 880 ContentValues c = new ContentValues(); 881 c.put(FORMURL_URL_COL, url); 882 urlid = mDatabase.insert( 883 mTableNames[TABLE_FORMURL_ID], null, c); 884 } 885 cursor.close(); 886 if (urlid >= 0) { 887 Set<Entry<String, String>> set = formdata.entrySet(); 888 Iterator<Entry<String, String>> iter = set.iterator(); 889 ContentValues map = new ContentValues(); 890 map.put(FORMDATA_URLID_COL, urlid); 891 while (iter.hasNext()) { 892 Entry<String, String> entry = iter.next(); 893 map.put(FORMDATA_NAME_COL, entry.getKey()); 894 map.put(FORMDATA_VALUE_COL, entry.getValue()); 895 mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map); 896 } 897 } 898 } 899 } 900 901 /** 902 * Get all the values for a form entry with "name" in a given site 903 * 904 * @param url The url of the site 905 * @param name The name of the form entry 906 * @return A list of values. Return empty list if nothing is found. 907 */ 908 ArrayList<String> getFormData(String url, String name) { 909 ArrayList<String> values = new ArrayList<String>(); 910 if (url == null || name == null || mDatabase == null) { 911 return values; 912 } 913 914 final String urlSelection = "(" + FORMURL_URL_COL + " == ?)"; 915 final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND (" 916 + FORMDATA_NAME_COL + " == ?)"; 917 synchronized (mFormLock) { 918 Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], 919 ID_PROJECTION, urlSelection, new String[] { url }, null, 920 null, null); 921 if (cursor.moveToFirst()) { 922 long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); 923 Cursor dataCursor = mDatabase.query( 924 mTableNames[TABLE_FORMDATA_ID], 925 new String[] { ID_COL, FORMDATA_VALUE_COL }, 926 dataSelection, 927 new String[] { Long.toString(urlid), name }, null, 928 null, null); 929 if (dataCursor.moveToFirst()) { 930 int valueCol = 931 dataCursor.getColumnIndex(FORMDATA_VALUE_COL); 932 do { 933 values.add(dataCursor.getString(valueCol)); 934 } while (dataCursor.moveToNext()); 935 } 936 dataCursor.close(); 937 } 938 cursor.close(); 939 return values; 940 } 941 } 942 943 /** 944 * Find out if there is form data saved. 945 * 946 * @return TRUE if there is form data in the database 947 */ 948 public boolean hasFormData() { 949 synchronized (mFormLock) { 950 return hasEntries(TABLE_FORMURL_ID); 951 } 952 } 953 954 /** 955 * Clear form database 956 */ 957 public void clearFormData() { 958 if (mDatabase == null) { 959 return; 960 } 961 962 synchronized (mFormLock) { 963 mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null); 964 mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null); 965 } 966 } 967} 968