aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/lockclock/weather
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/cyanogenmod/lockclock/weather')
-rw-r--r--src/com/cyanogenmod/lockclock/weather/ForecastActivity.java8
-rw-r--r--src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java187
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/weather/HttpRetriever.java45
-rw-r--r--src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java330
-rw-r--r--src/com/cyanogenmod/lockclock/weather/Utils.java181
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java187
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/weather/WeatherInfo.java334
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherProvider.java39
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java83
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java410
-rw-r--r--src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java313
11 files changed, 671 insertions, 1446 deletions
diff --git a/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java b/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
index b08a069..70fe61c 100644
--- a/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
+++ b/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
@@ -18,13 +18,10 @@ package com.cyanogenmod.lockclock.weather;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.KeyguardManager;
-import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@@ -35,11 +32,10 @@ import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
-
-import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
-import com.cyanogenmod.lockclock.R;
+import cyanogenmod.weather.WeatherInfo;
public class ForecastActivity extends Activity implements OnClickListener {
private static final String TAG = "ForecastActivity";
diff --git a/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java b/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
index f5e4e6d..4bb2b84 100644
--- a/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
+++ b/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
@@ -16,29 +16,34 @@
package com.cyanogenmod.lockclock.weather;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Locale;
-import java.util.TimeZone;
-
import android.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.Color;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-
+import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.IconUtils;
import com.cyanogenmod.lockclock.misc.Preferences;
-import com.cyanogenmod.lockclock.misc.WidgetUtils;
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-import com.cyanogenmod.lockclock.R;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WindSpeedUnit.KPH;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.CELSIUS;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherInfo.DayForecast;
+import cyanogenmod.weather.util.WeatherUtils;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
public class ForecastBuilder {
private static final String TAG = "ForecastBuilder";
@@ -58,50 +63,82 @@ public class ForecastBuilder {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int color = Preferences.weatherFontColor(context);
boolean invertLowHigh = Preferences.invertLowHighTemperature(context);
+ final boolean useMetric = Preferences.useMetricUnits(context);
+
+ //Make any conversion needed in case the data was not provided in the desired unit
+ double temp = w.getTemperature();
+ double todaysLow = w.getTodaysLow();
+ double todaysHigh = w.getTodaysHigh();
+ int tempUnit = w.getTemperatureUnit();
+ if (tempUnit == FAHRENHEIT && useMetric) {
+ temp = WeatherUtils.fahrenheitToCelsius(temp);
+ todaysLow = WeatherUtils.fahrenheitToCelsius(todaysLow);
+ todaysHigh = WeatherUtils.fahrenheitToCelsius(todaysHigh);
+ tempUnit = CELSIUS;
+ } else if (tempUnit == CELSIUS && !useMetric) {
+ temp = WeatherUtils.celsiusToFahrenheit(temp);
+ todaysLow = WeatherUtils.celsiusToFahrenheit(todaysLow);
+ todaysHigh = WeatherUtils.celsiusToFahrenheit(todaysHigh);
+ tempUnit = FAHRENHEIT;
+ }
+
+ double windSpeed = w.getWindSpeed();
+ int windSpeedUnit = w.getWindSpeedUnit();
+ if (windSpeedUnit == MPH && useMetric) {
+ windSpeedUnit = KPH;
+ windSpeed = Utils.milesToKilometers(windSpeed);
+ } else if (windSpeedUnit == KPH && !useMetric) {
+ windSpeedUnit = MPH;
+ windSpeed = Utils.kilometersToMiles(windSpeed);
+ }
View view = inflater.inflate(resourceId, null);
// Set the weather source
TextView weatherSource = (TextView) view.findViewById(R.id.weather_source);
- weatherSource.setText(Preferences.weatherProvider(context).getNameResourceId());
+ final CMWeatherManager cmWeatherManager = CMWeatherManager.getInstance(context);
+ String activeWeatherLabel = cmWeatherManager.getActiveWeatherServiceProviderLabel();
+ weatherSource.setText(activeWeatherLabel != null ? activeWeatherLabel : "");
// Set the current conditions
// Weather Image
ImageView weatherImage = (ImageView) view.findViewById(R.id.weather_image);
String iconsSet = Preferences.getWeatherIconSet(context);
- weatherImage.setImageBitmap(w.getConditionBitmap(iconsSet, color,
- IconUtils.getNextHigherDensity(context)));
+ weatherImage.setImageBitmap(IconUtils.getWeatherIconBitmap(context, iconsSet, color,
+ w.getConditionCode(), IconUtils.getNextHigherDensity(context)));
// Weather Condition
TextView weatherCondition = (TextView) view.findViewById(R.id.weather_condition);
- weatherCondition.setText(w.getCondition());
+ weatherCondition.setText(Utils.resolveWeatherCondition(context, w.getConditionCode()));
// Weather Temps
TextView weatherTemp = (TextView) view.findViewById(R.id.weather_temp);
- weatherTemp.setText(w.getFormattedTemperature());
+ weatherTemp.setText(WeatherUtils.formatTemperature(temp, tempUnit));
// Humidity and Wind
TextView weatherHumWind = (TextView) view.findViewById(R.id.weather_hum_wind);
- weatherHumWind.setText(w.getFormattedHumidity() + ", " + w.getFormattedWindSpeed() + " "
- + w.getWindDirection());
+ weatherHumWind.setText(Utils.formatHumidity(w.getHumidity()) + ", "
+ + Utils.formatWindSpeed(context, windSpeed, windSpeedUnit) + " "
+ + Utils.resolveWindDirection(context, w.getWindDirection()));
// City
TextView city = (TextView) view.findViewById(R.id.weather_city);
city.setText(w.getCity());
// Weather Update Time
- Date lastUpdate = w.getTimestamp();
+ Date lastUpdate = new Date(w.getTimestamp());
StringBuilder sb = new StringBuilder();
sb.append(DateFormat.format("E", lastUpdate));
sb.append(" ");
sb.append(DateFormat.getTimeFormat(context).format(lastUpdate));
TextView updateTime = (TextView) view.findViewById(R.id.update_time);
updateTime.setText(sb.toString());
- updateTime.setVisibility(Preferences.showWeatherTimestamp(context) ? View.VISIBLE : View.GONE);
+ updateTime.setVisibility(
+ Preferences.showWeatherTimestamp(context) ? View.VISIBLE : View.GONE);
// Weather Temps Panel additional items
- final String low = w.getFormattedLow();
- final String high = w.getFormattedHigh();
+ final String low = WeatherUtils.formatTemperature(todaysLow, tempUnit);
+ final String high = WeatherUtils.formatTemperature(todaysHigh, tempUnit);
TextView weatherLowHigh = (TextView) view.findViewById(R.id.weather_low_high);
weatherLowHigh.setText(invertLowHigh ? high + " | " + low : low + " | " + high);
@@ -113,6 +150,9 @@ public class ForecastBuilder {
if (buildSmallPanel(context, forecastView, w)) {
// Success, hide the progress container
progressIndicator.setVisibility(View.GONE);
+ } else {
+ // TODO: Display a text notifying the user that the forecast data is not available
+ // rather than keeping the indicator spinning forever
}
return view;
@@ -125,55 +165,72 @@ public class ForecastBuilder {
* @param w = the Weather info object that contains the forecast data
*/
public static boolean buildSmallPanel(Context context, LinearLayout smallPanel, WeatherInfo w) {
- if (smallPanel == null) {
+ if (smallPanel == null) {
Log.d(TAG, "Invalid view passed");
return false;
- }
+ }
- // Get things ready
- LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- int color = Preferences.weatherFontColor(context);
- boolean invertLowHigh = Preferences.invertLowHighTemperature(context);
+ // Get things ready
+ LayoutInflater inflater
+ = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ int color = Preferences.weatherFontColor(context);
+ boolean invertLowHigh = Preferences.invertLowHighTemperature(context);
+ final boolean useMetric = Preferences.useMetricUnits(context);
- ArrayList<DayForecast> forecasts = w.getForecasts();
- if (forecasts == null || forecasts.size() <= 1) {
+ List<DayForecast> forecasts = w.getForecasts();
+ if (forecasts == null || forecasts.size() <= 1) {
smallPanel.setVisibility(View.GONE);
return false;
- }
-
- TimeZone MyTimezone = TimeZone.getDefault();
- Calendar calendar = new GregorianCalendar(MyTimezone);
-
- // Iterate through the forecasts
- for (DayForecast d : forecasts) {
- // Load the views
- View forecastItem = inflater.inflate(R.layout.forecast_item, null);
-
- // The day of the week
- TextView day = (TextView) forecastItem.findViewById(R.id.forecast_day);
- day.setText(calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.getDefault()));
- calendar.roll(Calendar.DAY_OF_WEEK, true);
-
- // Weather Image
- ImageView image = (ImageView) forecastItem.findViewById(R.id.weather_image);
- String iconsSet = Preferences.getWeatherIconSet(context);
- int resId = d.getConditionResource(context, iconsSet);
- if (resId != 0) {
+ }
+
+ TimeZone MyTimezone = TimeZone.getDefault();
+ Calendar calendar = new GregorianCalendar(MyTimezone);
+ int weatherTempUnit = w.getTemperatureUnit();
+ // Iterate through the forecasts
+ for (DayForecast d : forecasts) {
+ // Load the views
+ View forecastItem = inflater.inflate(R.layout.forecast_item, null);
+
+ // The day of the week
+ TextView day = (TextView) forecastItem.findViewById(R.id.forecast_day);
+ day.setText(calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
+ Locale.getDefault()));
+ calendar.roll(Calendar.DAY_OF_WEEK, true);
+
+ // Weather Image
+ ImageView image = (ImageView) forecastItem.findViewById(R.id.weather_image);
+ String iconsSet = Preferences.getWeatherIconSet(context);
+ final int resId = IconUtils.getWeatherIconResource(context, iconsSet,
+ d.getConditionCode());
+ if (resId != 0) {
image.setImageResource(resId);
- } else {
- image.setImageBitmap(d.getConditionBitmap(context, iconsSet, color));
- }
-
- // Temperatures
- String dayLow = d.getFormattedLow();
- String dayHigh = d.getFormattedHigh();
- TextView temps = (TextView) forecastItem.findViewById(R.id.weather_temps);
- temps.setText(invertLowHigh ? dayHigh + " " + dayLow : dayLow + " " + dayHigh);
-
- // Add the view
- smallPanel.addView(forecastItem,
+ } else {
+ image.setImageBitmap(IconUtils.getWeatherIconBitmap(context, iconsSet,
+ color, d.getConditionCode()));
+ }
+
+ // Temperatures
+ double lowTemp = d.getLow();
+ double highTemp = d.getHigh();
+ int tempUnit = weatherTempUnit;
+ if (weatherTempUnit == FAHRENHEIT && useMetric) {
+ lowTemp = WeatherUtils.fahrenheitToCelsius(lowTemp);
+ highTemp = WeatherUtils.fahrenheitToCelsius(highTemp);
+ tempUnit = CELSIUS;
+ } else if (weatherTempUnit == CELSIUS && !useMetric) {
+ lowTemp = WeatherUtils.celsiusToFahrenheit(lowTemp);
+ highTemp = WeatherUtils.celsiusToFahrenheit(highTemp);
+ tempUnit = FAHRENHEIT;
+ }
+ String dayLow = WeatherUtils.formatTemperature(lowTemp, tempUnit);
+ String dayHigh = WeatherUtils.formatTemperature(highTemp, tempUnit);
+ TextView temps = (TextView) forecastItem.findViewById(R.id.weather_temps);
+ temps.setText(invertLowHigh ? dayHigh + " " + dayLow : dayLow + " " + dayHigh);
+
+ // Add the view
+ smallPanel.addView(forecastItem,
new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1));
- }
- return true;
+ }
+ return true;
}
}
diff --git a/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java b/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java
deleted file mode 100755
index 60723fa..0000000
--- a/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2013 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.util.Log;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.util.EntityUtils;
-
-import java.io.IOException;
-
-public class HttpRetriever {
- private static final String TAG = "HttpRetriever";
-
- public static String retrieve(String url) {
- HttpGet request = new HttpGet(url);
- try {
- HttpResponse response = new DefaultHttpClient().execute(request);
- HttpEntity entity = response.getEntity();
- if (entity != null) {
- return EntityUtils.toString(entity);
- }
- } catch (IOException e) {
- Log.e(TAG, "Couldn't retrieve data from url " + url, e);
- }
- return null;
- }
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java b/src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java
deleted file mode 100644
index ee2f46f..0000000
--- a/src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java
+++ /dev/null
@@ -1,330 +0,0 @@
-package com.cyanogenmod.lockclock.weather;
-
-import java.util.*;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.location.Location;
-import android.net.Uri;
-import android.util.Log;
-
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-import com.cyanogenmod.lockclock.R;
-
-public class OpenWeatherMapProvider implements WeatherProvider {
- private static final String TAG = "OpenWeatherMapProvider";
-
- private static final int FORECAST_DAYS = 5;
- private static final String SELECTION_LOCATION = "lat=%f&lon=%f";
- private static final String SELECTION_ID = "id=%s";
- private static final String APP_ID = "e2b075d68c39dc43e16995653fcd6fd0";
-
- private static final String URL_LOCATION =
- "http://api.openweathermap.org/data/2.5/find?q=%s&mode=json&lang=%s&appid="
- + APP_ID;
- private static final String URL_WEATHER =
- "http://api.openweathermap.org/data/2.5/weather?%s&mode=json&units=%s&lang=%s&appid="
- + APP_ID;
- private static final String URL_FORECAST =
- "http://api.openweathermap.org/data/2.5/forecast/daily?" +
- "%s&mode=json&units=%s&lang=%s&cnt=" + FORECAST_DAYS + "&appid=" + APP_ID;
-
- private Context mContext;
-
- public OpenWeatherMapProvider(Context context) {
- mContext = context;
- }
-
- @Override
- public int getNameResourceId() {
- return R.string.weather_source_openweathermap;
- }
-
- @Override
- public List<LocationResult> getLocations(String input) {
- String url = String.format(URL_LOCATION, Uri.encode(input), getLanguageCode());
- String response = HttpRetriever.retrieve(url);
- if (response == null) {
- return null;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "URL = " + url + " returning a response of " + response);
- }
-
- try {
- JSONArray jsonResults = new JSONObject(response).getJSONArray("list");
- ArrayList<LocationResult> results = new ArrayList<LocationResult>();
- int count = jsonResults.length();
-
- for (int i = 0; i < count; i++) {
- JSONObject result = jsonResults.getJSONObject(i);
- LocationResult location = new LocationResult();
-
- location.id = result.getString("id");
- location.city = result.getString("name");
- location.countryId = result.getJSONObject("sys").getString("country");
- results.add(location);
- }
-
- return results;
- } catch (JSONException e) {
- Log.w(TAG, "Received malformed location data (input=" + input + ")", e);
- }
-
- return null;
- }
-
- public WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metric) {
- String selection = String.format(Locale.US, SELECTION_ID, id);
- return handleWeatherRequest(selection, localizedCityName, metric);
- }
-
- public WeatherInfo getWeatherInfo(Location location, boolean metric) {
- String selection = String.format(Locale.US, SELECTION_LOCATION,
- location.getLatitude(), location.getLongitude());
- return handleWeatherRequest(selection, null, metric);
- }
-
- private WeatherInfo handleWeatherRequest(String selection,
- String localizedCityName, boolean metric) {
- String units = metric ? "metric" : "imperial";
- String locale = getLanguageCode();
- String conditionUrl = String.format(Locale.US, URL_WEATHER, selection, units, locale);
- String conditionResponse = HttpRetriever.retrieve(conditionUrl);
- if (conditionResponse == null) {
- return null;
- }
-
- String forecastUrl = String.format(Locale.US, URL_FORECAST, selection, units, locale);
- String forecastResponse = HttpRetriever.retrieve(forecastUrl);
- if (forecastResponse == null) {
- return null;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "URL = " + conditionUrl + " returning a response of " + conditionResponse);
- }
-
- try {
- JSONObject conditions = new JSONObject(conditionResponse);
- JSONObject weather = conditions.getJSONArray("weather").getJSONObject(0);
- JSONObject conditionData = conditions.getJSONObject("main");
- JSONObject windData = conditions.getJSONObject("wind");
- ArrayList<DayForecast> forecasts =
- parseForecasts(new JSONObject(forecastResponse).getJSONArray("list"), metric);
- int speedUnitResId = metric ? R.string.weather_kph : R.string.weather_mph;
- if (localizedCityName == null) {
- localizedCityName = conditions.getString("name");
- }
-
- WeatherInfo w = new WeatherInfo(mContext, conditions.getString("id"), localizedCityName,
- /* condition */ weather.getString("main"),
- /* conditionCode */ mapConditionIconToCode(
- weather.getString("icon"), weather.getInt("id")),
- /* temperature */ sanitizeTemperature(conditionData.getDouble("temp"), metric),
- /* tempUnit */ metric ? "C" : "F",
- /* humidity */ (float) conditionData.getDouble("humidity"),
- /* wind */ (float) windData.getDouble("speed"),
- /* windDir */ windData.getInt("deg"),
- /* speedUnit */ mContext.getString(speedUnitResId),
- forecasts,
- System.currentTimeMillis());
-
- Log.d(TAG, "Weather updated: " + w);
- return w;
- } catch (JSONException e) {
- Log.w(TAG, "Received malformed weather data (selection = " + selection
- + ", lang = " + locale + ")", e);
- }
-
- return null;
- }
-
- private ArrayList<DayForecast> parseForecasts(JSONArray forecasts, boolean metric) throws JSONException {
- ArrayList<DayForecast> result = new ArrayList<DayForecast>();
- int count = forecasts.length();
-
- if (count == 0) {
- throw new JSONException("Empty forecasts array");
- }
- for (int i = 0; i < count; i++) {
- JSONObject forecast = forecasts.getJSONObject(i);
- JSONObject temperature = forecast.getJSONObject("temp");
- JSONObject data = forecast.getJSONArray("weather").getJSONObject(0);
- DayForecast item = new DayForecast(
- /* low */ sanitizeTemperature(temperature.getDouble("min"), metric),
- /* high */ sanitizeTemperature(temperature.getDouble("max"), metric),
- /* condition */ data.getString("main"),
- /* conditionCode */ mapConditionIconToCode(
- data.getString("icon"), data.getInt("id")));
- result.add(item);
- }
-
- return result;
- }
-
- // OpenWeatherMap sometimes returns temperatures in Kelvin even if we ask it
- // for deg C or deg F. Detect this and convert accordingly.
- private static float sanitizeTemperature(double value, boolean metric) {
- // threshold chosen to work for both C and F. 170 deg F is hotter
- // than the hottest place on earth.
- if (value > 170) {
- // K -> deg C
- value -= 273.15;
- if (!metric) {
- // deg C -> deg F
- value = (value * 1.8) + 32;
- }
- }
- return (float) value;
- }
-
- private static final HashMap<String, Integer> ICON_MAPPING = new HashMap<String, Integer>();
- static {
- ICON_MAPPING.put("01d", 32);
- ICON_MAPPING.put("01n", 31);
- ICON_MAPPING.put("02d", 30);
- ICON_MAPPING.put("02n", 29);
- ICON_MAPPING.put("03d", 26);
- ICON_MAPPING.put("03n", 26);
- ICON_MAPPING.put("04d", 28);
- ICON_MAPPING.put("04n", 27);
- ICON_MAPPING.put("09d", 12);
- ICON_MAPPING.put("09n", 11);
- ICON_MAPPING.put("10d", 40);
- ICON_MAPPING.put("10n", 45);
- ICON_MAPPING.put("11d", 4);
- ICON_MAPPING.put("11n", 4);
- ICON_MAPPING.put("13d", 16);
- ICON_MAPPING.put("13n", 16);
- ICON_MAPPING.put("50d", 21);
- ICON_MAPPING.put("50n", 20);
- }
-
- private int mapConditionIconToCode(String icon, int conditionId) {
-
- // First, use condition ID for specific cases
- switch (conditionId) {
- // Thunderstorms
- case 202: // thunderstorm with heavy rain
- case 232: // thunderstorm with heavy drizzle
- case 211: // thunderstorm
- return 4;
- case 212: // heavy thunderstorm
- return 3;
- case 221: // ragged thunderstorm
- case 231: // thunderstorm with drizzle
- case 201: // thunderstorm with rain
- return 38;
- case 230: // thunderstorm with light drizzle
- case 200: // thunderstorm with light rain
- case 210: // light thunderstorm
- return 37;
-
- // Drizzle
- case 300: // light intensity drizzle
- case 301: // drizzle
- case 302: // heavy intensity drizzle
- case 310: // light intensity drizzle rain
- case 311: // drizzle rain
- case 312: // heavy intensity drizzle rain
- case 313: // shower rain and drizzle
- case 314: // heavy shower rain and drizzle
- case 321: // shower drizzle
- return 9;
-
- // Rain
- case 500: // light rain
- case 501: // moderate rain
- case 520: // light intensity shower rain
- case 521: // shower rain
- case 531: // ragged shower rain
- return 11;
- case 502: // heavy intensity rain
- case 503: // very heavy rain
- case 504: // extreme rain
- case 522: // heavy intensity shower rain
- return 12;
- case 511: // freezing rain
- return 10;
-
- // Snow
- case 600: case 620: return 14; // light snow
- case 601: case 621: return 16; // snow
- case 602: case 622: return 41; // heavy snow
- case 611: case 612: return 18; // sleet
- case 615: case 616: return 5; // rain and snow
-
- // Atmosphere
- case 741: // fog
- return 20;
- case 711: // smoke
- case 762: // volcanic ash
- return 22;
- case 701: // mist
- case 721: // haze
- return 21;
- case 731: // sand/dust whirls
- case 751: // sand
- case 761: // dust
- return 19;
- case 771: // squalls
- return 23;
- case 781: // tornado
- return 0;
-
- // Extreme
- case 900: return 0; // tornado
- case 901: return 1; // tropical storm
- case 902: return 2; // hurricane
- case 903: return 25; // cold
- case 904: return 36; // hot
- case 905: return 24; // windy
- case 906: return 17; // hail
- }
-
- // Not yet handled - Use generic icon mapping
- Integer condition = ICON_MAPPING.get(icon);
- if (condition != null) {
- return condition;
- }
-
- return -1;
- }
-
- private static final HashMap<String, String> LANGUAGE_CODE_MAPPING = new HashMap<String, String>();
- static {
- LANGUAGE_CODE_MAPPING.put("bg-", "bg");
- LANGUAGE_CODE_MAPPING.put("de-", "de");
- LANGUAGE_CODE_MAPPING.put("es-", "sp");
- LANGUAGE_CODE_MAPPING.put("fi-", "fi");
- LANGUAGE_CODE_MAPPING.put("fr-", "fr");
- LANGUAGE_CODE_MAPPING.put("it-", "it");
- LANGUAGE_CODE_MAPPING.put("nl-", "nl");
- LANGUAGE_CODE_MAPPING.put("pl-", "pl");
- LANGUAGE_CODE_MAPPING.put("pt-", "pt");
- LANGUAGE_CODE_MAPPING.put("ro-", "ro");
- LANGUAGE_CODE_MAPPING.put("ru-", "ru");
- LANGUAGE_CODE_MAPPING.put("se-", "se");
- LANGUAGE_CODE_MAPPING.put("tr-", "tr");
- LANGUAGE_CODE_MAPPING.put("uk-", "ua");
- LANGUAGE_CODE_MAPPING.put("zh-CN", "zh_cn");
- LANGUAGE_CODE_MAPPING.put("zh-TW", "zh_tw");
- }
- private String getLanguageCode() {
- Locale locale = mContext.getResources().getConfiguration().locale;
- String selector = locale.getLanguage() + "-" + locale.getCountry();
-
- for (Map.Entry<String, String> entry : LANGUAGE_CODE_MAPPING.entrySet()) {
- if (selector.startsWith(entry.getKey())) {
- return entry.getValue();
- }
- }
-
- return "en";
- }
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/Utils.java b/src/com/cyanogenmod/lockclock/weather/Utils.java
new file mode 100644
index 0000000..411fe29
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/Utils.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.lockclock.weather;
+
+import android.content.Context;
+import android.content.res.Resources;
+import com.cyanogenmod.lockclock.R;
+import cyanogenmod.providers.WeatherContract;
+
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_THUNDERSTORMS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_SNOW_SHOWERS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.ISOLATED_THUNDERSHOWERS;
+
+import java.text.DecimalFormat;
+
+public final class Utils {
+
+ private static final DecimalFormat sNoDigitsFormat = new DecimalFormat("0");
+
+ // In doubt? See https://en.wikipedia.org/wiki/Points_of_the_compass
+ private static final double DIRECTION_NORTH = 23d;
+ private static final double DIRECTION_NORTH_EAST = 68d;
+ private static final double DIRECTION_EAST = 113d;
+ private static final double DIRECTION_SOUTH_EAST = 158d;
+ private static final double DIRECTION_SOUTH = 203d;
+ private static final double DIRECTION_SOUTH_WEST = 248d;
+ private static final double DIRECTION_WEST = 293d;
+ private static final double DIRECTION_NORTH_WEST = 338d;
+
+ /**
+ * Returns a localized string of the wind direction
+ * @param context Application context to access resources
+ * @param windDirection The wind direction in degrees
+ * @return
+ */
+ public static String resolveWindDirection(Context context, double windDirection) {
+ int resId;
+
+ if (windDirection < 0) {
+ resId = R.string.unknown;
+ } else if (windDirection < DIRECTION_NORTH) {
+ resId = R.string.weather_N;
+ } else if (windDirection < DIRECTION_NORTH_EAST) {
+ resId = R.string.weather_NE;
+ } else if (windDirection < DIRECTION_EAST) {
+ resId = R.string.weather_E;
+ } else if (windDirection < DIRECTION_SOUTH_EAST) {
+ resId = R.string.weather_SE;
+ } else if (windDirection < DIRECTION_SOUTH) {
+ resId = R.string.weather_S;
+ } else if (windDirection < DIRECTION_SOUTH_WEST) {
+ resId = R.string.weather_SW;
+ } else if (windDirection < DIRECTION_WEST) {
+ resId = R.string.weather_W;
+ } else if (windDirection < DIRECTION_NORTH_WEST) {
+ resId = R.string.weather_NW;
+ } else {
+ resId = R.string.weather_N;
+ }
+
+ return context.getString(resId);
+ }
+
+ /**
+ * Returns the resource name associated to the supplied weather condition code
+ * @param context Application context to access resources
+ * @param conditionCode The weather condition code
+ * @return The resource name if a valid condition code is passed, empty string otherwise
+ */
+ public static String resolveWeatherCondition(Context context, int conditionCode) {
+ final Resources res = context.getResources();
+ final int resId = res.getIdentifier("weather_"
+ + Utils.addOffsetToConditionCodeFromWeatherContract(conditionCode), "string",
+ context.getPackageName());
+ if (resId != 0) {
+ return res.getString(resId);
+ }
+ return "";
+ }
+
+ private static String getFormattedValue(double value, String unit) {
+ if (Double.isNaN(value)) {
+ return "-";
+ }
+ String formatted = sNoDigitsFormat.format(value);
+ if (formatted.equals("-0")) {
+ formatted = "0";
+ }
+ return formatted + unit;
+ }
+
+ /**
+ * Returns a string with the format xx% (where xx is the humidity value provided)
+ * @param humidity The humidity value
+ * @return The formatted string if a valid value is provided, "-" otherwise. Decimals are
+ * removed
+ */
+ public static String formatHumidity(double humidity) {
+ return getFormattedValue(humidity, "%");
+ }
+
+ /**
+ * Returns a localized string of the speed and speed unit
+ * @param context Application context to access resources
+ * @param windSpeed The wind speed
+ * @param windSpeedUnit The speed unit. See
+ * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.WindSpeedUnit}
+ * @return The formatted string if a valid speed and speed unit a provided.
+ * {@link com.cyanogenmod.lockclock.R.string#unknown} otherwise
+ */
+ public static String formatWindSpeed(Context context, double windSpeed, int windSpeedUnit) {
+ if (windSpeed < 0) {
+ return context.getString(R.string.unknown);
+ }
+
+ String localizedSpeedUnit;
+ switch (windSpeedUnit) {
+ case WeatherContract.WeatherColumns.WindSpeedUnit.MPH:
+ localizedSpeedUnit = context.getString(R.string.weather_mph);
+ break;
+ case WeatherContract.WeatherColumns.WindSpeedUnit.KPH:
+ localizedSpeedUnit = context.getString(R.string.weather_kph);
+ break;
+ default:
+ return context.getString(R.string.unknown);
+ }
+ return getFormattedValue(windSpeed, localizedSpeedUnit);
+ }
+
+ /**
+ * Helper method to convert miles to kilometers
+ * @param miles The value in miles
+ * @return The value in kilometers
+ */
+ public static double milesToKilometers(double miles) {
+ return miles * 1.609344d;
+ }
+
+ /**
+ * Helper method to convert kilometers to miles
+ * @param km The value in kilometers
+ * @return The value in miles
+ */
+ public static double kilometersToMiles(double km) {
+ return km * 0.6214d;
+ }
+
+ /**
+ * Adds an offset to the condition code reported by the active weather service provider.
+ * @param conditionCode The condition code from the Weather API
+ * @return A condition code that correctly maps to our resource IDs
+ */
+ public static int addOffsetToConditionCodeFromWeatherContract(int conditionCode) {
+ if (conditionCode <= WeatherContract.WeatherColumns.WeatherCode.SHOWERS) {
+ return conditionCode;
+ } else if (conditionCode <= SCATTERED_THUNDERSTORMS) {
+ return conditionCode + 1;
+ } else if (conditionCode <= SCATTERED_SNOW_SHOWERS) {
+ return conditionCode + 2;
+ } else if (conditionCode <= ISOLATED_THUNDERSHOWERS) {
+ return conditionCode + 3;
+ } else {
+ return NOT_AVAILABLE;
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java b/src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java
deleted file mode 100644
index a2ab385..0000000
--- a/src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java
+++ /dev/null
@@ -1,187 +0,0 @@
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.util.Log;
-
-import com.cyanogenmod.lockclock.misc.Preferences;
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-
-public class WeatherContentProvider extends ContentProvider {
-
- public static final String TAG = WeatherContentProvider.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- static WeatherInfo sCachedWeatherInfo;
-
- private static final int URI_TYPE_EVERYTHING = 1;
- private static final int URI_TYPE_CURRENT = 2;
- private static final int URI_TYPE_FORECAST = 3;
-
- private static final String COLUMN_CURRENT_CITY_ID = "city_id";
- private static final String COLUMN_CURRENT_CITY = "city";
- private static final String COLUMN_CURRENT_CONDITION = "condition";
- private static final String COLUMN_CURRENT_TEMPERATURE = "temperature";
- private static final String COLUMN_CURRENT_HUMIDITY = "humidity";
- private static final String COLUMN_CURRENT_WIND = "wind";
- private static final String COLUMN_CURRENT_TIME_STAMP = "time_stamp";
- private static final String COLUMN_CURRENT_CONDITION_CODE = "condition_code";
-
- private static final String COLUMN_FORECAST_LOW = "forecast_low";
- private static final String COLUMN_FORECAST_HIGH = "forecast_high";
- private static final String COLUMN_FORECAST_CONDITION = "forecast_condition";
- private static final String COLUMN_FORECAST_CONDITION_CODE = "forecast_condition_code";
-
- private static final String[] PROJECTION_DEFAULT_CURRENT = new String[] {
- COLUMN_CURRENT_CITY_ID,
- COLUMN_CURRENT_CITY,
- COLUMN_CURRENT_CONDITION,
- COLUMN_CURRENT_TEMPERATURE,
- COLUMN_CURRENT_HUMIDITY,
- COLUMN_CURRENT_WIND,
- COLUMN_CURRENT_TIME_STAMP,
- COLUMN_CURRENT_CONDITION_CODE
- };
-
- private static final String[] PROJECTION_DEFAULT_FORECAST = new String[] {
- COLUMN_FORECAST_LOW,
- COLUMN_FORECAST_HIGH,
- COLUMN_FORECAST_CONDITION,
- COLUMN_FORECAST_CONDITION_CODE
- };
-
- private static final String[] PROJECTION_DEFAULT_EVERYTHING = new String[] {
- COLUMN_CURRENT_CITY_ID,
- COLUMN_CURRENT_CITY,
- COLUMN_CURRENT_CONDITION,
- COLUMN_CURRENT_TEMPERATURE,
- COLUMN_CURRENT_HUMIDITY,
- COLUMN_CURRENT_WIND,
- COLUMN_CURRENT_TIME_STAMP,
- COLUMN_CURRENT_CONDITION_CODE,
-
- COLUMN_FORECAST_LOW,
- COLUMN_FORECAST_HIGH,
- COLUMN_FORECAST_CONDITION,
- COLUMN_FORECAST_CONDITION_CODE
- };
-
- public static final String AUTHORITY = "com.cyanogenmod.lockclock.weather.provider";
-
- private static final UriMatcher sUriMatcher;
- static {
- sUriMatcher = new UriMatcher(URI_TYPE_EVERYTHING);
- sUriMatcher.addURI(AUTHORITY, "weather", URI_TYPE_EVERYTHING);
- sUriMatcher.addURI(AUTHORITY, "weather/current", URI_TYPE_CURRENT);
- sUriMatcher.addURI(AUTHORITY, "weather/forecast", URI_TYPE_FORECAST);
- }
-
- private Context mContext;
-
- @Override
- public boolean onCreate() {
- mContext = getContext();
- sCachedWeatherInfo = Preferences.getCachedWeatherInfo(mContext);
- return true;
- }
-
- @Override
- public Cursor query(
- Uri uri,
- String[] projection,
- String selection,
- String[] selectionArgs,
- String sortOrder) {
-
- final int projectionType = sUriMatcher.match(uri);
- final MatrixCursor result = new MatrixCursor(resolveProjection(projection, projectionType));
-
- WeatherInfo weather = sCachedWeatherInfo;
- if (weather != null) {
- // current
- result.newRow()
- .add(COLUMN_CURRENT_CITY, weather.getCity())
- .add(COLUMN_CURRENT_CITY_ID, weather.getId())
- .add(COLUMN_CURRENT_CONDITION, weather.getCondition())
- .add(COLUMN_CURRENT_HUMIDITY, weather.getFormattedHumidity())
- .add(COLUMN_CURRENT_WIND, weather.getFormattedWindSpeed()
- + " " + weather.getWindDirection())
- .add(COLUMN_CURRENT_TEMPERATURE, weather.getFormattedTemperature())
- .add(COLUMN_CURRENT_TIME_STAMP, weather.getTimestamp().toString())
- .add(COLUMN_CURRENT_CONDITION_CODE, weather.getConditionCode());
-
- // forecast
- for (DayForecast day : weather.getForecasts()) {
- result.newRow()
- .add(COLUMN_FORECAST_CONDITION, day.getCondition(mContext))
- .add(COLUMN_FORECAST_LOW, day.getFormattedLow())
- .add(COLUMN_FORECAST_HIGH, day.getFormattedHigh())
- .add(COLUMN_FORECAST_CONDITION_CODE, day.getConditionCode());
- }
- return result;
- } else {
- if (DEBUG) Log.e(TAG, "sCachedWeatherInfo is null");
- Intent updateWeather = new Intent(WeatherUpdateService.ACTION_FORCE_UPDATE);
- updateWeather.setClass(mContext, WeatherUpdateService.class);
- mContext.startService(updateWeather);
- }
- return null;
- }
-
- private String[] resolveProjection(String[] projection, int uriType) {
- if (projection != null)
- return projection;
- switch (uriType) {
- default:
- case URI_TYPE_EVERYTHING:
- return PROJECTION_DEFAULT_EVERYTHING;
-
- case URI_TYPE_CURRENT:
- return PROJECTION_DEFAULT_CURRENT;
-
- case URI_TYPE_FORECAST:
- return PROJECTION_DEFAULT_FORECAST;
- }
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
-
- public static void updateCachedWeatherInfo(Context context, WeatherInfo info) {
- if (DEBUG) Log.e(TAG, "updateCachedWeatherInfo()");
- if(info != null) {
- if (DEBUG) Log.e(TAG, "set new weather info");
- sCachedWeatherInfo = WeatherInfo.fromSerializedString(context, info.toSerializedString());
- } else {
- if(DEBUG) Log.e(TAG, "nulled out cached weather info");
- sCachedWeatherInfo = null;
- }
- context.getContentResolver().notifyChange(
- Uri.parse("content://" + WeatherContentProvider.AUTHORITY + "/weather"), null);
- }
-
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java b/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
deleted file mode 100755
index 7ad4339..0000000
--- a/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2012 The AOKP Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-
-import com.cyanogenmod.lockclock.R;
-import com.cyanogenmod.lockclock.misc.IconUtils;
-
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Date;
-
-public class WeatherInfo {
- private static final DecimalFormat sNoDigitsFormat = new DecimalFormat("0");
-
- private Context mContext;
-
- private String id;
- private String city;
- private String condition;
- private int conditionCode;
- private float temperature;
- private String tempUnit;
- private float humidity;
- private float wind;
- private int windDirection;
- private String speedUnit;
- private long timestamp;
- private ArrayList<DayForecast> forecasts;
-
- public WeatherInfo(Context context, String id,
- String city, String condition, int conditionCode, float temp,
- String tempUnit, float humidity, float wind, int windDir,
- String speedUnit, ArrayList<DayForecast> forecasts, long timestamp) {
- this.mContext = context.getApplicationContext();
- this.id = id;
- this.city = city;
- this.condition = condition;
- this.conditionCode = conditionCode;
- this.humidity = humidity;
- this.wind = wind;
- this.windDirection = windDir;
- this.speedUnit = speedUnit;
- this.timestamp = timestamp;
- this.temperature = temp;
- this.tempUnit = tempUnit;
- this.forecasts = forecasts;
- }
-
- public static class DayForecast {
- public final float low, high;
- public final int conditionCode;
- public final String condition;
-
- public DayForecast(float low, float high, String condition, int conditionCode) {
- this.low = low;
- this.high = high;
- this.condition = condition;
- this.conditionCode = conditionCode;
- }
-
- public String getFormattedLow() {
- return getFormattedValue(low, "\u00b0");
- }
-
- public String getFormattedHigh() {
- return getFormattedValue(high, "\u00b0");
- }
-
- public int getConditionResource(Context context, String set) {
- return IconUtils.getWeatherIconResource(context, set, conditionCode);
- }
-
- public Bitmap getConditionBitmap(Context context, String set, int color) {
- return IconUtils.getWeatherIconBitmap(context, set, color, conditionCode);
- }
-
- public Bitmap getConditionBitmap(Context context, String set, int color, int density) {
- return IconUtils.getWeatherIconBitmap(context, set, color, conditionCode, density);
- }
-
- public String getCondition(Context context) {
- return WeatherInfo.getCondition(context, conditionCode, condition);
- }
-
- public int getConditionCode() {
- return conditionCode;
- }
- }
-
- public int getConditionResource(String set) {
- return IconUtils.getWeatherIconResource(mContext, set, conditionCode);
- }
-
- public Bitmap getConditionBitmap(String set, int color) {
- return IconUtils.getWeatherIconBitmap(mContext, set, color, conditionCode);
- }
-
- public Bitmap getConditionBitmap(String set, int color, int density) {
- return IconUtils.getWeatherIconBitmap(mContext, set, color, conditionCode, density);
- }
-
- public String getId() {
- return id;
- }
-
- public String getCity() {
- return city;
- }
-
- public String getCondition() {
- return getCondition(mContext, conditionCode, condition);
- }
-
- public int getConditionCode() {
- return conditionCode;
- }
-
- private static String getCondition(Context context, int conditionCode, String condition) {
- final Resources res = context.getResources();
- final int resId = res.getIdentifier("weather_" + conditionCode, "string", context.getPackageName());
- if (resId != 0) {
- return res.getString(resId);
- }
- return condition;
- }
-
- public Date getTimestamp() {
- return new Date(timestamp);
- }
-
- private static String getFormattedValue(float value, String unit) {
- if (Float.isNaN(value)) {
- return "-";
- }
- String formatted = sNoDigitsFormat.format(value);
- if (formatted.equals("-0")) {
- formatted = "0";
- }
- return formatted + unit;
- }
-
- public String getFormattedTemperature() {
- return getFormattedValue(temperature, "\u00b0" + tempUnit);
- }
-
- public String getFormattedLow() {
- return forecasts.get(0).getFormattedLow();
- }
-
- public String getFormattedHigh() {
- return forecasts.get(0).getFormattedHigh();
- }
-
- public String getFormattedHumidity() {
- return getFormattedValue(humidity, "%");
- }
-
- public String getFormattedWindSpeed() {
- if (wind < 0) {
- return mContext.getString(R.string.unknown);
- }
- return getFormattedValue(wind, speedUnit);
- }
-
- public String getWindDirection() {
- int resId;
-
- if (windDirection < 0) resId = R.string.unknown;
- else if (windDirection < 23) resId = R.string.weather_N;
- else if (windDirection < 68) resId = R.string.weather_NE;
- else if (windDirection < 113) resId = R.string.weather_E;
- else if (windDirection < 158) resId = R.string.weather_SE;
- else if (windDirection < 203) resId = R.string.weather_S;
- else if (windDirection < 248) resId = R.string.weather_SW;
- else if (windDirection < 293) resId = R.string.weather_W;
- else if (windDirection < 338) resId = R.string.weather_NW;
- else resId = R.string.weather_N;
-
- return mContext.getString(resId);
- }
-
- public ArrayList<DayForecast> getForecasts() {
- return forecasts;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("WeatherInfo for ");
- builder.append(city);
- builder.append(" (");
- builder.append(id);
- builder.append(") @ ");
- builder.append(getTimestamp());
- builder.append(": ");
- builder.append(getCondition());
- builder.append("(");
- builder.append(conditionCode);
- builder.append("), temperature ");
- builder.append(getFormattedTemperature());
- builder.append(", low ");
- builder.append(getFormattedLow());
- builder.append(", high ");
- builder.append(getFormattedHigh());
- builder.append(", humidity ");
- builder.append(getFormattedHumidity());
- builder.append(", wind ");
- builder.append(getFormattedWindSpeed());
- builder.append(" at ");
- builder.append(getWindDirection());
- if (forecasts.size() > 0) {
- builder.append(", forecasts:");
- }
- for (int i = 0; i < forecasts.size(); i++) {
- DayForecast d = forecasts.get(i);
- if (i != 0) {
- builder.append(";");
- }
- builder.append(" day ").append(i + 1).append(": ");
- builder.append("high ").append(d.getFormattedHigh());
- builder.append(", low ").append(d.getFormattedLow());
- builder.append(", ").append(d.condition);
- builder.append("(").append(d.conditionCode).append(")");
- }
- return builder.toString();
- }
-
- public String toSerializedString() {
- StringBuilder builder = new StringBuilder();
- builder.append(id).append('|');
- builder.append(city).append('|');
- builder.append(condition).append('|');
- builder.append(conditionCode).append('|');
- builder.append(temperature).append('|');
- builder.append(tempUnit).append('|');
- builder.append(humidity).append('|');
- builder.append(wind).append('|');
- builder.append(windDirection).append('|');
- builder.append(speedUnit).append('|');
- builder.append(timestamp).append('|');
- serializeForecasts(builder);
- return builder.toString();
- }
-
- private void serializeForecasts(StringBuilder builder) {
- builder.append(forecasts.size());
- for (DayForecast d : forecasts) {
- builder.append(';');
- builder.append(d.high).append(';');
- builder.append(d.low).append(';');
- builder.append(d.condition).append(';');
- builder.append(d.conditionCode);
- }
- }
-
- public static WeatherInfo fromSerializedString(Context context, String input) {
- if (input == null) {
- return null;
- }
-
- String[] parts = input.split("\\|");
- if (parts == null || parts.length != 12) {
- return null;
- }
-
- int conditionCode, windDirection;
- long timestamp;
- float temperature, humidity, wind;
- String[] forecastParts = parts[11].split(";");
- int forecastItems;
- ArrayList<DayForecast> forecasts = new ArrayList<DayForecast>();
-
- // Parse the core data
- try {
- conditionCode = Integer.parseInt(parts[3]);
- temperature = Float.parseFloat(parts[4]);
- humidity = Float.parseFloat(parts[6]);
- wind = Float.parseFloat(parts[7]);
- windDirection = Integer.parseInt(parts[8]);
- timestamp = Long.parseLong(parts[10]);
- forecastItems = forecastParts == null ? 0 : Integer.parseInt(forecastParts[0]);
- } catch (NumberFormatException e) {
- return null;
- }
-
- if (forecastItems == 0 || forecastParts.length != 4 * forecastItems + 1) {
- return null;
- }
-
- // Parse the forecast data
- try {
- for (int item = 0; item < forecastItems; item ++) {
- int offset = item * 4 + 1;
- DayForecast day = new DayForecast(
- /* low */ Float.parseFloat(forecastParts[offset + 1]),
- /* high */ Float.parseFloat(forecastParts[offset]),
- /* condition */ forecastParts[offset + 2],
- /* conditionCode */ Integer.parseInt(forecastParts[offset + 3]));
- if (!Float.isNaN(day.low) && !Float.isNaN(day.high) && day.conditionCode >= 0) {
- forecasts.add(day);
- }
- }
- } catch (NumberFormatException ignored) {
- }
-
- if (forecasts.isEmpty()) {
- return null;
- }
-
- return new WeatherInfo(context,
- /* id */ parts[0], /* city */ parts[1], /* condition */ parts[2],
- conditionCode, temperature, /* tempUnit */ parts[5],
- humidity, wind, windDirection, /* speedUnit */ parts[9],
- /* forecasts */ forecasts, timestamp);
- }
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java b/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java
deleted file mode 100644
index 70fbf42..0000000
--- a/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2013 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.location.Location;
-
-import java.util.List;
-
-public interface WeatherProvider {
- public class LocationResult {
- public String id;
- public String city;
- public String postal;
- public String countryId;
- public String country;
- }
-
- List<LocationResult> getLocations(String input);
-
- WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metricUnits);
-
- WeatherInfo getWeatherInfo(Location location, boolean metricUnits);
-
- int getNameResourceId();
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java b/src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java
new file mode 100644
index 0000000..4bc816a
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.lockclock.weather;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import com.cyanogenmod.lockclock.ClockWidgetService;
+import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.misc.Preferences;
+import cyanogenmod.weather.CMWeatherManager;
+
+public class WeatherSourceListenerService extends Service
+ implements CMWeatherManager.WeatherServiceProviderChangeListener {
+
+ private static final String TAG = WeatherSourceListenerService.class.getSimpleName();
+ private static final boolean D = Constants.DEBUG;
+ private Context mContext;
+
+ @Override
+ public void onWeatherServiceProviderChanged(String providerLabel) {
+ if (D) Log.d(TAG, "Weather Source changed " + providerLabel);
+ Preferences.setWeatherSource(mContext, providerLabel);
+ Preferences.setCachedWeatherInfo(mContext, 0, null);
+ //The data contained in WeatherLocation is tightly coupled to the weather provider
+ //that generated that data, so we need to clear the cached weather location and let the new
+ //weather provider regenerate the data if the user decides to use custom location again
+ Preferences.setCustomWeatherLocationCity(mContext, null);
+ Preferences.setCustomWeatherLocation(mContext, null);
+ Preferences.setUseCustomWeatherLocation(mContext, false);
+
+ //Refresh the widget
+ mContext.startService(new Intent(mContext, ClockWidgetService.class)
+ .setAction(ClockWidgetService.ACTION_REFRESH));
+
+ if (providerLabel != null) {
+ mContext.startService(new Intent(mContext, WeatherUpdateService.class)
+ .putExtra(WeatherUpdateService.ACTION_FORCE_UPDATE, true));
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ mContext = getApplicationContext();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ final CMWeatherManager weatherManager
+ = CMWeatherManager.getInstance(mContext);
+ weatherManager.registerWeatherServiceProviderChangeListener(this);
+ if (D) Log.d(TAG, "Listener registered");
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ weatherManager.unregisterWeatherServiceProviderChangeListener(this);
+ if (D) Log.d(TAG, "Listener unregistered");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
index ea0b89c..fc652b1 100644
--- a/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
@@ -26,24 +26,31 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
-
+import android.widget.Toast;
import com.cyanogenmod.lockclock.ClockWidgetProvider;
+import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Constants;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
import com.cyanogenmod.lockclock.preference.WeatherPreferences;
-
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherLocation;
+import java.lang.ref.WeakReference;
import java.util.Date;
public class WeatherUpdateService extends Service {
@@ -54,6 +61,10 @@ public class WeatherUpdateService extends Service {
private static final String ACTION_CANCEL_LOCATION_UPDATE =
"com.cyanogenmod.lockclock.action.CANCEL_LOCATION_UPDATE";
+ private static final String ACTION_CANCEL_UPDATE_WEATHER_REQUEST =
+ "com.cyanogenmod.lockclock.action.CANCEL_UPDATE_WEATHER_REQUEST";
+ private static final long WEATHER_UPDATE_REQUEST_TIMEOUT_MS = 30L * 1000L;
+
// Broadcast action for end of update
public static final String ACTION_UPDATE_FINISHED = "com.cyanogenmod.lockclock.action.WEATHER_UPDATE_FINISHED";
public static final String EXTRA_UPDATE_CANCELLED = "update_cancelled";
@@ -62,7 +73,8 @@ public class WeatherUpdateService extends Service {
private static final long OUTDATED_LOCATION_THRESHOLD_MILLIS = 10L * 60L * 1000L; // 10 minutes
private static final float LOCATION_ACCURACY_THRESHOLD_METERS = 50000;
- private WeatherUpdateTask mTask;
+ private WorkerThread mWorkerThread;
+ private Handler mHandler;
private static final Criteria sLocationCriteria;
static {
@@ -73,107 +85,243 @@ public class WeatherUpdateService extends Service {
}
@Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate");
+ mWorkerThread = new WorkerThread(getApplicationContext());
+ mWorkerThread.start();
+ mWorkerThread.prepareHandler();
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (D) Log.v(TAG, "Got intent " + intent);
- boolean active = mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED;
-
if (ACTION_CANCEL_LOCATION_UPDATE.equals(intent.getAction())) {
WeatherLocationListener.cancel(this);
- if (!active) {
+ if (!mWorkerThread.isProcessing()) {
stopSelf();
}
return START_NOT_STICKY;
}
- if (active) {
- if (D) Log.v(TAG, "Weather update is still active, not starting new update");
- return START_REDELIVER_INTENT;
+ if (ACTION_CANCEL_UPDATE_WEATHER_REQUEST.equals(intent.getAction())) {
+ if (mWorkerThread.isProcessing()) {
+ mWorkerThread.getHandler().obtainMessage(
+ WorkerThread.MSG_CANCEL_UPDATE_WEATHER_REQUEST).sendToTarget();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final Context context = getApplicationContext();
+ final CMWeatherManager weatherManager
+ = CMWeatherManager.getInstance(context);
+ final String activeProviderLabel
+ = weatherManager.getActiveWeatherServiceProviderLabel();
+ final String noData
+ = getString(R.string.weather_cannot_reach_provider,
+ activeProviderLabel);
+ Toast.makeText(context, noData, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ stopSelf();
+ return START_NOT_STICKY;
}
boolean force = ACTION_FORCE_UPDATE.equals(intent.getAction());
if (!shouldUpdate(force)) {
Log.d(TAG, "Service started, but shouldn't update ... stopping");
- stopSelf();
sendCancelledBroadcast();
+ stopSelf();
return START_NOT_STICKY;
}
- mTask = new WeatherUpdateTask();
- mTask.execute();
+ mWorkerThread.getHandler().obtainMessage(WorkerThread.MSG_ON_NEW_WEATHER_REQUEST)
+ .sendToTarget();
return START_REDELIVER_INTENT;
}
- private void sendCancelledBroadcast() {
- Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
- finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, true);
- sendBroadcast(finishedIntent);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onDestroy() {
- if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) {
- mTask.cancel(true);
- mTask = null;
+ private boolean shouldUpdate(boolean force) {
+ final CMWeatherManager weatherManager
+ = CMWeatherManager.getInstance(getApplicationContext());
+ if (weatherManager.getActiveWeatherServiceProviderLabel() == null) {
+ //Why bother if we don't even have an active provider
+ if (D) Log.d(TAG, "No active weather service provider found, skip");
+ return false;
}
- }
- private boolean shouldUpdate(boolean force) {
- long interval = Preferences.weatherRefreshIntervalInMs(this);
+ final long interval = Preferences.weatherRefreshIntervalInMs(this);
if (interval == 0 && !force) {
- if (D) Log.v(TAG, "Interval set to manual and update not forced, skip update");
+ if (D) Log.v(TAG, "Interval set to manual and update not forced, skip");
return false;
}
if (!WeatherPreferences.hasLocationPermission(this)) {
- if (D) Log.v(TAG, "Application does not have the location permission");
+ if (D) Log.v(TAG, "Application does not have the location permission, skip");
return false;
}
- if (force) {
- Preferences.setCachedWeatherInfo(this, 0, null);
- }
-
- long now = System.currentTimeMillis();
- long lastUpdate = Preferences.lastWeatherUpdateTimestamp(this);
- long due = lastUpdate + interval;
-
- if (D) Log.d(TAG, "Now " + now + " due " + due + "(" + new Date(due) + ")");
+ if (WidgetUtils.isNetworkAvailable(this)) {
+ if (force) {
+ if (D) Log.d(TAG, "Forcing weather update");
+ return true;
+ } else {
+ final long now = SystemClock.elapsedRealtime();
+ final long lastUpdate = Preferences.lastWeatherUpdateTimestamp(this);
+ final long due = lastUpdate + interval;
+ if (D) {
+ Log.d(TAG, "Now " + now + " Last update " + lastUpdate
+ + " interval " + interval);
+ }
- if (lastUpdate != 0 && now < due) {
- if (D) Log.v(TAG, "Weather update is not due yet");
+ if (lastUpdate == 0 || due - now < 0) {
+ if (D) Log.d(TAG, "Should update");
+ return true;
+ } else {
+ if (D) Log.v(TAG, "Next weather update due in " + (due - now) + " ms, skip");
+ return false;
+ }
+ }
+ } else {
+ if (D) Log.d(TAG, "Network is not available, skip");
return false;
}
-
- return WidgetUtils.isNetworkAvailable(this);
}
- private class WeatherUpdateTask extends AsyncTask<Void, Void, WeatherInfo> {
+ private static class WorkerThread extends HandlerThread
+ implements CMWeatherManager.WeatherUpdateRequestListener {
+
+ public static final int MSG_ON_NEW_WEATHER_REQUEST = 1;
+ public static final int MSG_ON_WEATHER_REQUEST_COMPLETED = 2;
+ public static final int MSG_WEATHER_REQUEST_FAILED = 3;
+ public static final int MSG_CANCEL_UPDATE_WEATHER_REQUEST = 4;
+
+ private Handler mHandler;
+ private boolean mIsProcessingWeatherUpdate = false;
private WakeLock mWakeLock;
- private Context mContext;
+ private PendingIntent mTimeoutPendingIntent;
+ private int mRequestId;
+ private final CMWeatherManager mWeatherManager;
+ final private Context mContext;
+
+ public WorkerThread(Context context) {
+ super("weather-service-worker");
+ mContext = context;
+ mWeatherManager = CMWeatherManager.getInstance(mContext);
+ }
+
+ public synchronized void prepareHandler() {
+ mHandler = new Handler(getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (D) Log.d(TAG, "Msg " + msg.what);
+ switch (msg.what) {
+ case MSG_ON_NEW_WEATHER_REQUEST:
+ onNewWeatherRequest();
+ break;
+ case MSG_ON_WEATHER_REQUEST_COMPLETED:
+ WeatherInfo info = (WeatherInfo) msg.obj;
+ onWeatherRequestCompleted(info);
+ break;
+ case MSG_WEATHER_REQUEST_FAILED:
+ int status = msg.arg1;
+ onWeatherRequestFailed(status);
+ break;
+ case MSG_CANCEL_UPDATE_WEATHER_REQUEST:
+ onCancelUpdateWeatherRequest();
+ break;
+ default:
+ //Unknown message, pass it on...
+ super.handleMessage(msg);
+ }
+ }
+ };
+ }
+
+ private void startTimeoutAlarm() {
+ Intent intent = new Intent(mContext, WeatherUpdateService.class);
+ intent.setAction(ACTION_CANCEL_UPDATE_WEATHER_REQUEST);
+
+ mTimeoutPendingIntent = PendingIntent.getService(mContext, 0, intent,
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT);
+
+ AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE);
+ long elapseTime = SystemClock.elapsedRealtime() + WEATHER_UPDATE_REQUEST_TIMEOUT_MS;
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapseTime, mTimeoutPendingIntent);
+ if (D) Log.v(TAG, "Timeout alarm set to expire in " + elapseTime + " ms");
+ }
+
+ private void cancelTimeoutAlarm() {
+ if (mTimeoutPendingIntent != null) {
+ AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE);
+ am.cancel(mTimeoutPendingIntent);
+ mTimeoutPendingIntent = null;
+ if (D) Log.v(TAG, "Timeout alarm cancelled");
+ }
+ }
- public WeatherUpdateTask() {
- if (D) Log.d(TAG, "Starting weather update task");
- PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ public synchronized Handler getHandler() {
+ return mHandler;
+ }
+
+ private void onNewWeatherRequest() {
+ if (mIsProcessingWeatherUpdate) {
+ Log.d(TAG, "Already processing weather update, discarding request...");
+ return;
+ }
+
+ mIsProcessingWeatherUpdate = true;
+ final PowerManager pm
+ = (PowerManager) mContext.getSystemService(POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mWakeLock.setReferenceCounted(false);
- mContext = WeatherUpdateService.this;
+ if (D) Log.v(TAG, "ACQUIRING WAKELOCK");
+ mWakeLock.acquire();
+
+ WeatherLocation customWeatherLocation = null;
+ if (Preferences.useCustomWeatherLocation(mContext)) {
+ customWeatherLocation = Preferences.getCustomWeatherLocation(mContext);
+ }
+ if (customWeatherLocation != null) {
+ mRequestId = mWeatherManager.requestWeatherUpdate(customWeatherLocation, this);
+ if (D) Log.d(TAG, "Request submitted using WeatherLocation");
+ startTimeoutAlarm();
+ } else {
+ final Location location = getCurrentLocation();
+ if (location != null) {
+ mRequestId = mWeatherManager.requestWeatherUpdate(location, this);
+ if (D) Log.d(TAG, "Request submitted using Location");
+ startTimeoutAlarm();
+ } else {
+ // work with cached location from last request for now
+ // a listener to update it is already scheduled if possible
+ WeatherInfo cachedInfo = Preferences.getCachedWeatherInfo(mContext);
+ if (cachedInfo != null) {
+ mHandler.obtainMessage(MSG_ON_WEATHER_REQUEST_COMPLETED,
+ cachedInfo).sendToTarget();
+ if (D) Log.d(TAG, "Returning cached weather data [ "
+ + cachedInfo.toString()+ " ]");
+ } else {
+ mHandler.obtainMessage(MSG_WEATHER_REQUEST_FAILED).sendToTarget();
+ }
+ }
+ }
}
- @Override
- protected void onPreExecute() {
- if (D) Log.d(TAG, "ACQUIRING WAKELOCK");
- mWakeLock.acquire();
+ public void tearDown() {
+ if (D) Log.d(TAG, "Tearing down worker thread");
+ if (isProcessing()) mWeatherManager.cancelRequest(mRequestId);
+ quit();
+ }
+
+ public boolean isProcessing() {
+ return mIsProcessingWeatherUpdate;
}
private Location getCurrentLocation() {
- LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+ final LocationManager lm
+ = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
Location location = lm.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
if (D) Log.v(TAG, "Current location is " + location);
@@ -204,7 +352,6 @@ public class WeatherUpdateService extends Service {
WeatherLocationListener.registerIfNeeded(mContext, locationProvider);
}
}
-
return location;
}
@@ -214,77 +361,81 @@ public class WeatherUpdateService extends Service {
|| result == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED;
}
- @Override
- protected WeatherInfo doInBackground(Void... params) {
- WeatherProvider provider = Preferences.weatherProvider(mContext);
- boolean metric = Preferences.useMetricUnits(mContext);
- String customLocationId = null, customLocationName = null;
-
- if (Preferences.useCustomWeatherLocation(mContext)) {
- customLocationId = Preferences.customWeatherLocationId(mContext);
- customLocationName = Preferences.customWeatherLocationCity(mContext);
- }
+ private void onWeatherRequestCompleted(WeatherInfo result) {
+ if (D) Log.d(TAG, "Weather update received, caching data and updating widget");
+ cancelTimeoutAlarm();
+ long now = SystemClock.elapsedRealtime();
+ Preferences.setCachedWeatherInfo(mContext, now, result);
+ scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false);
- if (customLocationId != null) {
- return provider.getWeatherInfo(customLocationId, customLocationName, metric);
- }
+ Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
+ mContext.sendBroadcast(updateIntent);
+ broadcastAndCleanUp(false);
+ }
- Location location = getCurrentLocation();
- if (location != null) {
- WeatherInfo info = provider.getWeatherInfo(location, metric);
- if (info != null) {
- return info;
- }
+ private void onWeatherRequestFailed(int status) {
+ if (D) Log.d(TAG, "Weather refresh failed ["+status+"]");
+ cancelTimeoutAlarm();
+ if (status == CMWeatherManager.RequestStatus.ALREADY_IN_PROGRESS) {
+ if (D) Log.d(TAG, "A request is already in progress, no need to schedule again");
+ } else if (status == CMWeatherManager.RequestStatus.FAILED) {
+ //Something went wrong, let's schedule an update at the next interval from now
+ //A force update might happen earlier anyway
+ scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false);
+ } else {
+ //Wait until the next update is due
+ scheduleNextUpdate(mContext, false);
}
+ broadcastAndCleanUp(true);
+ }
- // work with cached location from last request for now
- // a listener to update it is already scheduled if possible
- WeatherInfo cachedInfo = Preferences.getCachedWeatherInfo(mContext);
- if (cachedInfo != null) {
- return provider.getWeatherInfo(cachedInfo.getId(), cachedInfo.getCity(), metric);
+ private void onCancelUpdateWeatherRequest() {
+ if (D) Log.d(TAG, "Cancelling active weather request");
+ if (mIsProcessingWeatherUpdate) {
+ cancelTimeoutAlarm();
+ mWeatherManager.cancelRequest(mRequestId);
+ broadcastAndCleanUp(true);
}
-
- return null;
}
- @Override
- protected void onPostExecute(WeatherInfo result) {
- finish(result);
- }
+ private void broadcastAndCleanUp(boolean updateCancelled) {
+ Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
+ finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, updateCancelled);
+ mContext.sendBroadcast(finishedIntent);
- @Override
- protected void onCancelled() {
- finish(null);
+ if (D) Log.d(TAG, "RELEASING WAKELOCK");
+ mWakeLock.release();
+ mIsProcessingWeatherUpdate = false;
+ mContext.stopService(new Intent(mContext, WeatherUpdateService.class));
}
- private void finish(WeatherInfo result) {
- if (result != null) {
- if (D) Log.d(TAG, "Weather update received, caching data and updating widget");
- long now = System.currentTimeMillis();
- Preferences.setCachedWeatherInfo(mContext, now, result);
- scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false);
-
- Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
- sendBroadcast(updateIntent);
- } else if (isCancelled()) {
- // cancelled, likely due to lost network - we'll get restarted
- // when network comes back
+ @Override
+ public void onWeatherRequestCompleted(int state, WeatherInfo weatherInfo) {
+ if (state == CMWeatherManager.RequestStatus.COMPLETED) {
+ mHandler.obtainMessage(WorkerThread.MSG_ON_WEATHER_REQUEST_COMPLETED, weatherInfo)
+ .sendToTarget();
} else {
- // failure, schedule next download in 30 minutes
- if (D) Log.d(TAG, "Weather refresh failed, scheduling update in 30 minutes");
- long interval = 30 * 60 * 1000;
- scheduleUpdate(mContext, interval, false);
+ mHandler.obtainMessage(WorkerThread.MSG_WEATHER_REQUEST_FAILED, state, 0)
+ .sendToTarget();
}
- WeatherContentProvider.updateCachedWeatherInfo(mContext, result);
+ }
+ }
- Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
- finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, result == null);
- sendBroadcast(finishedIntent);
+ private void sendCancelledBroadcast() {
+ Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
+ finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, true);
+ sendBroadcast(finishedIntent);
+ }
- if (D) Log.d(TAG, "RELEASING WAKELOCK");
- mWakeLock.release();
- stopSelf();
- }
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ mWorkerThread.tearDown();
}
private static class WeatherLocationListener implements LocationListener {
@@ -362,7 +513,7 @@ public class WeatherUpdateService extends Service {
// Now, we have a location to use. Schedule a weather update right now.
if (D) Log.d(TAG, "The location has changed, schedule an update ");
synchronized (WeatherLocationListener.class) {
- WeatherUpdateService.scheduleUpdate(mContext, 0, true);
+ scheduleUpdate(mContext, 0, true);
cancelTimeoutAlarm();
sInstance = null;
}
@@ -374,7 +525,7 @@ public class WeatherUpdateService extends Service {
if (D) Log.d(TAG, "The location service has become available, schedule an update ");
if (status == LocationProvider.AVAILABLE) {
synchronized (WeatherLocationListener.class) {
- WeatherUpdateService.scheduleUpdate(mContext, 0, true);
+ scheduleUpdate(mContext, 0, true);
cancelTimeoutAlarm();
sInstance = null;
}
@@ -392,21 +543,26 @@ public class WeatherUpdateService extends Service {
}
}
- private static void scheduleUpdate(Context context, long timeFromNow, boolean force) {
+ private static void scheduleUpdate(Context context, long millisFromNow, boolean force) {
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- long due = System.currentTimeMillis() + timeFromNow;
-
- if (D) Log.d(TAG, "Scheduling next update at " + new Date(due));
- am.set(AlarmManager.RTC_WAKEUP, due, getUpdateIntent(context, force));
+ long due = SystemClock.elapsedRealtime() + millisFromNow;
+ if (D) Log.d(TAG, "Next update scheduled at "
+ + new Date(System.currentTimeMillis() + millisFromNow));
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, due, getUpdateIntent(context, force));
}
public static void scheduleNextUpdate(Context context, boolean force) {
- long lastUpdate = Preferences.lastWeatherUpdateTimestamp(context);
- if (lastUpdate == 0 || force) {
+ if (force) {
+ if (D) Log.d(TAG, "Scheduling next update immediately");
scheduleUpdate(context, 0, true);
} else {
- long interval = Preferences.weatherRefreshIntervalInMs(context);
- scheduleUpdate(context, lastUpdate + interval - System.currentTimeMillis(), false);
+ final long lastUpdate = Preferences.lastWeatherUpdateTimestamp(context);
+ final long interval = Preferences.weatherRefreshIntervalInMs(context);
+ final long now = SystemClock.elapsedRealtime();
+ long due = (interval + lastUpdate) - now;
+ if (due < 0) due = 0;
+ if (D) Log.d(TAG, "Scheduling in " + due + " ms");
+ scheduleUpdate(context, due, false);
}
}
diff --git a/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java b/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java
deleted file mode 100644
index 21bc9e4..0000000
--- a/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2013 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.content.Context;
-import android.location.Location;
-import android.net.Uri;
-import android.text.Html;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-import com.cyanogenmod.lockclock.R;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-public class YahooWeatherProvider implements WeatherProvider {
- private static final String TAG = "YahooWeatherProvider";
-
- private static final String URL_WEATHER =
- "https://weather.yahooapis.com/forecastrss?w=%s&u=%s";
- private static final String URL_LOCATION =
- "https://query.yahooapis.com/v1/public/yql?format=json&q=" +
- Uri.encode("select woeid, postal, admin1, admin2, admin3, " +
- "locality1, locality2, country from geo.places where " +
- "(placetype = 7 or placetype = 8 or placetype = 9 " +
- "or placetype = 10 or placetype = 11 or placetype = 20) and text =");
- private static final String URL_PLACEFINDER =
- "https://query.yahooapis.com/v1/public/yql?format=json&q=" +
- Uri.encode("select * from geo.places where " +
- "text =");
-
- private static final String[] LOCALITY_NAMES = new String[] {
- "locality1", "locality2", "admin3", "admin2", "admin1"
- };
-
- private Context mContext;
-
- public YahooWeatherProvider(Context context) {
- mContext = context;
- }
-
- @Override
- public int getNameResourceId() {
- return R.string.weather_source_yahoo;
- }
-
- @Override
- public List<LocationResult> getLocations(String input) {
- String language = getLanguage();
- String params = "\"" + input + "\" and lang = \"" + language + "\"";
- String url = URL_LOCATION + Uri.encode(params);
- JSONObject jsonResults = fetchResults(url);
- if (jsonResults == null) {
- return null;
- }
-
- try {
- JSONArray places = jsonResults.optJSONArray("place");
- if (places == null) {
- // Yahoo returns an object instead of an array when there's only one result
- places = new JSONArray();
- places.put(jsonResults.getJSONObject("place"));
- }
-
- ArrayList<LocationResult> results = new ArrayList<LocationResult>();
- for (int i = 0; i < places.length(); i++) {
- LocationResult result = parsePlace(places.getJSONObject(i));
- if (result != null) {
- results.add(result);
- }
- }
- return results;
- } catch (JSONException e) {
- Log.e(TAG, "Received malformed places data (input=" + input + ", lang=" + language + ")", e);
- }
- return null;
- }
-
- @Override
- public WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metric) {
- String url = String.format(URL_WEATHER, id, metric ? "c" : "f");
- String response = HttpRetriever.retrieve(url);
-
- if (response == null) {
- return null;
- }
-
- SAXParserFactory factory = SAXParserFactory.newInstance();
- try {
- SAXParser parser = factory.newSAXParser();
- StringReader reader = new StringReader(response);
- WeatherHandler handler = new WeatherHandler();
- parser.parse(new InputSource(reader), handler);
-
- if (handler.isComplete()) {
- // There are cases where the current condition is unknown, but the forecast
- // is not - using the (inaccurate) forecast is probably better than showing
- // the question mark
- if (handler.conditionCode == 3200) {
- handler.condition = handler.forecasts.get(0).condition;
- handler.conditionCode = handler.forecasts.get(0).conditionCode;
- }
-
- WeatherInfo w = new WeatherInfo(mContext, id,
- localizedCityName != null ? localizedCityName : handler.city,
- handler.condition, handler.conditionCode, handler.temperature,
- handler.temperatureUnit, handler.humidity, handler.windSpeed,
- handler.windDirection, handler.speedUnit, handler.forecasts,
- System.currentTimeMillis());
- Log.d(TAG, "Weather updated: " + w);
- return w;
- } else {
- Log.w(TAG, "Received incomplete weather XML (id=" + id + ")");
- }
- } catch (ParserConfigurationException e) {
- Log.e(TAG, "Could not create XML parser", e);
- } catch (SAXException e) {
- Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
- } catch (IOException e) {
- Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
- }
-
- return null;
- }
-
- private static class WeatherHandler extends DefaultHandler {
- String city;
- String temperatureUnit, speedUnit;
- int windDirection, conditionCode;
- float humidity, temperature, windSpeed;
- String condition;
- ArrayList<DayForecast> forecasts = new ArrayList<DayForecast>();
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
- if (qName.equals("yweather:location")) {
- city = attributes.getValue("city");
- } else if (qName.equals("yweather:units")) {
- temperatureUnit = attributes.getValue("temperature");
- speedUnit = attributes.getValue("speed");
- } else if (qName.equals("yweather:wind")) {
- windDirection = (int) stringToFloat(attributes.getValue("direction"), -1);
- windSpeed = stringToFloat(attributes.getValue("speed"), -1);
- } else if (qName.equals("yweather:atmosphere")) {
- humidity = stringToFloat(attributes.getValue("humidity"), -1);
- } else if (qName.equals("yweather:condition")) {
- condition = attributes.getValue("text");
- conditionCode = (int) stringToFloat(attributes.getValue("code"), -1);
- temperature = stringToFloat(attributes.getValue("temp"), Float.NaN);
- } else if (qName.equals("yweather:forecast")) {
- DayForecast day = new DayForecast(
- /* low */ stringToFloat(attributes.getValue("low"), Float.NaN),
- /* high */ stringToFloat(attributes.getValue("high"), Float.NaN),
- /* condition */ attributes.getValue("text"),
- /* conditionCode */ (int) stringToFloat(attributes.getValue("code"), -1));
- if (!Float.isNaN(day.low) && !Float.isNaN(day.high) && day.conditionCode >= 0) {
- forecasts.add(day);
- }
- }
- }
- public boolean isComplete() {
- return temperatureUnit != null && speedUnit != null && conditionCode >= 0
- && !Float.isNaN(temperature) && !forecasts.isEmpty();
- }
- private float stringToFloat(String value, float defaultValue) {
- try {
- if (value != null) {
- return Float.parseFloat(value);
- }
- } catch (NumberFormatException e) {
- // fall through to the return line below
- }
- return defaultValue;
- }
- }
-
- @Override
- public WeatherInfo getWeatherInfo(Location location, boolean metric) {
- String language = getLanguage();
- String params = String.format(Locale.US, "\"(%f,%f)\" and lang=\"%s\"",
- location.getLatitude(), location.getLongitude(), language);
- String url = URL_PLACEFINDER + Uri.encode(params);
- JSONObject results = fetchResults(url);
- if (results == null) {
- return null;
- }
- try {
- JSONObject place = results.getJSONObject("place");
- LocationResult result = parsePlace(place);
- String woeid = null;
- String city = null;
- if (result != null) {
- woeid = result.id;
- city = result.city;
- }
- // The city name in the placefinder result is HTML encoded :-(
- if (city != null) {
- city = Html.fromHtml(city).toString();
- } else {
- Log.w(TAG, "Can not resolve place name for " + location);
- }
-
- Log.d(TAG, "Resolved location " + location + " to " + city + " (" + woeid + ")");
-
- WeatherInfo info = getWeatherInfo(woeid, city, metric);
- if (info != null) {
- return info;
- }
- } catch (JSONException e) {
- Log.e(TAG, "Received malformed placefinder data (location="
- + location + ", lang=" + language + ")", e);
- }
-
- return null;
- }
-
- private LocationResult parsePlace(JSONObject place) throws JSONException {
- LocationResult result = new LocationResult();
- JSONObject country = place.getJSONObject("country");
-
- result.id = place.getString("woeid");
- result.country = country.getString("content");
- result.countryId = country.getString("code");
- if (!place.isNull("postal")) {
- result.postal = place.getJSONObject("postal").getString("content");
- }
-
- for (String name : LOCALITY_NAMES) {
- if (!place.isNull(name)) {
- JSONObject localeObject = place.getJSONObject(name);
- result.city = localeObject.getString("content");
- if (localeObject.optString("woeid") != null) {
- result.id = localeObject.getString("woeid");
- }
- break;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "JSON data " + place.toString() + " -> id=" + result.id
- + ", city=" + result.city + ", country=" + result.countryId);
- }
-
- if (result.id == null || result.city == null || result.countryId == null) {
- return null;
- }
-
- return result;
- }
-
- private JSONObject fetchResults(String url) {
- String response = HttpRetriever.retrieve(url);
- if (response == null) {
- return null;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Request URL is " + url + ", response is " + response);
- }
-
- try {
- JSONObject rootObject = new JSONObject(response);
- return rootObject.getJSONObject("query").getJSONObject("results");
- } catch (JSONException e) {
- Log.w(TAG, "Received malformed places data (url=" + url + ")", e);
- }
-
- return null;
- }
-
- private String getLanguage() {
- Locale locale = mContext.getResources().getConfiguration().locale;
- String country = locale.getCountry();
- String language = locale.getLanguage();
-
- if (TextUtils.isEmpty(country)) {
- return language;
- }
- return language + "-" + country;
- }
-}