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.CalendarContract.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  * <p>
     34  * TODO: there must be only one of these active within CalendarProvider.  Enforce this.
     35  */
     36 public class MetaData {
     37     /**
     38      * These fields are updated atomically with the database.
     39      * If fields are added or removed from the CalendarMetaData table, those
     40      * changes must also be reflected here.
     41      */
     42     public class Fields {
     43         public String timezone;     // local timezone used for Instance expansion
     44         public long minInstance;    // UTC millis
     45         public long maxInstance;    // UTC millis
     46     }
     47 
     48     /**
     49      * The cached copy of the meta-data fields from the database.
     50      */
     51     private Fields mFields = new Fields();
     52 
     53     private final SQLiteOpenHelper mOpenHelper;
     54     private boolean mInitialized;
     55 
     56     /**
     57      * The column names in the CalendarMetaData table.  This projection
     58      * must contain all of the columns.
     59      */
     60     private static final String[] sCalendarMetaDataProjection = {
     61         CalendarMetaData.LOCAL_TIMEZONE,
     62         CalendarMetaData.MIN_INSTANCE,
     63         CalendarMetaData.MAX_INSTANCE};
     64 
     65     private static final int METADATA_INDEX_LOCAL_TIMEZONE = 0;
     66     private static final int METADATA_INDEX_MIN_INSTANCE = 1;
     67     private static final int METADATA_INDEX_MAX_INSTANCE = 2;
     68 
     69     public MetaData(SQLiteOpenHelper openHelper) {
     70         mOpenHelper = openHelper;
     71     }
     72 
     73     /**
     74      * Returns a copy of all the MetaData fields.  This method grabs a
     75      * database lock to read all the fields atomically.
     76      *
     77      * @return a copy of all the MetaData fields.
     78      */
     79     public Fields getFields() {
     80         Fields fields = new Fields();
     81         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
     82         db.beginTransaction();
     83         try {
     84             // If the fields have not been initialized from the database,
     85             // then read the database.
     86             if (!mInitialized) {
     87                 readLocked(db);
     88             }
     89             fields.timezone = mFields.timezone;
     90             fields.minInstance = mFields.minInstance;
     91             fields.maxInstance = mFields.maxInstance;
     92             db.setTransactionSuccessful();
     93         } finally {
     94             db.endTransaction();
     95         }
     96         return fields;
     97     }
     98 
     99     /**
    100      * This method must be called only while holding a database lock.
    101      *
    102      * <p>
    103      * Returns a copy of all the MetaData fields.  This method assumes
    104      * the database lock has already been acquired.
    105      * </p>
    106      *
    107      * @return a copy of all the MetaData fields.
    108      */
    109     public Fields getFieldsLocked() {
    110         Fields fields = new Fields();
    111 
    112         // If the fields have not been initialized from the database,
    113         // then read the database.
    114         if (!mInitialized) {
    115             SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    116             readLocked(db);
    117         }
    118         fields.timezone = mFields.timezone;
    119         fields.minInstance = mFields.minInstance;
    120         fields.maxInstance = mFields.maxInstance;
    121         return fields;
    122     }
    123 
    124     /**
    125      * Reads the meta-data for the CalendarProvider from the database and
    126      * updates the member variables.  This method executes while the database
    127      * lock is held.  If there were no exceptions reading the database,
    128      * mInitialized is set to true.
    129      */
    130     private void readLocked(SQLiteDatabase db) {
    131         String timezone = null;
    132         long minInstance = 0, maxInstance = 0;
    133 
    134         // Read the database directly.  We only do this once to initialize
    135         // the members of this class.
    136         Cursor cursor = db.query("CalendarMetaData", sCalendarMetaDataProjection,
    137                 null, null, null, null, null);
    138         try {
    139             if (cursor.moveToNext()) {
    140                 timezone = cursor.getString(METADATA_INDEX_LOCAL_TIMEZONE);
    141                 minInstance = cursor.getLong(METADATA_INDEX_MIN_INSTANCE);
    142                 maxInstance = cursor.getLong(METADATA_INDEX_MAX_INSTANCE);
    143             }
    144         } finally {
    145             if (cursor != null) {
    146                 cursor.close();
    147             }
    148         }
    149 
    150         // Cache the result of reading the database
    151         mFields.timezone = timezone;
    152         mFields.minInstance = minInstance;
    153         mFields.maxInstance = maxInstance;
    154 
    155         // Mark the fields as initialized
    156         mInitialized = true;
    157     }
    158 
    159     /**
    160      * Writes the meta-data for the CalendarProvider.  The values to write are
    161      * passed in as parameters.  All of the values are updated atomically,
    162      * including the cached copy of the meta-data.
    163      *
    164      * @param timezone the local timezone used for Instance expansion
    165      * @param begin the start of the Instance expansion in UTC milliseconds
    166      * @param end the end of the Instance expansion in UTC milliseconds
    167      * @param startDay the start of the BusyBit expansion (the start Julian day)
    168      * @param endDay the end of the BusyBit expansion (the end Julian day)
    169      */
    170     public void write(String timezone, long begin, long end, int startDay, int endDay) {
    171         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    172         db.beginTransaction();
    173         try {
    174             writeLocked(timezone, begin, end);
    175             db.setTransactionSuccessful();
    176         } finally {
    177             db.endTransaction();
    178         }
    179     }
    180 
    181     /**
    182      * This method must be called only while holding a database lock.
    183      *
    184      * <p>
    185      * Writes the meta-data for the CalendarProvider.  The values to write are
    186      * passed in as parameters.  All of the values are updated atomically,
    187      * including the cached copy of the meta-data.
    188      * </p>
    189      *
    190      * @param timezone the local timezone used for Instance expansion
    191      * @param begin the start of the Instance expansion in UTC milliseconds
    192      * @param end the end of the Instance expansion in UTC milliseconds
    193      */
    194     public void writeLocked(String timezone, long begin, long end) {
    195         ContentValues values = new ContentValues();
    196         values.put("_id", 1);
    197         values.put(CalendarMetaData.LOCAL_TIMEZONE, timezone);
    198         values.put(CalendarMetaData.MIN_INSTANCE, begin);
    199         values.put(CalendarMetaData.MAX_INSTANCE, end);
    200 
    201         // Atomically update the database and the cached members.
    202         try {
    203             SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    204             db.replace("CalendarMetaData", null, values);
    205         } catch (RuntimeException e) {
    206             // Failed: zero the in-memory fields to force recomputation.
    207             mFields.timezone = null;
    208             mFields.minInstance = mFields.maxInstance = 0;
    209             throw e;
    210         }
    211 
    212         // Update the cached members last in case the database update fails
    213         mFields.timezone = timezone;
    214         mFields.minInstance = begin;
    215         mFields.maxInstance = end;
    216     }
    217 
    218     /**
    219      * Clears the time range for the Instances table.  The rows in the
    220      * Instances table will be deleted (and regenerated) the next time
    221      * that the Instances table is queried.
    222      *
    223      * Also clears the time range for the BusyBits table because that depends
    224      * on the Instances table.
    225      */
    226     public void clearInstanceRange() {
    227         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    228         db.beginTransaction();
    229         try {
    230             // If the fields have not been initialized from the database,
    231             // then read the database.
    232             if (!mInitialized) {
    233                 readLocked(db);
    234             }
    235             writeLocked(mFields.timezone, 0 /* begin */, 0 /* end */);
    236             db.setTransactionSuccessful();
    237         } finally {
    238             db.endTransaction();
    239         }
    240     }
    241 }
    242