blob: 1004e30ef5e623513282e74088215a92f0c9beb7 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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}