/Sky/src/org/jsharkey/sky/ForecastProvider.java
Java | 442 lines | 281 code | 60 blank | 101 comment | 21 complexity | 2ae0baf00806ce698baba3563f6d0a2a MD5 | raw file
1/* 2 * Copyright (C) 2009 Jeff Sharkey, http://jsharkey.org/ 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.jsharkey.sky; 18 19import android.appwidget.AppWidgetManager; 20import android.content.ContentProvider; 21import android.content.ContentResolver; 22import android.content.ContentUris; 23import android.content.ContentValues; 24import android.content.Context; 25import android.content.UriMatcher; 26import android.database.Cursor; 27import android.database.sqlite.SQLiteDatabase; 28import android.database.sqlite.SQLiteOpenHelper; 29import android.database.sqlite.SQLiteQueryBuilder; 30import android.net.Uri; 31import android.provider.BaseColumns; 32import android.util.Log; 33 34/** 35 * Provider that holds widget configuration details, and any cached forecast 36 * data. Provides easy {@link ContentResolver} access to the data when building 37 * widget updates or showing detailed lists. 38 */ 39public class ForecastProvider extends ContentProvider { 40 private static final String TAG = "ForecastProvider"; 41 private static final boolean LOGD = true; 42 43 public static final String AUTHORITY = "org.jsharkey.sky"; 44 45 public interface AppWidgetsColumns { 46 /** 47 * Title given by user to this widget, usually shown in medium widgets 48 * and details title bar. 49 */ 50 public static final String TITLE = "title"; 51 public static final String LAT = "lat"; 52 public static final String LON = "lon"; 53 54 /** 55 * Temperature units to use when displaying forecasts for this widget, 56 * usually defaults to {@link #UNITS_FAHRENHEIT}. 57 */ 58 public static final String UNITS = "units"; 59 public static final int UNITS_FAHRENHEIT = 1; 60 public static final int UNITS_CELSIUS = 2; 61 62 /** 63 * Last system time when forecasts for this widget were updated, usually 64 * as read from {@link System#currentTimeMillis()}. 65 */ 66 public static final String LAST_UPDATED = "lastUpdated"; 67 68 /** 69 * Country code where this widget exists, such as US or FR. This code is 70 * used when updating forecasts to use the best-available data source. 71 */ 72 public static final String COUNTRY_CODE = "countryCode"; 73 74 /** 75 * If known, the nearest METAR station to this location. The METAR data 76 * is used as a fall-back when no better forecast data is available. 77 */ 78 public static final String METAR_STATION = "metarStation"; 79 80 /** 81 * Flag specifying if this widget has been configured yet, used to skip 82 * building widget updates. 83 */ 84 public static final String CONFIGURED = "configured"; 85 86 public static final int CONFIGURED_TRUE = 1; 87 } 88 89 public static class AppWidgets implements BaseColumns, AppWidgetsColumns { 90 public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/appwidgets"); 91 92 /** 93 * Directory twig to request all forecasts for a specific widget. 94 */ 95 public static final String TWIG_FORECASTS = "forecasts"; 96 97 /** 98 * Directory twig to request the forecast nearest the requested time. 99 */ 100 public static final String TWIG_FORECAST_AT = "forecast_at"; 101 102 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/appwidget"; 103 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/appwidget"; 104 105 } 106 107 public interface ForecastsColumns { 108 /** 109 * The parent {@link AppWidgetManager#EXTRA_APPWIDGET_ID} of this 110 * forecast. 111 */ 112 public static final String APPWIDGET_ID = "widgetId"; 113 114 /** 115 * Flag if this forecast is an alert. 116 */ 117 public static final String ALERT = "alert"; 118 public static final int ALERT_TRUE = 1; 119 120 /** 121 * Timestamp when this forecast becomes valid, in base ready for 122 * comparison with {@link System#currentTimeMillis()}. 123 */ 124 public static final String VALID_START = "validStart"; 125 126 /** 127 * High temperature during this forecast period, stored in Fahrenheit. 128 */ 129 public static final String TEMP_HIGH = "tempHigh"; 130 131 /** 132 * Low temperature during this forecast period, stored in Fahrenheit. 133 */ 134 public static final String TEMP_LOW = "tempLow"; 135 136 /** 137 * String describing the weather conditions. 138 */ 139 public static final String CONDITIONS = "conditions"; 140 141 /** 142 * Web link where more details can be found about this forecast. 143 */ 144 public static final String URL = "url"; 145 146 } 147 148 public static class Forecasts implements BaseColumns, ForecastsColumns { 149 public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/forecasts"); 150 151 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/forecast"; 152 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/forecast"; 153 154 } 155 156 private static final String TABLE_APPWIDGETS = "appwidgets"; 157 private static final String TABLE_FORECASTS = "forecasts"; 158 159 private DatabaseHelper mOpenHelper; 160 161 /** 162 * Helper to manage upgrading between versions of the forecast database. 163 */ 164 private static class DatabaseHelper extends SQLiteOpenHelper { 165 private static final String DATABASE_NAME = "forecasts.db"; 166 167 private static final int VER_ORIGINAL = 2; 168 private static final int VER_ADD_METAR = 3; 169 170 private static final int DATABASE_VERSION = VER_ADD_METAR; 171 172 public DatabaseHelper(Context context) { 173 super(context, DATABASE_NAME, null, DATABASE_VERSION); 174 } 175 176 @Override 177 public void onCreate(SQLiteDatabase db) { 178 db.execSQL("CREATE TABLE " + TABLE_APPWIDGETS + " (" 179 + BaseColumns._ID + " INTEGER PRIMARY KEY," 180 + AppWidgetsColumns.TITLE + " TEXT," 181 + AppWidgetsColumns.LAT + " REAL," 182 + AppWidgetsColumns.LON + " REAL," 183 + AppWidgetsColumns.UNITS + " INTEGER," 184 + AppWidgetsColumns.LAST_UPDATED + " INTEGER," 185 + AppWidgetsColumns.COUNTRY_CODE + " TEXT," 186 + AppWidgetsColumns.CONFIGURED + " INTEGER);"); 187 188 db.execSQL("CREATE TABLE " + TABLE_FORECASTS + " (" 189 + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," 190 + ForecastsColumns.APPWIDGET_ID + " INTEGER," 191 + ForecastsColumns.ALERT + " INTEGER DEFAULT 0," 192 + ForecastsColumns.VALID_START + " INTEGER," 193 + ForecastsColumns.TEMP_HIGH + " INTEGER," 194 + ForecastsColumns.TEMP_LOW + " INTEGER," 195 + ForecastsColumns.CONDITIONS + " TEXT," 196 + ForecastsColumns.URL + " TEXT);"); 197 } 198 199 @Override 200 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 201 int version = oldVersion; 202 203 switch (version) { 204 case VER_ORIGINAL: 205 db.execSQL("ALTER TABLE " + TABLE_APPWIDGETS + " ADD COLUMN " 206 + AppWidgetsColumns.COUNTRY_CODE + " TEXT"); 207 db.execSQL("ALTER TABLE " + TABLE_APPWIDGETS + " ADD COLUMN " 208 + AppWidgetsColumns.METAR_STATION + " TEXT"); 209 version = VER_ADD_METAR; 210 } 211 212 if (version != DATABASE_VERSION) { 213 Log.w(TAG, "Destroying old data during upgrade."); 214 db.execSQL("DROP TABLE IF EXISTS " + TABLE_APPWIDGETS); 215 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FORECASTS); 216 onCreate(db); 217 } 218 } 219 } 220 221 /** 222 * {@inheritDoc} 223 */ 224 @Override 225 public int delete(Uri uri, String selection, String[] selectionArgs) { 226 if (LOGD) Log.d(TAG, "delete() with uri=" + uri); 227 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 228 229 int count = 0; 230 231 switch (sUriMatcher.match(uri)) { 232 case APPWIDGETS: { 233 count = db.delete(TABLE_APPWIDGETS, selection, selectionArgs); 234 break; 235 } 236 case APPWIDGETS_ID: { 237 // Delete a specific widget and all its forecasts 238 long appWidgetId = Long.parseLong(uri.getPathSegments().get(1)); 239 count = db.delete(TABLE_APPWIDGETS, BaseColumns._ID + "=" + appWidgetId, null); 240 count += db.delete(TABLE_FORECASTS, ForecastsColumns.APPWIDGET_ID + "=" 241 + appWidgetId, null); 242 break; 243 } 244 case APPWIDGETS_FORECASTS: { 245 // Delete all the forecasts for a specific widget 246 long appWidgetId = Long.parseLong(uri.getPathSegments().get(1)); 247 if (selection == null) { 248 selection = ""; 249 } else { 250 selection = "(" + selection + ") AND "; 251 } 252 selection += ForecastsColumns.APPWIDGET_ID + "=" + appWidgetId; 253 count = db.delete(TABLE_FORECASTS, selection, selectionArgs); 254 break; 255 } 256 case FORECASTS: { 257 count = db.delete(TABLE_FORECASTS, selection, selectionArgs); 258 break; 259 } 260 default: 261 throw new UnsupportedOperationException(); 262 } 263 264 return count; 265 } 266 267 /** 268 * {@inheritDoc} 269 */ 270 @Override 271 public String getType(Uri uri) { 272 switch (sUriMatcher.match(uri)) { 273 case APPWIDGETS: 274 return AppWidgets.CONTENT_TYPE; 275 case APPWIDGETS_ID: 276 return AppWidgets.CONTENT_ITEM_TYPE; 277 case APPWIDGETS_FORECASTS: 278 return Forecasts.CONTENT_TYPE; 279 case FORECASTS: 280 return Forecasts.CONTENT_TYPE; 281 case FORECASTS_ID: 282 return Forecasts.CONTENT_ITEM_TYPE; 283 } 284 throw new IllegalStateException(); 285 } 286 287 /** 288 * {@inheritDoc} 289 */ 290 @Override 291 public Uri insert(Uri uri, ContentValues values) { 292 if (LOGD) Log.d(TAG, "insert() with uri=" + uri); 293 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 294 295 Uri resultUri = null; 296 297 switch (sUriMatcher.match(uri)) { 298 case APPWIDGETS: { 299 long rowId = db.insert(TABLE_APPWIDGETS, AppWidgetsColumns.TITLE, values); 300 if (rowId != -1) { 301 resultUri = ContentUris.withAppendedId(AppWidgets.CONTENT_URI, rowId); 302 } 303 break; 304 } 305 case APPWIDGETS_FORECASTS: { 306 // Insert a forecast into a specific widget 307 long appWidgetId = Long.parseLong(uri.getPathSegments().get(1)); 308 values.put(ForecastsColumns.APPWIDGET_ID, appWidgetId); 309 long rowId = db.insert(TABLE_FORECASTS, ForecastsColumns.CONDITIONS, values); 310 if (rowId != -1) { 311 resultUri = ContentUris.withAppendedId(AppWidgets.CONTENT_URI, rowId); 312 } 313 break; 314 } 315 case FORECASTS: { 316 long rowId = db.insert(TABLE_FORECASTS, ForecastsColumns.CONDITIONS, values); 317 if (rowId != -1) { 318 resultUri = ContentUris.withAppendedId(Forecasts.CONTENT_URI, rowId); 319 } 320 break; 321 } 322 default: 323 throw new UnsupportedOperationException(); 324 } 325 326 return resultUri; 327 } 328 329 @Override 330 public boolean onCreate() { 331 mOpenHelper = new DatabaseHelper(getContext()); 332 return true; 333 } 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override 339 public Cursor query(Uri uri, String[] projection, String selection, 340 String[] selectionArgs, String sortOrder) { 341 if (LOGD) Log.d(TAG, "query() with uri=" + uri); 342 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 343 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 344 345 String limit = null; 346 347 switch (sUriMatcher.match(uri)) { 348 case APPWIDGETS: { 349 qb.setTables(TABLE_APPWIDGETS); 350 break; 351 } 352 case APPWIDGETS_ID: { 353 String appWidgetId = uri.getPathSegments().get(1); 354 qb.setTables(TABLE_APPWIDGETS); 355 qb.appendWhere(BaseColumns._ID + "=" + appWidgetId); 356 break; 357 } 358 case APPWIDGETS_FORECASTS: { 359 // Pick all the forecasts for given widget, sorted by date and 360 // importance 361 String appWidgetId = uri.getPathSegments().get(1); 362 qb.setTables(TABLE_FORECASTS); 363 qb.appendWhere(ForecastsColumns.APPWIDGET_ID + "=" + appWidgetId); 364 sortOrder = ForecastsColumns.VALID_START + " ASC, " + ForecastsColumns.ALERT 365 + " DESC"; 366 break; 367 } 368 case APPWIDGETS_FORECAST_AT: { 369 // Pick the forecast nearest for given widget nearest the given 370 // timestamp 371 String appWidgetId = uri.getPathSegments().get(1); 372 String atTime = uri.getPathSegments().get(3); 373 qb.setTables(TABLE_FORECASTS); 374 qb.appendWhere(ForecastsColumns.APPWIDGET_ID + "=" + appWidgetId); 375 sortOrder = "ABS(" + ForecastsColumns.VALID_START + " - " + atTime + ") ASC, " 376 + ForecastsColumns.ALERT + " DESC"; 377 limit = "1"; 378 break; 379 } 380 case FORECASTS: { 381 qb.setTables(TABLE_FORECASTS); 382 break; 383 } 384 case FORECASTS_ID: { 385 String forecastId = uri.getPathSegments().get(1); 386 qb.setTables(TABLE_FORECASTS); 387 qb.appendWhere(BaseColumns._ID + "=" + forecastId); 388 break; 389 } 390 } 391 392 return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, limit); 393 } 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override 399 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 400 if (LOGD) Log.d(TAG, "update() with uri=" + uri); 401 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 402 403 switch (sUriMatcher.match(uri)) { 404 case APPWIDGETS: { 405 return db.update(TABLE_APPWIDGETS, values, selection, selectionArgs); 406 } 407 case APPWIDGETS_ID: { 408 long appWidgetId = Long.parseLong(uri.getPathSegments().get(1)); 409 return db.update(TABLE_APPWIDGETS, values, BaseColumns._ID + "=" + appWidgetId, 410 null); 411 } 412 case FORECASTS: { 413 return db.update(TABLE_FORECASTS, values, selection, selectionArgs); 414 } 415 } 416 417 throw new UnsupportedOperationException(); 418 } 419 420 /** 421 * Matcher used to filter an incoming {@link Uri}. 422 */ 423 private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 424 425 private static final int APPWIDGETS = 101; 426 private static final int APPWIDGETS_ID = 102; 427 private static final int APPWIDGETS_FORECASTS = 103; 428 private static final int APPWIDGETS_FORECAST_AT = 104; 429 430 private static final int FORECASTS = 201; 431 private static final int FORECASTS_ID = 202; 432 433 static { 434 sUriMatcher.addURI(AUTHORITY, "appwidgets", APPWIDGETS); 435 sUriMatcher.addURI(AUTHORITY, "appwidgets/#", APPWIDGETS_ID); 436 sUriMatcher.addURI(AUTHORITY, "appwidgets/#/forecasts", APPWIDGETS_FORECASTS); 437 sUriMatcher.addURI(AUTHORITY, "appwidgets/#/forecast_at/*", APPWIDGETS_FORECAST_AT); 438 439 sUriMatcher.addURI(AUTHORITY, "forecasts", FORECASTS); 440 sUriMatcher.addURI(AUTHORITY, "forecasts/#", FORECASTS_ID); 441 } 442}