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