aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDanny Baumann <dannybaumann@web.de>2014-01-05 14:13:16 +0100
committerDanny Baumann <dannybaumann@web.de>2014-01-07 12:06:59 +0100
commitf1487b06a4671ddc79f713aaac4f9a425a3937e1 (patch)
tree9445aadafaccf7a6f5ec9593238b8c6f323197ec /src
parentc547e3dce2216eb2879a03f07266d3845da11c01 (diff)
downloadandroid_packages_apps_LockClock-f1487b06a4671ddc79f713aaac4f9a425a3937e1.tar.gz
android_packages_apps_LockClock-f1487b06a4671ddc79f713aaac4f9a425a3937e1.tar.bz2
android_packages_apps_LockClock-f1487b06a4671ddc79f713aaac4f9a425a3937e1.zip
Port over improvements from Chronus
- new weather source: OpenWeatherMap - weather icon pack support - weather forecast activity and popup - updated weather and in-app icons - some new translations (AR, TR, SL) Change-Id: I2bcc2042bf83d0e0bb4a00200de1310042303e9c
Diffstat (limited to 'src')
-rw-r--r--src/com/cyanogenmod/lockclock/ClockWidgetProvider.java11
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/ClockWidgetService.java46
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/calendar/CalendarViewsService.java (renamed from src/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java)2
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/misc/Constants.java9
-rw-r--r--src/com/cyanogenmod/lockclock/misc/IconUtils.java153
-rw-r--r--src/com/cyanogenmod/lockclock/misc/Preferences.java23
-rw-r--r--src/com/cyanogenmod/lockclock/misc/WidgetUtils.java47
-rw-r--r--src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java3
-rw-r--r--src/com/cyanogenmod/lockclock/preference/IconSelectionPreference.java314
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/preference/Preferences.java11
-rw-r--r--src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java53
-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
19 files changed, 1567 insertions, 196 deletions
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
index d2265cb..0441e00 100644
--- a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.cyanogenmod.lockclock.misc.Constants;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
+import com.cyanogenmod.lockclock.weather.ForecastActivity;
import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
import com.cyanogenmod.lockclock.ClockWidgetService;
import com.cyanogenmod.lockclock.WidgetApplication;
@@ -65,7 +66,7 @@ public class ClockWidgetProvider extends AppWidgetProvider {
} 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);
+ WeatherUpdateService.scheduleNextUpdate(context, false);
// A widget has been deleted, prevent our handling and ask the super class handle it
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)
@@ -86,6 +87,12 @@ public class ClockWidgetProvider extends AppWidgetProvider {
} else if (ClockWidgetService.ACTION_HIDE_CALENDAR.equals(action)) {
updateWidgets(context, false, true);
+ // The intent is to launch the modal pop-up forecast dialog
+ } else if (Constants.ACTION_SHOW_FORECAST.equals(action)) {
+ Intent i = new Intent(context, ForecastActivity.class);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(i);
+
// Something we did not handle, let the super class deal with it.
// This includes the REFRESH_CLOCK intent from Clock settings
} else {
@@ -117,7 +124,7 @@ public class ClockWidgetProvider extends AppWidgetProvider {
@Override
public void onEnabled(Context context) {
if (D) Log.d(TAG, "Scheduling next weather update");
- WeatherUpdateService.scheduleNextUpdate(context);
+ WeatherUpdateService.scheduleNextUpdate(context, true);
// Start the broadcast receiver (API 16 devices)
// This will schedule a repeating alarm every minute to handle the clock refresh
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetService.java b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
index 1698122..ea5da49 100755
--- a/src/com/cyanogenmod/lockclock/ClockWidgetService.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
@@ -35,8 +35,9 @@ import android.util.TypedValue;
import android.view.View;
import android.widget.RemoteViews;
-import com.cyanogenmod.lockclock.calendar.CalendarWidgetService;
+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;
@@ -328,10 +329,11 @@ public class ClockWidgetService extends IntentService {
if (!TextUtils.isEmpty(nextAlarm)) {
// An alarm is set, deal with displaying it
int color = Preferences.clockAlarmFontColor(this);
+ final Resources res = getResources();
// Overlay the selected color on the alarm icon and set the imageview
alarmViews.setImageViewBitmap(R.id.alarm_icon,
- WidgetUtils.getOverlaidBitmap(this, R.drawable.ic_alarm_small, color));
+ IconUtils.getOverlaidBitmap(res, R.drawable.ic_alarm_small, color));
alarmViews.setViewVisibility(R.id.alarm_icon, View.VISIBLE);
if (!smallWidget) {
@@ -389,19 +391,18 @@ public class ClockWidgetService extends IntentService {
private void setWeatherData(RemoteViews weatherViews, boolean smallWidget, WeatherInfo w) {
int color = Preferences.weatherFontColor(this);
int timestampColor = Preferences.weatherTimestampFontColor(this);
- boolean colorIcons = Preferences.useAlternateWeatherIcons(this);
+ String iconsSet = Preferences.getWeatherIconSet(this);
// Reset no weather visibility
weatherViews.setViewVisibility(R.id.weather_no_data, View.GONE);
weatherViews.setViewVisibility(R.id.weather_refresh, View.GONE);
// Weather Image
- if (colorIcons) {
- // No additional color overlays needed
- weatherViews.setImageViewResource(R.id.weather_image, w.getConditionResource());
+ int resId = w.getConditionResource(iconsSet);
+ if (resId != 0) {
+ weatherViews.setImageViewResource(R.id.weather_image, w.getConditionResource(iconsSet));
} else {
- // Overlay the condition image with the appropriate color
- weatherViews.setImageViewBitmap(R.id.weather_image, w.getConditionBitmap(color));
+ weatherViews.setImageViewBitmap(R.id.weather_image, w.getConditionBitmap(iconsSet, color));
}
// Weather Condition
@@ -448,7 +449,7 @@ public class ClockWidgetService extends IntentService {
}
// Register an onClickListener on Weather
- setWeatherClickListener(weatherViews);
+ setWeatherClickListener(weatherViews, false);
}
/**
@@ -459,7 +460,8 @@ public class ClockWidgetService extends IntentService {
boolean firstRun = Preferences.isFirstWeatherUpdate(this);
// Hide the normal weather stuff
- String noData = getString(R.string.weather_cannot_reach_provider, getString(R.string.weather_source));
+ int providerNameResource = Preferences.weatherProvider(this).getNameResourceId();
+ String noData = getString(R.string.weather_cannot_reach_provider, getString(providerNameResource));
weatherViews.setViewVisibility(R.id.weather_image, View.INVISIBLE);
if (!smallWidget) {
weatherViews.setViewVisibility(R.id.weather_city, View.GONE);
@@ -485,32 +487,44 @@ public class ClockWidgetService extends IntentService {
// Register an onClickListener on Weather with the default (Refresh) action
if (!firstRun) {
- setWeatherClickListener(weatherViews);
+ setWeatherClickListener(weatherViews, true);
}
}
- private void setWeatherClickListener(RemoteViews weatherViews) {
- weatherViews.setOnClickPendingIntent(R.id.weather_panel,
- WeatherUpdateService.getUpdateIntent(this, true));
+ private void setWeatherClickListener(RemoteViews weatherViews, boolean forceRefresh) {
+ // Register an onClickListener on the Weather panel, default action is show forecast
+ PendingIntent pi = null;
+ if (forceRefresh) {
+ pi = WeatherUpdateService.getUpdateIntent(this, true);
+ }
+
+ if (pi == null) {
+ Intent i = new Intent(this, ClockWidgetProvider.class);
+ i.setAction(Constants.ACTION_SHOW_FORECAST);
+ pi = PendingIntent.getBroadcast(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+ weatherViews.setOnClickPendingIntent(R.id.weather_panel, pi);
}
+
//===============================================================================================
// Calendar related functionality
//===============================================================================================
private void refreshCalendar(RemoteViews calendarViews, int widgetId) {
+ final Resources res = getResources();
// Calendar icon: Overlay the selected color and set the imageview
int color = Preferences.calendarFontColor(this);
// Hide the icon if preference set
if (Preferences.showCalendarIcon(this)) {
calendarViews.setImageViewBitmap(R.id.calendar_icon,
- WidgetUtils.getOverlaidBitmap(this, R.drawable.ic_lock_idle_calendar, color));
+ IconUtils.getOverlaidBitmap(res, R.drawable.ic_lock_idle_calendar, color));
} else {
calendarViews.setImageViewBitmap(R.id.calendar_icon, null);
}
// Set up and start the Calendar RemoteViews service
- final Intent remoteAdapterIntent = new Intent(this, CalendarWidgetService.class);
+ final Intent remoteAdapterIntent = new Intent(this, CalendarViewsService.class);
remoteAdapterIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
remoteAdapterIntent.setData(Uri.parse(remoteAdapterIntent.toUri(Intent.URI_INTENT_SCHEME)));
calendarViews.setRemoteAdapter(R.id.calendar_list, remoteAdapterIntent);
diff --git a/src/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java b/src/com/cyanogenmod/lockclock/calendar/CalendarViewsService.java
index a073280..893d8f7 100755
--- a/src/com/cyanogenmod/lockclock/calendar/CalendarWidgetService.java
+++ b/src/com/cyanogenmod/lockclock/calendar/CalendarViewsService.java
@@ -48,7 +48,7 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Set;
-public class CalendarWidgetService extends RemoteViewsService {
+public class CalendarViewsService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
diff --git a/src/com/cyanogenmod/lockclock/misc/Constants.java b/src/com/cyanogenmod/lockclock/misc/Constants.java
index db1b89f..751de2d 100755
--- a/src/com/cyanogenmod/lockclock/misc/Constants.java
+++ b/src/com/cyanogenmod/lockclock/misc/Constants.java
@@ -34,6 +34,7 @@ public class Constants {
public static final String CLOCK_AM_PM_INDICATOR = "clock_am_pm_indicator";
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";
@@ -42,12 +43,13 @@ public class Constants {
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_USE_ALTERNATE_ICONS = "weather_use_alternate_icons";
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";
-
+ public static final String WEATHER_ICONS = "weather_icons";
+ public static final String MONOCHROME = "mono";
+ public static final String COLOR_STD = "color";
public static final String SHOW_CALENDAR = "show_calendar";
public static final String CALENDAR_LIST = "calendar_list";
public static final String CALENDAR_LOOKAHEAD = "calendar_lookahead";
@@ -89,5 +91,8 @@ public class Constants {
public static final String DEFAULT_LIGHT_COLOR = "#ffffffff";
public static final String DEFAULT_DARK_COLOR = "#80ffffff";
+
+ // Intent actions
+ public static final String ACTION_SHOW_FORECAST = "com.cyanogenmod.lockclock.action.SHOW_FORECAST";
}
diff --git a/src/com/cyanogenmod/lockclock/misc/IconUtils.java b/src/com/cyanogenmod/lockclock/misc/IconUtils.java
new file mode 100644
index 0000000..437d075
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/misc/IconUtils.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project (DvTonder)
+ *
+ * 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.misc;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import com.cyanogenmod.lockclock.R;
+
+public class IconUtils {
+ private static final String TAG = "IconUtils";
+ private static boolean D = Constants.DEBUG;
+
+ public static int getWeatherIconResource(Context context, String iconSet, int conditionCode) {
+ if (iconSet.startsWith("ext:") || iconSet.equals(Constants.MONOCHROME)) {
+ return 0;
+ }
+
+ final Resources res = context.getResources();
+ final int resId = res.getIdentifier("weather_" + iconSet + "_" + conditionCode,
+ "drawable", context.getPackageName());
+
+ if (resId != 0) {
+ return resId;
+ }
+
+ // Use the default color set unknown icon
+ return R.drawable.weather_color_na;
+ }
+
+ public static Bitmap getWeatherIconBitmap(Context context, String iconSet,
+ int color, int conditionCode) {
+ return getWeatherIconBitmap(context, iconSet, color, conditionCode, 0);
+ }
+
+ public static Bitmap getWeatherIconBitmap(Context context, String iconSet,
+ int color, int conditionCode, int density) {
+ boolean isMonoSet = Constants.MONOCHROME.equals(iconSet);
+ Resources res = null;
+ int resId = 0;
+
+ if (iconSet.startsWith("ext:")) {
+ String packageName = iconSet.substring(4);
+ try {
+ res = context.getPackageManager().getResourcesForApplication(packageName);
+ resId = res.getIdentifier("weather_" + conditionCode, "drawable", packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ // fall back to colored icons
+ iconSet = Constants.COLOR_STD;
+ }
+ }
+ if (resId == 0) {
+ String identifier = isMonoSet
+ ? "weather_" + conditionCode : "weather_" + iconSet + "_" + conditionCode;
+ res = context.getResources();
+ resId = res.getIdentifier(identifier, "drawable", context.getPackageName());
+ }
+
+ if (resId == 0) {
+ resId = isMonoSet ? R.drawable.weather_na : R.drawable.weather_color_na;
+ }
+
+ return getOverlaidBitmap(res, resId, isMonoSet ? color : 0, density);
+ }
+
+ public static Bitmap getOverlaidBitmap(Resources res, int resId, int color) {
+ return getOverlaidBitmap(res, resId, color, 0);
+ }
+
+ public static Bitmap getOverlaidBitmap(Resources res, int resId, int color, int density) {
+ Bitmap src = getBitmapFromResource(res, resId, density);
+ if (color == 0 || src == null) {
+ return src;
+ }
+
+ final Bitmap dest = Bitmap.createBitmap(src.getWidth(), src.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(dest);
+ final Paint paint = new Paint();
+
+ // Overlay the selected color and set the imageview
+ paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+ c.drawBitmap(src, 0, 0, paint);
+ return dest;
+ }
+
+ public static Bitmap getBitmapFromResource(Resources res, int resId, int density) {
+ if (density == 0) {
+ if (D) Log.d(TAG, "Decoding resource id = " + resId + " for default density");
+ return BitmapFactory.decodeResource(res, resId);
+ }
+
+ if (D) Log.d(TAG, "Decoding resource id = " + resId + " for density = " + density);
+ Drawable d = res.getDrawableForDensity(resId, density);
+ if (d instanceof BitmapDrawable) {
+ BitmapDrawable bd = (BitmapDrawable) d;
+ return bd.getBitmap();
+ }
+
+ Bitmap result = Bitmap.createBitmap(d.getIntrinsicWidth(),
+ d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ d.setBounds(0, 0, result.getWidth(), result.getHeight());
+ d.draw(canvas);
+ canvas.setBitmap(null);
+
+ return result;
+ }
+
+ public static int getNextHigherDensity(Context context) {
+ Resources res = context.getResources();
+ int density = res.getDisplayMetrics().densityDpi;
+
+ if (density == DisplayMetrics.DENSITY_LOW) {
+ return DisplayMetrics.DENSITY_MEDIUM;
+ } else if (density == DisplayMetrics.DENSITY_MEDIUM) {
+ return DisplayMetrics.DENSITY_HIGH;
+ } else if (density == DisplayMetrics.DENSITY_HIGH) {
+ return DisplayMetrics.DENSITY_XHIGH;
+ } else if (density == DisplayMetrics.DENSITY_XHIGH) {
+ return DisplayMetrics.DENSITY_XXHIGH;
+ } else if (density == DisplayMetrics.DENSITY_XXHIGH) {
+ return DisplayMetrics.DENSITY_XXXHIGH;
+ }
+
+ // fallback: use current density
+ return density;
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/misc/Preferences.java b/src/com/cyanogenmod/lockclock/misc/Preferences.java
index a6c5c77..8e4c165 100644
--- a/src/com/cyanogenmod/lockclock/misc/Preferences.java
+++ b/src/com/cyanogenmod/lockclock/misc/Preferences.java
@@ -20,7 +20,10 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
+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.Calendar;
import java.util.Set;
@@ -137,8 +140,8 @@ public class Preferences {
return getPrefs(context).getBoolean(Constants.WEATHER_INVERT_LOWHIGH, false);
}
- public static boolean useAlternateWeatherIcons(Context context) {
- return getPrefs(context).getBoolean(Constants.WEATHER_USE_ALTERNATE_ICONS, true);
+ public static String getWeatherIconSet(Context context) {
+ return getPrefs(context).getString(Constants.WEATHER_ICONS, "color");
}
public static boolean useMetricUnits(Context context) {
@@ -154,6 +157,10 @@ public class Preferences {
return getPrefs(context).getBoolean(Constants.WEATHER_USE_CUSTOM_LOCATION, false);
}
+ public static void setUseCustomWeatherLocation(Context context, boolean value) {
+ 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);
}
@@ -166,6 +173,18 @@ public class Preferences {
return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_CITY, null);
}
+ public static void setCustomWeatherLocationCity(Context context, String city) {
+ getPrefs(context).edit().putString(Constants.WEATHER_CUSTOM_LOCATION_CITY, city).apply();
+ }
+
+ public static WeatherProvider weatherProvider(Context context) {
+ String name = getPrefs(context).getString(Constants.WEATHER_SOURCE, "yahoo");
+ if (name.equals("openweathermap")) {
+ return new OpenWeatherMapProvider(context);
+ }
+ return new YahooWeatherProvider(context);
+ }
+
public static void setCachedWeatherInfo(Context context, long timestamp, WeatherInfo data) {
SharedPreferences.Editor editor = getPrefs(context).edit();
editor.putLong(Constants.WEATHER_LAST_UPDATE, timestamp);
diff --git a/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java b/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java
index 465ddc2..e080793 100644
--- a/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java
+++ b/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java
@@ -23,13 +23,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Bitmap.Config;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
@@ -46,22 +41,6 @@ public class WidgetUtils {
private static final boolean D = Constants.DEBUG;
/**
- * Load a resource by Id and overlay with a specified color
- */
- public static Bitmap getOverlaidBitmap(Context context, int resId, int overlayColor) {
- final Resources res = context.getResources();
- final Bitmap src = BitmapFactory.decodeResource(res, resId);
- final Bitmap dest = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Config.ARGB_8888);
- Canvas c = new Canvas(dest);
- final Paint paint = new Paint();
-
- // Overlay the selected color and set the imageview
- paint.setColorFilter(new PorterDuffColorFilter(overlayColor, PorterDuff.Mode.SRC_ATOP));
- c.drawBitmap(src, 0, 0, paint);
- return dest;
- }
-
- /**
* Decide whether to show the small Weather panel
*/
public static boolean showSmallWidget(Context context, int id, boolean digitalClock, boolean isKeyguard) {
@@ -214,7 +193,27 @@ public class WidgetUtils {
/**
* API level check to see if the new API 17 TextClock is available
*/
- public static boolean isTextClockAvailable(){
+ public static boolean isTextClockAvailable() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
}
+
+ /**
+ * API level check to see if the new API 19 transparencies are available
+ */
+ public static boolean isTranslucencyAvailable() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ }
+
+ /**
+ * Networking available check
+ */
+ public static boolean isNetworkAvailable(Context context) {
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo info = cm.getActiveNetworkInfo();
+ if (info == null || !info.isConnected() || !info.isAvailable()) {
+ if (D) Log.d(TAG, "No network connection is available for weather update");
+ return false;
+ }
+ return true;
+ }
}
diff --git a/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java b/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
index d88809a..6d0992f 100644
--- a/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
+++ b/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
@@ -33,7 +33,6 @@ import android.widget.Toast;
import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.weather.WeatherProvider.LocationResult;
-import com.cyanogenmod.lockclock.weather.YahooWeatherProvider;
import java.util.HashSet;
import java.util.List;
@@ -112,7 +111,7 @@ public class CustomLocationPreference extends EditTextPreference {
@Override
protected List<LocationResult> doInBackground(Void... input) {
- return new YahooWeatherProvider(getContext()).getLocations(mLocation);
+ return Preferences.weatherProvider(getContext()).getLocations(mLocation);
}
@Override
diff --git a/src/com/cyanogenmod/lockclock/preference/IconSelectionPreference.java b/src/com/cyanogenmod/lockclock/preference/IconSelectionPreference.java
new file mode 100644
index 0000000..87309a2
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/preference/IconSelectionPreference.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project (DvTonder)
+ *
+ * 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.preference;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Locale;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.cyanogenmod.lockclock.R;
+
+public class IconSelectionPreference extends DialogPreference implements
+ AdapterView.OnItemClickListener {
+ private static final String INTENT_CATEGORY_ICONPACK = "com.dvtonder.chronus.ICON_PACK";
+
+ private static final String SEARCH_URI = "https://market.android.com/search?q=%s&c=apps";
+ private static final String APP_URI = "market://details?id=%s";
+
+ private static class IconSetDescriptor {
+ String name;
+ CharSequence description;
+ int descriptionResId;
+ Drawable previewDrawable;
+ int previewResId;
+ public IconSetDescriptor(String name, int descriptionResId,
+ int previewResId) {
+ this.name = name;
+ this.descriptionResId = descriptionResId;
+ this.previewResId = previewResId;
+ }
+ public IconSetDescriptor(String packageName, CharSequence description,
+ Drawable preview) {
+ this.name = "ext:" + packageName;
+ this.description = description;
+ this.previewDrawable = preview;
+ }
+ public CharSequence getDescription(Context context) {
+ if (description != null) {
+ return description;
+ }
+ return context.getString(descriptionResId);
+ }
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof IconSetDescriptor) {
+ IconSetDescriptor o = (IconSetDescriptor) other;
+ return name.equals(o.name);
+ }
+ return false;
+ }
+ }
+
+ private static final IconSetDescriptor ICON_SETS[] = new IconSetDescriptor[] {
+ new IconSetDescriptor("color", R.string.weather_icons_standard,
+ R.drawable.weather_color_28),
+ new IconSetDescriptor("mono", R.string.weather_icons_monochrome,
+ R.drawable.weather_28),
+ new IconSetDescriptor("vclouds", R.string.weather_icons_vclouds,
+ R.drawable.weather_vclouds_28)
+ };
+
+ private static final IntentFilter PACKAGE_CHANGE_FILTER = new IntentFilter();
+ static {
+ PACKAGE_CHANGE_FILTER.addAction(Intent.ACTION_PACKAGE_ADDED);
+ PACKAGE_CHANGE_FILTER.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ PACKAGE_CHANGE_FILTER.addDataScheme("package");
+ }
+
+ private IconSetAdapter mAdapter;
+ private GridView mGrid;
+ private String mValue;
+ private String mSelectedValue;
+ private String mPreviousSelection;
+
+ private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mAdapter.reenumerateIconSets();
+ if (getValueIndex(mSelectedValue) == GridView.INVALID_POSITION) {
+ selectValue(mAdapter.getItem(0).name);
+ } else {
+ // index might have changed
+ selectValue(mSelectedValue);
+ }
+ }
+ };
+
+ public IconSelectionPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAdapter = new IconSetAdapter(getContext());
+ }
+
+ public CharSequence getEntry() {
+ int index = getValueIndex(mValue);
+ if (index != GridView.INVALID_POSITION) {
+ return mAdapter.getItem(index).getDescription(getContext());
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ builder.setNeutralButton(R.string.icon_set_selection_get_more, null);
+ }
+
+ @Override
+ protected void showDialog(Bundle state) {
+ getContext().registerReceiver(mPackageChangeReceiver, PACKAGE_CHANGE_FILTER);
+ super.showDialog(state);
+
+ AlertDialog d = (AlertDialog) getDialog();
+ d.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ String uri = String.format(Locale.US, SEARCH_URI,
+ getContext().getString(R.string.icon_set_store_filter));
+ viewUri(getContext(), uri);
+ }
+ });
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ int selected = mGrid.getCheckedItemPosition();
+ if (positiveResult && selected != GridView.INVALID_POSITION) {
+ IconSetDescriptor descriptor = mAdapter.getItem(selected);
+ if (callChangeListener(descriptor.name)) {
+ mValue = descriptor.name;
+ persistString(descriptor.name);
+ }
+ }
+
+ getContext().unregisterReceiver(mPackageChangeReceiver);
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getString(index);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+ String defValue = (String) defaultValue;
+ mValue = restorePersistedValue ? getPersistedString(defValue) : defValue;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ IconSetDescriptor descriptor = mAdapter.getItem(mGrid.getCheckedItemPosition());
+ mSelectedValue = descriptor.name;
+ mPreviousSelection = mSelectedValue;
+ }
+
+ @Override
+ protected View onCreateDialogView() {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ View view = inflater.inflate(R.layout.icon_style_selection, null);
+
+ mGrid = (GridView) view.findViewById(R.id.icon_list);
+ mGrid.setAdapter(mAdapter);
+ mGrid.setOnItemClickListener(this);
+
+ selectValue(mValue);
+
+ return view;
+ }
+
+ private void selectValue(String value) {
+ int index = getValueIndex(value);
+ if (index == GridView.INVALID_POSITION) {
+ index = 0;
+ }
+ mGrid.setItemChecked(index, true);
+ mSelectedValue = mAdapter.getItem(index).name;
+ mPreviousSelection = mSelectedValue;
+ }
+
+ private int getValueIndex(String value) {
+ int count = mAdapter.getCount();
+ for (int i = 0; i < count; i++) {
+ if (mAdapter.getItem(i).name.equals(value)) {
+ return i;
+ }
+ }
+ return GridView.INVALID_POSITION;
+ }
+
+ private static void viewUri(Context context, String uri) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(uri));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ context.startActivity(intent);
+ }
+
+ private static class IconSetAdapter extends ArrayAdapter<IconSetDescriptor> {
+ private LayoutInflater mInflater;
+
+ public IconSetAdapter(Context context) {
+ super(context, R.layout.icon_item, 0, populateIconSets(context));
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ public void reenumerateIconSets() {
+ ArrayList<IconSetDescriptor> newSets = populateIconSets(getContext());
+ boolean changed = false;
+
+ if (newSets.size() != getCount()) {
+ changed = true;
+ } else {
+ for (int i = 0; i < getCount(); i++) {
+ if (!newSets.get(i).equals(getItem(i))) {
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ if (changed) {
+ setNotifyOnChange(false);
+ clear();
+ addAll(newSets);
+ notifyDataSetChanged();
+ }
+ }
+
+ private static ArrayList<IconSetDescriptor> populateIconSets(Context context) {
+ ArrayList<IconSetDescriptor> result = new ArrayList<IconSetDescriptor>();
+ for (IconSetDescriptor desc : ICON_SETS) {
+ result.add(desc);
+ }
+
+ PackageManager pm = context.getPackageManager();
+ Intent i = new Intent(Intent.ACTION_MAIN);
+ i.addCategory(INTENT_CATEGORY_ICONPACK);
+
+ HashSet<String> installedIconPacks = new HashSet<String>();
+
+ for (ResolveInfo info : pm.queryIntentActivities(i, 0)) {
+ ApplicationInfo appInfo = info.activityInfo.applicationInfo;
+ try {
+ Resources res = pm.getResourcesForApplication(appInfo);
+ int previewResId = res.getIdentifier("weather_28", "drawable", appInfo.packageName);
+ Drawable preview = previewResId != 0 ? res.getDrawable(previewResId) : null;
+ result.add(new IconSetDescriptor(appInfo.packageName,
+ appInfo.loadLabel(pm), preview));
+ installedIconPacks.add(appInfo.packageName.toLowerCase(Locale.US));
+ } catch (PackageManager.NameNotFoundException e) {
+ // shouldn't happen, ignore package
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.icon_item, parent, false);
+ }
+
+ IconSetDescriptor descriptor = getItem(position);
+ ImageView preview = (ImageView) convertView.findViewById(R.id.preview);
+ TextView name = (TextView) convertView.findViewById(R.id.name);
+
+ if (descriptor.previewDrawable != null) {
+ preview.setImageDrawable(descriptor.previewDrawable);
+ } else {
+ preview.setImageResource(descriptor.previewResId);
+ }
+ name.setText(descriptor.getDescription(getContext()));
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/preference/Preferences.java b/src/com/cyanogenmod/lockclock/preference/Preferences.java
index bb9faa2..f60ab4d 100755
--- a/src/com/cyanogenmod/lockclock/preference/Preferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/Preferences.java
@@ -16,6 +16,7 @@
package com.cyanogenmod.lockclock.preference;
+import android.annotation.SuppressLint;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.preference.PreferenceActivity;
@@ -77,4 +78,14 @@ public class Preferences extends PreferenceActivity {
setResult(result, new Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mNewWidgetId));
}
}
+
+ /**
+ * This is required to be able to build with API level 19
+ */
+ @SuppressLint("Override")
+ @Override
+ public boolean isValidFragment(String fragmentName) {
+ // Assume a valid fragment name at all times
+ return true;
+ }
}
diff --git a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
index 327b3e1..9b06246 100644
--- a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
@@ -58,6 +58,8 @@ public class WeatherPreferences extends PreferenceFragment implements
private ListPreference mFontColor;
private ListPreference mTimestampFontColor;
private CheckBoxPreference mUseMetric;
+ private IconSelectionPreference mIconSet;
+ private CheckBoxPreference mUseCustomlocation;
private Context mContext;
private ContentResolver mResolver;
@@ -73,16 +75,15 @@ public class WeatherPreferences extends PreferenceFragment implements
// Load items that need custom summaries etc.
mUseCustomLoc = (CheckBoxPreference) findPreference(Constants.WEATHER_USE_CUSTOM_LOCATION);
mCustomWeatherLoc = (EditTextPreference) findPreference(Constants.WEATHER_CUSTOM_LOCATION_CITY);
-
mFontColor = (ListPreference) findPreference(Constants.WEATHER_FONT_COLOR);
mTimestampFontColor = (ListPreference) findPreference(Constants.WEATHER_TIMESTAMP_FONT_COLOR);
-
+ mIconSet = (IconSelectionPreference) findPreference(Constants.WEATHER_ICONS);
mUseMetric = (CheckBoxPreference) findPreference(Constants.WEATHER_USE_METRIC);
+ mUseCustomlocation = (CheckBoxPreference) findPreference(Constants.WEATHER_USE_CUSTOM_LOCATION);
// Show a warning if location manager is disabled and there is no custom location set
- if (!Settings.Secure.isLocationProviderEnabled(mResolver,
- LocationManager.NETWORK_PROVIDER)
- && !mUseCustomLoc.isChecked()) {
+ LocationManager lm = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE);
+ if (!lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER) && !mUseCustomLoc.isChecked()) {
showDialog();
}
}
@@ -93,6 +94,7 @@ public class WeatherPreferences extends PreferenceFragment implements
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
updateLocationSummary();
updateFontColorsSummary();
+ updateIconSetSummary();
}
@Override
@@ -109,12 +111,16 @@ public class WeatherPreferences extends PreferenceFragment implements
pref.setSummary(listPref.getEntry());
}
+ boolean needWeatherUpdate = false;
+ boolean forceWeatherUpdate = false;
+
if (pref == mUseCustomLoc || pref == mCustomWeatherLoc) {
updateLocationSummary();
}
- boolean needWeatherUpdate = false;
- boolean forceWeatherUpdate = false;
+ if (pref == mIconSet) {
+ updateIconSetSummary();
+ }
if (pref == mUseMetric) {
// The display format of the temperatures have changed
@@ -122,20 +128,23 @@ public class WeatherPreferences extends PreferenceFragment implements
forceWeatherUpdate = true;
}
- for (String k : LOCATION_PREF_KEYS) {
- if (TextUtils.equals(key, k)) {
- // location pref has changed -> clear out location id cache
- Preferences.setCachedLocationId(mContext, null);
- forceWeatherUpdate = true;
- break;
- }
+ // 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);
+ Preferences.setCustomWeatherLocationCity(mContext, null);
+ Preferences.setUseCustomWeatherLocation(mContext, false);
+ mUseCustomlocation.setChecked(false);
+ updateLocationSummary();
}
- for (String k : WEATHER_REFRESH_KEYS) {
- if (TextUtils.equals(key, k)) {
- needWeatherUpdate = true;
- break;
- }
+ if (key.equals(Constants.WEATHER_USE_CUSTOM_LOCATION)
+ || key.equals(Constants.WEATHER_CUSTOM_LOCATION_CITY)) {
+ forceWeatherUpdate = true;
+ }
+
+ if (key.equals(Constants.SHOW_WEATHER) || key.equals(Constants.WEATHER_REFRESH_INTERVAL)) {
+ needWeatherUpdate = true;
}
if (Constants.DEBUG) {
@@ -200,4 +209,10 @@ public class WeatherPreferences extends PreferenceFragment implements
mTimestampFontColor.setSummary(mTimestampFontColor.getEntry());
}
}
+
+ private void updateIconSetSummary() {
+ if (mIconSet != null) {
+ mIconSet.setSummary(mIconSet.getEntry());
+ }
+ }
}
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;
+ }
+}