Home | History | Annotate | Download | only in calendar
      1 /*
      2 **
      3 ** Copyright 2008, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** See the License for the specific language governing permissions and
     14 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 ** limitations under the License.
     16 */
     17 
     18 package com.android.providers.calendar;
     19 
     20 
     21 import android.content.ContentValues;
     22 import android.database.Cursor;
     23 import android.database.sqlite.SQLiteDatabase;
     24 import android.database.sqlite.SQLiteOpenHelper;
     25 import android.provider.Calendar.CalendarMetaData;
     26 
     27 /**
     28  * The global meta-data used for expanding the Instances table is stored in one
     29  * row of the "CalendarMetaData" table.  This class is used for caching those
     30  * values to avoid repeatedly banging on the database.  It is also used
     31  * for writing the values back to the database, while maintaining the
     32  * consistency of the cache.
     33  */
     34 public class MetaData {
     35     /**
     36      * These fields are updated atomically with the database.
     37      * If fields are added or removed from the CalendarMetaData table, those
     38      * changes must also be reflected here.
     39      */
     40     public class Fields {
     41         public String timezone;     // local timezone used for Instance expansion
     42         public long minInstance;    // UTC millis
     43         public long maxInstance;    // UTC millis
     44     }
     45 
     46     /**
     47      * The cached copy of the meta-data fields from the database.
     48      */
     49     private Fields mFields = new Fields();
     50 
     51     private final SQLiteOpenHelper mOpenHelper;
     52     private boolean mInitialized;
     53 
     54     /**
     55      * The column names in the CalendarMetaData table.  This projection
     56      * must contain all of the columns.
     57      */
     58     private static final String[] sCalendarMetaDataProjection = {
     59         CalendarMetaData.LOCAL_TIMEZONE,
     60         CalendarMetaData.MIN_INSTANCE,
     61         CalendarMetaData.MAX_INSTANCE};
     62 
     63     private static final int METADATA_INDEX_LOCAL_TIMEZONE = 0;
     64     private static final int METADATA_INDEX_MIN_INSTANCE = 1;
     65     private static final int METADATA_INDEX_MAX_INSTANCE = 2;
     66 
     67     public MetaData(SQLiteOpenHelper openHelper) {
     68         mOpenHelper = openHelper;
     69     }
     70 
     71     /**
     72      * Returns a copy of all the MetaData fields.  This method grabs a
     73      * database lock to read all the fields atomically.
     74      *
     75      * @return a copy of all the MetaData fields.
     76      */
     77     public Fields getFields() {
     78         Fields fields = new Fields();
     79         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
     80         db.beginTransaction();
     81         try {
     82             // If the fields have not been initialized from the database,
     83             // then read the database.
     84             if (!mInitialized) {
     85                 readLocked(db);
     86             }
     87             fields.timezone = mFields.timezone;
     88             fields.minInstance = mFields.minInstance;
     89             fields.maxInstance = mFields.maxInstance;
     90             db.setTransactionSuccessful();
     91         } finally {
     92             db.endTransaction();
     93         }
     94         return fields;
     95     }
     96 
     97     /**
     98      * This method must be called only while holding a database lock.
     99      *
    100      * <p>
    101      * Returns a copy of all the MetaData fields.  This method assumes
    102      * the database lock has already been acquired.
    103      * </p>
    104      *
    105      * @return a copy of all the MetaData fields.
    106      */
    107     public Fields getFieldsLocked() {
    108         Fields fields = new Fields();
    109 
    110         // If the fields have not been initialized from the database,
    111         // then read the database.
    112         if (!mInitialized) {
    113             SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    114             readLocked(db);
    115         }
    116         fields.timezone = mFields.timezone;
    117         fields.minInstance = mFields.minInstance;
    118         fields.maxInstance = mFields.maxInstance;
    119         return fields;
    120     }
    121 
    122     /**
    123      * Reads the meta-data for the CalendarProvider from the database and
    124      * updates the member variables.  This method executes while the database
    125      * lock is held.  If there were no exceptions reading the database,
    126      * mInitialized is set to true.
    127      */
    128     private void readLocked(SQLiteDatabase db) {
    129         String timezone = null;
    130         long minInstance = 0, maxInstance = 0;
    131 
    132         // Read the database directly.  We only do this once to initialize
    133         // the members of this class.
    134         Cursor cursor = db.query("CalendarMetaData", sCalendarMetaDataProjection,
    135                 null, null, null, null, null);
    136         try {
    137             if (cursor.moveToNext()) {
    138                 timezone = cursor.getString(METADATA_INDEX_LOCAL_TIMEZONE);
    139                 minInstance = cursor.getLong(METADATA_INDEX_MIN_INSTANCE);
    140                 maxInstance = cursor.getLong(METADATA_INDEX_MAX_INSTANCE);
    141             }
    142         } finally {
    143             if (cursor != null) {
    144                 cursor.close();
    145             }
    146         }
    147 
    148         // Cache the result of reading the database
    149         mFields.timezone = timezone;
    150         mFields.minInstance = minInstance;
    151         mFields.maxInstance = maxInstance;
    152 
    153         // Mark the fields as initialized
    154         mInitialized = true;
    155     }
    156 
    157     /**
    158      * Writes the meta-data for the CalendarProvider.  The values to write are
    159      * passed in as parameters.  All of the values are updated atomically,
    160      * including the cached copy of the meta-data.
    161      *
    162      * @param timezone the local timezone used for Instance expansion
    163      * @param begin the start of the Instance expansion in UTC milliseconds
    164      * @param end the end of the Instance expansion in UTC milliseconds
    165      * @param startDay the start of the BusyBit expansion (the start Julian day)
    166      * @param endDay the end of the BusyBit expansion (the end Julian day)
    167      */
    168     public void write(String timezone, long begin, long end, int startDay, int endDay) {
    169         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    170         db.beginTransaction();
    171         try {
    172             writeLocked(timezone, begin, end);
    173             db.setTransactionSuccessful();
    174         } finally {
    175             db.endTransaction();
    176         }
    177     }
    178 
    179     /**
    180      * This method must be called only while holding a database lock.
    181      *
    182      * <p>
    183      * Writes the meta-data for the CalendarProvider.  The values to write are
    184      * passed in as parameters.  All of the values are updated atomically,
    185      * including the cached copy of the meta-data.
    186      * </p>
    187      *
    188      * @param timezone the local timezone used for Instance expansion
    189      * @param begin the start of the Instance expansion in UTC milliseconds
    190      * @param end the end of the Instance expansion in UTC milliseconds
    191      */
    192     public void writeLocked(String timezone, long begin, long end) {
    193         ContentValues values = new ContentValues();
    194         values.put("_id", 1);
    195         values.put(CalendarMetaData.LOCAL_TIMEZONE, timezone);
    196         values.put(CalendarMetaData.MIN_INSTANCE, begin);
    197         values.put(CalendarMetaData.MAX_INSTANCE, end);
    198 
    199         // Atomically update the database and the cached members.
    200         try {
    201             SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    202             db.replace("CalendarMetaData", null, values);
    203         } catch (RuntimeException e) {
    204             // Failed: zero the in-memory fields to force recomputation.
    205             mFields.timezone = null;
    206             mFields.minInstance = mFields.maxInstance = 0;
    207             throw e;
    208         }
    209 
    210         // Update the cached members last in case the database update fails
    211         mFields.timezone = timezone;
    212         mFields.minInstance = begin;
    213         mFields.maxInstance = end;
    214     }
    215 
    216     /**
    217      * Clears the time range for the Instances table.  The rows in the
    218      * Instances table will be deleted (and regenerated) the next time
    219      * that the Instances table is queried.
    220      *
    221      * Also clears the time range for the BusyBits table because that depends
    222      * on the Instances table.
    223      */
    224     public void clearInstanceRange() {
    225         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    226         db.beginTransaction();
    227         try {
    228             // If the fields have not been initialized from the database,
    229             // then read the database.
    230             if (!mInitialized) {
    231                 readLocked(db);
    232             }
    233             writeLocked(mFields.timezone, 0 /* begin */, 0 /* end */);
    234             db.setTransactionSuccessful();
    235         } finally {
    236             db.endTransaction();
    237         }
    238     }
    239 }
    240