aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/lockclock/weather
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/cyanogenmod/lockclock/weather')
-rw-r--r--src/com/cyanogenmod/lockclock/weather/ForecastActivity.java147
-rw-r--r--src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java174
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/weather/HttpRetriever.java2
-rw-r--r--src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java311
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/weather/WeatherInfo.java188
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherProvider.java10
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java154
-rw-r--r--src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java105
8 files changed, 963 insertions, 128 deletions
diff --git a/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java b/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
new file mode 100644
index 0000000..2409cb8
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2013 David van Tonder
+ *
+ * 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.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;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.view.WindowManager;
+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.misc.Preferences;
+import com.cyanogenmod.lockclock.misc.WidgetUtils;
+import com.cyanogenmod.lockclock.R;
+
+public class ForecastActivity extends Activity implements OnClickListener {
+ private static final String TAG = "ForecastActivity";
+
+ private BroadcastReceiver mUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Stop the animation
+ ImageView view = (ImageView) findViewById(R.id.weather_refresh);
+ view.setAnimation(null);
+
+ if (!intent.getBooleanExtra(WeatherUpdateService.EXTRA_UPDATE_CANCELLED, false)) {
+ updateForecastPanel();
+ }
+ }
+ };
+
+ @SuppressLint("InlinedApi")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ // If we are in keyguard, override the default transparent theme
+ KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+ boolean locked = km.isKeyguardLocked();
+ if (locked) {
+ if (WidgetUtils.isTranslucencyAvailable()) {
+ setTheme(android.R.style.Theme_Holo_NoActionBar_TranslucentDecor);
+ } else {
+ setTheme(android.R.style.Theme_Holo_NoActionBar);
+ }
+ }
+ super.onCreate(savedInstanceState);
+
+ // Get the window ready
+ Window window = getWindow();
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ if (locked) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ final WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
+ final Drawable wallpaperDrawable = wallpaperManager.getFastDrawable();
+ window.setBackgroundDrawable(wallpaperDrawable);
+ } else if (WidgetUtils.isTranslucencyAvailable()) {
+ window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
+ WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
+ WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+
+ registerReceiver(mUpdateReceiver, new IntentFilter(WeatherUpdateService.ACTION_UPDATE_FINISHED));
+ updateForecastPanel();
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mUpdateReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ finish();
+ }
+
+ private void updateForecastPanel() {
+ // Get the forecasts data
+ WeatherInfo weather = Preferences.getCachedWeatherInfo(this);
+ if (weather == null) {
+ Log.e(TAG, "Error retrieving forecast data, exiting");
+ finish();
+ return;
+ }
+
+ View fullLayout = ForecastBuilder.buildFullPanel(this, R.layout.forecast_activity, weather);
+ setContentView(fullLayout);
+ fullLayout.requestFitSystemWindows();
+
+ // Register an onClickListener on Weather refresh
+ findViewById(R.id.weather_refresh).setOnClickListener(this);
+
+ // Register an onClickListener on the fake done button
+ findViewById(R.id.button).setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() != R.id.button) {
+ // Setup anim with desired properties and start the animation
+ ImageView view = (ImageView) findViewById(R.id.weather_refresh);
+ RotateAnimation anim = new RotateAnimation(0.0f, 360.0f,
+ Animation.RELATIVE_TO_SELF, 0.5f,
+ Animation.RELATIVE_TO_SELF, 0.5f);
+ anim.setInterpolator(new LinearInterpolator());
+ anim.setRepeatCount(Animation.INFINITE);
+ anim.setDuration(700);
+ view.startAnimation(anim);
+
+ Intent i = new Intent(this, WeatherUpdateService.class);
+ i.setAction(WeatherUpdateService.ACTION_FORCE_UPDATE);
+ startService(i);
+ } else {
+ finish();
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java b/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
new file mode 100644
index 0000000..6f8da53
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2013 David van Tonder
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use context 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 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.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.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;
+
+public class ForecastBuilder {
+ private static final String TAG = "ForecastBuilder";
+
+ /**
+ * This method is used to build the full current conditions and horizontal forecasts
+ * panels
+ *
+ * @param context
+ * @param w = the Weather info object that contains the forecast data
+ * @return = a built view that can be displayed
+ */
+ @SuppressLint("SetJavaScriptEnabled")
+ public static View buildFullPanel(Context context, int resourceId, WeatherInfo w) {
+
+ // Load some basic settings
+ LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ int color = Preferences.weatherFontColor(context);
+ boolean invertLowHigh = Preferences.invertLowHighTemperature(context);
+
+ 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());
+
+ // 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)));
+
+ // Weather Condition
+ TextView weatherCondition = (TextView) view.findViewById(R.id.weather_condition);
+ weatherCondition.setText(w.getCondition());
+
+ // Weather Temps
+ TextView weatherTemp = (TextView) view.findViewById(R.id.weather_temp);
+ weatherTemp.setText(w.getFormattedTemperature());
+
+ // City
+ TextView city = (TextView) view.findViewById(R.id.weather_city);
+ city.setText(w.getCity());
+
+ // Weather Update Time
+ Date lastUpdate = 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);
+
+ // Weather Temps Panel additional items
+ final String low = w.getFormattedLow();
+ final String high = w.getFormattedHigh();
+ TextView weatherLowHigh = (TextView) view.findViewById(R.id.weather_low_high);
+ weatherLowHigh.setText(invertLowHigh ? high + " | " + low : low + " | " + high);
+
+ // Get things ready
+ LinearLayout forecastView = (LinearLayout) view.findViewById(R.id.forecast_view);
+ final View progressIndicator = view.findViewById(R.id.progress_indicator);
+
+ // Build the forecast panel
+ if (buildSmallPanel(context, forecastView, w)) {
+ // Success, hide the progress container
+ progressIndicator.setVisibility(View.GONE);
+ }
+
+ return view;
+ }
+
+ /**
+ * This method is used to build the small, horizontal forecasts panel
+ * @param context
+ * @param smallPanel = a horizontal linearlayout that will contain the forecasts
+ * @param w = the Weather info object that contains the forecast data
+ */
+ public static boolean buildSmallPanel(Context context, LinearLayout smallPanel, WeatherInfo w) {
+ 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);
+
+ ArrayList<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) {
+ 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,
+ new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1));
+ }
+ return true;
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java b/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java
index 957d6dc..60723fa 100755
--- a/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java
+++ b/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java
@@ -38,7 +38,7 @@ public class HttpRetriever {
return EntityUtils.toString(entity);
}
} catch (IOException e) {
- Log.e(TAG, "Couldn't retrieve data", 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
new file mode 100644
index 0000000..808077c
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java
@@ -0,0 +1,311 @@
+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 URL_LOCATION =
+ "http://api.openweathermap.org/data/2.5/find?q=%s&mode=json&lang=%s";
+ private static final String URL_WEATHER =
+ "http://api.openweathermap.org/data/2.5/weather?%s&mode=json&units=%s&lang=%s";
+ 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;
+
+ 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"));
+ 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 */ (float) conditionData.getDouble("temp"),
+ /* 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) 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 */ (float) temperature.getDouble("min"),
+ /* high */ (float) temperature.getDouble("max"),
+ /* condition */ data.getString("main"),
+ /* conditionCode */ mapConditionIconToCode(
+ data.getString("icon"), data.getInt("id")));
+ result.add(item);
+ }
+
+ return result;
+ }
+
+ 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/WeatherInfo.java b/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
index a857734..b7c09d5 100755
--- a/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
@@ -21,9 +21,10 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import com.cyanogenmod.lockclock.R;
-import com.cyanogenmod.lockclock.misc.WidgetUtils;
+import com.cyanogenmod.lockclock.misc.IconUtils;
import java.text.DecimalFormat;
+import java.util.ArrayList;
import java.util.Date;
public class WeatherInfo {
@@ -33,27 +34,24 @@ public class WeatherInfo {
private String id;
private String city;
- private String forecastDate;
private String condition;
private int conditionCode;
private float temperature;
- private float lowTemperature;
- private float highTemperature;
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 fdate, String condition, int conditionCode,
- float temp, float low, float high, String tempUnit, float humidity,
- float wind, int windDir, String speedUnit, long timestamp) {
+ 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.forecastDate = fdate;
this.condition = condition;
this.conditionCode = conditionCode;
this.humidity = humidity;
@@ -62,27 +60,57 @@ public class WeatherInfo {
this.speedUnit = speedUnit;
this.timestamp = timestamp;
this.temperature = temp;
- this.lowTemperature = low;
- this.highTemperature = high;
this.tempUnit = tempUnit;
+ this.forecasts = forecasts;
}
- public int getConditionResource() {
- final Resources res = mContext.getResources();
- final int resId = res.getIdentifier("weather2_" + conditionCode, "drawable", mContext.getPackageName());
- if (resId != 0) {
- return resId;
+ 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");
}
- return R.drawable.weather2_na;
- }
- public Bitmap getConditionBitmap(int color) {
- final Resources res = mContext.getResources();
- int resId = res.getIdentifier("weather_" + conditionCode, "drawable", mContext.getPackageName());
- if (resId == 0) {
- resId = R.drawable.weather_na;
+ public String getFormattedHigh() {
+ return getFormattedValue(high, "\u00b0");
}
- return WidgetUtils.getOverlaidBitmap(mContext, resId, color);
+
+ 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 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() {
@@ -94,8 +122,12 @@ public class WeatherInfo {
}
public String getCondition() {
- final Resources res = mContext.getResources();
- final int resId = res.getIdentifier("weather_" + conditionCode, "string", mContext.getPackageName());
+ return getCondition(mContext, conditionCode, condition);
+ }
+
+ 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);
}
@@ -106,23 +138,27 @@ public class WeatherInfo {
return new Date(timestamp);
}
- private String getFormattedValue(float value, String unit) {
- if (Float.isNaN(highTemperature)) {
+ private static String getFormattedValue(float value, String unit) {
+ if (Float.isNaN(value)) {
return "-";
}
- return sNoDigitsFormat.format(value) + unit;
+ String formatted = sNoDigitsFormat.format(value);
+ if (formatted.equals("-0")) {
+ formatted = "0";
+ }
+ return formatted + unit;
}
public String getFormattedTemperature() {
- return getFormattedValue(temperature, "°" + tempUnit);
+ return getFormattedValue(temperature, "\u00b0" + tempUnit);
}
public String getFormattedLow() {
- return getFormattedValue(lowTemperature, "°");
+ return forecasts.get(0).getFormattedLow();
}
public String getFormattedHigh() {
- return getFormattedValue(highTemperature, "°");
+ return forecasts.get(0).getFormattedHigh();
}
public String getFormattedHumidity() {
@@ -153,6 +189,10 @@ public class WeatherInfo {
return mContext.getString(resId);
}
+ public ArrayList<DayForecast> getForecasts() {
+ return forecasts;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -178,6 +218,20 @@ public class WeatherInfo {
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();
}
@@ -185,52 +239,88 @@ public class WeatherInfo {
StringBuilder builder = new StringBuilder();
builder.append(id).append('|');
builder.append(city).append('|');
- builder.append(forecastDate).append('|');
builder.append(condition).append('|');
builder.append(conditionCode).append('|');
builder.append(temperature).append('|');
- builder.append(lowTemperature).append('|');
- builder.append(highTemperature).append('|');
builder.append(tempUnit).append('|');
builder.append(humidity).append('|');
builder.append(wind).append('|');
builder.append(windDirection).append('|');
builder.append(speedUnit).append('|');
- builder.append(timestamp);
+ 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 != 14) {
+ if (parts == null || parts.length != 12) {
return null;
}
int conditionCode, windDirection;
long timestamp;
- float temperature, low, high, humidity, wind;
+ 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[4]);
- temperature = Float.parseFloat(parts[5]);
- low = Float.parseFloat(parts[6]);
- high = Float.parseFloat(parts[7]);
- humidity = Float.parseFloat(parts[9]);
- wind = Float.parseFloat(parts[10]);
- windDirection = Integer.parseInt(parts[11]);
- timestamp = Long.parseLong(parts[13]);
+ 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], /* date */ parts[2],
- /* condition */ parts[3], conditionCode, temperature, low, high,
- /* tempUnit */ parts[8], humidity, wind, windDirection,
- /* speedUnit */ parts[12], timestamp);
+ /* 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
index 15c8aff..70fbf42 100644
--- a/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java
@@ -27,11 +27,13 @@ public interface WeatherProvider {
public String postal;
public String countryId;
public String country;
- };
+ }
List<LocationResult> getLocations(String input);
- WeatherInfo getWeatherInfo(String id, String localizedCityName);
+ WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metricUnits);
- WeatherInfo getWeatherInfo(Location location);
-};
+ WeatherInfo getWeatherInfo(Location location, boolean metricUnits);
+
+ int getNameResourceId();
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
index 94fca71..f38c046 100755
--- a/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
@@ -26,19 +26,19 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import com.cyanogenmod.lockclock.ClockWidgetProvider;
import com.cyanogenmod.lockclock.misc.Constants;
import com.cyanogenmod.lockclock.misc.Preferences;
+import com.cyanogenmod.lockclock.misc.WidgetUtils;
import java.util.Date;
@@ -47,6 +47,15 @@ public class WeatherUpdateService extends Service {
private static final boolean D = Constants.DEBUG;
public static final String ACTION_FORCE_UPDATE = "com.cyanogenmod.lockclock.action.FORCE_WEATHER_UPDATE";
+ private static final String ACTION_CANCEL_LOCATION_UPDATE =
+ "com.cyanogenmod.lockclock.action.CANCEL_LOCATION_UPDATE";
+
+ // 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";
+
+ private static final long LOCATION_REQUEST_TIMEOUT = 5L * 60L * 1000L; // request for at most 5 minutes
+ private static final long OUTDATED_LOCATION_THRESHOLD_MILLIS = 10L * 60L * 1000L; // 10 minutes
private WeatherUpdateTask mTask;
@@ -62,18 +71,26 @@ public class WeatherUpdateService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
if (D) Log.v(TAG, "Got intent " + intent);
- if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) {
+ boolean active = mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED;
+
+ if (ACTION_CANCEL_LOCATION_UPDATE.equals(intent.getAction())) {
+ WeatherLocationListener.cancel(this);
+ if (!active) {
+ 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;
}
boolean force = ACTION_FORCE_UPDATE.equals(intent.getAction());
- if (force) {
- Preferences.setCachedWeatherInfo(this, 0, null);
- }
if (!shouldUpdate(force)) {
Log.d(TAG, "Service started, but shouldn't update ... stopping");
stopSelf();
+ sendCancelledBroadcast();
return START_NOT_STICKY;
}
@@ -83,6 +100,12 @@ public class WeatherUpdateService extends Service {
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;
@@ -97,25 +120,16 @@ public class WeatherUpdateService extends Service {
}
private boolean shouldUpdate(boolean force) {
- ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
- NetworkInfo info = cm.getActiveNetworkInfo();
-
- if (info == null || !info.isConnected()) {
- if (D) Log.d(TAG, "No network connection is available for weather update");
- return false;
- }
-
- if (!Preferences.showWeather(this)) {
- if (D) Log.v(TAG, "Weather isn't shown, skip update");
- return false;
- }
-
long interval = Preferences.weatherRefreshIntervalInMs(this);
if (interval == 0 && !force) {
if (D) Log.v(TAG, "Interval set to manual and update not forced, skip update");
return false;
}
+ if (force) {
+ Preferences.setCachedWeatherInfo(this, 0, null);
+ }
+
long now = System.currentTimeMillis();
long lastUpdate = Preferences.lastWeatherUpdateTimestamp(this);
long due = lastUpdate + interval;
@@ -127,7 +141,7 @@ public class WeatherUpdateService extends Service {
return false;
}
- return true;
+ return WidgetUtils.isNetworkAvailable(this);
}
private class WeatherUpdateTask extends AsyncTask<Void, Void, WeatherInfo> {
@@ -138,6 +152,7 @@ public class WeatherUpdateService extends Service {
if (D) Log.d(TAG, "Starting weather update task");
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.setReferenceCounted(false);
mContext = WeatherUpdateService.this;
}
@@ -151,12 +166,32 @@ public class WeatherUpdateService extends Service {
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Location location = lm.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
if (D) Log.v(TAG, "Current location is " + location);
+
+ // If lastKnownLocation is not present (because none of the apps in the
+ // device has requested the current location to the system yet) or outdated,
+ // then try to get the current location use the provider that best matches the criteria.
+ boolean needsUpdate = location == null;
+ if (location != null) {
+ long delta = System.currentTimeMillis() - location.getTime();
+ needsUpdate = delta > OUTDATED_LOCATION_THRESHOLD_MILLIS;
+ }
+ if (needsUpdate) {
+ if (D) Log.d(TAG, "Getting best location provider");
+ String locationProvider = lm.getBestProvider(sLocationCriteria, true);
+ if (TextUtils.isEmpty(locationProvider)) {
+ Log.e(TAG, "No available location providers matching criteria.");
+ } else {
+ WeatherLocationListener.registerIfNeeded(mContext, locationProvider);
+ }
+ }
+
return location;
}
@Override
protected WeatherInfo doInBackground(Void... params) {
- WeatherProvider provider = new YahooWeatherProvider(mContext);
+ WeatherProvider provider = Preferences.weatherProvider(mContext);
+ boolean metric = Preferences.useMetricUnits(mContext);
String customLocationId = null, customLocationName = null;
if (Preferences.useCustomWeatherLocation(mContext)) {
@@ -165,31 +200,22 @@ public class WeatherUpdateService extends Service {
}
if (customLocationId != null) {
- return provider.getWeatherInfo(customLocationId, customLocationName);
+ return provider.getWeatherInfo(customLocationId, customLocationName, metric);
}
Location location = getCurrentLocation();
if (location != null) {
- WeatherInfo info = provider.getWeatherInfo(location);
+ WeatherInfo info = provider.getWeatherInfo(location, metric);
if (info != null) {
return info;
}
}
+
// 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());
- }
- // If lastKnownLocation is not present because none of the apps in the
- // device has requested the current location to the system yet, then try to
- // get the current location use the provider that best matches the criteria.
- if (D) Log.d(TAG, "Getting best location provider");
- LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
- String locationProvider = lm.getBestProvider(sLocationCriteria, true);
- if (TextUtils.isEmpty(locationProvider)) {
- Log.e(TAG, "No available location providers matching criteria.");
- } else {
- WeatherLocationListener.registerIfNeeded(mContext, locationProvider);
+ return provider.getWeatherInfo(cachedInfo.getId(), cachedInfo.getCity(), metric);
}
return null;
@@ -224,6 +250,10 @@ public class WeatherUpdateService extends Service {
scheduleUpdate(mContext, interval, false);
}
+ Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
+ finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, result == null);
+ sendBroadcast(finishedIntent);
+
if (D) Log.d(TAG, "RELEASING WAKELOCK");
mWakeLock.release();
stopSelf();
@@ -232,6 +262,7 @@ public class WeatherUpdateService extends Service {
private static class WeatherLocationListener implements LocationListener {
private Context mContext;
+ private PendingIntent mTimeoutIntent;
private static WeatherLocationListener sInstance = null;
static void registerIfNeeded(Context context, String provider) {
@@ -248,35 +279,79 @@ public class WeatherUpdateService extends Service {
// Check whether the provider is supported.
// NOTE!!! Actually only WeatherUpdateService class is calling this function
// with the NETWORK_PROVIDER, so setting the instance is safe. We must
- // change this if this call receive differents providers
+ // change this if this call receive different providers
LocationProvider lp = locationManager.getProvider(provider);
if (lp != null) {
if (D) Log.d(TAG, "LocationManager - Requesting single update");
locationManager.requestSingleUpdate(provider, sInstance,
appContext.getMainLooper());
+ sInstance.setTimeoutAlarm();
}
}
}
}
+ static void cancel(Context context) {
+ synchronized (WeatherLocationListener.class) {
+ if (sInstance != null) {
+ final Context appContext = context.getApplicationContext();
+ final LocationManager locationManager =
+ (LocationManager) appContext.getSystemService(Context.LOCATION_SERVICE);
+ if (D) Log.d(TAG, "Aborting location request after timeout");
+ locationManager.removeUpdates(sInstance);
+ sInstance.cancelTimeoutAlarm();
+ sInstance = null;
+ }
+ }
+ }
+
private WeatherLocationListener(Context context) {
super();
mContext = context;
}
+ private void setTimeoutAlarm() {
+ Intent intent = new Intent(mContext, WeatherUpdateService.class);
+ intent.setAction(ACTION_CANCEL_LOCATION_UPDATE);
+
+ mTimeoutIntent = PendingIntent.getService(mContext, 0, intent,
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT);
+
+ AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE);
+ long elapseTime = SystemClock.elapsedRealtime() + LOCATION_REQUEST_TIMEOUT;
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapseTime, mTimeoutIntent);
+ }
+
+ private void cancelTimeoutAlarm() {
+ if (mTimeoutIntent != null) {
+ AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE);
+ am.cancel(mTimeoutIntent);
+ mTimeoutIntent = null;
+ }
+ }
+
@Override
public void onLocationChanged(Location location) {
// 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);
+ cancelTimeoutAlarm();
sInstance = null;
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
- // Not used
+ // Now, we have a location to use. Schedule a weather update right now.
+ 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);
+ cancelTimeoutAlarm();
+ sInstance = null;
+ }
+ }
}
@Override
@@ -298,9 +373,9 @@ public class WeatherUpdateService extends Service {
am.set(AlarmManager.RTC_WAKEUP, due, getUpdateIntent(context, force));
}
- public static void scheduleNextUpdate(Context context) {
+ public static void scheduleNextUpdate(Context context, boolean force) {
long lastUpdate = Preferences.lastWeatherUpdateTimestamp(context);
- if (lastUpdate == 0) {
+ if (lastUpdate == 0 || force) {
scheduleUpdate(context, 0, true);
} else {
long interval = Preferences.weatherRefreshIntervalInMs(context);
@@ -320,5 +395,6 @@ public class WeatherUpdateService extends Service {
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.cancel(getUpdateIntent(context, true));
am.cancel(getUpdateIntent(context, false));
+ WeatherLocationListener.cancel(context);
}
}
diff --git a/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java b/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java
index dc714cb..5abe35d 100644
--- a/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java
+++ b/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java
@@ -19,9 +19,12 @@ 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.misc.Preferences;
+import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
+import com.cyanogenmod.lockclock.R;
import org.json.JSONArray;
import org.json.JSONException;
@@ -51,7 +54,7 @@ public class YahooWeatherProvider implements WeatherProvider {
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) and text =");
+ "or placetype = 10 or placetype = 11 or placetype = 20) and text =");
private static final String URL_PLACEFINDER =
"http://query.yahooapis.com/v1/public/yql?format=json&q=" +
Uri.encode("select woeid, city from geo.placefinder where gflags=\"R\" and text =");
@@ -67,9 +70,14 @@ public class YahooWeatherProvider implements WeatherProvider {
}
@Override
+ public int getNameResourceId() {
+ return R.string.weather_source_yahoo;
+ }
+
+ @Override
public List<LocationResult> getLocations(String input) {
- String locale = mContext.getResources().getConfiguration().locale.getCountry();
- String params = "\"" + input + "\" and lang = \"" + locale + "\"";
+ String language = getLanguage();
+ String params = "\"" + input + "\" and lang = \"" + language + "\"";
String url = URL_LOCATION + Uri.encode(params);
JSONObject jsonResults = fetchResults(url);
if (jsonResults == null) {
@@ -93,14 +101,14 @@ public class YahooWeatherProvider implements WeatherProvider {
}
return results;
} catch (JSONException e) {
- Log.e(TAG, "Received malformed places data", e);
+ Log.e(TAG, "Received malformed places data (input=" + input + ", lang=" + language + ")", e);
}
return null;
}
- public WeatherInfo getWeatherInfo(String id, String localizedCityName) {
- String unit = Preferences.useMetricUnits(mContext) ? "c" : "f";
- String url = String.format(URL_WEATHER, id, unit);
+ @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) {
@@ -115,22 +123,31 @@ public class YahooWeatherProvider implements WeatherProvider {
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, null,
+ localizedCityName != null ? localizedCityName : handler.city,
handler.condition, handler.conditionCode, handler.temperature,
- handler.forecasts.get(0).low, handler.forecasts.get(0).high,
handler.temperatureUnit, handler.humidity, handler.windSpeed,
- handler.windDirection, handler.speedUnit,
+ 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", e);
+ Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
} catch (IOException e) {
- Log.e(TAG, "Could not parse weather XML", e);
+ Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
}
return null;
@@ -144,12 +161,6 @@ public class YahooWeatherProvider implements WeatherProvider {
String condition;
ArrayList<DayForecast> forecasts = new ArrayList<DayForecast>();
- private static class DayForecast {
- float low, high;
- int conditionCode;
- String condition;
- }
-
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
@@ -168,11 +179,11 @@ public class YahooWeatherProvider implements WeatherProvider {
conditionCode = (int) stringToFloat(attributes.getValue("code"), -1);
temperature = stringToFloat(attributes.getValue("temp"), Float.NaN);
} else if (qName.equals("yweather:forecast")) {
- DayForecast day = new DayForecast();
- day.low = stringToFloat(attributes.getValue("low"), Float.NaN);
- day.high = stringToFloat(attributes.getValue("high"), Float.NaN);
- day.condition = attributes.getValue("text");
- day.conditionCode = (int) stringToFloat(attributes.getValue("code"), -1);
+ 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);
}
@@ -194,10 +205,11 @@ public class YahooWeatherProvider implements WeatherProvider {
}
}
- public WeatherInfo getWeatherInfo(Location location) {
- String locale = mContext.getResources().getConfiguration().locale.getCountry();
- String params = String.format(Locale.US, "\"%f %f\" and lang=\"%s\"",
- location.getLatitude(), location.getLongitude(), locale);
+ @Override
+ public WeatherInfo getWeatherInfo(Location location, boolean metric) {
+ String language = getLanguage();
+ String params = String.format(Locale.US, "\"%f %f\" and locale=\"%s\"",
+ location.getLatitude(), location.getLongitude(), language);
String url = URL_PLACEFINDER + Uri.encode(params);
JSONObject results = fetchResults(url);
if (results == null) {
@@ -209,17 +221,24 @@ public class YahooWeatherProvider implements WeatherProvider {
String woeid = result.getString("woeid");
String city = result.getString("city");
+ if (city == null) {
+ city = result.getString("neighborhood");
+ }
+
+ // The city name in the placefinder result is HTML encoded :-(
+ if (city != null) {
+ city = Html.fromHtml(city).toString();
+ }
+
Log.d(TAG, "Resolved location " + location + " to " + city + " (" + woeid + ")");
- WeatherInfo info = getWeatherInfo(woeid, city);
+ WeatherInfo info = getWeatherInfo(woeid, city, metric);
if (info != null) {
- // cache the result for potential reuse
- // (the placefinder service API is rate limited)
- Preferences.setCachedLocationId(mContext, woeid);
return info;
}
} catch (JSONException e) {
- Log.e(TAG, "Received malformed placefinder data", e);
+ Log.e(TAG, "Received malformed placefinder data (location="
+ + location + ", lang=" + language + ")", e);
}
return null;
@@ -243,6 +262,11 @@ public class YahooWeatherProvider implements WeatherProvider {
}
}
+ 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;
}
@@ -264,9 +288,20 @@ public class YahooWeatherProvider implements WeatherProvider {
JSONObject rootObject = new JSONObject(response);
return rootObject.getJSONObject("query").getJSONObject("results");
} catch (JSONException e) {
- Log.w(TAG, "Received malformed places data", 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;
+ }
+}