Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2012 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 
     35 final class WebViewDatabaseClassic extends WebViewDatabase {
     36     private static final String LOGTAG = "WebViewDatabaseClassic";
     37     private static final String DATABASE_FILE = "webview.db";
     38     private static final String CACHE_DATABASE_FILE = "webviewCache.db";
     39 
     40     private static final int DATABASE_VERSION = 11;
     41     // 2 -> 3 Modified Cache table to allow cache of redirects
     42     // 3 -> 4 Added Oma-Downloads table
     43     // 4 -> 5 Modified Cache table to support persistent contentLength
     44     // 5 -> 4 Removed Oma-Downoads table
     45     // 5 -> 6 Add INDEX for cache table
     46     // 6 -> 7 Change cache localPath from int to String
     47     // 7 -> 8 Move cache to its own db
     48     // 8 -> 9 Store both scheme and host when storing passwords
     49     // 9 -> 10 Update httpauth table UNIQUE
     50     // 10 -> 11 Drop cookies and cache now managed by the chromium stack,
     51     //          and update the form data table to use the new format
     52     //          implemented for b/5265606.
     53 
     54     private static WebViewDatabaseClassic sInstance = null;
     55     private static final Object sInstanceLock = new Object();
     56 
     57     private static SQLiteDatabase sDatabase = null;
     58 
     59     // synchronize locks
     60     private final Object mPasswordLock = new Object();
     61     private final Object mFormLock = new Object();
     62     private final Object mHttpAuthLock = new Object();
     63 
     64     private static final String mTableNames[] = {
     65         "password", "formurl", "formdata", "httpauth"
     66     };
     67 
     68     // Table ids (they are index to mTableNames)
     69     private static final int TABLE_PASSWORD_ID = 0;
     70     private static final int TABLE_FORMURL_ID = 1;
     71     private static final int TABLE_FORMDATA_ID = 2;
     72     private static final int TABLE_HTTPAUTH_ID = 3;
     73 
     74     // column id strings for "_id" which can be used by any table
     75     private static final String ID_COL = "_id";
     76 
     77     private static final String[] ID_PROJECTION = new String[] {
     78         "_id"
     79     };
     80 
     81     // column id strings for "password" table
     82     private static final String PASSWORD_HOST_COL = "host";
     83     private static final String PASSWORD_USERNAME_COL = "username";
     84     private static final String PASSWORD_PASSWORD_COL = "password";
     85 
     86     // column id strings for "formurl" table
     87     private static final String FORMURL_URL_COL = "url";
     88 
     89     // column id strings for "formdata" table
     90     private static final String FORMDATA_URLID_COL = "urlid";
     91     private static final String FORMDATA_NAME_COL = "name";
     92     private static final String FORMDATA_VALUE_COL = "value";
     93 
     94     // column id strings for "httpauth" table
     95     private static final String HTTPAUTH_HOST_COL = "host";
     96     private static final String HTTPAUTH_REALM_COL = "realm";
     97     private static final String HTTPAUTH_USERNAME_COL = "username";
     98     private static final String HTTPAUTH_PASSWORD_COL = "password";
     99 
    100     // Initially true until the background thread completes.
    101     private boolean mInitialized = false;
    102 
    103     private WebViewDatabaseClassic(final Context context) {
    104         JniUtil.setContext(context);
    105         new Thread() {
    106             @Override
    107             public void run() {
    108                 init(context);
    109             }
    110         }.start();
    111 
    112         // Singleton only, use getInstance()
    113     }
    114 
    115     public static WebViewDatabaseClassic getInstance(Context context) {
    116         synchronized (sInstanceLock) {
    117             if (sInstance == null) {
    118                 sInstance = new WebViewDatabaseClassic(context);
    119             }
    120             return sInstance;
    121         }
    122     }
    123 
    124     private synchronized void init(Context context) {
    125         if (mInitialized) {
    126             return;
    127         }
    128 
    129         initDatabase(context);
    130         // Before using the Chromium HTTP stack, we stored the WebKit cache in
    131         // our own DB. Clean up the DB file if it's still around.
    132         context.deleteDatabase(CACHE_DATABASE_FILE);
    133 
    134         // Thread done, notify.
    135         mInitialized = true;
    136         notify();
    137     }
    138 
    139     private void initDatabase(Context context) {
    140         try {
    141             sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
    142         } catch (SQLiteException e) {
    143             // try again by deleting the old db and create a new one
    144             if (context.deleteDatabase(DATABASE_FILE)) {
    145                 sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
    146                         null);
    147             }
    148         }
    149 
    150         // sDatabase should not be null,
    151         // the only case is RequestAPI test has problem to create db
    152         if (sDatabase == null) {
    153             mInitialized = true;
    154             notify();
    155             return;
    156         }
    157 
    158         if (sDatabase.getVersion() != DATABASE_VERSION) {
    159             sDatabase.beginTransactionNonExclusive();
    160             try {
    161                 upgradeDatabase();
    162                 sDatabase.setTransactionSuccessful();
    163             } finally {
    164                 sDatabase.endTransaction();
    165             }
    166         }
    167     }
    168 
    169     private static void upgradeDatabase() {
    170         upgradeDatabaseToV10();
    171         upgradeDatabaseFromV10ToV11();
    172         // Add future database upgrade functions here, one version at a
    173         // time.
    174         sDatabase.setVersion(DATABASE_VERSION);
    175     }
    176 
    177     private static void upgradeDatabaseFromV10ToV11() {
    178         int oldVersion = sDatabase.getVersion();
    179 
    180         if (oldVersion >= 11) {
    181             // Nothing to do.
    182             return;
    183         }
    184 
    185         // Clear out old java stack cookies - this data is now stored in
    186         // a separate database managed by the Chrome stack.
    187         sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
    188 
    189         // Likewise for the old cache table.
    190         sDatabase.execSQL("DROP TABLE IF EXISTS cache");
    191 
    192         // Update form autocomplete  URLs to match new ICS formatting.
    193         Cursor c = sDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null,
    194                 null, null, null, null);
    195         while (c.moveToNext()) {
    196             String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL)));
    197             String url = c.getString(c.getColumnIndex(FORMURL_URL_COL));
    198             ContentValues cv = new ContentValues(1);
    199             cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url));
    200             sDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?",
    201                     new String[] { urlId });
    202         }
    203         c.close();
    204     }
    205 
    206     private static void upgradeDatabaseToV10() {
    207         int oldVersion = sDatabase.getVersion();
    208 
    209         if (oldVersion >= 10) {
    210             // Nothing to do.
    211             return;
    212         }
    213 
    214         if (oldVersion != 0) {
    215             Log.i(LOGTAG, "Upgrading database from version "
    216                     + oldVersion + " to "
    217                     + DATABASE_VERSION + ", which will destroy old data");
    218         }
    219 
    220         if (9 == oldVersion) {
    221             sDatabase.execSQL("DROP TABLE IF EXISTS "
    222                     + mTableNames[TABLE_HTTPAUTH_ID]);
    223             sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
    224                     + " (" + ID_COL + " INTEGER PRIMARY KEY, "
    225                     + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
    226                     + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
    227                     + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
    228                     + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
    229                     + ") ON CONFLICT REPLACE);");
    230             return;
    231         }
    232 
    233         sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
    234         sDatabase.execSQL("DROP TABLE IF EXISTS cache");
    235         sDatabase.execSQL("DROP TABLE IF EXISTS "
    236                 + mTableNames[TABLE_FORMURL_ID]);
    237         sDatabase.execSQL("DROP TABLE IF EXISTS "
    238                 + mTableNames[TABLE_FORMDATA_ID]);
    239         sDatabase.execSQL("DROP TABLE IF EXISTS "
    240                 + mTableNames[TABLE_HTTPAUTH_ID]);
    241         sDatabase.execSQL("DROP TABLE IF EXISTS "
    242                 + mTableNames[TABLE_PASSWORD_ID]);
    243 
    244         // formurl
    245         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
    246                 + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
    247                 + " TEXT" + ");");
    248 
    249         // formdata
    250         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
    251                 + " (" + ID_COL + " INTEGER PRIMARY KEY, "
    252                 + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
    253                 + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
    254                 + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
    255                 + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
    256 
    257         // httpauth
    258         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
    259                 + " (" + ID_COL + " INTEGER PRIMARY KEY, "
    260                 + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
    261                 + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
    262                 + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
    263                 + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
    264                 + ") ON CONFLICT REPLACE);");
    265         // passwords
    266         sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
    267                 + " (" + ID_COL + " INTEGER PRIMARY KEY, "
    268                 + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
    269                 + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
    270                 + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
    271                 + ") ON CONFLICT REPLACE);");
    272     }
    273 
    274     // Wait for the background initialization thread to complete and check the
    275     // database creation status.
    276     private boolean checkInitialized() {
    277         synchronized (this) {
    278             while (!mInitialized) {
    279                 try {
    280                     wait();
    281                 } catch (InterruptedException e) {
    282                     Log.e(LOGTAG, "Caught exception while checking " +
    283                                   "initialization");
    284                     Log.e(LOGTAG, Log.getStackTraceString(e));
    285                 }
    286             }
    287         }
    288         return sDatabase != null;
    289     }
    290 
    291     private boolean hasEntries(int tableId) {
    292         if (!checkInitialized()) {
    293             return false;
    294         }
    295 
    296         Cursor cursor = null;
    297         boolean ret = false;
    298         try {
    299             cursor = sDatabase.query(mTableNames[tableId], ID_PROJECTION,
    300                     null, null, null, null, null);
    301             ret = cursor.moveToFirst() == true;
    302         } catch (IllegalStateException e) {
    303             Log.e(LOGTAG, "hasEntries", e);
    304         } finally {
    305             if (cursor != null) cursor.close();
    306         }
    307         return ret;
    308     }
    309 
    310     //
    311     // password functions
    312     //
    313 
    314     /**
    315      * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
    316      *
    317      * @param schemePlusHost The scheme and host for the password
    318      * @param username The username for the password. If it is null, it means
    319      *            password can't be saved.
    320      * @param password The password
    321      */
    322     void setUsernamePassword(String schemePlusHost, String username,
    323                 String password) {
    324         if (schemePlusHost == null || !checkInitialized()) {
    325             return;
    326         }
    327 
    328         synchronized (mPasswordLock) {
    329             final ContentValues c = new ContentValues();
    330             c.put(PASSWORD_HOST_COL, schemePlusHost);
    331             c.put(PASSWORD_USERNAME_COL, username);
    332             c.put(PASSWORD_PASSWORD_COL, password);
    333             sDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
    334                     c);
    335         }
    336     }
    337 
    338     /**
    339      * Retrieve the username and password for a given host
    340      *
    341      * @param schemePlusHost The scheme and host which passwords applies to
    342      * @return String[] if found, String[0] is username, which can be null and
    343      *         String[1] is password. Return null if it can't find anything.
    344      */
    345     String[] getUsernamePassword(String schemePlusHost) {
    346         if (schemePlusHost == null || !checkInitialized()) {
    347             return null;
    348         }
    349 
    350         final String[] columns = new String[] {
    351                 PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL
    352         };
    353         final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
    354         synchronized (mPasswordLock) {
    355             String[] ret = null;
    356             Cursor cursor = null;
    357             try {
    358                 cursor = sDatabase.query(mTableNames[TABLE_PASSWORD_ID],
    359                         columns, selection, new String[] { schemePlusHost }, null,
    360                         null, null);
    361                 if (cursor.moveToFirst()) {
    362                     ret = new String[2];
    363                     ret[0] = cursor.getString(
    364                             cursor.getColumnIndex(PASSWORD_USERNAME_COL));
    365                     ret[1] = cursor.getString(
    366                             cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
    367                 }
    368             } catch (IllegalStateException e) {
    369                 Log.e(LOGTAG, "getUsernamePassword", e);
    370             } finally {
    371                 if (cursor != null) cursor.close();
    372             }
    373             return ret;
    374         }
    375     }
    376 
    377     /**
    378      * @see WebViewDatabase#hasUsernamePassword
    379      */
    380     @Override
    381     public boolean hasUsernamePassword() {
    382         synchronized (mPasswordLock) {
    383             return hasEntries(TABLE_PASSWORD_ID);
    384         }
    385     }
    386 
    387     /**
    388      * @see WebViewDatabase#clearUsernamePassword
    389      */
    390     @Override
    391     public void clearUsernamePassword() {
    392         if (!checkInitialized()) {
    393             return;
    394         }
    395 
    396         synchronized (mPasswordLock) {
    397             sDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null);
    398         }
    399     }
    400 
    401     //
    402     // http authentication password functions
    403     //
    404 
    405     /**
    406      * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL,
    407      * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique.
    408      *
    409      * @param host The host for the password
    410      * @param realm The realm for the password
    411      * @param username The username for the password. If it is null, it means
    412      *            password can't be saved.
    413      * @param password The password
    414      */
    415     void setHttpAuthUsernamePassword(String host, String realm, String username,
    416             String password) {
    417         if (host == null || realm == null || !checkInitialized()) {
    418             return;
    419         }
    420 
    421         synchronized (mHttpAuthLock) {
    422             final ContentValues c = new ContentValues();
    423             c.put(HTTPAUTH_HOST_COL, host);
    424             c.put(HTTPAUTH_REALM_COL, realm);
    425             c.put(HTTPAUTH_USERNAME_COL, username);
    426             c.put(HTTPAUTH_PASSWORD_COL, password);
    427             sDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL,
    428                     c);
    429         }
    430     }
    431 
    432     /**
    433      * Retrieve the HTTP authentication username and password for a given
    434      * host+realm pair
    435      *
    436      * @param host The host the password applies to
    437      * @param realm The realm the password applies to
    438      * @return String[] if found, String[0] is username, which can be null and
    439      *         String[1] is password. Return null if it can't find anything.
    440      */
    441     String[] getHttpAuthUsernamePassword(String host, String realm) {
    442         if (host == null || realm == null || !checkInitialized()){
    443             return null;
    444         }
    445 
    446         final String[] columns = new String[] {
    447                 HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
    448         };
    449         final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND ("
    450                 + HTTPAUTH_REALM_COL + " == ?)";
    451         synchronized (mHttpAuthLock) {
    452             String[] ret = null;
    453             Cursor cursor = null;
    454             try {
    455                 cursor = sDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
    456                         columns, selection, new String[] { host, realm }, null,
    457                         null, null);
    458                 if (cursor.moveToFirst()) {
    459                     ret = new String[2];
    460                     ret[0] = cursor.getString(
    461                             cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
    462                     ret[1] = cursor.getString(
    463                             cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
    464                 }
    465             } catch (IllegalStateException e) {
    466                 Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
    467             } finally {
    468                 if (cursor != null) cursor.close();
    469             }
    470             return ret;
    471         }
    472     }
    473 
    474     /**
    475      * @see WebViewDatabase#hasHttpAuthUsernamePassword
    476      */
    477     @Override
    478     public boolean hasHttpAuthUsernamePassword() {
    479         synchronized (mHttpAuthLock) {
    480             return hasEntries(TABLE_HTTPAUTH_ID);
    481         }
    482     }
    483 
    484     /**
    485      * @see WebViewDatabase#clearHttpAuthUsernamePassword
    486      */
    487     @Override
    488     public void clearHttpAuthUsernamePassword() {
    489         if (!checkInitialized()) {
    490             return;
    491         }
    492 
    493         synchronized (mHttpAuthLock) {
    494             sDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null);
    495         }
    496     }
    497 
    498     //
    499     // form data functions
    500     //
    501 
    502     /**
    503      * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL,
    504      * FORMDATA_VALUE_COL) is unique
    505      *
    506      * @param url The url of the site
    507      * @param formdata The form data in HashMap
    508      */
    509     void setFormData(String url, HashMap<String, String> formdata) {
    510         if (url == null || formdata == null || !checkInitialized()) {
    511             return;
    512         }
    513 
    514         final String selection = "(" + FORMURL_URL_COL + " == ?)";
    515         synchronized (mFormLock) {
    516             long urlid = -1;
    517             Cursor cursor = null;
    518             try {
    519                 cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
    520                         ID_PROJECTION, selection, new String[] { url }, null, null,
    521                         null);
    522                 if (cursor.moveToFirst()) {
    523                     urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
    524                 } else {
    525                     ContentValues c = new ContentValues();
    526                     c.put(FORMURL_URL_COL, url);
    527                     urlid = sDatabase.insert(
    528                             mTableNames[TABLE_FORMURL_ID], null, c);
    529                 }
    530             } catch (IllegalStateException e) {
    531                 Log.e(LOGTAG, "setFormData", e);
    532             } finally {
    533                 if (cursor != null) cursor.close();
    534             }
    535             if (urlid >= 0) {
    536                 Set<Entry<String, String>> set = formdata.entrySet();
    537                 Iterator<Entry<String, String>> iter = set.iterator();
    538                 ContentValues map = new ContentValues();
    539                 map.put(FORMDATA_URLID_COL, urlid);
    540                 while (iter.hasNext()) {
    541                     Entry<String, String> entry = iter.next();
    542                     map.put(FORMDATA_NAME_COL, entry.getKey());
    543                     map.put(FORMDATA_VALUE_COL, entry.getValue());
    544                     sDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map);
    545                 }
    546             }
    547         }
    548     }
    549 
    550     /**
    551      * Get all the values for a form entry with "name" in a given site
    552      *
    553      * @param url The url of the site
    554      * @param name The name of the form entry
    555      * @return A list of values. Return empty list if nothing is found.
    556      */
    557     ArrayList<String> getFormData(String url, String name) {
    558         ArrayList<String> values = new ArrayList<String>();
    559         if (url == null || name == null || !checkInitialized()) {
    560             return values;
    561         }
    562 
    563         final String urlSelection = "(" + FORMURL_URL_COL + " == ?)";
    564         final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
    565                 + FORMDATA_NAME_COL + " == ?)";
    566         synchronized (mFormLock) {
    567             Cursor cursor = null;
    568             try {
    569                 cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
    570                         ID_PROJECTION, urlSelection, new String[] { url }, null,
    571                         null, null);
    572                 while (cursor.moveToNext()) {
    573                     long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
    574                     Cursor dataCursor = null;
    575                     try {
    576                         dataCursor = sDatabase.query(
    577                                 mTableNames[TABLE_FORMDATA_ID],
    578                                 new String[] { ID_COL, FORMDATA_VALUE_COL },
    579                                 dataSelection,
    580                                 new String[] { Long.toString(urlid), name },
    581                                 null, null, null);
    582                         if (dataCursor.moveToFirst()) {
    583                             int valueCol = dataCursor.getColumnIndex(
    584                                     FORMDATA_VALUE_COL);
    585                             do {
    586                                 values.add(dataCursor.getString(valueCol));
    587                             } while (dataCursor.moveToNext());
    588                         }
    589                     } catch (IllegalStateException e) {
    590                         Log.e(LOGTAG, "getFormData dataCursor", e);
    591                     } finally {
    592                         if (dataCursor != null) dataCursor.close();
    593                     }
    594                 }
    595             } catch (IllegalStateException e) {
    596                 Log.e(LOGTAG, "getFormData cursor", e);
    597             } finally {
    598                 if (cursor != null) cursor.close();
    599             }
    600             return values;
    601         }
    602     }
    603 
    604     /**
    605      * @see WebViewDatabase#hasFormData
    606      */
    607     @Override
    608     public boolean hasFormData() {
    609         synchronized (mFormLock) {
    610             return hasEntries(TABLE_FORMURL_ID);
    611         }
    612     }
    613 
    614     /**
    615      * @see WebViewDatabase#clearFormData
    616      */
    617     @Override
    618     public void clearFormData() {
    619         if (!checkInitialized()) {
    620             return;
    621         }
    622 
    623         synchronized (mFormLock) {
    624             sDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null);
    625             sDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null);
    626         }
    627     }
    628 }
    629