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