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