| /* |
| * Copyright (C) 2010 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 com.android.providers.calendar; |
| |
| import android.content.ContentValues; |
| import android.database.Cursor; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.database.sqlite.SQLiteOpenHelper; |
| import android.util.Log; |
| import com.google.common.annotations.VisibleForTesting; |
| |
| import java.util.TimeZone; |
| |
| /** |
| * Class for managing a persistent Cache of (key, value) pairs. The persistent storage used is |
| * a SQLite database. |
| */ |
| public class CalendarCache { |
| private static final String TAG = "CalendarCache"; |
| |
| public static final String DATABASE_NAME = "CalendarCache"; |
| |
| public static final String KEY_TIMEZONE_DATABASE_VERSION = "timezoneDatabaseVersion"; |
| public static final String DEFAULT_TIMEZONE_DATABASE_VERSION = "2009s"; |
| |
| public static final String KEY_TIMEZONE_TYPE = "timezoneType"; |
| public static final String TIMEZONE_TYPE_AUTO = "auto"; |
| public static final String TIMEZONE_TYPE_HOME = "home"; |
| |
| public static final String KEY_TIMEZONE_INSTANCES = "timezoneInstances"; |
| public static final String KEY_TIMEZONE_INSTANCES_PREVIOUS = "timezoneInstancesPrevious"; |
| |
| public static final String COLUMN_NAME_ID = "_id"; |
| public static final String COLUMN_NAME_KEY = "key"; |
| public static final String COLUMN_NAME_VALUE = "value"; |
| |
| private static final String[] sProjection = { |
| COLUMN_NAME_KEY, |
| COLUMN_NAME_VALUE |
| }; |
| |
| private static final int COLUMN_INDEX_KEY = 0; |
| private static final int COLUMN_INDEX_VALUE = 1; |
| |
| private final SQLiteOpenHelper mOpenHelper; |
| |
| /** |
| * This exception is thrown when the cache encounter a null key or a null database reference |
| */ |
| public static class CacheException extends Exception { |
| public CacheException() { |
| } |
| |
| public CacheException(String detailMessage) { |
| super(detailMessage); |
| } |
| } |
| |
| public CalendarCache(SQLiteOpenHelper openHelper) { |
| mOpenHelper = openHelper; |
| } |
| |
| public void writeTimezoneDatabaseVersion(String timezoneDatabaseVersion) throws CacheException { |
| writeData(KEY_TIMEZONE_DATABASE_VERSION, timezoneDatabaseVersion); |
| } |
| |
| public String readTimezoneDatabaseVersion() { |
| try { |
| return readData(KEY_TIMEZONE_DATABASE_VERSION); |
| } catch (CacheException e) { |
| Log.e(TAG, "Could not read timezone database version from CalendarCache"); |
| } |
| return null; |
| } |
| |
| @VisibleForTesting |
| public void writeTimezoneType(String timezoneType) throws CacheException { |
| writeData(KEY_TIMEZONE_TYPE, timezoneType); |
| } |
| |
| public String readTimezoneType() { |
| try { |
| return readData(KEY_TIMEZONE_TYPE); |
| } catch (CacheException e) { |
| Log.e(TAG, "Cannot read timezone type from CalendarCache - using AUTO as default", e); |
| } |
| return TIMEZONE_TYPE_AUTO; |
| } |
| |
| public void writeTimezoneInstances(String timezone) { |
| try { |
| writeData(KEY_TIMEZONE_INSTANCES, timezone); |
| } catch (CacheException e) { |
| Log.e(TAG, "Cannot write instances timezone to CalendarCache"); |
| } |
| } |
| |
| public String readTimezoneInstances() { |
| try { |
| return readData(KEY_TIMEZONE_INSTANCES); |
| } catch (CacheException e) { |
| String localTimezone = TimeZone.getDefault().getID(); |
| Log.e(TAG, "Cannot read instances timezone from CalendarCache - using device one: " + |
| localTimezone, e); |
| return localTimezone; |
| } |
| } |
| |
| public void writeTimezoneInstancesPrevious(String timezone) { |
| try { |
| writeData(KEY_TIMEZONE_INSTANCES_PREVIOUS, timezone); |
| } catch (CacheException e) { |
| Log.e(TAG, "Cannot write previous instance timezone to CalendarCache"); |
| } |
| } |
| |
| public String readTimezoneInstancesPrevious() { |
| try { |
| return readData(KEY_TIMEZONE_INSTANCES_PREVIOUS); |
| } catch (CacheException e) { |
| Log.e(TAG, "Cannot read previous instances timezone from CalendarCache", e); |
| } |
| return null; |
| } |
| |
| /** |
| * Write a (key, value) pair in the Cache. |
| * |
| * @param key the key (must not be null) |
| * @param value the value (can be null) |
| * @throws CacheException when key is null |
| */ |
| public void writeData(String key, String value) throws CacheException { |
| SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| db.beginTransaction(); |
| try { |
| writeDataLocked(db, key, value); |
| db.setTransactionSuccessful(); |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.i(TAG, "Wrote (key, value) = [ " + key + ", " + value + "] "); |
| } |
| } finally { |
| db.endTransaction(); |
| } |
| } |
| |
| /** |
| * Write a (key, value) pair in the database used by the cache. This method should be called in |
| * a transaction. |
| * |
| * @param db the database (must not be null) |
| * @param key the key (must not be null) |
| * @param value the value |
| * @throws CacheException when key or database are null |
| */ |
| protected void writeDataLocked(SQLiteDatabase db, String key, String value) |
| throws CacheException { |
| if (null == db) { |
| throw new CacheException("Database cannot be null"); |
| } |
| if (null == key) { |
| throw new CacheException("Cannot use null key for write"); |
| } |
| |
| /* |
| * Storing the hash code of a String into the _id column carries a (very) small risk |
| * of weird behavior, because we're using it as a unique key, but hash codes aren't |
| * guaranteed to be unique. CalendarCache has a small set of keys that are known |
| * ahead of time, so we should be okay. |
| */ |
| ContentValues values = new ContentValues(); |
| values.put(COLUMN_NAME_ID, key.hashCode()); |
| values.put(COLUMN_NAME_KEY, key); |
| values.put(COLUMN_NAME_VALUE, value); |
| |
| db.replace(DATABASE_NAME, null /* null column hack */, values); |
| } |
| |
| /** |
| * Read a value from the database used by the cache and depending on a key. |
| * |
| * @param key the key from which we want the value (must not be null) |
| * @return the value that was found for the key. Can be null if no key has been found |
| * @throws CacheException when key is null |
| */ |
| public String readData(String key) throws CacheException { |
| SQLiteDatabase db = mOpenHelper.getReadableDatabase(); |
| return readDataLocked(db, key); |
| } |
| |
| /** |
| * Read a value from the database used by the cache and depending on a key. The database should |
| * be "readable" at minimum |
| * |
| * @param db the database (must not be null) |
| * @param key the key from which we want the value (must not be null) |
| * @return the value that was found for the key. Can be null if no value has been found for the |
| * key. |
| * @throws CacheException when key or database are null |
| */ |
| protected String readDataLocked(SQLiteDatabase db, String key) throws CacheException { |
| if (null == db) { |
| throw new CacheException("Database cannot be null"); |
| } |
| if (null == key) { |
| throw new CacheException("Cannot use null key for read"); |
| } |
| |
| String rowValue = null; |
| |
| Cursor cursor = db.query(DATABASE_NAME, sProjection, |
| COLUMN_NAME_KEY + "=?", new String[] { key }, null, null, null); |
| try { |
| if (cursor.moveToNext()) { |
| rowValue = cursor.getString(COLUMN_INDEX_VALUE); |
| } |
| else { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.i(TAG, "Could not find key = [ " + key + " ]"); |
| } |
| } |
| } finally { |
| cursor.close(); |
| cursor = null; |
| } |
| return rowValue; |
| } |
| } |