/* ** ** Copyright 2008, 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, ** See the License for the specific language governing permissions and ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** 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.provider.CalendarContract.CalendarMetaData; /** * The global meta-data used for expanding the Instances table is stored in one * row of the "CalendarMetaData" table. This class is used for caching those * values to avoid repeatedly banging on the database. It is also used * for writing the values back to the database, while maintaining the * consistency of the cache. * <p> * TODO: there must be only one of these active within CalendarProvider. Enforce this. */ public class MetaData { /** * These fields are updated atomically with the database. * If fields are added or removed from the CalendarMetaData table, those * changes must also be reflected here. */ public class Fields { public String timezone; // local timezone used for Instance expansion public long minInstance; // UTC millis public long maxInstance; // UTC millis } /** * The cached copy of the meta-data fields from the database. */ private Fields mFields = new Fields(); private final SQLiteOpenHelper mOpenHelper; private boolean mInitialized; /** * The column names in the CalendarMetaData table. This projection * must contain all of the columns. */ private static final String[] sCalendarMetaDataProjection = { CalendarMetaData.LOCAL_TIMEZONE, CalendarMetaData.MIN_INSTANCE, CalendarMetaData.MAX_INSTANCE}; private static final int METADATA_INDEX_LOCAL_TIMEZONE = 0; private static final int METADATA_INDEX_MIN_INSTANCE = 1; private static final int METADATA_INDEX_MAX_INSTANCE = 2; public MetaData(SQLiteOpenHelper openHelper) { mOpenHelper = openHelper; } /** * Returns a copy of all the MetaData fields. This method grabs a * database lock to read all the fields atomically. * * @return a copy of all the MetaData fields. */ public Fields getFields() { Fields fields = new Fields(); SQLiteDatabase db = mOpenHelper.getReadableDatabase(); db.beginTransaction(); try { // If the fields have not been initialized from the database, // then read the database. if (!mInitialized) { readLocked(db); } fields.timezone = mFields.timezone; fields.minInstance = mFields.minInstance; fields.maxInstance = mFields.maxInstance; db.setTransactionSuccessful(); } finally { db.endTransaction(); } return fields; } /** * This method must be called only while holding a database lock. * * <p> * Returns a copy of all the MetaData fields. This method assumes * the database lock has already been acquired. * </p> * * @return a copy of all the MetaData fields. */ public Fields getFieldsLocked() { Fields fields = new Fields(); // If the fields have not been initialized from the database, // then read the database. if (!mInitialized) { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); readLocked(db); } fields.timezone = mFields.timezone; fields.minInstance = mFields.minInstance; fields.maxInstance = mFields.maxInstance; return fields; } /** * Reads the meta-data for the CalendarProvider from the database and * updates the member variables. This method executes while the database * lock is held. If there were no exceptions reading the database, * mInitialized is set to true. */ private void readLocked(SQLiteDatabase db) { String timezone = null; long minInstance = 0, maxInstance = 0; // Read the database directly. We only do this once to initialize // the members of this class. Cursor cursor = db.query("CalendarMetaData", sCalendarMetaDataProjection, null, null, null, null, null); try { if (cursor.moveToNext()) { timezone = cursor.getString(METADATA_INDEX_LOCAL_TIMEZONE); minInstance = cursor.getLong(METADATA_INDEX_MIN_INSTANCE); maxInstance = cursor.getLong(METADATA_INDEX_MAX_INSTANCE); } } finally { if (cursor != null) { cursor.close(); } } // Cache the result of reading the database mFields.timezone = timezone; mFields.minInstance = minInstance; mFields.maxInstance = maxInstance; // Mark the fields as initialized mInitialized = true; } /** * Writes the meta-data for the CalendarProvider. The values to write are * passed in as parameters. All of the values are updated atomically, * including the cached copy of the meta-data. * * @param timezone the local timezone used for Instance expansion * @param begin the start of the Instance expansion in UTC milliseconds * @param end the end of the Instance expansion in UTC milliseconds * @param startDay the start of the BusyBit expansion (the start Julian day) * @param endDay the end of the BusyBit expansion (the end Julian day) */ public void write(String timezone, long begin, long end, int startDay, int endDay) { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); db.beginTransaction(); try { writeLocked(timezone, begin, end); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } /** * This method must be called only while holding a database lock. * * <p> * Writes the meta-data for the CalendarProvider. The values to write are * passed in as parameters. All of the values are updated atomically, * including the cached copy of the meta-data. * </p> * * @param timezone the local timezone used for Instance expansion * @param begin the start of the Instance expansion in UTC milliseconds * @param end the end of the Instance expansion in UTC milliseconds */ public void writeLocked(String timezone, long begin, long end) { ContentValues values = new ContentValues(); values.put("_id", 1); values.put(CalendarMetaData.LOCAL_TIMEZONE, timezone); values.put(CalendarMetaData.MIN_INSTANCE, begin); values.put(CalendarMetaData.MAX_INSTANCE, end); // Atomically update the database and the cached members. try { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.replace("CalendarMetaData", null, values); } catch (RuntimeException e) { // Failed: zero the in-memory fields to force recomputation. mFields.timezone = null; mFields.minInstance = mFields.maxInstance = 0; throw e; } // Update the cached members last in case the database update fails mFields.timezone = timezone; mFields.minInstance = begin; mFields.maxInstance = end; } /** * Clears the time range for the Instances table. The rows in the * Instances table will be deleted (and regenerated) the next time * that the Instances table is queried. * * Also clears the time range for the BusyBits table because that depends * on the Instances table. */ public void clearInstanceRange() { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); db.beginTransaction(); try { // If the fields have not been initialized from the database, // then read the database. if (!mInitialized) { readLocked(db); } writeLocked(mFields.timezone, 0 /* begin */, 0 /* end */); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } }