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