PageRenderTime 26ms CodeModel.GetById 9ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/Sky/src/org/jsharkey/sky/webservice/WebserviceHelper.java

http://android-sky.googlecode.com/
Java | 213 lines | 131 code | 34 blank | 48 comment | 18 complexity | 1aa963328e10318ba72d3ad8e7a91d4f 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.webservice;
 18
 19import java.io.IOException;
 20import java.io.InputStreamReader;
 21import java.io.Reader;
 22import java.util.List;
 23
 24import org.apache.http.HttpEntity;
 25import org.apache.http.HttpResponse;
 26import org.apache.http.StatusLine;
 27import org.apache.http.client.HttpClient;
 28import org.apache.http.client.methods.HttpGet;
 29import org.apache.http.impl.client.DefaultHttpClient;
 30import org.jsharkey.sky.ForecastProvider;
 31import org.jsharkey.sky.ForecastUtils;
 32import org.jsharkey.sky.R;
 33import org.jsharkey.sky.ForecastProvider.AppWidgets;
 34import org.jsharkey.sky.ForecastProvider.AppWidgetsColumns;
 35import org.jsharkey.sky.ForecastProvider.Forecasts;
 36import org.jsharkey.sky.ForecastProvider.ForecastsColumns;
 37import org.jsharkey.sky.webservice.Forecast.ParseException;
 38
 39import android.content.ContentResolver;
 40import android.content.ContentValues;
 41import android.content.Context;
 42import android.content.pm.PackageInfo;
 43import android.content.pm.PackageManager;
 44import android.content.pm.PackageManager.NameNotFoundException;
 45import android.database.Cursor;
 46import android.net.Uri;
 47import android.text.format.DateUtils;
 48import android.util.Log;
 49
 50/**
 51 * Helper class to handle querying a webservice for forecast details and parsing
 52 * results into {@link ForecastProvider}.
 53 */
 54public class WebserviceHelper {
 55    private static final String TAG = "ForcastHelper";
 56
 57    private static final String[] PROJECTION_APPWIDGET = {
 58        AppWidgetsColumns.LAT,
 59        AppWidgetsColumns.LON,
 60        AppWidgetsColumns.COUNTRY_CODE
 61    };
 62
 63    private static final int COL_LAT = 0;
 64    private static final int COL_LON = 1;
 65    private static final int COL_COUNTRY_CODE = 2;
 66    
 67    public static final String COUNTRY_US = "US";
 68
 69    /**
 70     * Timeout to wait for webservice to respond. Because we're in the
 71     * background, we don't mind waiting for good data.
 72     */
 73    static final long WEBSERVICE_TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
 74
 75    /**
 76     * User-agent string to use when making requests. Should be filled using
 77     * {@link #prepareUserAgent(Context)} before making any other calls.
 78     */
 79    private static String sUserAgent = null;
 80    
 81    private static HttpClient sClient = new DefaultHttpClient();
 82
 83    /**
 84     * Prepare the internal User-Agent string for use. This requires a
 85     * {@link Context} to pull the package name and version number for this
 86     * application.
 87     */
 88    public static void prepareUserAgent(Context context) {
 89        try {
 90            // Read package name and version number from manifest
 91            PackageManager manager = context.getPackageManager();
 92            PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
 93            sUserAgent = String.format(context.getString(R.string.template_user_agent),
 94                    info.packageName, info.versionName);
 95            
 96        } catch(NameNotFoundException e) {
 97            Log.e(TAG, "Couldn't find package information in PackageManager", e);
 98        }
 99    }
100    
101    /**
102     * Open a request to the given URL, returning a {@link Reader} across the
103     * response from that API.
104     */
105    public static Reader queryApi(String url) throws ParseException {
106        if (sUserAgent == null) {
107            throw new ParseException("Must prepare user agent string");
108        }
109        
110        Reader reader = null;
111        HttpGet request = new HttpGet(url);
112        request.setHeader("User-Agent", sUserAgent);
113
114        try {
115            HttpResponse response = sClient.execute(request);
116
117            StatusLine status = response.getStatusLine();
118            Log.d(TAG, "Request returned status " + status);
119
120            HttpEntity entity = response.getEntity();
121            reader = new InputStreamReader(entity.getContent());
122
123        } catch (IOException e) {
124            throw new ParseException("Problem calling forecast API", e);
125        }
126        
127        return reader;
128    }
129
130    /**
131     * Perform a webservice query to retrieve and store the forecast for the
132     * given widget. This call blocks until request is finished and
133     * {@link Forecasts#CONTENT_URI} has been updated.
134     */
135    public static void updateForecasts(Context context, Uri appWidgetUri, int days)
136            throws ParseException {
137        
138        if (sUserAgent == null) {
139            prepareUserAgent(context);
140        }
141
142        Uri appWidgetForecasts = Uri.withAppendedPath(appWidgetUri, AppWidgets.TWIG_FORECASTS);
143
144        ContentResolver resolver = context.getContentResolver();
145
146        Cursor cursor = null;
147        double lat = Double.NaN;
148        double lon = Double.NaN;
149        String countryCode = null;
150
151        // Pull exact forecast location from database
152        try {
153            cursor = resolver.query(appWidgetUri, PROJECTION_APPWIDGET, null, null, null);
154            if (cursor != null && cursor.moveToFirst()) {
155                lat = cursor.getDouble(COL_LAT);
156                lon = cursor.getDouble(COL_LON);
157                countryCode = cursor.getString(COL_COUNTRY_CODE);
158            }
159        } finally {
160            if (cursor != null) {
161                cursor.close();
162            }
163        }
164
165        Log.d(TAG, "using country code=" + countryCode);
166
167        // Query webservice for this location
168        List<Forecast> forecasts = null;
169        if (COUNTRY_US.equals(countryCode)) {
170            forecasts = new NoaaSource().getForecasts(lat, lon, days);
171        } else {
172            forecasts = new MetarSource().getForecasts(lat, lon, days);
173        }
174
175        if (forecasts == null || forecasts.size() == 0) {
176            throw new ParseException("No forecasts found from webservice query");
177        }
178
179        // Purge existing forecasts covered by incoming data, and anything
180        // before today
181        long lastMidnight = ForecastUtils.getLastMidnight();
182        long earliest = Long.MAX_VALUE;
183        for (Forecast forecast : forecasts) {
184            earliest = Math.min(earliest, forecast.validStart);
185        }
186
187        resolver.delete(appWidgetForecasts,
188            ForecastsColumns.VALID_START + " >= " + earliest + " OR " +
189            ForecastsColumns.VALID_START + " <= " + lastMidnight, null);
190
191        // Insert any new forecasts found
192        ContentValues values = new ContentValues();
193        for (Forecast forecast : forecasts) {
194            Log.d(TAG, "inserting forecast with validStart=" + forecast.validStart);
195            values.clear();
196            values.put(ForecastsColumns.VALID_START, forecast.validStart);
197            values.put(ForecastsColumns.TEMP_HIGH, forecast.tempHigh);
198            values.put(ForecastsColumns.TEMP_LOW, forecast.tempLow);
199            values.put(ForecastsColumns.CONDITIONS, forecast.conditions);
200            values.put(ForecastsColumns.URL, forecast.url);
201            if (forecast.alert) {
202                values.put(ForecastsColumns.ALERT, ForecastsColumns.ALERT_TRUE);
203            }
204            resolver.insert(appWidgetForecasts, values);
205        }
206
207        // Mark widget cache as being updated
208        values.clear();
209        values.put(AppWidgetsColumns.LAST_UPDATED, System.currentTimeMillis());
210        resolver.update(appWidgetUri, values, null, null);
211    }
212
213}