blob: a8709312b3b4a2a6ca16b977267bd14b4900d946 [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;
Grace Kloba2036dba2010-02-15 02:15:37 -080022import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import 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;
Grace Kloba58def692009-10-13 12:57:08 -070031import android.database.sqlite.SQLiteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import 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
Grace Klobab6e164c2009-09-14 10:47:07 -070044 private static final int DATABASE_VERSION = 10;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045 // 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
Grace Klobab6e164c2009-09-14 10:47:07 -070053 // 9 -> 10 Update httpauth table UNIQUE
Grace Kloba0b956e12009-06-29 14:49:10 -070054 private static final int CACHE_DATABASE_VERSION = 3;
Grace Klobae64c5562009-06-19 15:56:08 -070055 // 1 -> 2 Add expires String
Grace Kloba0b956e12009-06-29 14:49:10 -070056 // 2 -> 3 Add content-disposition
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057
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
Grace Klobae64c5562009-06-19 15:56:08 -0700115 private static final String CACHE_EXPIRES_STRING_COL = "expiresstring";
116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 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
Grace Kloba0b956e12009-06-29 14:49:10 -0700127 private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition";
128
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 // 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;
Grace Klobae64c5562009-06-19 15:56:08 -0700162 private static int mCacheExpiresStringColIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163 private static int mCacheMimeTypeColIndex;
164 private static int mCacheEncodingColIndex;
165 private static int mCacheHttpStatusColIndex;
166 private static int mCacheLocationColIndex;
167 private static int mCacheContentLengthColIndex;
Grace Kloba0b956e12009-06-29 14:49:10 -0700168 private static int mCacheContentDispositionColIndex;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169
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();
Grace Kloba58def692009-10-13 12:57:08 -0700179 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189
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
Grace Kloba58def692009-10-13 12:57:08 -0700208 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218
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) {
Grace Kloba2036dba2010-02-15 02:15:37 -0800238 // 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 // 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);
Grace Klobae64c5562009-06-19 15:56:08 -0700258 mCacheExpiresStringColIndex = mCacheInserter
259 .getColumnIndex(CACHE_EXPIRES_STRING_COL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 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);
Grace Kloba0b956e12009-06-29 14:49:10 -0700270 mCacheContentDispositionColIndex = mCacheInserter
271 .getColumnIndex(CACHE_CONTENTDISPOSITION_COL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 }
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;
Grace Klobab6e164c2009-09-14 10:47:07 -0700286 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 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 ("
Grace Klobab6e164c2009-09-14 10:47:07 -0700346 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
347 + ") ON CONFLICT REPLACE);");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 }
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, "
Grace Klobae64c5562009-06-19 15:56:08 -0700376 + CACHE_EXPIRES_STRING_COL + " TEXT, "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL
378 + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, "
379 + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL
Grace Kloba0b956e12009-06-29 14:49:10 -0700380 + " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, "
381 + " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 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 //
Grace Kloba2036dba2010-02-15 02:15:37 -0800559 // cache functions
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 //
561
Grace Kloba2036dba2010-02-15 02:15:37 -0800562 // only called from WebViewWorkerThread
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 boolean startCacheTransaction() {
564 if (++mCacheTransactionRefcount == 1) {
Grace Kloba2036dba2010-02-15 02:15:37 -0800565 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800571 mCacheDatabase.beginTransaction();
572 return true;
573 }
574 return false;
575 }
576
Grace Kloba2036dba2010-02-15 02:15:37 -0800577 // only called from WebViewWorkerThread
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 boolean endCacheTransaction() {
579 if (--mCacheTransactionRefcount == 0) {
Grace Kloba2036dba2010-02-15 02:15:37 -0800580 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 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, "
Grace Kloba0b956e12009-06-29 14:49:10 -0700608 + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, "
609 + "contentdisposition FROM cache WHERE url = ?",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610 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);
Grace Klobae64c5562009-06-19 15:56:08 -0700619 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);
Grace Kloba0b956e12009-06-29 14:49:10 -0700625 ret.contentdisposition = cursor.getString(10);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 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);
Grace Klobae64c5562009-06-19 15:56:08 -0700664 mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 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);
Grace Kloba0b956e12009-06-29 14:49:10 -0700670 mCacheInserter.bind(mCacheContentDispositionColIndex,
671 c.contentdisposition);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672 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
Grace Kloba2036dba2010-02-15 02:15:37 -0800709 List<String> trimCache(long amount) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 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
Grace Kloba2036dba2010-02-15 02:15:37 -0800752 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 //
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}