MetaData.java revision b558dececce20291e0a0195a4bd9835f4a8a1918
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
18package com.android.providers.calendar;
19
20
21import android.content.ContentValues;
22import android.database.Cursor;
23import android.database.sqlite.SQLiteDatabase;
24import android.database.sqlite.SQLiteOpenHelper;
25import 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 */
34public 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        public int minBusyBit;      // Julian start day
45        public int maxBusyBit;      // Julian end day
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        CalendarMetaData.MIN_BUSYBITS,
65        CalendarMetaData.MAX_BUSYBITS };
66
67    private static final int METADATA_INDEX_LOCAL_TIMEZONE = 0;
68    private static final int METADATA_INDEX_MIN_INSTANCE = 1;
69    private static final int METADATA_INDEX_MAX_INSTANCE = 2;
70    private static final int METADATA_INDEX_MIN_BUSYBIT = 3;
71    private static final int METADATA_INDEX_MAX_BUSYBIT = 4;
72
73    public MetaData(SQLiteOpenHelper openHelper) {
74        mOpenHelper = openHelper;
75    }
76
77    /**
78     * Returns a copy of all the MetaData fields.  This method grabs a
79     * database lock to read all the fields atomically.
80     *
81     * @return a copy of all the MetaData fields.
82     */
83    public Fields getFields() {
84        Fields fields = new Fields();
85        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
86        db.beginTransaction();
87        try {
88            // If the fields have not been initialized from the database,
89            // then read the database.
90            if (!mInitialized) {
91                readLocked(db);
92            }
93            fields.timezone = mFields.timezone;
94            fields.minInstance = mFields.minInstance;
95            fields.maxInstance = mFields.maxInstance;
96            fields.minBusyBit = mFields.minBusyBit;
97            fields.maxBusyBit = mFields.maxBusyBit;
98            db.setTransactionSuccessful();
99        } finally {
100            db.endTransaction();
101        }
102        return fields;
103    }
104
105    /**
106     * This method must be called only while holding a database lock.
107     *
108     * <p>
109     * Returns a copy of all the MetaData fields.  This method assumes
110     * the database lock has already been acquired.
111     * </p>
112     *
113     * @return a copy of all the MetaData fields.
114     */
115    public Fields getFieldsLocked() {
116        Fields fields = new Fields();
117
118        // If the fields have not been initialized from the database,
119        // then read the database.
120        if (!mInitialized) {
121            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
122            readLocked(db);
123        }
124        fields.timezone = mFields.timezone;
125        fields.minInstance = mFields.minInstance;
126        fields.maxInstance = mFields.maxInstance;
127        fields.minBusyBit = mFields.minBusyBit;
128        fields.maxBusyBit = mFields.maxBusyBit;
129        return fields;
130    }
131
132    /**
133     * Reads the meta-data for the CalendarProvider from the database and
134     * updates the member variables.  This method executes while the database
135     * lock is held.  If there were no exceptions reading the database,
136     * mInitialized is set to true.
137     */
138    private void readLocked(SQLiteDatabase db) {
139        String timezone = null;
140        long minInstance = 0, maxInstance = 0;
141        int minBusyBit = 0, maxBusyBit = 0;
142
143        // Read the database directly.  We only do this once to initialize
144        // the members of this class.
145        Cursor cursor = db.query("CalendarMetaData", sCalendarMetaDataProjection,
146                null, null, null, null, null);
147        try {
148            if (cursor.moveToNext()) {
149                timezone = cursor.getString(METADATA_INDEX_LOCAL_TIMEZONE);
150                minInstance = cursor.getLong(METADATA_INDEX_MIN_INSTANCE);
151                maxInstance = cursor.getLong(METADATA_INDEX_MAX_INSTANCE);
152                minBusyBit = cursor.getInt(METADATA_INDEX_MIN_BUSYBIT);
153                maxBusyBit = cursor.getInt(METADATA_INDEX_MAX_BUSYBIT);
154            }
155        } finally {
156            if (cursor != null) {
157                cursor.close();
158            }
159        }
160
161        // Cache the result of reading the database
162        mFields.timezone = timezone;
163        mFields.minInstance = minInstance;
164        mFields.maxInstance = maxInstance;
165        mFields.minBusyBit = minBusyBit;
166        mFields.maxBusyBit = maxBusyBit;
167
168        // Mark the fields as initialized
169        mInitialized = true;
170    }
171
172    /**
173     * Writes the meta-data for the CalendarProvider.  The values to write are
174     * passed in as parameters.  All of the values are updated atomically,
175     * including the cached copy of the meta-data.
176     *
177     * @param timezone the local timezone used for Instance expansion
178     * @param begin the start of the Instance expansion in UTC milliseconds
179     * @param end the end of the Instance expansion in UTC milliseconds
180     * @param startDay the start of the BusyBit expansion (the start Julian day)
181     * @param endDay the end of the BusyBit expansion (the end Julian day)
182     */
183    public void write(String timezone, long begin, long end, int startDay, int endDay) {
184        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
185        db.beginTransaction();
186        try {
187            writeLocked(timezone, begin, end, startDay, endDay);
188            db.setTransactionSuccessful();
189        } finally {
190            db.endTransaction();
191        }
192    }
193
194    /**
195     * This method must be called only while holding a database lock.
196     *
197     * <p>
198     * Writes the meta-data for the CalendarProvider.  The values to write are
199     * passed in as parameters.  All of the values are updated atomically,
200     * including the cached copy of the meta-data.
201     * </p>
202     *
203     * @param timezone the local timezone used for Instance expansion
204     * @param begin the start of the Instance expansion in UTC milliseconds
205     * @param end the end of the Instance expansion in UTC milliseconds
206     * @param startDay the start of the BusyBit expansion (the start Julian day)
207     * @param endDay the end of the BusyBit expansion (the end Julian day)
208     */
209    public void writeLocked(String timezone, long begin, long end, int startDay, int endDay) {
210        ContentValues values = new ContentValues();
211        values.put("_id", 1);
212        values.put(CalendarMetaData.LOCAL_TIMEZONE, timezone);
213        values.put(CalendarMetaData.MIN_INSTANCE, begin);
214        values.put(CalendarMetaData.MAX_INSTANCE, end);
215        values.put(CalendarMetaData.MIN_BUSYBITS, startDay);
216        values.put(CalendarMetaData.MAX_BUSYBITS, endDay);
217
218        // Atomically update the database and the cached members.
219        try {
220            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
221            db.replace("CalendarMetaData", null, values);
222        } catch (RuntimeException e) {
223            // Failed: zero the in-memory fields to force recomputation.
224            mFields.timezone = null;
225            mFields.minInstance = mFields.maxInstance = 0;
226            mFields.minBusyBit = mFields.maxBusyBit = 0;
227            throw e;
228        }
229
230        // Update the cached members last in case the database update fails
231        mFields.timezone = timezone;
232        mFields.minInstance = begin;
233        mFields.maxInstance = end;
234        mFields.minBusyBit = startDay;
235        mFields.maxBusyBit = endDay;
236    }
237
238    /**
239     * Clears the time range for the Instances table.  The rows in the
240     * Instances table will be deleted (and regenerated) the next time
241     * that the Instances table is queried.
242     *
243     * Also clears the time range for the BusyBits table because that depends
244     * on the Instances table.
245     */
246    public void clearInstanceRange() {
247        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
248        db.beginTransaction();
249        try {
250            // If the fields have not been initialized from the database,
251            // then read the database.
252            if (!mInitialized) {
253                readLocked(db);
254            }
255            writeLocked(mFields.timezone, 0 /* begin */, 0 /* end */,
256                    0 /* startDay */, 0 /* endDay */);
257            db.setTransactionSuccessful();
258        } finally {
259            db.endTransaction();
260        }
261    }
262
263    /**
264     * Clears the time range for the BusyBits table.
265     */
266    public void clearBusyBitRange() {
267        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
268        db.beginTransaction();
269        try {
270            // If the fields have not been initialized from the database,
271            // then read the database.
272            if (!mInitialized) {
273                readLocked(db);
274            }
275            writeLocked(mFields.timezone, mFields.minInstance, mFields.maxInstance,
276                    0 /* startDay */, 0 /* endDay */);
277            db.setTransactionSuccessful();
278        } finally {
279            db.endTransaction();
280        }
281    }
282}
283