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

http://android-sky.googlecode.com/ · Java · 417 lines · 268 code · 56 blank · 93 comment · 20 complexity · 615457206fbd9852793081e0fda2cbc7 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. * Flag specifying if this widget has been configured yet, used to skip
  62. * building widget updates.
  63. */
  64. public static final String CONFIGURED = "configured";
  65. public static final int CONFIGURED_TRUE = 1;
  66. }
  67. public static class AppWidgets implements BaseColumns, AppWidgetsColumns {
  68. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/appwidgets");
  69. /**
  70. * Directory twig to request all forecasts for a specific widget.
  71. */
  72. public static final String TWIG_FORECASTS = "forecasts";
  73. /**
  74. * Directory twig to request the forecast nearest the requested time.
  75. */
  76. public static final String TWIG_FORECAST_AT = "forecast_at";
  77. public static final String CONTENT_TYPE = "vnd.android.cursor.dir/appwidget";
  78. public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/appwidget";
  79. }
  80. public interface ForecastsColumns {
  81. /**
  82. * The parent {@link AppWidgetManager#EXTRA_APPWIDGET_ID} of this
  83. * forecast.
  84. */
  85. public static final String APPWIDGET_ID = "widgetId";
  86. /**
  87. * Flag if this forecast is an alert.
  88. */
  89. public static final String ALERT = "alert";
  90. public static final int ALERT_TRUE = 1;
  91. /**
  92. * Timestamp when this forecast becomes valid, in base ready for
  93. * comparison with {@link System#currentTimeMillis()}.
  94. */
  95. public static final String VALID_START = "validStart";
  96. /**
  97. * High temperature during this forecast period, stored in Fahrenheit.
  98. */
  99. public static final String TEMP_HIGH = "tempHigh";
  100. /**
  101. * Low temperature during this forecast period, stored in Fahrenheit.
  102. */
  103. public static final String TEMP_LOW = "tempLow";
  104. /**
  105. * String describing the weather conditions.
  106. */
  107. public static final String CONDITIONS = "conditions";
  108. /**
  109. * Web link where more details can be found about this forecast.
  110. */
  111. public static final String URL = "url";
  112. }
  113. public static class Forecasts implements BaseColumns, ForecastsColumns {
  114. public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/forecasts");
  115. public static final String CONTENT_TYPE = "vnd.android.cursor.dir/forecast";
  116. public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/forecast";
  117. }
  118. private static final String TABLE_APPWIDGETS = "appwidgets";
  119. private static final String TABLE_FORECASTS = "forecasts";
  120. private DatabaseHelper mOpenHelper;
  121. /**
  122. * Helper to manage upgrading between versions of the forecast database.
  123. */
  124. private static class DatabaseHelper extends SQLiteOpenHelper {
  125. private static final String DATABASE_NAME = "forecasts.db";
  126. private static final int DATABASE_VERSION = 2;
  127. public DatabaseHelper(Context context) {
  128. super(context, DATABASE_NAME, null, DATABASE_VERSION);
  129. }
  130. @Override
  131. public void onCreate(SQLiteDatabase db) {
  132. db.execSQL("CREATE TABLE " + TABLE_APPWIDGETS + " ("
  133. + BaseColumns._ID + " INTEGER PRIMARY KEY,"
  134. + AppWidgetsColumns.TITLE + " TEXT,"
  135. + AppWidgetsColumns.LAT + " REAL,"
  136. + AppWidgetsColumns.LON + " REAL,"
  137. + AppWidgetsColumns.UNITS + " INTEGER,"
  138. + AppWidgetsColumns.LAST_UPDATED + " INTEGER,"
  139. + AppWidgetsColumns.CONFIGURED + " INTEGER);");
  140. db.execSQL("CREATE TABLE " + TABLE_FORECASTS + " ("
  141. + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
  142. + ForecastsColumns.APPWIDGET_ID + " INTEGER,"
  143. + ForecastsColumns.ALERT + " INTEGER DEFAULT 0,"
  144. + ForecastsColumns.VALID_START + " INTEGER,"
  145. + ForecastsColumns.TEMP_HIGH + " INTEGER,"
  146. + ForecastsColumns.TEMP_LOW + " INTEGER,"
  147. + ForecastsColumns.CONDITIONS + " TEXT,"
  148. + ForecastsColumns.URL + " TEXT);");
  149. }
  150. @Override
  151. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  152. int version = oldVersion;
  153. if (version != DATABASE_VERSION) {
  154. Log.w(TAG, "Destroying old data during upgrade.");
  155. db.execSQL("DROP TABLE IF EXISTS " + TABLE_APPWIDGETS);
  156. db.execSQL("DROP TABLE IF EXISTS " + TABLE_FORECASTS);
  157. onCreate(db);
  158. }
  159. }
  160. }
  161. /**
  162. * {@inheritDoc}
  163. */
  164. @Override
  165. public int delete(Uri uri, String selection, String[] selectionArgs) {
  166. if (LOGD) Log.d(TAG, "delete() with uri=" + uri);
  167. SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  168. int count = 0;
  169. switch (sUriMatcher.match(uri)) {
  170. case APPWIDGETS: {
  171. count = db.delete(TABLE_APPWIDGETS, selection, selectionArgs);
  172. break;
  173. }
  174. case APPWIDGETS_ID: {
  175. // Delete a specific widget and all its forecasts
  176. long appWidgetId = Long.parseLong(uri.getPathSegments().get(1));
  177. count = db.delete(TABLE_APPWIDGETS, BaseColumns._ID + "=" + appWidgetId, null);
  178. count += db.delete(TABLE_FORECASTS, ForecastsColumns.APPWIDGET_ID + "="
  179. + appWidgetId, null);
  180. break;
  181. }
  182. case APPWIDGETS_FORECASTS: {
  183. // Delete all the forecasts for a specific widget
  184. long appWidgetId = Long.parseLong(uri.getPathSegments().get(1));
  185. if (selection == null) {
  186. selection = "";
  187. } else {
  188. selection = "(" + selection + ") AND ";
  189. }
  190. selection += ForecastsColumns.APPWIDGET_ID + "=" + appWidgetId;
  191. count = db.delete(TABLE_FORECASTS, selection, selectionArgs);
  192. break;
  193. }
  194. case FORECASTS: {
  195. count = db.delete(TABLE_FORECASTS, selection, selectionArgs);
  196. break;
  197. }
  198. default:
  199. throw new UnsupportedOperationException();
  200. }
  201. return count;
  202. }
  203. /**
  204. * {@inheritDoc}
  205. */
  206. @Override
  207. public String getType(Uri uri) {
  208. switch (sUriMatcher.match(uri)) {
  209. case APPWIDGETS:
  210. return AppWidgets.CONTENT_TYPE;
  211. case APPWIDGETS_ID:
  212. return AppWidgets.CONTENT_ITEM_TYPE;
  213. case APPWIDGETS_FORECASTS:
  214. return Forecasts.CONTENT_TYPE;
  215. case FORECASTS:
  216. return Forecasts.CONTENT_TYPE;
  217. case FORECASTS_ID:
  218. return Forecasts.CONTENT_ITEM_TYPE;
  219. }
  220. throw new IllegalStateException();
  221. }
  222. /**
  223. * {@inheritDoc}
  224. */
  225. @Override
  226. public Uri insert(Uri uri, ContentValues values) {
  227. if (LOGD) Log.d(TAG, "insert() with uri=" + uri);
  228. SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  229. Uri resultUri = null;
  230. switch (sUriMatcher.match(uri)) {
  231. case APPWIDGETS: {
  232. long rowId = db.insert(TABLE_APPWIDGETS, AppWidgetsColumns.TITLE, values);
  233. if (rowId != -1) {
  234. resultUri = ContentUris.withAppendedId(AppWidgets.CONTENT_URI, rowId);
  235. }
  236. break;
  237. }
  238. case APPWIDGETS_FORECASTS: {
  239. // Insert a forecast into a specific widget
  240. long appWidgetId = Long.parseLong(uri.getPathSegments().get(1));
  241. values.put(ForecastsColumns.APPWIDGET_ID, appWidgetId);
  242. long rowId = db.insert(TABLE_FORECASTS, ForecastsColumns.CONDITIONS, values);
  243. if (rowId != -1) {
  244. resultUri = ContentUris.withAppendedId(AppWidgets.CONTENT_URI, rowId);
  245. }
  246. break;
  247. }
  248. case FORECASTS: {
  249. long rowId = db.insert(TABLE_FORECASTS, ForecastsColumns.CONDITIONS, values);
  250. if (rowId != -1) {
  251. resultUri = ContentUris.withAppendedId(Forecasts.CONTENT_URI, rowId);
  252. }
  253. break;
  254. }
  255. default:
  256. throw new UnsupportedOperationException();
  257. }
  258. return resultUri;
  259. }
  260. @Override
  261. public boolean onCreate() {
  262. mOpenHelper = new DatabaseHelper(getContext());
  263. return true;
  264. }
  265. /**
  266. * {@inheritDoc}
  267. */
  268. @Override
  269. public Cursor query(Uri uri, String[] projection, String selection,
  270. String[] selectionArgs, String sortOrder) {
  271. if (LOGD) Log.d(TAG, "query() with uri=" + uri);
  272. SQLiteDatabase db = mOpenHelper.getReadableDatabase();
  273. SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
  274. String limit = null;
  275. switch (sUriMatcher.match(uri)) {
  276. case APPWIDGETS: {
  277. qb.setTables(TABLE_APPWIDGETS);
  278. break;
  279. }
  280. case APPWIDGETS_ID: {
  281. String appWidgetId = uri.getPathSegments().get(1);
  282. qb.setTables(TABLE_APPWIDGETS);
  283. qb.appendWhere(BaseColumns._ID + "=" + appWidgetId);
  284. break;
  285. }
  286. case APPWIDGETS_FORECASTS: {
  287. // Pick all the forecasts for given widget, sorted by date and
  288. // importance
  289. String appWidgetId = uri.getPathSegments().get(1);
  290. qb.setTables(TABLE_FORECASTS);
  291. qb.appendWhere(ForecastsColumns.APPWIDGET_ID + "=" + appWidgetId);
  292. sortOrder = ForecastsColumns.VALID_START + " ASC, " + ForecastsColumns.ALERT
  293. + " DESC";
  294. break;
  295. }
  296. case APPWIDGETS_FORECAST_AT: {
  297. // Pick the forecast nearest for given widget nearest the given
  298. // timestamp
  299. String appWidgetId = uri.getPathSegments().get(1);
  300. String atTime = uri.getPathSegments().get(3);
  301. qb.setTables(TABLE_FORECASTS);
  302. qb.appendWhere(ForecastsColumns.APPWIDGET_ID + "=" + appWidgetId);
  303. sortOrder = "ABS(" + ForecastsColumns.VALID_START + " - " + atTime + ") ASC, "
  304. + ForecastsColumns.ALERT + " DESC";
  305. limit = "1";
  306. break;
  307. }
  308. case FORECASTS: {
  309. qb.setTables(TABLE_FORECASTS);
  310. break;
  311. }
  312. case FORECASTS_ID: {
  313. String forecastId = uri.getPathSegments().get(1);
  314. qb.setTables(TABLE_FORECASTS);
  315. qb.appendWhere(BaseColumns._ID + "=" + forecastId);
  316. break;
  317. }
  318. }
  319. return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, limit);
  320. }
  321. /**
  322. * {@inheritDoc}
  323. */
  324. @Override
  325. public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
  326. if (LOGD) Log.d(TAG, "update() with uri=" + uri);
  327. SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  328. switch (sUriMatcher.match(uri)) {
  329. case APPWIDGETS: {
  330. return db.update(TABLE_APPWIDGETS, values, selection, selectionArgs);
  331. }
  332. case APPWIDGETS_ID: {
  333. long appWidgetId = Long.parseLong(uri.getPathSegments().get(1));
  334. return db.update(TABLE_APPWIDGETS, values, BaseColumns._ID + "=" + appWidgetId,
  335. null);
  336. }
  337. case FORECASTS: {
  338. return db.update(TABLE_FORECASTS, values, selection, selectionArgs);
  339. }
  340. }
  341. throw new UnsupportedOperationException();
  342. }
  343. /**
  344. * Matcher used to filter an incoming {@link Uri}.
  345. */
  346. private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  347. private static final int APPWIDGETS = 101;
  348. private static final int APPWIDGETS_ID = 102;
  349. private static final int APPWIDGETS_FORECASTS = 103;
  350. private static final int APPWIDGETS_FORECAST_AT = 104;
  351. private static final int FORECASTS = 201;
  352. private static final int FORECASTS_ID = 202;
  353. static {
  354. sUriMatcher.addURI(AUTHORITY, "appwidgets", APPWIDGETS);
  355. sUriMatcher.addURI(AUTHORITY, "appwidgets/#", APPWIDGETS_ID);
  356. sUriMatcher.addURI(AUTHORITY, "appwidgets/#/forecasts", APPWIDGETS_FORECASTS);
  357. sUriMatcher.addURI(AUTHORITY, "appwidgets/#/forecast_at/*", APPWIDGETS_FORECAST_AT);
  358. sUriMatcher.addURI(AUTHORITY, "forecasts", FORECASTS);
  359. sUriMatcher.addURI(AUTHORITY, "forecasts/#", FORECASTS_ID);
  360. }
  361. }