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