PageRenderTime 68ms CodeModel.GetById 40ms app.highlight 23ms RepoModel.GetById 2ms app.codeStats 0ms

/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
 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}