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