| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.webkit; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.Map.Entry; |
| |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.database.DatabaseUtils; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.database.sqlite.SQLiteException; |
| import android.database.sqlite.SQLiteStatement; |
| import android.util.Log; |
| import android.webkit.CookieManager.Cookie; |
| import android.webkit.CacheManager.CacheResult; |
| |
| public class WebViewDatabase { |
| private static final String DATABASE_FILE = "webview.db"; |
| private static final String CACHE_DATABASE_FILE = "webviewCache.db"; |
| |
| // log tag |
| protected static final String LOGTAG = "webviewdatabase"; |
| |
| private static final int DATABASE_VERSION = 10; |
| // 2 -> 3 Modified Cache table to allow cache of redirects |
| // 3 -> 4 Added Oma-Downloads table |
| // 4 -> 5 Modified Cache table to support persistent contentLength |
| // 5 -> 4 Removed Oma-Downoads table |
| // 5 -> 6 Add INDEX for cache table |
| // 6 -> 7 Change cache localPath from int to String |
| // 7 -> 8 Move cache to its own db |
| // 8 -> 9 Store both scheme and host when storing passwords |
| // 9 -> 10 Update httpauth table UNIQUE |
| private static final int CACHE_DATABASE_VERSION = 4; |
| // 1 -> 2 Add expires String |
| // 2 -> 3 Add content-disposition |
| // 3 -> 4 Add crossdomain (For x-permitted-cross-domain-policies header) |
| |
| private static WebViewDatabase mInstance = null; |
| |
| private static SQLiteDatabase mDatabase = null; |
| private static SQLiteDatabase mCacheDatabase = null; |
| |
| // synchronize locks |
| private final Object mCookieLock = new Object(); |
| private final Object mPasswordLock = new Object(); |
| private final Object mFormLock = new Object(); |
| private final Object mHttpAuthLock = new Object(); |
| |
| private static final String mTableNames[] = { |
| "cookies", "password", "formurl", "formdata", "httpauth" |
| }; |
| |
| // Table ids (they are index to mTableNames) |
| private static final int TABLE_COOKIES_ID = 0; |
| |
| private static final int TABLE_PASSWORD_ID = 1; |
| |
| private static final int TABLE_FORMURL_ID = 2; |
| |
| private static final int TABLE_FORMDATA_ID = 3; |
| |
| private static final int TABLE_HTTPAUTH_ID = 4; |
| |
| // column id strings for "_id" which can be used by any table |
| private static final String ID_COL = "_id"; |
| |
| private static final String[] ID_PROJECTION = new String[] { |
| "_id" |
| }; |
| |
| // column id strings for "cookies" table |
| private static final String COOKIES_NAME_COL = "name"; |
| |
| private static final String COOKIES_VALUE_COL = "value"; |
| |
| private static final String COOKIES_DOMAIN_COL = "domain"; |
| |
| private static final String COOKIES_PATH_COL = "path"; |
| |
| private static final String COOKIES_EXPIRES_COL = "expires"; |
| |
| private static final String COOKIES_SECURE_COL = "secure"; |
| |
| // column id strings for "cache" table |
| private static final String CACHE_URL_COL = "url"; |
| |
| private static final String CACHE_FILE_PATH_COL = "filepath"; |
| |
| private static final String CACHE_LAST_MODIFY_COL = "lastmodify"; |
| |
| private static final String CACHE_ETAG_COL = "etag"; |
| |
| private static final String CACHE_EXPIRES_COL = "expires"; |
| |
| private static final String CACHE_EXPIRES_STRING_COL = "expiresstring"; |
| |
| private static final String CACHE_MIMETYPE_COL = "mimetype"; |
| |
| private static final String CACHE_ENCODING_COL = "encoding"; |
| |
| private static final String CACHE_HTTP_STATUS_COL = "httpstatus"; |
| |
| private static final String CACHE_LOCATION_COL = "location"; |
| |
| private static final String CACHE_CONTENTLENGTH_COL = "contentlength"; |
| |
| private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition"; |
| |
| private static final String CACHE_CROSSDOMAIN_COL = "crossdomain"; |
| |
| // column id strings for "password" table |
| private static final String PASSWORD_HOST_COL = "host"; |
| |
| private static final String PASSWORD_USERNAME_COL = "username"; |
| |
| private static final String PASSWORD_PASSWORD_COL = "password"; |
| |
| // column id strings for "formurl" table |
| private static final String FORMURL_URL_COL = "url"; |
| |
| // column id strings for "formdata" table |
| private static final String FORMDATA_URLID_COL = "urlid"; |
| |
| private static final String FORMDATA_NAME_COL = "name"; |
| |
| private static final String FORMDATA_VALUE_COL = "value"; |
| |
| // column id strings for "httpauth" table |
| private static final String HTTPAUTH_HOST_COL = "host"; |
| |
| private static final String HTTPAUTH_REALM_COL = "realm"; |
| |
| private static final String HTTPAUTH_USERNAME_COL = "username"; |
| |
| private static final String HTTPAUTH_PASSWORD_COL = "password"; |
| |
| // use InsertHelper to improve insert performance by 40% |
| private static DatabaseUtils.InsertHelper mCacheInserter; |
| private static int mCacheUrlColIndex; |
| private static int mCacheFilePathColIndex; |
| private static int mCacheLastModifyColIndex; |
| private static int mCacheETagColIndex; |
| private static int mCacheExpiresColIndex; |
| private static int mCacheExpiresStringColIndex; |
| private static int mCacheMimeTypeColIndex; |
| private static int mCacheEncodingColIndex; |
| private static int mCacheHttpStatusColIndex; |
| private static int mCacheLocationColIndex; |
| private static int mCacheContentLengthColIndex; |
| private static int mCacheContentDispositionColIndex; |
| private static int mCacheCrossDomainColIndex; |
| |
| private static int mCacheTransactionRefcount; |
| |
| private WebViewDatabase() { |
| // Singleton only, use getInstance() |
| } |
| |
| public static synchronized WebViewDatabase getInstance(Context context) { |
| if (mInstance == null) { |
| mInstance = new WebViewDatabase(); |
| try { |
| mDatabase = context |
| .openOrCreateDatabase(DATABASE_FILE, 0, null); |
| } catch (SQLiteException e) { |
| // try again by deleting the old db and create a new one |
| if (context.deleteDatabase(DATABASE_FILE)) { |
| mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, |
| null); |
| } |
| } |
| |
| // mDatabase should not be null, |
| // the only case is RequestAPI test has problem to create db |
| if (mDatabase != null && mDatabase.getVersion() != DATABASE_VERSION) { |
| mDatabase.beginTransaction(); |
| try { |
| upgradeDatabase(); |
| mDatabase.setTransactionSuccessful(); |
| } finally { |
| mDatabase.endTransaction(); |
| } |
| } |
| |
| if (mDatabase != null) { |
| // use per table Mutex lock, turn off database lock, this |
| // improves performance as database's ReentrantLock is expansive |
| mDatabase.setLockingEnabled(false); |
| } |
| |
| try { |
| mCacheDatabase = context.openOrCreateDatabase( |
| CACHE_DATABASE_FILE, 0, null); |
| } catch (SQLiteException e) { |
| // try again by deleting the old db and create a new one |
| if (context.deleteDatabase(CACHE_DATABASE_FILE)) { |
| mCacheDatabase = context.openOrCreateDatabase( |
| CACHE_DATABASE_FILE, 0, null); |
| } |
| } |
| |
| // mCacheDatabase should not be null, |
| // the only case is RequestAPI test has problem to create db |
| if (mCacheDatabase != null |
| && mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) { |
| mCacheDatabase.beginTransaction(); |
| try { |
| upgradeCacheDatabase(); |
| bootstrapCacheDatabase(); |
| mCacheDatabase.setTransactionSuccessful(); |
| } finally { |
| mCacheDatabase.endTransaction(); |
| } |
| // Erase the files from the file system in the |
| // case that the database was updated and the |
| // there were existing cache content |
| CacheManager.removeAllCacheFiles(); |
| } |
| |
| if (mCacheDatabase != null) { |
| // use read_uncommitted to speed up READ |
| mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;"); |
| // as only READ can be called in the non-WebViewWorkerThread, |
| // and read_uncommitted is used, we can turn off database lock |
| // to use transaction. |
| mCacheDatabase.setLockingEnabled(false); |
| |
| // use InsertHelper for faster insertion |
| mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase, |
| "cache"); |
| mCacheUrlColIndex = mCacheInserter |
| .getColumnIndex(CACHE_URL_COL); |
| mCacheFilePathColIndex = mCacheInserter |
| .getColumnIndex(CACHE_FILE_PATH_COL); |
| mCacheLastModifyColIndex = mCacheInserter |
| .getColumnIndex(CACHE_LAST_MODIFY_COL); |
| mCacheETagColIndex = mCacheInserter |
| .getColumnIndex(CACHE_ETAG_COL); |
| mCacheExpiresColIndex = mCacheInserter |
| .getColumnIndex(CACHE_EXPIRES_COL); |
| mCacheExpiresStringColIndex = mCacheInserter |
| .getColumnIndex(CACHE_EXPIRES_STRING_COL); |
| mCacheMimeTypeColIndex = mCacheInserter |
| .getColumnIndex(CACHE_MIMETYPE_COL); |
| mCacheEncodingColIndex = mCacheInserter |
| .getColumnIndex(CACHE_ENCODING_COL); |
| mCacheHttpStatusColIndex = mCacheInserter |
| .getColumnIndex(CACHE_HTTP_STATUS_COL); |
| mCacheLocationColIndex = mCacheInserter |
| .getColumnIndex(CACHE_LOCATION_COL); |
| mCacheContentLengthColIndex = mCacheInserter |
| .getColumnIndex(CACHE_CONTENTLENGTH_COL); |
| mCacheContentDispositionColIndex = mCacheInserter |
| .getColumnIndex(CACHE_CONTENTDISPOSITION_COL); |
| mCacheCrossDomainColIndex = mCacheInserter |
| .getColumnIndex(CACHE_CROSSDOMAIN_COL); |
| } |
| } |
| |
| return mInstance; |
| } |
| |
| private static void upgradeDatabase() { |
| int oldVersion = mDatabase.getVersion(); |
| if (oldVersion != 0) { |
| Log.i(LOGTAG, "Upgrading database from version " |
| + oldVersion + " to " |
| + DATABASE_VERSION + ", which will destroy old data"); |
| } |
| boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION; |
| boolean justAuth = 9 == oldVersion && 10 == DATABASE_VERSION; |
| if (justAuth) { |
| mDatabase.execSQL("DROP TABLE IF EXISTS " |
| + mTableNames[TABLE_HTTPAUTH_ID]); |
| mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] |
| + " (" + ID_COL + " INTEGER PRIMARY KEY, " |
| + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL |
| + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " |
| + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" |
| + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL |
| + ") ON CONFLICT REPLACE);"); |
| return; |
| } |
| |
| if (!justPasswords) { |
| mDatabase.execSQL("DROP TABLE IF EXISTS " |
| + mTableNames[TABLE_COOKIES_ID]); |
| mDatabase.execSQL("DROP TABLE IF EXISTS cache"); |
| mDatabase.execSQL("DROP TABLE IF EXISTS " |
| + mTableNames[TABLE_FORMURL_ID]); |
| mDatabase.execSQL("DROP TABLE IF EXISTS " |
| + mTableNames[TABLE_FORMDATA_ID]); |
| mDatabase.execSQL("DROP TABLE IF EXISTS " |
| + mTableNames[TABLE_HTTPAUTH_ID]); |
| } |
| mDatabase.execSQL("DROP TABLE IF EXISTS " |
| + mTableNames[TABLE_PASSWORD_ID]); |
| |
| mDatabase.setVersion(DATABASE_VERSION); |
| |
| if (!justPasswords) { |
| // cookies |
| mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID] |
| + " (" + ID_COL + " INTEGER PRIMARY KEY, " |
| + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL |
| + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, " |
| + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL |
| + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");"); |
| mDatabase.execSQL("CREATE INDEX cookiesIndex ON " |
| + mTableNames[TABLE_COOKIES_ID] + " (path)"); |
| |
| // formurl |
| mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] |
| + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL |
| + " TEXT" + ");"); |
| |
| // formdata |
| mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] |
| + " (" + ID_COL + " INTEGER PRIMARY KEY, " |
| + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL |
| + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" |
| + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " |
| + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); |
| |
| // httpauth |
| mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] |
| + " (" + ID_COL + " INTEGER PRIMARY KEY, " |
| + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL |
| + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " |
| + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" |
| + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL |
| + ") ON CONFLICT REPLACE);"); |
| } |
| // passwords |
| mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID] |
| + " (" + ID_COL + " INTEGER PRIMARY KEY, " |
| + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL |
| + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE (" |
| + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL |
| + ") ON CONFLICT REPLACE);"); |
| } |
| |
| private static void upgradeCacheDatabase() { |
| int oldVersion = mCacheDatabase.getVersion(); |
| if (oldVersion != 0) { |
| Log.i(LOGTAG, "Upgrading cache database from version " |
| + oldVersion + " to " |
| + DATABASE_VERSION + ", which will destroy all old data"); |
| } |
| mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache"); |
| mCacheDatabase.setVersion(CACHE_DATABASE_VERSION); |
| } |
| |
| private static void bootstrapCacheDatabase() { |
| if (mCacheDatabase != null) { |
| mCacheDatabase.execSQL("CREATE TABLE cache" |
| + " (" + ID_COL + " INTEGER PRIMARY KEY, " + CACHE_URL_COL |
| + " TEXT, " + CACHE_FILE_PATH_COL + " TEXT, " |
| + CACHE_LAST_MODIFY_COL + " TEXT, " + CACHE_ETAG_COL |
| + " TEXT, " + CACHE_EXPIRES_COL + " INTEGER, " |
| + CACHE_EXPIRES_STRING_COL + " TEXT, " |
| + CACHE_MIMETYPE_COL + " TEXT, " + CACHE_ENCODING_COL |
| + " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, " |
| + CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL |
| + " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, " |
| + CACHE_CROSSDOMAIN_COL + " TEXT," |
| + " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);"); |
| mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache (" |
| + CACHE_URL_COL + ")"); |
| } |
| } |
| |
| private boolean hasEntries(int tableId) { |
| if (mDatabase == null) { |
| return false; |
| } |
| |
| Cursor cursor = null; |
| boolean ret = false; |
| try { |
| cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION, |
| null, null, null, null, null); |
| ret = cursor.moveToFirst() == true; |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "hasEntries", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return ret; |
| } |
| |
| // |
| // cookies functions |
| // |
| |
| /** |
| * Get cookies in the format of CookieManager.Cookie inside an ArrayList for |
| * a given domain |
| * |
| * @return ArrayList<Cookie> If nothing is found, return an empty list. |
| */ |
| ArrayList<Cookie> getCookiesForDomain(String domain) { |
| ArrayList<Cookie> list = new ArrayList<Cookie>(); |
| if (domain == null || mDatabase == null) { |
| return list; |
| } |
| |
| synchronized (mCookieLock) { |
| final String[] columns = new String[] { |
| ID_COL, COOKIES_DOMAIN_COL, COOKIES_PATH_COL, |
| COOKIES_NAME_COL, COOKIES_VALUE_COL, COOKIES_EXPIRES_COL, |
| COOKIES_SECURE_COL |
| }; |
| final String selection = "(" + COOKIES_DOMAIN_COL |
| + " GLOB '*' || ?)"; |
| Cursor cursor = null; |
| try { |
| cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID], |
| columns, selection, new String[] { domain }, null, null, |
| null); |
| if (cursor.moveToFirst()) { |
| int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL); |
| int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL); |
| int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL); |
| int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL); |
| int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL); |
| int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL); |
| do { |
| Cookie cookie = new Cookie(); |
| cookie.domain = cursor.getString(domainCol); |
| cookie.path = cursor.getString(pathCol); |
| cookie.name = cursor.getString(nameCol); |
| cookie.value = cursor.getString(valueCol); |
| if (cursor.isNull(expiresCol)) { |
| cookie.expires = -1; |
| } else { |
| cookie.expires = cursor.getLong(expiresCol); |
| } |
| cookie.secure = cursor.getShort(secureCol) != 0; |
| cookie.mode = Cookie.MODE_NORMAL; |
| list.add(cookie); |
| } while (cursor.moveToNext()); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getCookiesForDomain", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return list; |
| } |
| } |
| |
| /** |
| * Delete cookies which matches (domain, path, name). |
| * |
| * @param domain If it is null, nothing happens. |
| * @param path If it is null, all the cookies match (domain) will be |
| * deleted. |
| * @param name If it is null, all the cookies match (domain, path) will be |
| * deleted. |
| */ |
| void deleteCookies(String domain, String path, String name) { |
| if (domain == null || mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mCookieLock) { |
| final String where = "(" + COOKIES_DOMAIN_COL + " == ?) AND (" |
| + COOKIES_PATH_COL + " == ?) AND (" + COOKIES_NAME_COL |
| + " == ?)"; |
| mDatabase.delete(mTableNames[TABLE_COOKIES_ID], where, |
| new String[] { domain, path, name }); |
| } |
| } |
| |
| /** |
| * Add a cookie to the database |
| * |
| * @param cookie |
| */ |
| void addCookie(Cookie cookie) { |
| if (cookie.domain == null || cookie.path == null || cookie.name == null |
| || mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mCookieLock) { |
| ContentValues cookieVal = new ContentValues(); |
| cookieVal.put(COOKIES_DOMAIN_COL, cookie.domain); |
| cookieVal.put(COOKIES_PATH_COL, cookie.path); |
| cookieVal.put(COOKIES_NAME_COL, cookie.name); |
| cookieVal.put(COOKIES_VALUE_COL, cookie.value); |
| if (cookie.expires != -1) { |
| cookieVal.put(COOKIES_EXPIRES_COL, cookie.expires); |
| } |
| cookieVal.put(COOKIES_SECURE_COL, cookie.secure); |
| mDatabase.insert(mTableNames[TABLE_COOKIES_ID], null, cookieVal); |
| } |
| } |
| |
| /** |
| * Whether there is any cookies in the database |
| * |
| * @return TRUE if there is cookie. |
| */ |
| boolean hasCookies() { |
| synchronized (mCookieLock) { |
| return hasEntries(TABLE_COOKIES_ID); |
| } |
| } |
| |
| /** |
| * Clear cookie database |
| */ |
| void clearCookies() { |
| if (mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mCookieLock) { |
| mDatabase.delete(mTableNames[TABLE_COOKIES_ID], null, null); |
| } |
| } |
| |
| /** |
| * Clear session cookies, which means cookie doesn't have EXPIRES. |
| */ |
| void clearSessionCookies() { |
| if (mDatabase == null) { |
| return; |
| } |
| |
| final String sessionExpired = COOKIES_EXPIRES_COL + " ISNULL"; |
| synchronized (mCookieLock) { |
| mDatabase.delete(mTableNames[TABLE_COOKIES_ID], sessionExpired, |
| null); |
| } |
| } |
| |
| /** |
| * Clear expired cookies |
| * |
| * @param now Time for now |
| */ |
| void clearExpiredCookies(long now) { |
| if (mDatabase == null) { |
| return; |
| } |
| |
| final String expires = COOKIES_EXPIRES_COL + " <= ?"; |
| synchronized (mCookieLock) { |
| mDatabase.delete(mTableNames[TABLE_COOKIES_ID], expires, |
| new String[] { Long.toString(now) }); |
| } |
| } |
| |
| // |
| // cache functions |
| // |
| |
| // only called from WebViewWorkerThread |
| boolean startCacheTransaction() { |
| if (++mCacheTransactionRefcount == 1) { |
| if (!Thread.currentThread().equals( |
| WebViewWorker.getHandler().getLooper().getThread())) { |
| Log.w(LOGTAG, "startCacheTransaction should be called from " |
| + "WebViewWorkerThread instead of from " |
| + Thread.currentThread().getName()); |
| } |
| mCacheDatabase.beginTransaction(); |
| return true; |
| } |
| return false; |
| } |
| |
| // only called from WebViewWorkerThread |
| boolean endCacheTransaction() { |
| if (--mCacheTransactionRefcount == 0) { |
| if (!Thread.currentThread().equals( |
| WebViewWorker.getHandler().getLooper().getThread())) { |
| Log.w(LOGTAG, "endCacheTransaction should be called from " |
| + "WebViewWorkerThread instead of from " |
| + Thread.currentThread().getName()); |
| } |
| try { |
| mCacheDatabase.setTransactionSuccessful(); |
| } finally { |
| mCacheDatabase.endTransaction(); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Get a cache item. |
| * |
| * @param url The url |
| * @return CacheResult The CacheManager.CacheResult |
| */ |
| CacheResult getCache(String url) { |
| if (url == null || mCacheDatabase == null) { |
| return null; |
| } |
| |
| Cursor cursor = null; |
| final String query = "SELECT filepath, lastmodify, etag, expires, " |
| + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, " |
| + "contentdisposition, crossdomain FROM cache WHERE url = ?"; |
| try { |
| cursor = mCacheDatabase.rawQuery(query, new String[] { url }); |
| if (cursor.moveToFirst()) { |
| CacheResult ret = new CacheResult(); |
| ret.localPath = cursor.getString(0); |
| ret.lastModified = cursor.getString(1); |
| ret.etag = cursor.getString(2); |
| ret.expires = cursor.getLong(3); |
| ret.expiresString = cursor.getString(4); |
| ret.mimeType = cursor.getString(5); |
| ret.encoding = cursor.getString(6); |
| ret.httpStatusCode = cursor.getInt(7); |
| ret.location = cursor.getString(8); |
| ret.contentLength = cursor.getLong(9); |
| ret.contentdisposition = cursor.getString(10); |
| ret.crossDomain = cursor.getString(11); |
| return ret; |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getCache", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return null; |
| } |
| |
| /** |
| * Remove a cache item. |
| * |
| * @param url The url |
| */ |
| void removeCache(String url) { |
| if (url == null || mCacheDatabase == null) { |
| return; |
| } |
| |
| mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url }); |
| } |
| |
| /** |
| * Add or update a cache. CACHE_URL_COL is unique in the table. |
| * |
| * @param url The url |
| * @param c The CacheManager.CacheResult |
| */ |
| void addCache(String url, CacheResult c) { |
| if (url == null || mCacheDatabase == null) { |
| return; |
| } |
| |
| mCacheInserter.prepareForInsert(); |
| mCacheInserter.bind(mCacheUrlColIndex, url); |
| mCacheInserter.bind(mCacheFilePathColIndex, c.localPath); |
| mCacheInserter.bind(mCacheLastModifyColIndex, c.lastModified); |
| mCacheInserter.bind(mCacheETagColIndex, c.etag); |
| mCacheInserter.bind(mCacheExpiresColIndex, c.expires); |
| mCacheInserter.bind(mCacheExpiresStringColIndex, c.expiresString); |
| mCacheInserter.bind(mCacheMimeTypeColIndex, c.mimeType); |
| mCacheInserter.bind(mCacheEncodingColIndex, c.encoding); |
| mCacheInserter.bind(mCacheHttpStatusColIndex, c.httpStatusCode); |
| mCacheInserter.bind(mCacheLocationColIndex, c.location); |
| mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength); |
| mCacheInserter.bind(mCacheContentDispositionColIndex, |
| c.contentdisposition); |
| mCacheInserter.bind(mCacheCrossDomainColIndex, c.crossDomain); |
| mCacheInserter.execute(); |
| } |
| |
| /** |
| * Clear cache database |
| */ |
| void clearCache() { |
| if (mCacheDatabase == null) { |
| return; |
| } |
| |
| mCacheDatabase.delete("cache", null, null); |
| } |
| |
| boolean hasCache() { |
| if (mCacheDatabase == null) { |
| return false; |
| } |
| |
| Cursor cursor = null; |
| boolean ret = false; |
| try { |
| cursor = mCacheDatabase.query("cache", ID_PROJECTION, |
| null, null, null, null, null); |
| ret = cursor.moveToFirst() == true; |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "hasCache", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return ret; |
| } |
| |
| long getCacheTotalSize() { |
| if (mCacheDatabase == null) { |
| return 0; |
| } |
| long size = 0; |
| Cursor cursor = null; |
| final String query = "SELECT SUM(contentlength) as sum FROM cache"; |
| try { |
| cursor = mCacheDatabase.rawQuery(query, null); |
| if (cursor.moveToFirst()) { |
| size = cursor.getLong(0); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getCacheTotalSize", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return size; |
| } |
| |
| List<String> trimCache(long amount) { |
| ArrayList<String> pathList = new ArrayList<String>(100); |
| Cursor cursor = null; |
| final String query = "SELECT contentlength, filepath FROM cache ORDER BY expires ASC"; |
| try { |
| cursor = mCacheDatabase.rawQuery(query, null); |
| if (cursor.moveToFirst()) { |
| int batchSize = 100; |
| StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize); |
| pathStr.append("DELETE FROM cache WHERE filepath IN (?"); |
| for (int i = 1; i < batchSize; i++) { |
| pathStr.append(", ?"); |
| } |
| pathStr.append(")"); |
| SQLiteStatement statement = null; |
| try { |
| statement = mCacheDatabase.compileStatement( |
| pathStr.toString()); |
| // as bindString() uses 1-based index, initialize index to 1 |
| int index = 1; |
| do { |
| long length = cursor.getLong(0); |
| if (length == 0) { |
| continue; |
| } |
| amount -= length; |
| String filePath = cursor.getString(1); |
| statement.bindString(index, filePath); |
| pathList.add(filePath); |
| if (index++ == batchSize) { |
| statement.execute(); |
| statement.clearBindings(); |
| index = 1; |
| } |
| } while (cursor.moveToNext() && amount > 0); |
| if (index > 1) { |
| // there may be old bindings from the previous statement |
| // if index is less than batchSize, which is Ok. |
| statement.execute(); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "trimCache SQLiteStatement", e); |
| } finally { |
| if (statement != null) statement.close(); |
| } |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "trimCache Cursor", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return pathList; |
| } |
| |
| List<String> getAllCacheFileNames() { |
| ArrayList<String> pathList = null; |
| Cursor cursor = null; |
| try { |
| cursor = mCacheDatabase.rawQuery("SELECT filepath FROM cache", |
| null); |
| if (cursor != null && cursor.moveToFirst()) { |
| pathList = new ArrayList<String>(cursor.getCount()); |
| do { |
| pathList.add(cursor.getString(0)); |
| } while (cursor.moveToNext()); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getAllCacheFileNames", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return pathList; |
| } |
| |
| // |
| // password functions |
| // |
| |
| /** |
| * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique. |
| * |
| * @param schemePlusHost The scheme and host for the password |
| * @param username The username for the password. If it is null, it means |
| * password can't be saved. |
| * @param password The password |
| */ |
| void setUsernamePassword(String schemePlusHost, String username, |
| String password) { |
| if (schemePlusHost == null || mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mPasswordLock) { |
| final ContentValues c = new ContentValues(); |
| c.put(PASSWORD_HOST_COL, schemePlusHost); |
| c.put(PASSWORD_USERNAME_COL, username); |
| c.put(PASSWORD_PASSWORD_COL, password); |
| mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL, |
| c); |
| } |
| } |
| |
| /** |
| * Retrieve the username and password for a given host |
| * |
| * @param schemePlusHost The scheme and host which passwords applies to |
| * @return String[] if found, String[0] is username, which can be null and |
| * String[1] is password. Return null if it can't find anything. |
| */ |
| String[] getUsernamePassword(String schemePlusHost) { |
| if (schemePlusHost == null || mDatabase == null) { |
| return null; |
| } |
| |
| final String[] columns = new String[] { |
| PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL |
| }; |
| final String selection = "(" + PASSWORD_HOST_COL + " == ?)"; |
| synchronized (mPasswordLock) { |
| String[] ret = null; |
| Cursor cursor = null; |
| try { |
| cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID], |
| columns, selection, new String[] { schemePlusHost }, null, |
| null, null); |
| if (cursor.moveToFirst()) { |
| ret = new String[2]; |
| ret[0] = cursor.getString( |
| cursor.getColumnIndex(PASSWORD_USERNAME_COL)); |
| ret[1] = cursor.getString( |
| cursor.getColumnIndex(PASSWORD_PASSWORD_COL)); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getUsernamePassword", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return ret; |
| } |
| } |
| |
| /** |
| * Find out if there are any passwords saved. |
| * |
| * @return TRUE if there is passwords saved |
| */ |
| public boolean hasUsernamePassword() { |
| synchronized (mPasswordLock) { |
| return hasEntries(TABLE_PASSWORD_ID); |
| } |
| } |
| |
| /** |
| * Clear password database |
| */ |
| public void clearUsernamePassword() { |
| if (mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mPasswordLock) { |
| mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null); |
| } |
| } |
| |
| // |
| // http authentication password functions |
| // |
| |
| /** |
| * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, |
| * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique. |
| * |
| * @param host The host for the password |
| * @param realm The realm for the password |
| * @param username The username for the password. If it is null, it means |
| * password can't be saved. |
| * @param password The password |
| */ |
| void setHttpAuthUsernamePassword(String host, String realm, String username, |
| String password) { |
| if (host == null || realm == null || mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mHttpAuthLock) { |
| final ContentValues c = new ContentValues(); |
| c.put(HTTPAUTH_HOST_COL, host); |
| c.put(HTTPAUTH_REALM_COL, realm); |
| c.put(HTTPAUTH_USERNAME_COL, username); |
| c.put(HTTPAUTH_PASSWORD_COL, password); |
| mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL, |
| c); |
| } |
| } |
| |
| /** |
| * Retrieve the HTTP authentication username and password for a given |
| * host+realm pair |
| * |
| * @param host The host the password applies to |
| * @param realm The realm the password applies to |
| * @return String[] if found, String[0] is username, which can be null and |
| * String[1] is password. Return null if it can't find anything. |
| */ |
| String[] getHttpAuthUsernamePassword(String host, String realm) { |
| if (host == null || realm == null || mDatabase == null){ |
| return null; |
| } |
| |
| final String[] columns = new String[] { |
| HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL |
| }; |
| final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND (" |
| + HTTPAUTH_REALM_COL + " == ?)"; |
| synchronized (mHttpAuthLock) { |
| String[] ret = null; |
| Cursor cursor = null; |
| try { |
| cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID], |
| columns, selection, new String[] { host, realm }, null, |
| null, null); |
| if (cursor.moveToFirst()) { |
| ret = new String[2]; |
| ret[0] = cursor.getString( |
| cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)); |
| ret[1] = cursor.getString( |
| cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getHttpAuthUsernamePassword", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return ret; |
| } |
| } |
| |
| /** |
| * Find out if there are any HTTP authentication passwords saved. |
| * |
| * @return TRUE if there are passwords saved |
| */ |
| public boolean hasHttpAuthUsernamePassword() { |
| synchronized (mHttpAuthLock) { |
| return hasEntries(TABLE_HTTPAUTH_ID); |
| } |
| } |
| |
| /** |
| * Clear HTTP authentication password database |
| */ |
| public void clearHttpAuthUsernamePassword() { |
| if (mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mHttpAuthLock) { |
| mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null); |
| } |
| } |
| |
| // |
| // form data functions |
| // |
| |
| /** |
| * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL, |
| * FORMDATA_VALUE_COL) is unique |
| * |
| * @param url The url of the site |
| * @param formdata The form data in HashMap |
| */ |
| void setFormData(String url, HashMap<String, String> formdata) { |
| if (url == null || formdata == null || mDatabase == null) { |
| return; |
| } |
| |
| final String selection = "(" + FORMURL_URL_COL + " == ?)"; |
| synchronized (mFormLock) { |
| long urlid = -1; |
| Cursor cursor = null; |
| try { |
| cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], |
| ID_PROJECTION, selection, new String[] { url }, null, null, |
| null); |
| if (cursor.moveToFirst()) { |
| urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); |
| } else { |
| ContentValues c = new ContentValues(); |
| c.put(FORMURL_URL_COL, url); |
| urlid = mDatabase.insert( |
| mTableNames[TABLE_FORMURL_ID], null, c); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "setFormData", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| if (urlid >= 0) { |
| Set<Entry<String, String>> set = formdata.entrySet(); |
| Iterator<Entry<String, String>> iter = set.iterator(); |
| ContentValues map = new ContentValues(); |
| map.put(FORMDATA_URLID_COL, urlid); |
| while (iter.hasNext()) { |
| Entry<String, String> entry = iter.next(); |
| map.put(FORMDATA_NAME_COL, entry.getKey()); |
| map.put(FORMDATA_VALUE_COL, entry.getValue()); |
| mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Get all the values for a form entry with "name" in a given site |
| * |
| * @param url The url of the site |
| * @param name The name of the form entry |
| * @return A list of values. Return empty list if nothing is found. |
| */ |
| ArrayList<String> getFormData(String url, String name) { |
| ArrayList<String> values = new ArrayList<String>(); |
| if (url == null || name == null || mDatabase == null) { |
| return values; |
| } |
| |
| final String urlSelection = "(" + FORMURL_URL_COL + " == ?)"; |
| final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND (" |
| + FORMDATA_NAME_COL + " == ?)"; |
| synchronized (mFormLock) { |
| Cursor cursor = null; |
| try { |
| cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], |
| ID_PROJECTION, urlSelection, new String[] { url }, null, |
| null, null); |
| if (cursor.moveToFirst()) { |
| long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); |
| Cursor dataCursor = null; |
| try { |
| dataCursor = mDatabase.query( |
| mTableNames[TABLE_FORMDATA_ID], |
| new String[] { ID_COL, FORMDATA_VALUE_COL }, |
| dataSelection, |
| new String[] { Long.toString(urlid), name }, |
| null, null, null); |
| if (dataCursor.moveToFirst()) { |
| int valueCol = dataCursor.getColumnIndex( |
| FORMDATA_VALUE_COL); |
| do { |
| values.add(dataCursor.getString(valueCol)); |
| } while (dataCursor.moveToNext()); |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getFormData dataCursor", e); |
| } finally { |
| if (dataCursor != null) dataCursor.close(); |
| } |
| } |
| } catch (IllegalStateException e) { |
| Log.e(LOGTAG, "getFormData cursor", e); |
| } finally { |
| if (cursor != null) cursor.close(); |
| } |
| return values; |
| } |
| } |
| |
| /** |
| * Find out if there is form data saved. |
| * |
| * @return TRUE if there is form data in the database |
| */ |
| public boolean hasFormData() { |
| synchronized (mFormLock) { |
| return hasEntries(TABLE_FORMURL_ID); |
| } |
| } |
| |
| /** |
| * Clear form database |
| */ |
| public void clearFormData() { |
| if (mDatabase == null) { |
| return; |
| } |
| |
| synchronized (mFormLock) { |
| mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null); |
| mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null); |
| } |
| } |
| } |