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