summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/cyanogenmod/lockclock/ClockWidgetProvider.java13
-rw-r--r--src/com/cyanogenmod/lockclock/ClockWidgetService.java81
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/misc/Constants.java4
-rw-r--r--src/com/cyanogenmod/lockclock/misc/IconUtils.java12
-rw-r--r--src/com/cyanogenmod/lockclock/misc/Preferences.java256
-rw-r--r--src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java208
-rw-r--r--src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java103
-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
18 files changed, 1163 insertions, 1631 deletions
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
index d13d519..c2ea11c 100644
--- a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
@@ -24,11 +24,11 @@ import android.net.ConnectivityManager;
import android.util.Log;
import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
import com.cyanogenmod.lockclock.weather.ForecastActivity;
+import com.cyanogenmod.lockclock.weather.WeatherSourceListenerService;
import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
-import com.cyanogenmod.lockclock.ClockWidgetService;
-import com.cyanogenmod.lockclock.WidgetApplication;
public class ClockWidgetProvider extends AppWidgetProvider {
private static final String TAG = "ClockWidgetProvider";
@@ -64,10 +64,9 @@ public class ClockWidgetProvider extends AppWidgetProvider {
// Boot completed, schedule next weather update
} else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- // On first boot lastUpdate will be 0 thus no need to force an update
- // Subsequent boots will use cached data
- WeatherUpdateService.scheduleNextUpdate(context, false);
-
+ //Since we're using elapsed time since boot, we can't use the timestamp from the
+ //previous boot so we need to reset the timer
+ Preferences.setLastWeatherUpadteTimestamp(context, 0);
// A widget has been deleted, prevent our handling and ask the super class handle it
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)
|| AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
@@ -124,6 +123,7 @@ public class ClockWidgetProvider extends AppWidgetProvider {
@Override
public void onEnabled(Context context) {
if (D) Log.d(TAG, "Scheduling next weather update");
+ context.startService(new Intent(context, WeatherSourceListenerService.class));
WeatherUpdateService.scheduleNextUpdate(context, true);
// Start the broadcast receiver (API 16 devices)
@@ -138,6 +138,7 @@ public class ClockWidgetProvider extends AppWidgetProvider {
@Override
public void onDisabled(Context context) {
if (D) Log.d(TAG, "Cleaning up: Clearing all pending alarms");
+ context.stopService(new Intent(context, WeatherSourceListenerService.class));
ClockWidgetService.cancelUpdates(context);
WeatherUpdateService.cancelUpdates(context);
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetService.java b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
index d5f6635..bf4be2c 100644
--- a/src/com/cyanogenmod/lockclock/ClockWidgetService.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
@@ -27,21 +27,24 @@ import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
-import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.RemoteViews;
-
import com.cyanogenmod.lockclock.calendar.CalendarViewsService;
import com.cyanogenmod.lockclock.misc.Constants;
import com.cyanogenmod.lockclock.misc.IconUtils;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
-import com.cyanogenmod.lockclock.weather.WeatherInfo;
+import com.cyanogenmod.lockclock.weather.Utils;
import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
+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.util.WeatherUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -60,6 +63,7 @@ public class ClockWidgetService extends IntentService {
private int[] mWidgetIds;
private AppWidgetManager mAppWidgetManager;
+ private Context mContext;
public ClockWidgetService() {
super("ClockWidgetService");
@@ -72,6 +76,7 @@ public class ClockWidgetService extends IntentService {
ComponentName thisWidget = new ComponentName(this, ClockWidgetProvider.class);
mAppWidgetManager = AppWidgetManager.getInstance(this);
mWidgetIds = mAppWidgetManager.getAppWidgetIds(thisWidget);
+ mContext = getApplicationContext();
}
@Override
@@ -413,27 +418,48 @@ public class ClockWidgetService extends IntentService {
int color = Preferences.weatherFontColor(this);
int timestampColor = Preferences.weatherTimestampFontColor(this);
String iconsSet = Preferences.getWeatherIconSet(this);
+ final boolean useMetric = Preferences.useMetricUnits(mContext);
// Reset no weather visibility
weatherViews.setViewVisibility(R.id.weather_no_data, View.GONE);
weatherViews.setViewVisibility(R.id.weather_refresh, View.GONE);
// Weather Image
- int resId = w.getConditionResource(iconsSet);
+ int resId = IconUtils.getWeatherIconResource(mContext, iconsSet, w.getConditionCode());
weatherViews.setViewVisibility(R.id.weather_image, View.VISIBLE);
if (resId != 0) {
- weatherViews.setImageViewResource(R.id.weather_image, w.getConditionResource(iconsSet));
+ weatherViews.setImageViewResource(R.id.weather_image,
+ IconUtils.getWeatherIconResource(mContext, iconsSet, w.getConditionCode()));
} else {
- weatherViews.setImageViewBitmap(R.id.weather_image, w.getConditionBitmap(iconsSet, color));
+ weatherViews.setImageViewBitmap(R.id.weather_image,
+ IconUtils.getWeatherIconBitmap(mContext, iconsSet, color,
+ w.getConditionCode()));
}
// Weather Condition
- weatherViews.setTextViewText(R.id.weather_condition, w.getCondition());
+ weatherViews.setTextViewText(R.id.weather_condition,
+ Utils.resolveWeatherCondition(mContext, w.getConditionCode()));
weatherViews.setViewVisibility(R.id.weather_condition, View.VISIBLE);
weatherViews.setTextColor(R.id.weather_condition, color);
// Weather Temps Panel
- weatherViews.setTextViewText(R.id.weather_temp, w.getFormattedTemperature());
+ 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;
+ }
+ weatherViews.setTextViewText(R.id.weather_temp,
+ WeatherUtils.formatTemperature(temp, tempUnit));
weatherViews.setViewVisibility(R.id.weather_temps_panel, View.VISIBLE);
weatherViews.setTextColor(R.id.weather_temp, color);
@@ -450,7 +476,7 @@ public class ClockWidgetService extends IntentService {
// Weather Update Time
if (showTimestamp) {
- Date updateTime = w.getTimestamp();
+ Date updateTime = new Date(w.getTimestamp());
StringBuilder sb = new StringBuilder();
sb.append(DateFormat.format("E", updateTime));
sb.append(" ");
@@ -464,8 +490,8 @@ public class ClockWidgetService extends IntentService {
// Weather Temps Panel additional items
boolean invertLowhigh = Preferences.invertLowHighTemperature(this);
- final String low = w.getFormattedLow();
- final String high = w.getFormattedHigh();
+ final String low = WeatherUtils.formatTemperature(todaysLow, tempUnit);
+ final String high = WeatherUtils.formatTemperature(todaysHigh, tempUnit);
weatherViews.setTextViewText(R.id.weather_low_high, invertLowhigh ? high + " | " + low : low + " | " + high);
weatherViews.setTextColor(R.id.weather_low_high, color);
}
@@ -482,8 +508,15 @@ public class ClockWidgetService extends IntentService {
boolean firstRun = Preferences.isFirstWeatherUpdate(this);
// Hide the normal weather stuff
- int providerNameResource = Preferences.weatherProvider(this).getNameResourceId();
- String noData = getString(R.string.weather_cannot_reach_provider, getString(providerNameResource));
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ final String activeProviderLabel = weatherManager.getActiveWeatherServiceProviderLabel();
+ String noData;
+ if (activeProviderLabel != null) {
+ noData = getString(R.string.weather_cannot_reach_provider, activeProviderLabel);
+ } else {
+ noData = getString(R.string.weather_source_title) + " "
+ + getString(R.string.weather_source_not_selected);
+ }
weatherViews.setViewVisibility(R.id.weather_image, View.INVISIBLE);
if (!smallWidget) {
weatherViews.setViewVisibility(R.id.weather_city, View.GONE);
@@ -493,7 +526,13 @@ public class ClockWidgetService extends IntentService {
// Set up the no data and refresh indicators
weatherViews.setTextViewText(R.id.weather_no_data, noData);
- weatherViews.setTextViewText(R.id.weather_refresh, getString(R.string.weather_tap_to_refresh));
+ if (activeProviderLabel != null) {
+ weatherViews.setTextViewText(R.id.weather_refresh,
+ getString(R.string.weather_tap_to_refresh));
+ } else {
+ weatherViews.setTextViewText(R.id.weather_refresh,
+ getString(R.string.weather_tap_to_select_source));
+ }
weatherViews.setTextColor(R.id.weather_no_data, color);
weatherViews.setTextColor(R.id.weather_refresh, color);
@@ -509,7 +548,11 @@ public class ClockWidgetService extends IntentService {
// Register an onClickListener on Weather with the default (Refresh) action
if (!firstRun) {
- setWeatherClickListener(weatherViews, true);
+ if (activeProviderLabel != null) {
+ setWeatherClickListener(weatherViews, true);
+ } else {
+ setWeatherClickListener(weatherViews);
+ }
}
}
@@ -528,7 +571,13 @@ public class ClockWidgetService extends IntentService {
weatherViews.setOnClickPendingIntent(R.id.weather_panel, pi);
}
-
+ private void setWeatherClickListener(RemoteViews weatherViews) {
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ new Intent("cyanogenmod.intent.action.MANAGE_WEATHER_PROVIDER_SERVICES"),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ weatherViews.setOnClickPendingIntent(R.id.weather_panel, pi);
+ }
+
//===============================================================================================
// Calendar related functionality
//===============================================================================================
diff --git a/src/com/cyanogenmod/lockclock/misc/Constants.java b/src/com/cyanogenmod/lockclock/misc/Constants.java
index a7113f2..fb35a57 100755
--- a/src/com/cyanogenmod/lockclock/misc/Constants.java
+++ b/src/com/cyanogenmod/lockclock/misc/Constants.java
@@ -38,14 +38,14 @@ public class Constants {
public static final String SHOW_WEATHER = "show_weather";
public static final String WEATHER_SOURCE = "weather_source";
public static final String WEATHER_USE_CUSTOM_LOCATION = "weather_use_custom_location";
- public static final String WEATHER_CUSTOM_LOCATION_ID = "weather_custom_location_id";
public static final String WEATHER_CUSTOM_LOCATION_CITY = "weather_custom_location_city";
+ public static final String WEATHER_CUSTOM_LOCATION = "weather_custom_location";
+ public static final String WEATHER_LOCATION = "weather_location";
public static final String WEATHER_SHOW_LOCATION = "weather_show_location";
public static final String WEATHER_SHOW_TIMESTAMP = "weather_show_timestamp";
public static final String WEATHER_USE_METRIC = "weather_use_metric";
public static final String WEATHER_INVERT_LOWHIGH = "weather_invert_lowhigh";
public static final String WEATHER_REFRESH_INTERVAL = "weather_refresh_interval";
- public static final String WEATHER_LOCATION_ID = "weather_woeid";
public static final String WEATHER_SHOW_WHEN_MINIMIZED = "weather_show_when_minimized";
public static final String WEATHER_FONT_COLOR = "weather_font_color";
public static final String WEATHER_TIMESTAMP_FONT_COLOR = "weather_timestamp_font_color";
diff --git a/src/com/cyanogenmod/lockclock/misc/IconUtils.java b/src/com/cyanogenmod/lockclock/misc/IconUtils.java
index 437d075..3a4f4d0 100644
--- a/src/com/cyanogenmod/lockclock/misc/IconUtils.java
+++ b/src/com/cyanogenmod/lockclock/misc/IconUtils.java
@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.Log;
import com.cyanogenmod.lockclock.R;
+import com.cyanogenmod.lockclock.weather.Utils;
public class IconUtils {
private static final String TAG = "IconUtils";
@@ -41,8 +42,9 @@ public class IconUtils {
}
final Resources res = context.getResources();
- final int resId = res.getIdentifier("weather_" + iconSet + "_" + conditionCode,
- "drawable", context.getPackageName());
+ final int resId = res.getIdentifier("weather_" + iconSet + "_"
+ + Utils.addOffsetToConditionCodeFromWeatherContract(conditionCode), "drawable",
+ context.getPackageName());
if (resId != 0) {
return resId;
@@ -62,12 +64,13 @@ public class IconUtils {
boolean isMonoSet = Constants.MONOCHROME.equals(iconSet);
Resources res = null;
int resId = 0;
+ int fixedConditionCode = Utils.addOffsetToConditionCodeFromWeatherContract(conditionCode);
if (iconSet.startsWith("ext:")) {
String packageName = iconSet.substring(4);
try {
res = context.getPackageManager().getResourcesForApplication(packageName);
- resId = res.getIdentifier("weather_" + conditionCode, "drawable", packageName);
+ resId = res.getIdentifier("weather_" + fixedConditionCode, "drawable", packageName);
} catch (PackageManager.NameNotFoundException e) {
// fall back to colored icons
iconSet = Constants.COLOR_STD;
@@ -75,7 +78,8 @@ public class IconUtils {
}
if (resId == 0) {
String identifier = isMonoSet
- ? "weather_" + conditionCode : "weather_" + iconSet + "_" + conditionCode;
+ ? "weather_" + fixedConditionCode : "weather_"
+ + iconSet + "_" + fixedConditionCode;
res = context.getResources();
resId = res.getIdentifier(identifier, "drawable", context.getPackageName());
}
diff --git a/src/com/cyanogenmod/lockclock/misc/Preferences.java b/src/com/cyanogenmod/lockclock/misc/Preferences.java
index 941e761..49a4632 100644
--- a/src/com/cyanogenmod/lockclock/misc/Preferences.java
+++ b/src/com/cyanogenmod/lockclock/misc/Preferences.java
@@ -19,17 +19,43 @@ package com.cyanogenmod.lockclock.misc;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherLocation;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
-import com.cyanogenmod.lockclock.weather.OpenWeatherMapProvider;
-import com.cyanogenmod.lockclock.weather.WeatherInfo;
-import com.cyanogenmod.lockclock.weather.WeatherProvider;
-import com.cyanogenmod.lockclock.weather.YahooWeatherProvider;
-
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import java.util.Set;
public class Preferences {
+
+ private static final String WEATHER_LOCATION_CITY_ID = "city_id";
+ private static final String WEATHER_LOCATION_CITY_NAME = "city_name";
+ private static final String WEATHER_LOCATION_STATE = "state";
+ private static final String WEATHER_LOCATION_POSTAL_CODE = "postal_code";
+ private static final String WEATHER_LOCATION_COUNTRY_ID = "country_id";
+ private static final String WEATHER_LOCATION_COUNTRY_NAME = "country_name";
+
+ private static final String WEATHER_INFO_CITY = "city";
+ private static final String WEATHER_INFO_CONDITION_CODE = "condition_code";
+ private static final String WEATHER_INFO_TEMPERATURE = "temperature";
+ private static final String WEATHER_INFO_TEMPERATURE_UNIT = "temperature_unit";
+ private static final String WEATHER_INFO_TIMESTAMP = "timestamp";
+ private static final String WEATHER_INFO_HUMIDITY = "humidity";
+ private static final String WEATHER_INFO_TODAYS_HIGH = "todays_high";
+ private static final String WEATHER_INFO_TODAYS_LOW = "todays_low";
+ private static final String WEATHER_INFO_WIND_SPEED = "wind_speed";
+ private static final String WEATHER_INFO_WIND_SPEED_UNIT = "wind_speed_unit";
+ private static final String WEATHER_INFO_WIND_SPEED_DIRECTION = "wind_speed_direction";
+ private static final String WEATHER_INFO_FORECAST = "forecasts";
+
+ private static final String DAY_FORECAST_CONDITION_CODE = "condition_code";
+ private static final String DAY_FORECAST_LOW = "low";
+ private static final String DAY_FORECAST_HIGH = "high";
+
private Preferences() {
}
@@ -172,7 +198,7 @@ public class Preferences {
public static long weatherRefreshIntervalInMs(Context context) {
String value = getPrefs(context).getString(Constants.WEATHER_REFRESH_INTERVAL, "60");
- return Long.parseLong(value) * 60 * 1000;
+ return Long.parseLong(value) * 60L * 1000L;
}
public static boolean useCustomWeatherLocation(Context context) {
@@ -183,37 +209,148 @@ public class Preferences {
getPrefs(context).edit().putBoolean(Constants.WEATHER_USE_CUSTOM_LOCATION, value).apply();
}
- public static String customWeatherLocationId(Context context) {
- return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_ID, null);
+ public static String getCustomWeatherLocationCity(Context context) {
+ return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_CITY, null);
}
- public static void setCustomWeatherLocationId(Context context, String id) {
- getPrefs(context).edit().putString(Constants.WEATHER_CUSTOM_LOCATION_ID, id).apply();
+ public static void setCustomWeatherLocationCity(Context context, String city) {
+ getPrefs(context).edit().putString(Constants.WEATHER_CUSTOM_LOCATION_CITY, city).apply();
}
- public static String customWeatherLocationCity(Context context) {
- return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_CITY, null);
+ public static boolean setCustomWeatherLocation(Context context, WeatherLocation weatherLocation) {
+ if (weatherLocation == null) {
+ getPrefs(context).edit()
+ .remove(Constants.WEATHER_CUSTOM_LOCATION).apply();
+ return true;
+ }
+ try {
+ JSONObject jsonObject = weatherLocationToJSON(weatherLocation);
+ getPrefs(context).edit()
+ .putString(Constants.WEATHER_CUSTOM_LOCATION, jsonObject.toString()).apply();
+ return true;
+ } catch (JSONException e) {
+ // We're here because weatherLocationToJSON() or jsonObject.toString() failed.
+ // Either way, it means the pref was not updated
+ return false;
+ }
}
- public static void setCustomWeatherLocationCity(Context context, String city) {
- getPrefs(context).edit().putString(Constants.WEATHER_CUSTOM_LOCATION_CITY, city).apply();
+ public static WeatherLocation getCustomWeatherLocation(Context context) {
+ String weatherLocation = getPrefs(context)
+ .getString(Constants.WEATHER_CUSTOM_LOCATION, null);
+
+ if (weatherLocation == null) {
+ return null;
+ }
+
+ try {
+ JSONObject jsonObject = new JSONObject(weatherLocation);
+ return JSONToWeatherLocation(jsonObject);
+ } catch (JSONException e) {
+ return null;
+ }
}
- public static WeatherProvider weatherProvider(Context context) {
- String name = getPrefs(context).getString(Constants.WEATHER_SOURCE, "yahoo");
- if (name.equals("openweathermap")) {
- return new OpenWeatherMapProvider(context);
+ private static WeatherLocation JSONToWeatherLocation(JSONObject jsonObject)
+ throws JSONException {
+ String cityId;
+ String cityName;
+ String state;
+ String postalCode;
+ String countryId;
+ String countryName;
+
+ cityId = jsonObject.getString(WEATHER_LOCATION_CITY_ID);
+ cityName = jsonObject.getString(WEATHER_LOCATION_CITY_NAME);
+ state = jsonObject.getString(WEATHER_LOCATION_STATE);
+ postalCode = jsonObject.getString(WEATHER_LOCATION_POSTAL_CODE);
+ countryId = jsonObject.getString(WEATHER_LOCATION_COUNTRY_ID);
+ countryName = jsonObject.getString(WEATHER_LOCATION_COUNTRY_NAME);
+
+ //We need at least city id and city name to build a WeatherLocation
+ if (cityId == null && cityName == null) {
+ return null;
}
- return new YahooWeatherProvider(context);
+
+ WeatherLocation.Builder location = new WeatherLocation.Builder(cityId, cityName);
+ if (countryId != null) location.setCountryId(countryId);
+ if (countryName != null) location.setCountry(countryName);
+ if (state != null) location.setState(state);
+ if (postalCode != null) location.setPostalCode(postalCode);
+
+ return location.build();
}
- public static void setCachedWeatherInfo(Context context, long timestamp, WeatherInfo data) {
+ private static JSONObject weatherLocationToJSON(WeatherLocation location) throws JSONException {
+ return new JSONObject()
+ .put(WEATHER_LOCATION_CITY_ID, location.getCityId())
+ .put(WEATHER_LOCATION_CITY_NAME, location.getCity())
+ .put(WEATHER_LOCATION_STATE, location.getState())
+ .put(WEATHER_LOCATION_POSTAL_CODE, location.getPostalCode())
+ .put(WEATHER_LOCATION_COUNTRY_ID, location.getCountryId())
+ .put(WEATHER_LOCATION_COUNTRY_NAME, location.getCountry());
+ }
+
+ public static void setCachedWeatherInfo(Context context, long timestamp, WeatherInfo info) {
SharedPreferences.Editor editor = getPrefs(context).edit();
editor.putLong(Constants.WEATHER_LAST_UPDATE, timestamp);
- if (data != null) {
+ if (info != null) {
// We now have valid weather data to display
- editor.putBoolean(Constants.WEATHER_FIRST_UPDATE, false);
- editor.putString(Constants.WEATHER_DATA, data.toSerializedString());
+ JSONObject jsonObject = new JSONObject();
+ boolean serialized = false;
+ try {
+ //These members always return a value that can be parsed
+ jsonObject
+ .put(WEATHER_INFO_CITY, info.getCity())
+ .put(WEATHER_INFO_CONDITION_CODE, info.getConditionCode())
+ .put(WEATHER_INFO_TEMPERATURE, info.getTemperature())
+ .put(WEATHER_INFO_TEMPERATURE_UNIT, info.getTemperatureUnit())
+ .put(WEATHER_INFO_TIMESTAMP, info.getTimestamp());
+
+ // Handle special cases. JSONObject.put(key, double) does not allow
+ // Double.NaN, so we store it as a string. JSONObject.getDouble() will parse the
+ // "NaN" string and return Double.NaN, which is what we want
+ double humidity = info.getHumidity();
+ jsonObject.put(WEATHER_INFO_HUMIDITY, Double.isNaN(humidity) ? "NaN" : humidity);
+
+ double todaysHigh = info.getTodaysHigh();
+ jsonObject.put(WEATHER_INFO_TODAYS_HIGH, Double.isNaN(todaysHigh)
+ ? "NaN" : todaysHigh);
+
+ double todaysLow = info.getTodaysLow();
+ jsonObject.put(WEATHER_INFO_TODAYS_LOW, Double.isNaN(todaysLow)
+ ? "NaN" : todaysLow);
+
+ double windSpeed = info.getWindSpeed();
+ double windDirection = info.getWindDirection();
+ jsonObject.put(WEATHER_INFO_WIND_SPEED, Double.isNaN(windSpeed) ? "NaN" : windSpeed)
+ .put(WEATHER_INFO_WIND_SPEED_UNIT, info.getWindSpeedUnit())
+ .put(WEATHER_INFO_WIND_SPEED_DIRECTION, Double.isNaN(windDirection)
+ ? "NaN" : windDirection);
+
+ JSONArray forecastArray = new JSONArray();
+ for (WeatherInfo.DayForecast forecast : info.getForecasts()) {
+ JSONObject jsonForecast = new JSONObject()
+ .put(DAY_FORECAST_CONDITION_CODE, forecast.getConditionCode());
+
+ double low = forecast.getLow();
+ jsonForecast.put(DAY_FORECAST_LOW, Double.isNaN(low) ? "NaN" : low);
+ double high = forecast.getHigh();
+ jsonForecast.put(DAY_FORECAST_HIGH, Double.isNaN(high) ? "NaN" : high);
+ forecastArray.put(jsonForecast);
+ }
+ jsonObject.put(WEATHER_INFO_FORECAST, forecastArray);
+ serialized = true;
+ } catch (JSONException e) {
+ // We're here because something went wrong while creating the JSON object.
+ // The code below will check for success and proceed accordingly
+ }
+ if (serialized) {
+ editor.putString(Constants.WEATHER_DATA, jsonObject.toString());
+ editor.putBoolean(Constants.WEATHER_FIRST_UPDATE, false);
+ }
+ } else {
+ editor.remove(Constants.WEATHER_DATA);
}
editor.apply();
}
@@ -222,17 +359,78 @@ public class Preferences {
return getPrefs(context).getLong(Constants.WEATHER_LAST_UPDATE, 0);
}
+ public static void setLastWeatherUpadteTimestamp(Context context, long timestamp) {
+ getPrefs(context).edit().putLong(Constants.WEATHER_LAST_UPDATE, timestamp).apply();
+ }
+
public static WeatherInfo getCachedWeatherInfo(Context context) {
- return WeatherInfo.fromSerializedString(context,
- getPrefs(context).getString(Constants.WEATHER_DATA, null));
+ final String cachedInfo = getPrefs(context).getString(Constants.WEATHER_DATA, null);
+
+ if (cachedInfo == null) return null;
+
+ String city;
+ int conditionCode;
+ double temperature;
+ int tempUnit;
+ double humidity;
+ double windSpeed;
+ double windDirection;
+ double todaysHigh;
+ double todaysLow;
+ int windSpeedUnit;
+ long timestamp;
+ ArrayList<WeatherInfo.DayForecast> forecastList = new ArrayList<>();
+
+ try {
+ JSONObject cached = new JSONObject(cachedInfo);
+ city = cached.getString(WEATHER_INFO_CITY);
+ conditionCode = cached.getInt(WEATHER_INFO_CONDITION_CODE);
+ temperature = cached.getDouble(WEATHER_INFO_TEMPERATURE);
+ tempUnit = cached.getInt(WEATHER_INFO_TEMPERATURE_UNIT);
+ humidity = cached.getDouble(WEATHER_INFO_HUMIDITY);
+ windSpeed = cached.getDouble(WEATHER_INFO_WIND_SPEED);
+ windDirection = cached.getDouble(WEATHER_INFO_WIND_SPEED_DIRECTION);
+ windSpeedUnit = cached.getInt(WEATHER_INFO_WIND_SPEED_UNIT);
+ timestamp = cached.getLong(WEATHER_INFO_TIMESTAMP);
+ todaysHigh = cached.getDouble(WEATHER_INFO_TODAYS_HIGH);
+ todaysLow = cached.getDouble(WEATHER_INFO_TODAYS_LOW);
+ JSONArray forecasts = cached.getJSONArray(WEATHER_INFO_FORECAST);
+ for (int indx = 0; indx < forecasts.length(); indx++) {
+ JSONObject forecast = forecasts.getJSONObject(indx);
+ double low;
+ double high;
+ int code;
+ low = forecast.getDouble(DAY_FORECAST_LOW);
+ high = forecast.getDouble(DAY_FORECAST_HIGH);
+ code = forecast.getInt(DAY_FORECAST_CONDITION_CODE);
+ WeatherInfo.DayForecast.Builder f = new WeatherInfo.DayForecast.Builder(code);
+ if (!Double.isNaN(low)) f.setLow(low);
+ if (!Double.isNaN(high)) f.setHigh(high);
+ forecastList.add(f.build());
+ }
+ WeatherInfo.Builder weatherInfo = new WeatherInfo.Builder(city, temperature, tempUnit)
+ .setWeatherCondition(conditionCode)
+ .setTimestamp(timestamp);
+
+ if (!Double.isNaN(humidity)) weatherInfo.setHumidity(humidity);
+ if (!Double.isNaN(windSpeed) && !Double.isNaN(windDirection)) {
+ weatherInfo.setWind(windSpeed, windDirection, windSpeedUnit);
+ }
+ if (forecastList.size() > 0) weatherInfo.setForecast(forecastList);
+ if (!Double.isNaN(todaysHigh)) weatherInfo.setTodaysHigh(todaysHigh);
+ if (!Double.isNaN(todaysLow)) weatherInfo.setTodaysLow(todaysLow);
+ return weatherInfo.build();
+ } catch (JSONException e) {
+ }
+ return null;
}
- public static String getCachedLocationId(Context context) {
- return getPrefs(context).getString(Constants.WEATHER_LOCATION_ID, null);
+ public static void setWeatherSource(Context context, String source) {
+ getPrefs(context).edit().putString(Constants.WEATHER_SOURCE, source).apply();
}
- public static void setCachedLocationId(Context context, String id) {
- getPrefs(context).edit().putString(Constants.WEATHER_LOCATION_ID, id).apply();
+ public static String getWeatherSource(Context context) {
+ return getPrefs(context).getString(Constants.WEATHER_SOURCE, null);
}
public static Set<String> calendarsToDisplay(Context context) {
diff --git a/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java b/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
index 6d0992f..c290cf5 100644
--- a/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
+++ b/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
@@ -17,27 +17,27 @@
package com.cyanogenmod.lockclock.preference;
import android.app.AlertDialog;
-import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.preference.EditTextPreference;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
-
import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Preferences;
-import com.cyanogenmod.lockclock.weather.WeatherProvider.LocationResult;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherLocation;
import java.util.HashSet;
import java.util.List;
-public class CustomLocationPreference extends EditTextPreference {
+public class CustomLocationPreference extends EditTextPreference
+ implements CMWeatherManager.LookupCityRequestListener {
public CustomLocationPreference(Context context) {
super(context);
}
@@ -48,18 +48,35 @@ public class CustomLocationPreference extends EditTextPreference {
super(context, attrs, defStyle);
}
+ private ProgressDialog mProgressDialog;
+ private int mCustomLocationRequestId;
+ private Handler mHandler;
@Override
protected void showDialog(Bundle state) {
super.showDialog(state);
+ mHandler = new Handler(getContext().getMainLooper());
final AlertDialog d = (AlertDialog) getDialog();
- Button okButton = d.getButton(DialogInterface.BUTTON_POSITIVE);
-
+ final Button okButton = d.getButton(DialogInterface.BUTTON_POSITIVE);
okButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CustomLocationPreference.this.onClick(d, DialogInterface.BUTTON_POSITIVE);
- new WeatherLocationTask(d, getEditText().getText().toString()).execute();
+ final String customLocationToLookUp = getEditText().getText().toString();
+ if (TextUtils.equals(customLocationToLookUp, "")) return;
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(getContext());
+ mProgressDialog = new ProgressDialog(getContext());
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setMessage(getContext().getString(R.string.weather_progress_title));
+ mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ weatherManager.cancelRequest(mCustomLocationRequestId);
+ }
+ });
+ mCustomLocationRequestId = weatherManager.lookupCity(customLocationToLookUp,
+ CustomLocationPreference.this);
+ mProgressDialog.show();
}
});
}
@@ -68,10 +85,12 @@ public class CustomLocationPreference extends EditTextPreference {
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
- String location = Preferences.customWeatherLocationCity(getContext());
+ String location = Preferences.getCustomWeatherLocationCity(getContext());
if (location != null) {
getEditText().setText(location);
getEditText().setSelection(location.length());
+ } else {
+ getEditText().setText("");
}
}
@@ -81,115 +100,88 @@ public class CustomLocationPreference extends EditTextPreference {
super.onDialogClosed(false);
}
- private class WeatherLocationTask extends AsyncTask<Void, Void, List<LocationResult>> {
- private Dialog mDialog;
- private ProgressDialog mProgressDialog;
- private String mLocation;
-
- public WeatherLocationTask(Dialog dialog, String location) {
- mDialog = dialog;
- mLocation = location;
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
-
- final Context context = getContext();
+ private void handleResultDisambiguation(final List<WeatherLocation> results) {
+ CharSequence[] items = buildItemList(results);
+ new AlertDialog.Builder(getContext())
+ .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ applyLocation(results.get(which));
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .setTitle(R.string.weather_select_location)
+ .show();
+ }
- mProgressDialog = new ProgressDialog(context);
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
- mProgressDialog.setMessage(context.getString(R.string.weather_progress_title));
- mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- cancel(true);
- }
- });
- mProgressDialog.show();
- }
+ private CharSequence[] buildItemList(List<WeatherLocation> results) {
+ boolean needCountry = false, needPostal = false;
+ String countryId = results.get(0).getCountryId();
+ HashSet<String> postalIds = new HashSet<>();
- @Override
- protected List<LocationResult> doInBackground(Void... input) {
- return Preferences.weatherProvider(getContext()).getLocations(mLocation);
+ for (WeatherLocation result : results) {
+ if (!TextUtils.equals(result.getCountryId(), countryId)) {
+ needCountry = true;
+ }
+ String postalId = result.getCountryId() + "##" + result.getCity();
+ if (postalIds.contains(postalId)) {
+ needPostal = true;
+ }
+ postalIds.add(postalId);
+ if (needPostal && needCountry) {
+ break;
+ }
}
- @Override
- protected void onPostExecute(List<LocationResult> results) {
- super.onPostExecute(results);
-
- final Context context = getContext();
-
- if (results == null || results.isEmpty()) {
- Toast.makeText(context,
- context.getString(R.string.weather_retrieve_location_dialog_title),
- Toast.LENGTH_SHORT)
- .show();
- } else if (results.size() > 1) {
- handleResultDisambiguation(results);
- } else {
- applyLocation(results.get(0));
+ int count = results.size();
+ CharSequence[] items = new CharSequence[count];
+ for (int i = 0; i < count; i++) {
+ WeatherLocation result = results.get(i);
+ StringBuilder builder = new StringBuilder();
+ if (needPostal && result.getPostalCode() != null) {
+ builder.append(result.getPostalCode()).append(" ");
}
- mProgressDialog.dismiss();
+ builder.append(result.getCity());
+ if (needCountry) {
+ String country = result.getCountry() != null
+ ? result.getCountry() : result.getCountryId();
+ builder.append(" (").append(country).append(")");
+ }
+ items[i] = builder.toString();
}
+ return items;
+ }
- private void handleResultDisambiguation(final List<LocationResult> results) {
- CharSequence[] items = buildItemList(results);
- new AlertDialog.Builder(getContext())
- .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- applyLocation(results.get(which));
- dialog.dismiss();
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .setTitle(R.string.weather_select_location)
- .show();
+ private void applyLocation(final WeatherLocation result) {
+ if (Preferences.setCustomWeatherLocation(getContext(), result)) {
+ String cityName = result.getCity();
+ String state = result.getState();
+ String country = result.getCountry();
+ setText(cityName + "," + state + "/" + country);
}
+ final AlertDialog d = (AlertDialog) getDialog();
+ d.dismiss();
+ }
- private CharSequence[] buildItemList(List<LocationResult> results) {
- boolean needCountry = false, needPostal = false;
- String countryId = results.get(0).countryId;
- HashSet<String> postalIds = new HashSet<String>();
-
- for (LocationResult result : results) {
- if (!TextUtils.equals(result.countryId, countryId)) {
- needCountry = true;
- }
- String postalId = result.countryId + "##" + result.city;
- if (postalIds.contains(postalId)) {
- needPostal = true;
- }
- postalIds.add(postalId);
- if (needPostal && needCountry) {
- break;
- }
- }
-
- int count = results.size();
- CharSequence[] items = new CharSequence[count];
- for (int i = 0; i < count; i++) {
- LocationResult result = results.get(i);
- StringBuilder builder = new StringBuilder();
- if (needPostal && result.postal != null) {
- builder.append(result.postal).append(" ");
- }
- builder.append(result.city);
- if (needCountry) {
- String country = result.country != null
- ? result.country : result.countryId;
- builder.append(" (").append(country).append(")");
+ @Override
+ public void onLookupCityRequestCompleted(int status, final List<WeatherLocation> locations) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final Context context = getContext();
+ if (locations == null || locations.isEmpty()) {
+ Toast.makeText(context,
+ context.getString(R.string.weather_retrieve_location_dialog_title),
+ Toast.LENGTH_SHORT)
+ .show();
+ } else if (locations.size() > 1) {
+ handleResultDisambiguation(locations);
+ } else {
+ applyLocation(locations.get(0));
}
- items[i] = builder.toString();
+ mProgressDialog.dismiss();
}
- return items;
- }
-
- private void applyLocation(final LocationResult result) {
- Preferences.setCustomWeatherLocationId(getContext(), result.id);
- setText(result.city);
- mDialog.dismiss();
- }
+ });
}
}
diff --git a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
index 1a42a20..0824a4b 100644
--- a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
@@ -19,7 +19,6 @@ package com.cyanogenmod.lockclock.preference;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -31,31 +30,24 @@ import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-
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.weather.WeatherUpdateService;
+import cyanogenmod.weather.CMWeatherManager;
public class WeatherPreferences extends PreferenceFragment implements
- SharedPreferences.OnSharedPreferenceChangeListener, Preference.OnPreferenceChangeListener {
+ SharedPreferences.OnSharedPreferenceChangeListener, Preference.OnPreferenceChangeListener,
+ CMWeatherManager.WeatherServiceProviderChangeListener {
private static final String TAG = "WeatherPreferences";
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
- private static final String[] LOCATION_PREF_KEYS = new String[] {
- Constants.WEATHER_USE_CUSTOM_LOCATION,
- Constants.WEATHER_CUSTOM_LOCATION_CITY
- };
- private static final String[] WEATHER_REFRESH_KEYS = new String[] {
- Constants.SHOW_WEATHER,
- Constants.WEATHER_REFRESH_INTERVAL
- };
-
private SwitchPreference mUseCustomLoc;
private EditTextPreference mCustomWeatherLoc;
private ListPreference mFontColor;
@@ -65,8 +57,8 @@ public class WeatherPreferences extends PreferenceFragment implements
private SwitchPreference mUseCustomlocation;
private SwitchPreference mShowWeather;
private Context mContext;
- private ContentResolver mResolver;
private Runnable mPostResumeRunnable;
+ private PreferenceScreen mWeatherSource;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -74,7 +66,6 @@ public class WeatherPreferences extends PreferenceFragment implements
getPreferenceManager().setSharedPreferencesName(Constants.PREF_NAME);
addPreferencesFromResource(R.xml.preferences_weather);
mContext = getActivity();
- mResolver = mContext.getContentResolver();
// Load items that need custom summaries etc.
mUseCustomLoc = (SwitchPreference) findPreference(Constants.WEATHER_USE_CUSTOM_LOCATION);
@@ -84,6 +75,18 @@ public class WeatherPreferences extends PreferenceFragment implements
mIconSet = (IconSelectionPreference) findPreference(Constants.WEATHER_ICONS);
mUseMetric = (SwitchPreference) findPreference(Constants.WEATHER_USE_METRIC);
mUseCustomlocation = (SwitchPreference) findPreference(Constants.WEATHER_USE_CUSTOM_LOCATION);
+ mWeatherSource = (PreferenceScreen) findPreference(Constants.WEATHER_SOURCE);
+ mWeatherSource.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ if (Preferences.getWeatherSource(mContext) != null && mShowWeather.isChecked()) {
+ mWeatherSource.notifyDependencyChange(false);
+ } else {
+ mWeatherSource.notifyDependencyChange(true);
+ }
+ return false;
+ }
+ });
mShowWeather = (SwitchPreference) findPreference(Constants.SHOW_WEATHER);
mShowWeather.setOnPreferenceChangeListener(this);
@@ -116,15 +119,34 @@ public class WeatherPreferences extends PreferenceFragment implements
mPostResumeRunnable = null;
}
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ weatherManager.registerWeatherServiceProviderChangeListener(this);
+
+ mWeatherSource.setEnabled(mShowWeather.isChecked());
+
updateLocationSummary();
updateFontColorsSummary();
updateIconSetSummary();
+ updateWeatherProviderSummary(getWeatherProviderName());
}
@Override
public void onPause() {
super.onPause();
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ weatherManager.unregisterWeatherServiceProviderChangeListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mUseCustomlocation.isChecked()
+ && Preferences.getCustomWeatherLocationCity(mContext) == null) {
+ //The user decided to toggle the custom location switch, but forgot to set a custom
+ //location, we need to go back to geo location
+ Preferences.setUseCustomWeatherLocation(mContext, false);
+ }
}
@Override
@@ -152,18 +174,24 @@ public class WeatherPreferences extends PreferenceFragment implements
forceWeatherUpdate = true;
}
- // If the weather source has changes, invalidate the custom location settings and change
- // back to GeoLocation to force the user to specify a new custom location if needed
if (TextUtils.equals(key, Constants.WEATHER_SOURCE)) {
- Preferences.setCustomWeatherLocationId(mContext, null);
+ // The weather source changed, invalidate the custom location settings and change
+ // back to GeoLocation to force the user to specify a new custom location if needed
Preferences.setCustomWeatherLocationCity(mContext, null);
+ Preferences.setCustomWeatherLocation(mContext, null);
Preferences.setUseCustomWeatherLocation(mContext, false);
mUseCustomlocation.setChecked(false);
updateLocationSummary();
}
- if (key.equals(Constants.WEATHER_USE_CUSTOM_LOCATION)
- || key.equals(Constants.WEATHER_CUSTOM_LOCATION_CITY)) {
+ if (key.equals(Constants.WEATHER_USE_CUSTOM_LOCATION)) {
+ if (!mUseCustomLoc.isChecked() || (mUseCustomLoc.isChecked() &&
+ Preferences.getCustomWeatherLocation(mContext) != null)) {
+ forceWeatherUpdate = true;
+ }
+ }
+
+ if (key.equals(Constants.WEATHER_CUSTOM_LOCATION_CITY) && mUseCustomLoc.isChecked()) {
forceWeatherUpdate = true;
}
@@ -171,6 +199,15 @@ public class WeatherPreferences extends PreferenceFragment implements
needWeatherUpdate = true;
}
+ if (key.equals(Constants.SHOW_WEATHER)) {
+ mWeatherSource.setEnabled(mShowWeather.isChecked());
+ if (Preferences.getWeatherSource(mContext) != null && mShowWeather.isChecked()) {
+ mWeatherSource.notifyDependencyChange(false);
+ } else {
+ mWeatherSource.notifyDependencyChange(true);
+ }
+ }
+
if (Constants.DEBUG) {
Log.v(TAG, "Preference " + key + " changed, need update " +
needWeatherUpdate + " force update " + forceWeatherUpdate);
@@ -199,7 +236,7 @@ public class WeatherPreferences extends PreferenceFragment implements
private void updateLocationSummary() {
if (mUseCustomLoc.isChecked()) {
- String location = Preferences.customWeatherLocationCity(mContext);
+ String location = Preferences.getCustomWeatherLocationCity(mContext);
if (location == null) {
location = getResources().getString(R.string.unknown);
}
@@ -274,4 +311,30 @@ public class WeatherPreferences extends PreferenceFragment implements
}
return true;
}
+
+ @Override
+ public void onWeatherServiceProviderChanged(String providerName) {
+ updateWeatherProviderSummary(providerName);
+ }
+
+ private void updateWeatherProviderSummary(String providerName) {
+ if (providerName != null) {
+ mWeatherSource.setSummary(providerName);
+ Preferences.setWeatherSource(mContext, providerName);
+ } else {
+ mWeatherSource.setSummary(R.string.weather_source_not_selected);
+ Preferences.setWeatherSource(mContext, null);
+ }
+
+ if (providerName != null && mShowWeather.isChecked()) {
+ mWeatherSource.notifyDependencyChange(false);
+ } else {
+ mWeatherSource.notifyDependencyChange(true);
+ }
+ }
+
+ private String getWeatherProviderName() {
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ return weatherManager.getActiveWeatherServiceProviderLabel();
+ }
}
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;
- }
-}