blob: 443d173d514c2b8a20ab54e08b5820e97d537075 [file] [log] [blame]
/*
* 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;
}
}