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 
MetaData(SQLiteOpenHelper openHelper)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      */
getFields()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      */
getFieldsLocked()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      */
readLocked(SQLiteDatabase db)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      */
write(String timezone, long begin, long end, int startDay, int endDay)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      */
writeLocked(String timezone, long begin, long end)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      */
clearInstanceRange()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