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