/Sky/src/org/jsharkey/sky/ForecastProvider.java

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