summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuis Vidal <lvidal@cyngn.com>2016-03-29 14:37:07 -0700
committerLuis Vidal <lvidal@cyngn.com>2016-04-22 15:18:37 -0700
commitc56159a628e4b7033cc33bac7df33a55f9b421a7 (patch)
treeabcf47dd59ec89bd2457abf5e351b8362e2d4036
parent8af8dab921671bdcb0cf0472c1a96d068ac6a640 (diff)
downloadpackages_apps_LockClock-c56159a628e4b7033cc33bac7df33a55f9b421a7.zip
packages_apps_LockClock-c56159a628e4b7033cc33bac7df33a55f9b421a7.tar.gz
packages_apps_LockClock-c56159a628e4b7033cc33bac7df33a55f9b421a7.tar.bz2
Refactor cLock to use the new Weather API
- Content provider was removed - Weather sources (Yahoo and OpenWeatherMap) were removed. Those providers will reside in their own package. - cLock was refactored to use the Weather API to process weather updates and city name disambiguation - cLock will let the active weather service provider decide whether back to back requests are OK. In other words, cLock does not enforce the 10 min wait period between requests anymore TICKET: CYNGNOS-2116 Change-Id: I87ccd5727013dbbd7e96ce0a8e3d4f766f823072
-rw-r--r--Android.mk2
-rw-r--r--AndroidManifest.xml16
-rw-r--r--res/layout/forecast_activity.xml9
-rw-r--r--res/values/arrays.xml10
-rw-r--r--res/values/strings.xml4
-rw-r--r--res/xml/preferences_weather.xml32
-rw-r--r--src/com/cyanogenmod/lockclock/ClockWidgetProvider.java13
-rw-r--r--src/com/cyanogenmod/lockclock/ClockWidgetService.java81
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/misc/Constants.java4
-rw-r--r--src/com/cyanogenmod/lockclock/misc/IconUtils.java12
-rw-r--r--src/com/cyanogenmod/lockclock/misc/Preferences.java256
-rw-r--r--src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java208
-rw-r--r--src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java103
-rw-r--r--src/com/cyanogenmod/lockclock/weather/ForecastActivity.java8
-rw-r--r--src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java187
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/weather/HttpRetriever.java45
-rw-r--r--src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java330
-rw-r--r--src/com/cyanogenmod/lockclock/weather/Utils.java181
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java187
-rwxr-xr-xsrc/com/cyanogenmod/lockclock/weather/WeatherInfo.java334
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherProvider.java39
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java83
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java410
-rw-r--r--src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java313
24 files changed, 1195 insertions, 1672 deletions
diff --git a/Android.mk b/Android.mk
index db27b6f..6e18a1e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -33,6 +33,6 @@ LOCAL_AAPT_FLAGS := --auto-add-overlay
LOCAL_AAPT_FLAGS += --extra-packages com.google.android.gms
LOCAL_STATIC_JAVA_LIBRARIES := play \
- org.apache.http.legacy
+ org.cyanogenmod.platform.sdk
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5b75d91..d4255e8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,13 +16,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cyanogenmod.lockclock"
- android:versionCode="21"
- android:versionName="3.4.1" >
+ android:versionCode="22"
+ android:versionName="3.5" >
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="23" />
- <permission android:name="com.cyanogenmod.lockclock.permission.READ_WEATHER" />
-
<!-- Weather -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
@@ -36,6 +34,9 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
+ <uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
+
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@@ -101,16 +102,11 @@
<service android:name=".ClockWidgetService"></service>
<service android:name=".weather.WeatherUpdateService"></service>
+ <service android:name=".weather.WeatherSourceListenerService"></service>
<service android:name=".calendar.CalendarViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS">
</service>
- <provider
- android:name=".weather.WeatherContentProvider"
- android:authorities="com.cyanogenmod.lockclock.weather.provider"
- android:exported="true"
- android:readPermission="com.cyanogenmod.lockclock.permission.READ_WEATHER" />
-
</application>
</manifest>
diff --git a/res/layout/forecast_activity.xml b/res/layout/forecast_activity.xml
index 1462617..25f881c 100644
--- a/res/layout/forecast_activity.xml
+++ b/res/layout/forecast_activity.xml
@@ -31,9 +31,10 @@
android:gravity="center_vertical"
android:orientation="vertical" >
- <RelativeLayout
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
+ android:weightSum="1"
android:gravity="center_horizontal">
<TextView
@@ -42,6 +43,9 @@
android:layout_height="match_parent"
android:gravity="center|center_horizontal"
android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_weight="0.8"
+ android:paddingLeft="3dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ImageView
@@ -52,8 +56,9 @@
android:layout_centerVertical="true"
android:padding="8dp"
android:src="@drawable/ic_menu_refresh"
+ android:layout_weight="0.2"
android:background="?android:attr/selectableItemBackground" />
- </RelativeLayout>
+ </LinearLayout>
<ImageView
android:layout_width="match_parent"
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 9c5598d..eff4b47 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -106,14 +106,4 @@
<item>#ffaa66cc</item>
</string-array>
- <string-array name="weather_source_entries" translatable="false">
- <item>@string/weather_source_yahoo</item>
- <item>@string/weather_source_openweathermap</item>
- </string-array>
-
- <string-array name="weather_source_values" translatable="false">
- <item>yahoo</item>
- <item>openweathermap</item>
- </string-array>
-
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4a5bdbe..4926cd6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -113,6 +113,8 @@
<string name="weather_refreshing">Refreshing</string>
<string name="weather_last_sync_just_now">Just now</string>
<string name="weather_cannot_reach_provider">Can\'t reach %s at the moment</string>
+ <string name="weather_source_not_selected">not selected</string>
+ <string name="weather_tap_to_select_source">Tap to select one</string>
<!-- Weather - Update frequency -->
<string name="weather_refresh_manual">Manual</string>
@@ -145,8 +147,6 @@
<string name="weather_enabled">Enabled</string>
<string name="weather_enable">Display weather</string>
<string name="weather_source_title">Weather source</string>
- <string name="weather_source_yahoo">Yahoo! Weather</string>
- <string name="weather_source_openweathermap" translatable="false">OpenWeatherMap</string>
<string name="weather_use_custom_location">Use custom location</string>
<string name="weather_geolocated">Geolocated using network</string>
<string name="weather_custom_location_dialog_title">Enter location</string>
diff --git a/res/xml/preferences_weather.xml b/res/xml/preferences_weather.xml
index db832a6..6f7842e 100644
--- a/res/xml/preferences_weather.xml
+++ b/res/xml/preferences_weather.xml
@@ -26,18 +26,17 @@
android:title="@string/weather_enable"
android:defaultValue="true" />
- <ListPreference
+ <PreferenceScreen
android:key="weather_source"
- android:defaultValue="yahoo"
- android:dependency="show_weather"
- android:summary="%s"
- android:entries="@array/weather_source_entries"
- android:entryValues="@array/weather_source_values"
- android:title="@string/weather_source_title" />
+ android:defaultValue=""
+ android:summary="@string/weather_source_not_selected"
+ android:title="@string/weather_source_title">
+ <intent android:action="cyanogenmod.intent.action.MANAGE_WEATHER_PROVIDER_SERVICES" />
+ </PreferenceScreen>
<ListPreference
android:key="weather_refresh_interval"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:defaultValue="60"
android:summary="%s"
android:entries="@array/weather_interval_entries"
@@ -46,7 +45,7 @@
<SwitchPreference
android:key="weather_use_custom_location"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:title="@string/weather_use_custom_location"
android:defaultValue="false" />
@@ -64,42 +63,43 @@
android:key="weather_icons"
android:title="@string/weather_icon_set_title"
android:dialogTitle="@string/weather_icon_set_title"
+ android:dependency="weather_source"
android:defaultValue="color" />
<SwitchPreference
android:key="weather_show_when_minimized"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:title="@string/weather_show_when_minimized_title"
android:summary="@string/weather_show_when_minimized_summary"
android:defaultValue="true" />
<SwitchPreference
android:key="weather_use_metric"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:title="@string/weather_use_metric" />
<SwitchPreference
android:key="weather_show_location"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:title="@string/weather_show_location_title"
android:defaultValue="true" />
<SwitchPreference
android:key="weather_show_timestamp"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:title="@string/weather_show_timestamp_title"
android:defaultValue="true" />
<SwitchPreference
android:key="weather_invert_lowhigh"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:title="@string/weather_invert_lowhigh"
android:defaultValue="false" />
<ListPreference
android:key="weather_font_color"
android:title="@string/font_color"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:defaultValue="#ffffffff"
android:entries="@array/font_color_entries"
android:entryValues="@array/font_color_values" />
@@ -107,7 +107,7 @@
<ListPreference
android:key="weather_timestamp_font_color"
android:title="@string/font_color_timestamp"
- android:dependency="show_weather"
+ android:dependency="weather_source"
android:defaultValue="#80ffffff"
android:entries="@array/font_color_entries"
android:entryValues="@array/font_color_values" />
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
index d13d519..c2ea11c 100644
--- a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
@@ -24,11 +24,11 @@ import android.net.ConnectivityManager;
import android.util.Log;
import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
import com.cyanogenmod.lockclock.weather.ForecastActivity;
+import com.cyanogenmod.lockclock.weather.WeatherSourceListenerService;
import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
-import com.cyanogenmod.lockclock.ClockWidgetService;
-import com.cyanogenmod.lockclock.WidgetApplication;
public class ClockWidgetProvider extends AppWidgetProvider {
private static final String TAG = "ClockWidgetProvider";
@@ -64,10 +64,9 @@ public class ClockWidgetProvider extends AppWidgetProvider {
// Boot completed, schedule next weather update
} else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- // On first boot lastUpdate will be 0 thus no need to force an update
- // Subsequent boots will use cached data
- WeatherUpdateService.scheduleNextUpdate(context, false);
-
+ //Since we're using elapsed time since boot, we can't use the timestamp from the
+ //previous boot so we need to reset the timer
+ Preferences.setLastWeatherUpadteTimestamp(context, 0);
// A widget has been deleted, prevent our handling and ask the super class handle it
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)
|| AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
@@ -124,6 +123,7 @@ public class ClockWidgetProvider extends AppWidgetProvider {
@Override
public void onEnabled(Context context) {
if (D) Log.d(TAG, "Scheduling next weather update");
+ context.startService(new Intent(context, WeatherSourceListenerService.class));
WeatherUpdateService.scheduleNextUpdate(context, true);
// Start the broadcast receiver (API 16 devices)
@@ -138,6 +138,7 @@ public class ClockWidgetProvider extends AppWidgetProvider {
@Override
public void onDisabled(Context context) {
if (D) Log.d(TAG, "Cleaning up: Clearing all pending alarms");
+ context.stopService(new Intent(context, WeatherSourceListenerService.class));
ClockWidgetService.cancelUpdates(context);
WeatherUpdateService.cancelUpdates(context);
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetService.java b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
index d5f6635..bf4be2c 100644
--- a/src/com/cyanogenmod/lockclock/ClockWidgetService.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
@@ -27,21 +27,24 @@ import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
-import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.RemoteViews;
-
import com.cyanogenmod.lockclock.calendar.CalendarViewsService;
import com.cyanogenmod.lockclock.misc.Constants;
import com.cyanogenmod.lockclock.misc.IconUtils;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
-import com.cyanogenmod.lockclock.weather.WeatherInfo;
+import com.cyanogenmod.lockclock.weather.Utils;
import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.CELSIUS;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.util.WeatherUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -60,6 +63,7 @@ public class ClockWidgetService extends IntentService {
private int[] mWidgetIds;
private AppWidgetManager mAppWidgetManager;
+ private Context mContext;
public ClockWidgetService() {
super("ClockWidgetService");
@@ -72,6 +76,7 @@ public class ClockWidgetService extends IntentService {
ComponentName thisWidget = new ComponentName(this, ClockWidgetProvider.class);
mAppWidgetManager = AppWidgetManager.getInstance(this);
mWidgetIds = mAppWidgetManager.getAppWidgetIds(thisWidget);
+ mContext = getApplicationContext();
}
@Override
@@ -413,27 +418,48 @@ public class ClockWidgetService extends IntentService {
int color = Preferences.weatherFontColor(this);
int timestampColor = Preferences.weatherTimestampFontColor(this);
String iconsSet = Preferences.getWeatherIconSet(this);
+ final boolean useMetric = Preferences.useMetricUnits(mContext);
// Reset no weather visibility
weatherViews.setViewVisibility(R.id.weather_no_data, View.GONE);
weatherViews.setViewVisibility(R.id.weather_refresh, View.GONE);
// Weather Image
- int resId = w.getConditionResource(iconsSet);
+ int resId = IconUtils.getWeatherIconResource(mContext, iconsSet, w.getConditionCode());
weatherViews.setViewVisibility(R.id.weather_image, View.VISIBLE);
if (resId != 0) {
- weatherViews.setImageViewResource(R.id.weather_image, w.getConditionResource(iconsSet));
+ weatherViews.setImageViewResource(R.id.weather_image,
+ IconUtils.getWeatherIconResource(mContext, iconsSet, w.getConditionCode()));
} else {
- weatherViews.setImageViewBitmap(R.id.weather_image, w.getConditionBitmap(iconsSet, color));
+ weatherViews.setImageViewBitmap(R.id.weather_image,
+ IconUtils.getWeatherIconBitmap(mContext, iconsSet, color,
+ w.getConditionCode()));
}
// Weather Condition
- weatherViews.setTextViewText(R.id.weather_condition, w.getCondition());
+ weatherViews.setTextViewText(R.id.weather_condition,
+ Utils.resolveWeatherCondition(mContext, w.getConditionCode()));
weatherViews.setViewVisibility(R.id.weather_condition, View.VISIBLE);
weatherViews.setTextColor(R.id.weather_condition, color);
// Weather Temps Panel
- weatherViews.setTextViewText(R.id.weather_temp, w.getFormattedTemperature());
+ double temp = w.getTemperature();
+ double todaysLow = w.getTodaysLow();
+ double todaysHigh = w.getTodaysHigh();
+ int tempUnit = w.getTemperatureUnit();
+ if (tempUnit == FAHRENHEIT && useMetric) {
+ temp = WeatherUtils.fahrenheitToCelsius(temp);
+ todaysLow = WeatherUtils.fahrenheitToCelsius(todaysLow);
+ todaysHigh = WeatherUtils.fahrenheitToCelsius(todaysHigh);
+ tempUnit = CELSIUS;
+ } else if (tempUnit == CELSIUS && !useMetric) {
+ temp = WeatherUtils.celsiusToFahrenheit(temp);
+ todaysLow = WeatherUtils.celsiusToFahrenheit(todaysLow);
+ todaysHigh = WeatherUtils.celsiusToFahrenheit(todaysHigh);
+ tempUnit = FAHRENHEIT;
+ }
+ weatherViews.setTextViewText(R.id.weather_temp,
+ WeatherUtils.formatTemperature(temp, tempUnit));
weatherViews.setViewVisibility(R.id.weather_temps_panel, View.VISIBLE);
weatherViews.setTextColor(R.id.weather_temp, color);
@@ -450,7 +476,7 @@ public class ClockWidgetService extends IntentService {
// Weather Update Time
if (showTimestamp) {
- Date updateTime = w.getTimestamp();
+ Date updateTime = new Date(w.getTimestamp());
StringBuilder sb = new StringBuilder();
sb.append(DateFormat.format("E", updateTime));
sb.append(" ");
@@ -464,8 +490,8 @@ public class ClockWidgetService extends IntentService {
// Weather Temps Panel additional items
boolean invertLowhigh = Preferences.invertLowHighTemperature(this);
- final String low = w.getFormattedLow();
- final String high = w.getFormattedHigh();
+ final String low = WeatherUtils.formatTemperature(todaysLow, tempUnit);
+ final String high = WeatherUtils.formatTemperature(todaysHigh, tempUnit);
weatherViews.setTextViewText(R.id.weather_low_high, invertLowhigh ? high + " | " + low : low + " | " + high);
weatherViews.setTextColor(R.id.weather_low_high, color);
}
@@ -482,8 +508,15 @@ public class ClockWidgetService extends IntentService {
boolean firstRun = Preferences.isFirstWeatherUpdate(this);
// Hide the normal weather stuff
- int providerNameResource = Preferences.weatherProvider(this).getNameResourceId();
- String noData = getString(R.string.weather_cannot_reach_provider, getString(providerNameResource));
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ final String activeProviderLabel = weatherManager.getActiveWeatherServiceProviderLabel();
+ String noData;
+ if (activeProviderLabel != null) {
+ noData = getString(R.string.weather_cannot_reach_provider, activeProviderLabel);
+ } else {
+ noData = getString(R.string.weather_source_title) + " "
+ + getString(R.string.weather_source_not_selected);
+ }
weatherViews.setViewVisibility(R.id.weather_image, View.INVISIBLE);
if (!smallWidget) {
weatherViews.setViewVisibility(R.id.weather_city, View.GONE);
@@ -493,7 +526,13 @@ public class ClockWidgetService extends IntentService {
// Set up the no data and refresh indicators
weatherViews.setTextViewText(R.id.weather_no_data, noData);
- weatherViews.setTextViewText(R.id.weather_refresh, getString(R.string.weather_tap_to_refresh));
+ if (activeProviderLabel != null) {
+ weatherViews.setTextViewText(R.id.weather_refresh,
+ getString(R.string.weather_tap_to_refresh));
+ } else {
+ weatherViews.setTextViewText(R.id.weather_refresh,
+ getString(R.string.weather_tap_to_select_source));
+ }
weatherViews.setTextColor(R.id.weather_no_data, color);
weatherViews.setTextColor(R.id.weather_refresh, color);
@@ -509,7 +548,11 @@ public class ClockWidgetService extends IntentService {
// Register an onClickListener on Weather with the default (Refresh) action
if (!firstRun) {
- setWeatherClickListener(weatherViews, true);
+ if (activeProviderLabel != null) {
+ setWeatherClickListener(weatherViews, true);
+ } else {
+ setWeatherClickListener(weatherViews);
+ }
}
}
@@ -528,7 +571,13 @@ public class ClockWidgetService extends IntentService {
weatherViews.setOnClickPendingIntent(R.id.weather_panel, pi);
}
-
+ private void setWeatherClickListener(RemoteViews weatherViews) {
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ new Intent("cyanogenmod.intent.action.MANAGE_WEATHER_PROVIDER_SERVICES"),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ weatherViews.setOnClickPendingIntent(R.id.weather_panel, pi);
+ }
+
//===============================================================================================
// Calendar related functionality
//===============================================================================================
diff --git a/src/com/cyanogenmod/lockclock/misc/Constants.java b/src/com/cyanogenmod/lockclock/misc/Constants.java
index a7113f2..fb35a57 100755
--- a/src/com/cyanogenmod/lockclock/misc/Constants.java
+++ b/src/com/cyanogenmod/lockclock/misc/Constants.java
@@ -38,14 +38,14 @@ public class Constants {
public static final String SHOW_WEATHER = "show_weather";
public static final String WEATHER_SOURCE = "weather_source";
public static final String WEATHER_USE_CUSTOM_LOCATION = "weather_use_custom_location";
- public static final String WEATHER_CUSTOM_LOCATION_ID = "weather_custom_location_id";
public static final String WEATHER_CUSTOM_LOCATION_CITY = "weather_custom_location_city";
+ public static final String WEATHER_CUSTOM_LOCATION = "weather_custom_location";
+ public static final String WEATHER_LOCATION = "weather_location";
public static final String WEATHER_SHOW_LOCATION = "weather_show_location";
public static final String WEATHER_SHOW_TIMESTAMP = "weather_show_timestamp";
public static final String WEATHER_USE_METRIC = "weather_use_metric";
public static final String WEATHER_INVERT_LOWHIGH = "weather_invert_lowhigh";
public static final String WEATHER_REFRESH_INTERVAL = "weather_refresh_interval";
- public static final String WEATHER_LOCATION_ID = "weather_woeid";
public static final String WEATHER_SHOW_WHEN_MINIMIZED = "weather_show_when_minimized";
public static final String WEATHER_FONT_COLOR = "weather_font_color";
public static final String WEATHER_TIMESTAMP_FONT_COLOR = "weather_timestamp_font_color";
diff --git a/src/com/cyanogenmod/lockclock/misc/IconUtils.java b/src/com/cyanogenmod/lockclock/misc/IconUtils.java
index 437d075..3a4f4d0 100644
--- a/src/com/cyanogenmod/lockclock/misc/IconUtils.java
+++ b/src/com/cyanogenmod/lockclock/misc/IconUtils.java
@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.Log;
import com.cyanogenmod.lockclock.R;
+import com.cyanogenmod.lockclock.weather.Utils;
public class IconUtils {
private static final String TAG = "IconUtils";
@@ -41,8 +42,9 @@ public class IconUtils {
}
final Resources res = context.getResources();
- final int resId = res.getIdentifier("weather_" + iconSet + "_" + conditionCode,
- "drawable", context.getPackageName());
+ final int resId = res.getIdentifier("weather_" + iconSet + "_"
+ + Utils.addOffsetToConditionCodeFromWeatherContract(conditionCode), "drawable",
+ context.getPackageName());
if (resId != 0) {
return resId;
@@ -62,12 +64,13 @@ public class IconUtils {
boolean isMonoSet = Constants.MONOCHROME.equals(iconSet);
Resources res = null;
int resId = 0;
+ int fixedConditionCode = Utils.addOffsetToConditionCodeFromWeatherContract(conditionCode);
if (iconSet.startsWith("ext:")) {
String packageName = iconSet.substring(4);
try {
res = context.getPackageManager().getResourcesForApplication(packageName);
- resId = res.getIdentifier("weather_" + conditionCode, "drawable", packageName);
+ resId = res.getIdentifier("weather_" + fixedConditionCode, "drawable", packageName);
} catch (PackageManager.NameNotFoundException e) {
// fall back to colored icons
iconSet = Constants.COLOR_STD;
@@ -75,7 +78,8 @@ public class IconUtils {
}
if (resId == 0) {
String identifier = isMonoSet
- ? "weather_" + conditionCode : "weather_" + iconSet + "_" + conditionCode;
+ ? "weather_" + fixedConditionCode : "weather_"
+ + iconSet + "_" + fixedConditionCode;
res = context.getResources();
resId = res.getIdentifier(identifier, "drawable", context.getPackageName());
}
diff --git a/src/com/cyanogenmod/lockclock/misc/Preferences.java b/src/com/cyanogenmod/lockclock/misc/Preferences.java
index 941e761..49a4632 100644
--- a/src/com/cyanogenmod/lockclock/misc/Preferences.java
+++ b/src/com/cyanogenmod/lockclock/misc/Preferences.java
@@ -19,17 +19,43 @@ package com.cyanogenmod.lockclock.misc;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherLocation;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
-import com.cyanogenmod.lockclock.weather.OpenWeatherMapProvider;
-import com.cyanogenmod.lockclock.weather.WeatherInfo;
-import com.cyanogenmod.lockclock.weather.WeatherProvider;
-import com.cyanogenmod.lockclock.weather.YahooWeatherProvider;
-
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import java.util.Set;
public class Preferences {
+
+ private static final String WEATHER_LOCATION_CITY_ID = "city_id";
+ private static final String WEATHER_LOCATION_CITY_NAME = "city_name";
+ private static final String WEATHER_LOCATION_STATE = "state";
+ private static final String WEATHER_LOCATION_POSTAL_CODE = "postal_code";
+ private static final String WEATHER_LOCATION_COUNTRY_ID = "country_id";
+ private static final String WEATHER_LOCATION_COUNTRY_NAME = "country_name";
+
+ private static final String WEATHER_INFO_CITY = "city";
+ private static final String WEATHER_INFO_CONDITION_CODE = "condition_code";
+ private static final String WEATHER_INFO_TEMPERATURE = "temperature";
+ private static final String WEATHER_INFO_TEMPERATURE_UNIT = "temperature_unit";
+ private static final String WEATHER_INFO_TIMESTAMP = "timestamp";
+ private static final String WEATHER_INFO_HUMIDITY = "humidity";
+ private static final String WEATHER_INFO_TODAYS_HIGH = "todays_high";
+ private static final String WEATHER_INFO_TODAYS_LOW = "todays_low";
+ private static final String WEATHER_INFO_WIND_SPEED = "wind_speed";
+ private static final String WEATHER_INFO_WIND_SPEED_UNIT = "wind_speed_unit";
+ private static final String WEATHER_INFO_WIND_SPEED_DIRECTION = "wind_speed_direction";
+ private static final String WEATHER_INFO_FORECAST = "forecasts";
+
+ private static final String DAY_FORECAST_CONDITION_CODE = "condition_code";
+ private static final String DAY_FORECAST_LOW = "low";
+ private static final String DAY_FORECAST_HIGH = "high";
+
private Preferences() {
}
@@ -172,7 +198,7 @@ public class Preferences {
public static long weatherRefreshIntervalInMs(Context context) {
String value = getPrefs(context).getString(Constants.WEATHER_REFRESH_INTERVAL, "60");
- return Long.parseLong(value) * 60 * 1000;
+ return Long.parseLong(value) * 60L * 1000L;
}
public static boolean useCustomWeatherLocation(Context context) {
@@ -183,37 +209,148 @@ public class Preferences {
getPrefs(context).edit().putBoolean(Constants.WEATHER_USE_CUSTOM_LOCATION, value).apply();
}
- public static String customWeatherLocationId(Context context) {
- return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_ID, null);
+ public static String getCustomWeatherLocationCity(Context context) {
+ return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_CITY, null);
}
- public static void setCustomWeatherLocationId(Context context, String id) {
- getPrefs(context).edit().putString(Constants.WEATHER_CUSTOM_LOCATION_ID, id).apply();
+ public static void setCustomWeatherLocationCity(Context context, String city) {
+ getPrefs(context).edit().putString(Constants.WEATHER_CUSTOM_LOCATION_CITY, city).apply();
}
- public static String customWeatherLocationCity(Context context) {
- return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_CITY, null);
+ public static boolean setCustomWeatherLocation(Context context, WeatherLocation weatherLocation) {
+ if (weatherLocation == null) {
+ getPrefs(context).edit()
+ .remove(Constants.WEATHER_CUSTOM_LOCATION).apply();
+ return true;
+ }
+ try {
+ JSONObject jsonObject = weatherLocationToJSON(weatherLocation);
+ getPrefs(context).edit()
+ .putString(Constants.WEATHER_CUSTOM_LOCATION, jsonObject.toString()).apply();
+ return true;
+ } catch (JSONException e) {
+ // We're here because weatherLocationToJSON() or jsonObject.toString() failed.
+ // Either way, it means the pref was not updated
+ return false;
+ }
}
- public static void setCustomWeatherLocationCity(Context context, String city) {
- getPrefs(context).edit().putString(Constants.WEATHER_CUSTOM_LOCATION_CITY, city).apply();
+ public static WeatherLocation getCustomWeatherLocation(Context context) {
+ String weatherLocation = getPrefs(context)
+ .getString(Constants.WEATHER_CUSTOM_LOCATION, null);
+
+ if (weatherLocation == null) {
+ return null;
+ }
+
+ try {
+ JSONObject jsonObject = new JSONObject(weatherLocation);
+ return JSONToWeatherLocation(jsonObject);
+ } catch (JSONException e) {
+ return null;
+ }
}
- public static WeatherProvider weatherProvider(Context context) {
- String name = getPrefs(context).getString(Constants.WEATHER_SOURCE, "yahoo");
- if (name.equals("openweathermap")) {
- return new OpenWeatherMapProvider(context);
+ private static WeatherLocation JSONToWeatherLocation(JSONObject jsonObject)
+ throws JSONException {
+ String cityId;
+ String cityName;
+ String state;
+ String postalCode;
+ String countryId;
+ String countryName;
+
+ cityId = jsonObject.getString(WEATHER_LOCATION_CITY_ID);
+ cityName = jsonObject.getString(WEATHER_LOCATION_CITY_NAME);
+ state = jsonObject.getString(WEATHER_LOCATION_STATE);
+ postalCode = jsonObject.getString(WEATHER_LOCATION_POSTAL_CODE);
+ countryId = jsonObject.getString(WEATHER_LOCATION_COUNTRY_ID);
+ countryName = jsonObject.getString(WEATHER_LOCATION_COUNTRY_NAME);
+
+ //We need at least city id and city name to build a WeatherLocation
+ if (cityId == null && cityName == null) {
+ return null;
}
- return new YahooWeatherProvider(context);
+
+ WeatherLocation.Builder location = new WeatherLocation.Builder(cityId, cityName);
+ if (countryId != null) location.setCountryId(countryId);
+ if (countryName != null) location.setCountry(countryName);
+ if (state != null) location.setState(state);
+ if (postalCode != null) location.setPostalCode(postalCode);
+
+ return location.build();
}
- public static void setCachedWeatherInfo(Context context, long timestamp, WeatherInfo data) {
+ private static JSONObject weatherLocationToJSON(WeatherLocation location) throws JSONException {
+ return new JSONObject()
+ .put(WEATHER_LOCATION_CITY_ID, location.getCityId())
+ .put(WEATHER_LOCATION_CITY_NAME, location.getCity())
+ .put(WEATHER_LOCATION_STATE, location.getState())
+ .put(WEATHER_LOCATION_POSTAL_CODE, location.getPostalCode())
+ .put(WEATHER_LOCATION_COUNTRY_ID, location.getCountryId())
+ .put(WEATHER_LOCATION_COUNTRY_NAME, location.getCountry());
+ }
+
+ public static void setCachedWeatherInfo(Context context, long timestamp, WeatherInfo info) {
SharedPreferences.Editor editor = getPrefs(context).edit();
editor.putLong(Constants.WEATHER_LAST_UPDATE, timestamp);
- if (data != null) {
+ if (info != null) {
// We now have valid weather data to display
- editor.putBoolean(Constants.WEATHER_FIRST_UPDATE, false);
- editor.putString(Constants.WEATHER_DATA, data.toSerializedString());
+ JSONObject jsonObject = new JSONObject();
+ boolean serialized = false;
+ try {
+ //These members always return a value that can be parsed
+ jsonObject
+ .put(WEATHER_INFO_CITY, info.getCity())
+ .put(WEATHER_INFO_CONDITION_CODE, info.getConditionCode())
+ .put(WEATHER_INFO_TEMPERATURE, info.getTemperature())
+ .put(WEATHER_INFO_TEMPERATURE_UNIT, info.getTemperatureUnit())
+ .put(WEATHER_INFO_TIMESTAMP, info.getTimestamp());
+
+ // Handle special cases. JSONObject.put(key, double) does not allow
+ // Double.NaN, so we store it as a string. JSONObject.getDouble() will parse the
+ // "NaN" string and return Double.NaN, which is what we want
+ double humidity = info.getHumidity();
+ jsonObject.put(WEATHER_INFO_HUMIDITY, Double.isNaN(humidity) ? "NaN" : humidity);
+
+ double todaysHigh = info.getTodaysHigh();
+ jsonObject.put(WEATHER_INFO_TODAYS_HIGH, Double.isNaN(todaysHigh)
+ ? "NaN" : todaysHigh);
+
+ double todaysLow = info.getTodaysLow();
+ jsonObject.put(WEATHER_INFO_TODAYS_LOW, Double.isNaN(todaysLow)
+ ? "NaN" : todaysLow);
+
+ double windSpeed = info.getWindSpeed();
+ double windDirection = info.getWindDirection();
+ jsonObject.put(WEATHER_INFO_WIND_SPEED, Double.isNaN(windSpeed) ? "NaN" : windSpeed)
+ .put(WEATHER_INFO_WIND_SPEED_UNIT, info.getWindSpeedUnit())
+ .put(WEATHER_INFO_WIND_SPEED_DIRECTION, Double.isNaN(windDirection)
+ ? "NaN" : windDirection);
+
+ JSONArray forecastArray = new JSONArray();
+ for (WeatherInfo.DayForecast forecast : info.getForecasts()) {
+ JSONObject jsonForecast = new JSONObject()
+ .put(DAY_FORECAST_CONDITION_CODE, forecast.getConditionCode());
+
+ double low = forecast.getLow();
+ jsonForecast.put(DAY_FORECAST_LOW, Double.isNaN(low) ? "NaN" : low);
+ double high = forecast.getHigh();
+ jsonForecast.put(DAY_FORECAST_HIGH, Double.isNaN(high) ? "NaN" : high);
+ forecastArray.put(jsonForecast);
+ }
+ jsonObject.put(WEATHER_INFO_FORECAST, forecastArray);
+ serialized = true;
+ } catch (JSONException e) {
+ // We're here because something went wrong while creating the JSON object.
+ // The code below will check for success and proceed accordingly
+ }
+ if (serialized) {
+ editor.putString(Constants.WEATHER_DATA, jsonObject.toString());
+ editor.putBoolean(Constants.WEATHER_FIRST_UPDATE, false);
+ }
+ } else {
+ editor.remove(Constants.WEATHER_DATA);
}
editor.apply();
}
@@ -222,17 +359,78 @@ public class Preferences {
return getPrefs(context).getLong(Constants.WEATHER_LAST_UPDATE, 0);
}
+ public static void setLastWeatherUpadteTimestamp(Context context, long timestamp) {
+ getPrefs(context).edit().putLong(Constants.WEATHER_LAST_UPDATE, timestamp).apply();
+ }
+
public static WeatherInfo getCachedWeatherInfo(Context context) {
- return WeatherInfo.fromSerializedString(context,
- getPrefs(context).getString(Constants.WEATHER_DATA, null));
+ final String cachedInfo = getPrefs(context).getString(Constants.WEATHER_DATA, null);
+
+ if (cachedInfo == null) return null;
+
+ String city;
+ int conditionCode;
+ double temperature;
+ int tempUnit;
+ double humidity;
+ double windSpeed;
+ double windDirection;
+ double todaysHigh;
+ double todaysLow;
+ int windSpeedUnit;
+ long timestamp;
+ ArrayList<WeatherInfo.DayForecast> forecastList = new ArrayList<>();
+
+ try {
+ JSONObject cached = new JSONObject(cachedInfo);
+ city = cached.getString(WEATHER_INFO_CITY);
+ conditionCode = cached.getInt(WEATHER_INFO_CONDITION_CODE);
+ temperature = cached.getDouble(WEATHER_INFO_TEMPERATURE);
+ tempUnit = cached.getInt(WEATHER_INFO_TEMPERATURE_UNIT);
+ humidity = cached.getDouble(WEATHER_INFO_HUMIDITY);
+ windSpeed = cached.getDouble(WEATHER_INFO_WIND_SPEED);
+ windDirection = cached.getDouble(WEATHER_INFO_WIND_SPEED_DIRECTION);
+ windSpeedUnit = cached.getInt(WEATHER_INFO_WIND_SPEED_UNIT);
+ timestamp = cached.getLong(WEATHER_INFO_TIMESTAMP);
+ todaysHigh = cached.getDouble(WEATHER_INFO_TODAYS_HIGH);
+ todaysLow = cached.getDouble(WEATHER_INFO_TODAYS_LOW);
+ JSONArray forecasts = cached.getJSONArray(WEATHER_INFO_FORECAST);
+ for (int indx = 0; indx < forecasts.length(); indx++) {
+ JSONObject forecast = forecasts.getJSONObject(indx);
+ double low;
+ double high;
+ int code;
+ low = forecast.getDouble(DAY_FORECAST_LOW);
+ high = forecast.getDouble(DAY_FORECAST_HIGH);
+ code = forecast.getInt(DAY_FORECAST_CONDITION_CODE);
+ WeatherInfo.DayForecast.Builder f = new WeatherInfo.DayForecast.Builder(code);
+ if (!Double.isNaN(low)) f.setLow(low);
+ if (!Double.isNaN(high)) f.setHigh(high);
+ forecastList.add(f.build());
+ }
+ WeatherInfo.Builder weatherInfo = new WeatherInfo.Builder(city, temperature, tempUnit)
+ .setWeatherCondition(conditionCode)
+ .setTimestamp(timestamp);
+
+ if (!Double.isNaN(humidity)) weatherInfo.setHumidity(humidity);
+ if (!Double.isNaN(windSpeed) && !Double.isNaN(windDirection)) {
+ weatherInfo.setWind(windSpeed, windDirection, windSpeedUnit);
+ }
+ if (forecastList.size() > 0) weatherInfo.setForecast(forecastList);
+ if (!Double.isNaN(todaysHigh)) weatherInfo.setTodaysHigh(todaysHigh);
+ if (!Double.isNaN(todaysLow)) weatherInfo.setTodaysLow(todaysLow);
+ return weatherInfo.build();
+ } catch (JSONException e) {
+ }
+ return null;
}
- public static String getCachedLocationId(Context context) {
- return getPrefs(context).getString(Constants.WEATHER_LOCATION_ID, null);
+ public static void setWeatherSource(Context context, String source) {
+ getPrefs(context).edit().putString(Constants.WEATHER_SOURCE, source).apply();
}
- public static void setCachedLocationId(Context context, String id) {
- getPrefs(context).edit().putString(Constants.WEATHER_LOCATION_ID, id).apply();
+ public static String getWeatherSource(Context context) {
+ return getPrefs(context).getString(Constants.WEATHER_SOURCE, null);
}
public static Set<String> calendarsToDisplay(Context context) {
diff --git a/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java b/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
index 6d0992f..c290cf5 100644
--- a/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
+++ b/src/com/cyanogenmod/lockclock/preference/CustomLocationPreference.java
@@ -17,27 +17,27 @@
package com.cyanogenmod.lockclock.preference;
import android.app.AlertDialog;
-import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.preference.EditTextPreference;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
-
import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Preferences;
-import com.cyanogenmod.lockclock.weather.WeatherProvider.LocationResult;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherLocation;
import java.util.HashSet;
import java.util.List;
-public class CustomLocationPreference extends EditTextPreference {
+public class CustomLocationPreference extends EditTextPreference
+ implements CMWeatherManager.LookupCityRequestListener {
public CustomLocationPreference(Context context) {
super(context);
}
@@ -48,18 +48,35 @@ public class CustomLocationPreference extends EditTextPreference {
super(context, attrs, defStyle);
}
+ private ProgressDialog mProgressDialog;
+ private int mCustomLocationRequestId;
+ private Handler mHandler;
@Override
protected void showDialog(Bundle state) {
super.showDialog(state);
+ mHandler = new Handler(getContext().getMainLooper());
final AlertDialog d = (AlertDialog) getDialog();
- Button okButton = d.getButton(DialogInterface.BUTTON_POSITIVE);
-
+ final Button okButton = d.getButton(DialogInterface.BUTTON_POSITIVE);
okButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CustomLocationPreference.this.onClick(d, DialogInterface.BUTTON_POSITIVE);
- new WeatherLocationTask(d, getEditText().getText().toString()).execute();
+ final String customLocationToLookUp = getEditText().getText().toString();
+ if (TextUtils.equals(customLocationToLookUp, "")) return;
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(getContext());
+ mProgressDialog = new ProgressDialog(getContext());
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setMessage(getContext().getString(R.string.weather_progress_title));
+ mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ weatherManager.cancelRequest(mCustomLocationRequestId);
+ }
+ });
+ mCustomLocationRequestId = weatherManager.lookupCity(customLocationToLookUp,
+ CustomLocationPreference.this);
+ mProgressDialog.show();
}
});
}
@@ -68,10 +85,12 @@ public class CustomLocationPreference extends EditTextPreference {
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
- String location = Preferences.customWeatherLocationCity(getContext());
+ String location = Preferences.getCustomWeatherLocationCity(getContext());
if (location != null) {
getEditText().setText(location);
getEditText().setSelection(location.length());
+ } else {
+ getEditText().setText("");
}
}
@@ -81,115 +100,88 @@ public class CustomLocationPreference extends EditTextPreference {
super.onDialogClosed(false);
}
- private class WeatherLocationTask extends AsyncTask<Void, Void, List<LocationResult>> {
- private Dialog mDialog;
- private ProgressDialog mProgressDialog;
- private String mLocation;
-
- public WeatherLocationTask(Dialog dialog, String location) {
- mDialog = dialog;
- mLocation = location;
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
-
- final Context context = getContext();
+ private void handleResultDisambiguation(final List<WeatherLocation> results) {
+ CharSequence[] items = buildItemList(results);
+ new AlertDialog.Builder(getContext())
+ .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ applyLocation(results.get(which));
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .setTitle(R.string.weather_select_location)
+ .show();
+ }
- mProgressDialog = new ProgressDialog(context);
- mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
- mProgressDialog.setMessage(context.getString(R.string.weather_progress_title));
- mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- cancel(true);
- }
- });
- mProgressDialog.show();
- }
+ private CharSequence[] buildItemList(List<WeatherLocation> results) {
+ boolean needCountry = false, needPostal = false;
+ String countryId = results.get(0).getCountryId();
+ HashSet<String> postalIds = new HashSet<>();
- @Override
- protected List<LocationResult> doInBackground(Void... input) {
- return Preferences.weatherProvider(getContext()).getLocations(mLocation);
+ for (WeatherLocation result : results) {
+ if (!TextUtils.equals(result.getCountryId(), countryId)) {
+ needCountry = true;
+ }
+ String postalId = result.getCountryId() + "##" + result.getCity();
+ if (postalIds.contains(postalId)) {
+ needPostal = true;
+ }
+ postalIds.add(postalId);
+ if (needPostal && needCountry) {
+ break;
+ }
}
- @Override
- protected void onPostExecute(List<LocationResult> results) {
- super.onPostExecute(results);
-
- final Context context = getContext();
-
- if (results == null || results.isEmpty()) {
- Toast.makeText(context,
- context.getString(R.string.weather_retrieve_location_dialog_title),
- Toast.LENGTH_SHORT)
- .show();
- } else if (results.size() > 1) {
- handleResultDisambiguation(results);
- } else {
- applyLocation(results.get(0));
+ int count = results.size();
+ CharSequence[] items = new CharSequence[count];
+ for (int i = 0; i < count; i++) {
+ WeatherLocation result = results.get(i);
+ StringBuilder builder = new StringBuilder();
+ if (needPostal && result.getPostalCode() != null) {
+ builder.append(result.getPostalCode()).append(" ");
}
- mProgressDialog.dismiss();
+ builder.append(result.getCity());
+ if (needCountry) {
+ String country = result.getCountry() != null
+ ? result.getCountry() : result.getCountryId();
+ builder.append(" (").append(country).append(")");
+ }
+ items[i] = builder.toString();
}
+ return items;
+ }
- private void handleResultDisambiguation(final List<LocationResult> results) {
- CharSequence[] items = buildItemList(results);
- new AlertDialog.Builder(getContext())
- .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- applyLocation(results.get(which));
- dialog.dismiss();
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .setTitle(R.string.weather_select_location)
- .show();
+ private void applyLocation(final WeatherLocation result) {
+ if (Preferences.setCustomWeatherLocation(getContext(), result)) {
+ String cityName = result.getCity();
+ String state = result.getState();
+ String country = result.getCountry();
+ setText(cityName + "," + state + "/" + country);
}
+ final AlertDialog d = (AlertDialog) getDialog();
+ d.dismiss();
+ }
- private CharSequence[] buildItemList(List<LocationResult> results) {
- boolean needCountry = false, needPostal = false;
- String countryId = results.get(0).countryId;
- HashSet<String> postalIds = new HashSet<String>();
-
- for (LocationResult result : results) {
- if (!TextUtils.equals(result.countryId, countryId)) {
- needCountry = true;
- }
- String postalId = result.countryId + "##" + result.city;
- if (postalIds.contains(postalId)) {
- needPostal = true;
- }
- postalIds.add(postalId);
- if (needPostal && needCountry) {
- break;
- }
- }
-
- int count = results.size();
- CharSequence[] items = new CharSequence[count];
- for (int i = 0; i < count; i++) {
- LocationResult result = results.get(i);
- StringBuilder builder = new StringBuilder();
- if (needPostal && result.postal != null) {
- builder.append(result.postal).append(" ");
- }
- builder.append(result.city);
- if (needCountry) {
- String country = result.country != null
- ? result.country : result.countryId;
- builder.append(" (").append(country).append(")");
+ @Override
+ public void onLookupCityRequestCompleted(int status, final List<WeatherLocation> locations) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final Context context = getContext();
+ if (locations == null || locations.isEmpty()) {
+ Toast.makeText(context,
+ context.getString(R.string.weather_retrieve_location_dialog_title),
+ Toast.LENGTH_SHORT)
+ .show();
+ } else if (locations.size() > 1) {
+ handleResultDisambiguation(locations);
+ } else {
+ applyLocation(locations.get(0));
}
- items[i] = builder.toString();
+ mProgressDialog.dismiss();
}
- return items;
- }
-
- private void applyLocation(final LocationResult result) {
- Preferences.setCustomWeatherLocationId(getContext(), result.id);
- setText(result.city);
- mDialog.dismiss();
- }
+ });
}
}
diff --git a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
index 1a42a20..0824a4b 100644
--- a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
@@ -19,7 +19,6 @@ package com.cyanogenmod.lockclock.preference;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -31,31 +30,24 @@ import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-
import com.cyanogenmod.lockclock.ClockWidgetProvider;
import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Constants;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
+import cyanogenmod.weather.CMWeatherManager;
public class WeatherPreferences extends PreferenceFragment implements
- SharedPreferences.OnSharedPreferenceChangeListener, Preference.OnPreferenceChangeListener {
+ SharedPreferences.OnSharedPreferenceChangeListener, Preference.OnPreferenceChangeListener,
+ CMWeatherManager.WeatherServiceProviderChangeListener {
private static final String TAG = "WeatherPreferences";
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
- private static final String[] LOCATION_PREF_KEYS = new String[] {
- Constants.WEATHER_USE_CUSTOM_LOCATION,
- Constants.WEATHER_CUSTOM_LOCATION_CITY
- };
- private static final String[] WEATHER_REFRESH_KEYS = new String[] {
- Constants.SHOW_WEATHER,
- Constants.WEATHER_REFRESH_INTERVAL
- };
-
private SwitchPreference mUseCustomLoc;
private EditTextPreference mCustomWeatherLoc;
private ListPreference mFontColor;
@@ -65,8 +57,8 @@ public class WeatherPreferences extends PreferenceFragment implements
private SwitchPreference mUseCustomlocation;
private SwitchPreference mShowWeather;
private Context mContext;
- private ContentResolver mResolver;
private Runnable mPostResumeRunnable;
+ private PreferenceScreen mWeatherSource;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -74,7 +66,6 @@ public class WeatherPreferences extends PreferenceFragment implements
getPreferenceManager().setSharedPreferencesName(Constants.PREF_NAME);
addPreferencesFromResource(R.xml.preferences_weather);
mContext = getActivity();
- mResolver = mContext.getContentResolver();
// Load items that need custom summaries etc.
mUseCustomLoc = (SwitchPreference) findPreference(Constants.WEATHER_USE_CUSTOM_LOCATION);
@@ -84,6 +75,18 @@ public class WeatherPreferences extends PreferenceFragment implements
mIconSet = (IconSelectionPreference) findPreference(Constants.WEATHER_ICONS);
mUseMetric = (SwitchPreference) findPreference(Constants.WEATHER_USE_METRIC);
mUseCustomlocation = (SwitchPreference) findPreference(Constants.WEATHER_USE_CUSTOM_LOCATION);
+ mWeatherSource = (PreferenceScreen) findPreference(Constants.WEATHER_SOURCE);
+ mWeatherSource.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ if (Preferences.getWeatherSource(mContext) != null && mShowWeather.isChecked()) {
+ mWeatherSource.notifyDependencyChange(false);
+ } else {
+ mWeatherSource.notifyDependencyChange(true);
+ }
+ return false;
+ }
+ });
mShowWeather = (SwitchPreference) findPreference(Constants.SHOW_WEATHER);
mShowWeather.setOnPreferenceChangeListener(this);
@@ -116,15 +119,34 @@ public class WeatherPreferences extends PreferenceFragment implements
mPostResumeRunnable = null;
}
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ weatherManager.registerWeatherServiceProviderChangeListener(this);
+
+ mWeatherSource.setEnabled(mShowWeather.isChecked());
+
updateLocationSummary();
updateFontColorsSummary();
updateIconSetSummary();
+ updateWeatherProviderSummary(getWeatherProviderName());
}
@Override
public void onPause() {
super.onPause();
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ weatherManager.unregisterWeatherServiceProviderChangeListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mUseCustomlocation.isChecked()
+ && Preferences.getCustomWeatherLocationCity(mContext) == null) {
+ //The user decided to toggle the custom location switch, but forgot to set a custom
+ //location, we need to go back to geo location
+ Preferences.setUseCustomWeatherLocation(mContext, false);
+ }
}
@Override
@@ -152,18 +174,24 @@ public class WeatherPreferences extends PreferenceFragment implements
forceWeatherUpdate = true;
}
- // If the weather source has changes, invalidate the custom location settings and change
- // back to GeoLocation to force the user to specify a new custom location if needed
if (TextUtils.equals(key, Constants.WEATHER_SOURCE)) {
- Preferences.setCustomWeatherLocationId(mContext, null);
+ // The weather source changed, invalidate the custom location settings and change
+ // back to GeoLocation to force the user to specify a new custom location if needed
Preferences.setCustomWeatherLocationCity(mContext, null);
+ Preferences.setCustomWeatherLocation(mContext, null);
Preferences.setUseCustomWeatherLocation(mContext, false);
mUseCustomlocation.setChecked(false);
updateLocationSummary();
}
- if (key.equals(Constants.WEATHER_USE_CUSTOM_LOCATION)
- || key.equals(Constants.WEATHER_CUSTOM_LOCATION_CITY)) {
+ if (key.equals(Constants.WEATHER_USE_CUSTOM_LOCATION)) {
+ if (!mUseCustomLoc.isChecked() || (mUseCustomLoc.isChecked() &&
+ Preferences.getCustomWeatherLocation(mContext) != null)) {
+ forceWeatherUpdate = true;
+ }
+ }
+
+ if (key.equals(Constants.WEATHER_CUSTOM_LOCATION_CITY) && mUseCustomLoc.isChecked()) {
forceWeatherUpdate = true;
}
@@ -171,6 +199,15 @@ public class WeatherPreferences extends PreferenceFragment implements
needWeatherUpdate = true;
}
+ if (key.equals(Constants.SHOW_WEATHER)) {
+ mWeatherSource.setEnabled(mShowWeather.isChecked());
+ if (Preferences.getWeatherSource(mContext) != null && mShowWeather.isChecked()) {
+ mWeatherSource.notifyDependencyChange(false);
+ } else {
+ mWeatherSource.notifyDependencyChange(true);
+ }
+ }
+
if (Constants.DEBUG) {
Log.v(TAG, "Preference " + key + " changed, need update " +
needWeatherUpdate + " force update " + forceWeatherUpdate);
@@ -199,7 +236,7 @@ public class WeatherPreferences extends PreferenceFragment implements
private void updateLocationSummary() {
if (mUseCustomLoc.isChecked()) {
- String location = Preferences.customWeatherLocationCity(mContext);
+ String location = Preferences.getCustomWeatherLocationCity(mContext);
if (location == null) {
location = getResources().getString(R.string.unknown);
}
@@ -274,4 +311,30 @@ public class WeatherPreferences extends PreferenceFragment implements
}
return true;
}
+
+ @Override
+ public void onWeatherServiceProviderChanged(String providerName) {
+ updateWeatherProviderSummary(providerName);
+ }
+
+ private void updateWeatherProviderSummary(String providerName) {
+ if (providerName != null) {
+ mWeatherSource.setSummary(providerName);
+ Preferences.setWeatherSource(mContext, providerName);
+ } else {
+ mWeatherSource.setSummary(R.string.weather_source_not_selected);
+ Preferences.setWeatherSource(mContext, null);
+ }
+
+ if (providerName != null && mShowWeather.isChecked()) {
+ mWeatherSource.notifyDependencyChange(false);
+ } else {
+ mWeatherSource.notifyDependencyChange(true);
+ }
+ }
+
+ private String getWeatherProviderName() {
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ return weatherManager.getActiveWeatherServiceProviderLabel();
+ }
}
diff --git a/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java b/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
index b08a069..70fe61c 100644
--- a/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
+++ b/src/com/cyanogenmod/lockclock/weather/ForecastActivity.java
@@ -18,13 +18,10 @@ package com.cyanogenmod.lockclock.weather;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.KeyguardManager;
-import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@@ -35,11 +32,10 @@ import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
-
-import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
-import com.cyanogenmod.lockclock.R;
+import cyanogenmod.weather.WeatherInfo;
public class ForecastActivity extends Activity implements OnClickListener {
private static final String TAG = "ForecastActivity";
diff --git a/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java b/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
index f5e4e6d..4bb2b84 100644
--- a/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
+++ b/src/com/cyanogenmod/lockclock/weather/ForecastBuilder.java
@@ -16,29 +16,34 @@
package com.cyanogenmod.lockclock.weather;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Locale;
-import java.util.TimeZone;
-
import android.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.Color;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-
+import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.IconUtils;
import com.cyanogenmod.lockclock.misc.Preferences;
-import com.cyanogenmod.lockclock.misc.WidgetUtils;
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-import com.cyanogenmod.lockclock.R;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WindSpeedUnit.MPH;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WindSpeedUnit.KPH;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.CELSIUS;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherInfo.DayForecast;
+import cyanogenmod.weather.util.WeatherUtils;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
public class ForecastBuilder {
private static final String TAG = "ForecastBuilder";
@@ -58,50 +63,82 @@ public class ForecastBuilder {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int color = Preferences.weatherFontColor(context);
boolean invertLowHigh = Preferences.invertLowHighTemperature(context);
+ final boolean useMetric = Preferences.useMetricUnits(context);
+
+ //Make any conversion needed in case the data was not provided in the desired unit
+ double temp = w.getTemperature();
+ double todaysLow = w.getTodaysLow();
+ double todaysHigh = w.getTodaysHigh();
+ int tempUnit = w.getTemperatureUnit();
+ if (tempUnit == FAHRENHEIT && useMetric) {
+ temp = WeatherUtils.fahrenheitToCelsius(temp);
+ todaysLow = WeatherUtils.fahrenheitToCelsius(todaysLow);
+ todaysHigh = WeatherUtils.fahrenheitToCelsius(todaysHigh);
+ tempUnit = CELSIUS;
+ } else if (tempUnit == CELSIUS && !useMetric) {
+ temp = WeatherUtils.celsiusToFahrenheit(temp);
+ todaysLow = WeatherUtils.celsiusToFahrenheit(todaysLow);
+ todaysHigh = WeatherUtils.celsiusToFahrenheit(todaysHigh);
+ tempUnit = FAHRENHEIT;
+ }
+
+ double windSpeed = w.getWindSpeed();
+ int windSpeedUnit = w.getWindSpeedUnit();
+ if (windSpeedUnit == MPH && useMetric) {
+ windSpeedUnit = KPH;
+ windSpeed = Utils.milesToKilometers(windSpeed);
+ } else if (windSpeedUnit == KPH && !useMetric) {
+ windSpeedUnit = MPH;
+ windSpeed = Utils.kilometersToMiles(windSpeed);
+ }
View view = inflater.inflate(resourceId, null);
// Set the weather source
TextView weatherSource = (TextView) view.findViewById(R.id.weather_source);
- weatherSource.setText(Preferences.weatherProvider(context).getNameResourceId());
+ final CMWeatherManager cmWeatherManager = CMWeatherManager.getInstance(context);
+ String activeWeatherLabel = cmWeatherManager.getActiveWeatherServiceProviderLabel();
+ weatherSource.setText(activeWeatherLabel != null ? activeWeatherLabel : "");
// Set the current conditions
// Weather Image
ImageView weatherImage = (ImageView) view.findViewById(R.id.weather_image);
String iconsSet = Preferences.getWeatherIconSet(context);
- weatherImage.setImageBitmap(w.getConditionBitmap(iconsSet, color,
- IconUtils.getNextHigherDensity(context)));
+ weatherImage.setImageBitmap(IconUtils.getWeatherIconBitmap(context, iconsSet, color,
+ w.getConditionCode(), IconUtils.getNextHigherDensity(context)));
// Weather Condition
TextView weatherCondition = (TextView) view.findViewById(R.id.weather_condition);
- weatherCondition.setText(w.getCondition());
+ weatherCondition.setText(Utils.resolveWeatherCondition(context, w.getConditionCode()));
// Weather Temps
TextView weatherTemp = (TextView) view.findViewById(R.id.weather_temp);
- weatherTemp.setText(w.getFormattedTemperature());
+ weatherTemp.setText(WeatherUtils.formatTemperature(temp, tempUnit));
// Humidity and Wind
TextView weatherHumWind = (TextView) view.findViewById(R.id.weather_hum_wind);
- weatherHumWind.setText(w.getFormattedHumidity() + ", " + w.getFormattedWindSpeed() + " "
- + w.getWindDirection());
+ weatherHumWind.setText(Utils.formatHumidity(w.getHumidity()) + ", "
+ + Utils.formatWindSpeed(context, windSpeed, windSpeedUnit) + " "
+ + Utils.resolveWindDirection(context, w.getWindDirection()));
// City
TextView city = (TextView) view.findViewById(R.id.weather_city);
city.setText(w.getCity());
// Weather Update Time
- Date lastUpdate = w.getTimestamp();
+ Date lastUpdate = new Date(w.getTimestamp());
StringBuilder sb = new StringBuilder();
sb.append(DateFormat.format("E", lastUpdate));
sb.append(" ");
sb.append(DateFormat.getTimeFormat(context).format(lastUpdate));
TextView updateTime = (TextView) view.findViewById(R.id.update_time);
updateTime.setText(sb.toString());
- updateTime.setVisibility(Preferences.showWeatherTimestamp(context) ? View.VISIBLE : View.GONE);
+ updateTime.setVisibility(
+ Preferences.showWeatherTimestamp(context) ? View.VISIBLE : View.GONE);
// Weather Temps Panel additional items
- final String low = w.getFormattedLow();
- final String high = w.getFormattedHigh();
+ final String low = WeatherUtils.formatTemperature(todaysLow, tempUnit);
+ final String high = WeatherUtils.formatTemperature(todaysHigh, tempUnit);
TextView weatherLowHigh = (TextView) view.findViewById(R.id.weather_low_high);
weatherLowHigh.setText(invertLowHigh ? high + " | " + low : low + " | " + high);
@@ -113,6 +150,9 @@ public class ForecastBuilder {
if (buildSmallPanel(context, forecastView, w)) {
// Success, hide the progress container
progressIndicator.setVisibility(View.GONE);
+ } else {
+ // TODO: Display a text notifying the user that the forecast data is not available
+ // rather than keeping the indicator spinning forever
}
return view;
@@ -125,55 +165,72 @@ public class ForecastBuilder {
* @param w = the Weather info object that contains the forecast data
*/
public static boolean buildSmallPanel(Context context, LinearLayout smallPanel, WeatherInfo w) {
- if (smallPanel == null) {
+ if (smallPanel == null) {
Log.d(TAG, "Invalid view passed");
return false;
- }
+ }
- // Get things ready
- LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- int color = Preferences.weatherFontColor(context);
- boolean invertLowHigh = Preferences.invertLowHighTemperature(context);
+ // Get things ready
+ LayoutInflater inflater
+ = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ int color = Preferences.weatherFontColor(context);
+ boolean invertLowHigh = Preferences.invertLowHighTemperature(context);
+ final boolean useMetric = Preferences.useMetricUnits(context);
- ArrayList<DayForecast> forecasts = w.getForecasts();
- if (forecasts == null || forecasts.size() <= 1) {
+ List<DayForecast> forecasts = w.getForecasts();
+ if (forecasts == null || forecasts.size() <= 1) {
smallPanel.setVisibility(View.GONE);
return false;
- }
-
- TimeZone MyTimezone = TimeZone.getDefault();
- Calendar calendar = new GregorianCalendar(MyTimezone);
-
- // Iterate through the forecasts
- for (DayForecast d : forecasts) {
- // Load the views
- View forecastItem = inflater.inflate(R.layout.forecast_item, null);
-
- // The day of the week
- TextView day = (TextView) forecastItem.findViewById(R.id.forecast_day);
- day.setText(calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.getDefault()));
- calendar.roll(Calendar.DAY_OF_WEEK, true);
-
- // Weather Image
- ImageView image = (ImageView) forecastItem.findViewById(R.id.weather_image);
- String iconsSet = Preferences.getWeatherIconSet(context);
- int resId = d.getConditionResource(context, iconsSet);
- if (resId != 0) {
+ }
+
+ TimeZone MyTimezone = TimeZone.getDefault();
+ Calendar calendar = new GregorianCalendar(MyTimezone);
+ int weatherTempUnit = w.getTemperatureUnit();
+ // Iterate through the forecasts
+ for (DayForecast d : forecasts) {
+ // Load the views
+ View forecastItem = inflater.inflate(R.layout.forecast_item, null);
+
+ // The day of the week
+ TextView day = (TextView) forecastItem.findViewById(R.id.forecast_day);
+ day.setText(calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
+ Locale.getDefault()));
+ calendar.roll(Calendar.DAY_OF_WEEK, true);
+
+ // Weather Image
+ ImageView image = (ImageView) forecastItem.findViewById(R.id.weather_image);
+ String iconsSet = Preferences.getWeatherIconSet(context);
+ final int resId = IconUtils.getWeatherIconResource(context, iconsSet,
+ d.getConditionCode());
+ if (resId != 0) {
image.setImageResource(resId);
- } else {
- image.setImageBitmap(d.getConditionBitmap(context, iconsSet, color));
- }
-
- // Temperatures
- String dayLow = d.getFormattedLow();
- String dayHigh = d.getFormattedHigh();
- TextView temps = (TextView) forecastItem.findViewById(R.id.weather_temps);
- temps.setText(invertLowHigh ? dayHigh + " " + dayLow : dayLow + " " + dayHigh);
-
- // Add the view
- smallPanel.addView(forecastItem,
+ } else {
+ image.setImageBitmap(IconUtils.getWeatherIconBitmap(context, iconsSet,
+ color, d.getConditionCode()));
+ }
+
+ // Temperatures
+ double lowTemp = d.getLow();
+ double highTemp = d.getHigh();
+ int tempUnit = weatherTempUnit;
+ if (weatherTempUnit == FAHRENHEIT && useMetric) {
+ lowTemp = WeatherUtils.fahrenheitToCelsius(lowTemp);
+ highTemp = WeatherUtils.fahrenheitToCelsius(highTemp);
+ tempUnit = CELSIUS;
+ } else if (weatherTempUnit == CELSIUS && !useMetric) {
+ lowTemp = WeatherUtils.celsiusToFahrenheit(lowTemp);
+ highTemp = WeatherUtils.celsiusToFahrenheit(highTemp);
+ tempUnit = FAHRENHEIT;
+ }
+ String dayLow = WeatherUtils.formatTemperature(lowTemp, tempUnit);
+ String dayHigh = WeatherUtils.formatTemperature(highTemp, tempUnit);
+ TextView temps = (TextView) forecastItem.findViewById(R.id.weather_temps);
+ temps.setText(invertLowHigh ? dayHigh + " " + dayLow : dayLow + " " + dayHigh);
+
+ // Add the view
+ smallPanel.addView(forecastItem,
new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1));
- }
- return true;
+ }
+ return true;
}
}
diff --git a/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java b/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java
deleted file mode 100755
index 60723fa..0000000
--- a/src/com/cyanogenmod/lockclock/weather/HttpRetriever.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2013 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.util.Log;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.util.EntityUtils;
-
-import java.io.IOException;
-
-public class HttpRetriever {
- private static final String TAG = "HttpRetriever";
-
- public static String retrieve(String url) {
- HttpGet request = new HttpGet(url);
- try {
- HttpResponse response = new DefaultHttpClient().execute(request);
- HttpEntity entity = response.getEntity();
- if (entity != null) {
- return EntityUtils.toString(entity);
- }
- } catch (IOException e) {
- Log.e(TAG, "Couldn't retrieve data from url " + url, e);
- }
- return null;
- }
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java b/src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java
deleted file mode 100644
index ee2f46f..0000000
--- a/src/com/cyanogenmod/lockclock/weather/OpenWeatherMapProvider.java
+++ /dev/null
@@ -1,330 +0,0 @@
-package com.cyanogenmod.lockclock.weather;
-
-import java.util.*;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.location.Location;
-import android.net.Uri;
-import android.util.Log;
-
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-import com.cyanogenmod.lockclock.R;
-
-public class OpenWeatherMapProvider implements WeatherProvider {
- private static final String TAG = "OpenWeatherMapProvider";
-
- private static final int FORECAST_DAYS = 5;
- private static final String SELECTION_LOCATION = "lat=%f&lon=%f";
- private static final String SELECTION_ID = "id=%s";
- private static final String APP_ID = "e2b075d68c39dc43e16995653fcd6fd0";
-
- private static final String URL_LOCATION =
- "http://api.openweathermap.org/data/2.5/find?q=%s&mode=json&lang=%s&appid="
- + APP_ID;
- private static final String URL_WEATHER =
- "http://api.openweathermap.org/data/2.5/weather?%s&mode=json&units=%s&lang=%s&appid="
- + APP_ID;
- private static final String URL_FORECAST =
- "http://api.openweathermap.org/data/2.5/forecast/daily?" +
- "%s&mode=json&units=%s&lang=%s&cnt=" + FORECAST_DAYS + "&appid=" + APP_ID;
-
- private Context mContext;
-
- public OpenWeatherMapProvider(Context context) {
- mContext = context;
- }
-
- @Override
- public int getNameResourceId() {
- return R.string.weather_source_openweathermap;
- }
-
- @Override
- public List<LocationResult> getLocations(String input) {
- String url = String.format(URL_LOCATION, Uri.encode(input), getLanguageCode());
- String response = HttpRetriever.retrieve(url);
- if (response == null) {
- return null;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "URL = " + url + " returning a response of " + response);
- }
-
- try {
- JSONArray jsonResults = new JSONObject(response).getJSONArray("list");
- ArrayList<LocationResult> results = new ArrayList<LocationResult>();
- int count = jsonResults.length();
-
- for (int i = 0; i < count; i++) {
- JSONObject result = jsonResults.getJSONObject(i);
- LocationResult location = new LocationResult();
-
- location.id = result.getString("id");
- location.city = result.getString("name");
- location.countryId = result.getJSONObject("sys").getString("country");
- results.add(location);
- }
-
- return results;
- } catch (JSONException e) {
- Log.w(TAG, "Received malformed location data (input=" + input + ")", e);
- }
-
- return null;
- }
-
- public WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metric) {
- String selection = String.format(Locale.US, SELECTION_ID, id);
- return handleWeatherRequest(selection, localizedCityName, metric);
- }
-
- public WeatherInfo getWeatherInfo(Location location, boolean metric) {
- String selection = String.format(Locale.US, SELECTION_LOCATION,
- location.getLatitude(), location.getLongitude());
- return handleWeatherRequest(selection, null, metric);
- }
-
- private WeatherInfo handleWeatherRequest(String selection,
- String localizedCityName, boolean metric) {
- String units = metric ? "metric" : "imperial";
- String locale = getLanguageCode();
- String conditionUrl = String.format(Locale.US, URL_WEATHER, selection, units, locale);
- String conditionResponse = HttpRetriever.retrieve(conditionUrl);
- if (conditionResponse == null) {
- return null;
- }
-
- String forecastUrl = String.format(Locale.US, URL_FORECAST, selection, units, locale);
- String forecastResponse = HttpRetriever.retrieve(forecastUrl);
- if (forecastResponse == null) {
- return null;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "URL = " + conditionUrl + " returning a response of " + conditionResponse);
- }
-
- try {
- JSONObject conditions = new JSONObject(conditionResponse);
- JSONObject weather = conditions.getJSONArray("weather").getJSONObject(0);
- JSONObject conditionData = conditions.getJSONObject("main");
- JSONObject windData = conditions.getJSONObject("wind");
- ArrayList<DayForecast> forecasts =
- parseForecasts(new JSONObject(forecastResponse).getJSONArray("list"), metric);
- int speedUnitResId = metric ? R.string.weather_kph : R.string.weather_mph;
- if (localizedCityName == null) {
- localizedCityName = conditions.getString("name");
- }
-
- WeatherInfo w = new WeatherInfo(mContext, conditions.getString("id"), localizedCityName,
- /* condition */ weather.getString("main"),
- /* conditionCode */ mapConditionIconToCode(
- weather.getString("icon"), weather.getInt("id")),
- /* temperature */ sanitizeTemperature(conditionData.getDouble("temp"), metric),
- /* tempUnit */ metric ? "C" : "F",
- /* humidity */ (float) conditionData.getDouble("humidity"),
- /* wind */ (float) windData.getDouble("speed"),
- /* windDir */ windData.getInt("deg"),
- /* speedUnit */ mContext.getString(speedUnitResId),
- forecasts,
- System.currentTimeMillis());
-
- Log.d(TAG, "Weather updated: " + w);
- return w;
- } catch (JSONException e) {
- Log.w(TAG, "Received malformed weather data (selection = " + selection
- + ", lang = " + locale + ")", e);
- }
-
- return null;
- }
-
- private ArrayList<DayForecast> parseForecasts(JSONArray forecasts, boolean metric) throws JSONException {
- ArrayList<DayForecast> result = new ArrayList<DayForecast>();
- int count = forecasts.length();
-
- if (count == 0) {
- throw new JSONException("Empty forecasts array");
- }
- for (int i = 0; i < count; i++) {
- JSONObject forecast = forecasts.getJSONObject(i);
- JSONObject temperature = forecast.getJSONObject("temp");
- JSONObject data = forecast.getJSONArray("weather").getJSONObject(0);
- DayForecast item = new DayForecast(
- /* low */ sanitizeTemperature(temperature.getDouble("min"), metric),
- /* high */ sanitizeTemperature(temperature.getDouble("max"), metric),
- /* condition */ data.getString("main"),
- /* conditionCode */ mapConditionIconToCode(
- data.getString("icon"), data.getInt("id")));
- result.add(item);
- }
-
- return result;
- }
-
- // OpenWeatherMap sometimes returns temperatures in Kelvin even if we ask it
- // for deg C or deg F. Detect this and convert accordingly.
- private static float sanitizeTemperature(double value, boolean metric) {
- // threshold chosen to work for both C and F. 170 deg F is hotter
- // than the hottest place on earth.
- if (value > 170) {
- // K -> deg C
- value -= 273.15;
- if (!metric) {
- // deg C -> deg F
- value = (value * 1.8) + 32;
- }
- }
- return (float) value;
- }
-
- private static final HashMap<String, Integer> ICON_MAPPING = new HashMap<String, Integer>();
- static {
- ICON_MAPPING.put("01d", 32);
- ICON_MAPPING.put("01n", 31);
- ICON_MAPPING.put("02d", 30);
- ICON_MAPPING.put("02n", 29);
- ICON_MAPPING.put("03d", 26);
- ICON_MAPPING.put("03n", 26);
- ICON_MAPPING.put("04d", 28);
- ICON_MAPPING.put("04n", 27);
- ICON_MAPPING.put("09d", 12);
- ICON_MAPPING.put("09n", 11);
- ICON_MAPPING.put("10d", 40);
- ICON_MAPPING.put("10n", 45);
- ICON_MAPPING.put("11d", 4);
- ICON_MAPPING.put("11n", 4);
- ICON_MAPPING.put("13d", 16);
- ICON_MAPPING.put("13n", 16);
- ICON_MAPPING.put("50d", 21);
- ICON_MAPPING.put("50n", 20);
- }
-
- private int mapConditionIconToCode(String icon, int conditionId) {
-
- // First, use condition ID for specific cases
- switch (conditionId) {
- // Thunderstorms
- case 202: // thunderstorm with heavy rain
- case 232: // thunderstorm with heavy drizzle
- case 211: // thunderstorm
- return 4;
- case 212: // heavy thunderstorm
- return 3;
- case 221: // ragged thunderstorm
- case 231: // thunderstorm with drizzle
- case 201: // thunderstorm with rain
- return 38;
- case 230: // thunderstorm with light drizzle
- case 200: // thunderstorm with light rain
- case 210: // light thunderstorm
- return 37;
-
- // Drizzle
- case 300: // light intensity drizzle
- case 301: // drizzle
- case 302: // heavy intensity drizzle
- case 310: // light intensity drizzle rain
- case 311: // drizzle rain
- case 312: // heavy intensity drizzle rain
- case 313: // shower rain and drizzle
- case 314: // heavy shower rain and drizzle
- case 321: // shower drizzle
- return 9;
-
- // Rain
- case 500: // light rain
- case 501: // moderate rain
- case 520: // light intensity shower rain
- case 521: // shower rain
- case 531: // ragged shower rain
- return 11;
- case 502: // heavy intensity rain
- case 503: // very heavy rain
- case 504: // extreme rain
- case 522: // heavy intensity shower rain
- return 12;
- case 511: // freezing rain
- return 10;
-
- // Snow
- case 600: case 620: return 14; // light snow
- case 601: case 621: return 16; // snow
- case 602: case 622: return 41; // heavy snow
- case 611: case 612: return 18; // sleet
- case 615: case 616: return 5; // rain and snow
-
- // Atmosphere
- case 741: // fog
- return 20;
- case 711: // smoke
- case 762: // volcanic ash
- return 22;
- case 701: // mist
- case 721: // haze
- return 21;
- case 731: // sand/dust whirls
- case 751: // sand
- case 761: // dust
- return 19;
- case 771: // squalls
- return 23;
- case 781: // tornado
- return 0;
-
- // Extreme
- case 900: return 0; // tornado
- case 901: return 1; // tropical storm
- case 902: return 2; // hurricane
- case 903: return 25; // cold
- case 904: return 36; // hot
- case 905: return 24; // windy
- case 906: return 17; // hail
- }
-
- // Not yet handled - Use generic icon mapping
- Integer condition = ICON_MAPPING.get(icon);
- if (condition != null) {
- return condition;
- }
-
- return -1;
- }
-
- private static final HashMap<String, String> LANGUAGE_CODE_MAPPING = new HashMap<String, String>();
- static {
- LANGUAGE_CODE_MAPPING.put("bg-", "bg");
- LANGUAGE_CODE_MAPPING.put("de-", "de");
- LANGUAGE_CODE_MAPPING.put("es-", "sp");
- LANGUAGE_CODE_MAPPING.put("fi-", "fi");
- LANGUAGE_CODE_MAPPING.put("fr-", "fr");
- LANGUAGE_CODE_MAPPING.put("it-", "it");
- LANGUAGE_CODE_MAPPING.put("nl-", "nl");
- LANGUAGE_CODE_MAPPING.put("pl-", "pl");
- LANGUAGE_CODE_MAPPING.put("pt-", "pt");
- LANGUAGE_CODE_MAPPING.put("ro-", "ro");
- LANGUAGE_CODE_MAPPING.put("ru-", "ru");
- LANGUAGE_CODE_MAPPING.put("se-", "se");
- LANGUAGE_CODE_MAPPING.put("tr-", "tr");
- LANGUAGE_CODE_MAPPING.put("uk-", "ua");
- LANGUAGE_CODE_MAPPING.put("zh-CN", "zh_cn");
- LANGUAGE_CODE_MAPPING.put("zh-TW", "zh_tw");
- }
- private String getLanguageCode() {
- Locale locale = mContext.getResources().getConfiguration().locale;
- String selector = locale.getLanguage() + "-" + locale.getCountry();
-
- for (Map.Entry<String, String> entry : LANGUAGE_CODE_MAPPING.entrySet()) {
- if (selector.startsWith(entry.getKey())) {
- return entry.getValue();
- }
- }
-
- return "en";
- }
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/Utils.java b/src/com/cyanogenmod/lockclock/weather/Utils.java
new file mode 100644
index 0000000..411fe29
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/Utils.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.lockclock.weather;
+
+import android.content.Context;
+import android.content.res.Resources;
+import com.cyanogenmod.lockclock.R;
+import cyanogenmod.providers.WeatherContract;
+
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.NOT_AVAILABLE;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_THUNDERSTORMS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.SCATTERED_SNOW_SHOWERS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.WeatherCode.ISOLATED_THUNDERSHOWERS;
+
+import java.text.DecimalFormat;
+
+public final class Utils {
+
+ private static final DecimalFormat sNoDigitsFormat = new DecimalFormat("0");
+
+ // In doubt? See https://en.wikipedia.org/wiki/Points_of_the_compass
+ private static final double DIRECTION_NORTH = 23d;
+ private static final double DIRECTION_NORTH_EAST = 68d;
+ private static final double DIRECTION_EAST = 113d;
+ private static final double DIRECTION_SOUTH_EAST = 158d;
+ private static final double DIRECTION_SOUTH = 203d;
+ private static final double DIRECTION_SOUTH_WEST = 248d;
+ private static final double DIRECTION_WEST = 293d;
+ private static final double DIRECTION_NORTH_WEST = 338d;
+
+ /**
+ * Returns a localized string of the wind direction
+ * @param context Application context to access resources
+ * @param windDirection The wind direction in degrees
+ * @return
+ */
+ public static String resolveWindDirection(Context context, double windDirection) {
+ int resId;
+
+ if (windDirection < 0) {
+ resId = R.string.unknown;
+ } else if (windDirection < DIRECTION_NORTH) {
+ resId = R.string.weather_N;
+ } else if (windDirection < DIRECTION_NORTH_EAST) {
+ resId = R.string.weather_NE;
+ } else if (windDirection < DIRECTION_EAST) {
+ resId = R.string.weather_E;
+ } else if (windDirection < DIRECTION_SOUTH_EAST) {
+ resId = R.string.weather_SE;
+ } else if (windDirection < DIRECTION_SOUTH) {
+ resId = R.string.weather_S;
+ } else if (windDirection < DIRECTION_SOUTH_WEST) {
+ resId = R.string.weather_SW;
+ } else if (windDirection < DIRECTION_WEST) {
+ resId = R.string.weather_W;
+ } else if (windDirection < DIRECTION_NORTH_WEST) {
+ resId = R.string.weather_NW;
+ } else {
+ resId = R.string.weather_N;
+ }
+
+ return context.getString(resId);
+ }
+
+ /**
+ * Returns the resource name associated to the supplied weather condition code
+ * @param context Application context to access resources
+ * @param conditionCode The weather condition code
+ * @return The resource name if a valid condition code is passed, empty string otherwise
+ */
+ public static String resolveWeatherCondition(Context context, int conditionCode) {
+ final Resources res = context.getResources();
+ final int resId = res.getIdentifier("weather_"
+ + Utils.addOffsetToConditionCodeFromWeatherContract(conditionCode), "string",
+ context.getPackageName());
+ if (resId != 0) {
+ return res.getString(resId);
+ }
+ return "";
+ }
+
+ private static String getFormattedValue(double value, String unit) {
+ if (Double.isNaN(value)) {
+ return "-";
+ }
+ String formatted = sNoDigitsFormat.format(value);
+ if (formatted.equals("-0")) {
+ formatted = "0";
+ }
+ return formatted + unit;
+ }
+
+ /**
+ * Returns a string with the format xx% (where xx is the humidity value provided)
+ * @param humidity The humidity value
+ * @return The formatted string if a valid value is provided, "-" otherwise. Decimals are
+ * removed
+ */
+ public static String formatHumidity(double humidity) {
+ return getFormattedValue(humidity, "%");
+ }
+
+ /**
+ * Returns a localized string of the speed and speed unit
+ * @param context Application context to access resources
+ * @param windSpeed The wind speed
+ * @param windSpeedUnit The speed unit. See
+ * {@link cyanogenmod.providers.WeatherContract.WeatherColumns.WindSpeedUnit}
+ * @return The formatted string if a valid speed and speed unit a provided.
+ * {@link com.cyanogenmod.lockclock.R.string#unknown} otherwise
+ */
+ public static String formatWindSpeed(Context context, double windSpeed, int windSpeedUnit) {
+ if (windSpeed < 0) {
+ return context.getString(R.string.unknown);
+ }
+
+ String localizedSpeedUnit;
+ switch (windSpeedUnit) {
+ case WeatherContract.WeatherColumns.WindSpeedUnit.MPH:
+ localizedSpeedUnit = context.getString(R.string.weather_mph);
+ break;
+ case WeatherContract.WeatherColumns.WindSpeedUnit.KPH:
+ localizedSpeedUnit = context.getString(R.string.weather_kph);
+ break;
+ default:
+ return context.getString(R.string.unknown);
+ }
+ return getFormattedValue(windSpeed, localizedSpeedUnit);
+ }
+
+ /**
+ * Helper method to convert miles to kilometers
+ * @param miles The value in miles
+ * @return The value in kilometers
+ */
+ public static double milesToKilometers(double miles) {
+ return miles * 1.609344d;
+ }
+
+ /**
+ * Helper method to convert kilometers to miles
+ * @param km The value in kilometers
+ * @return The value in miles
+ */
+ public static double kilometersToMiles(double km) {
+ return km * 0.6214d;
+ }
+
+ /**
+ * Adds an offset to the condition code reported by the active weather service provider.
+ * @param conditionCode The condition code from the Weather API
+ * @return A condition code that correctly maps to our resource IDs
+ */
+ public static int addOffsetToConditionCodeFromWeatherContract(int conditionCode) {
+ if (conditionCode <= WeatherContract.WeatherColumns.WeatherCode.SHOWERS) {
+ return conditionCode;
+ } else if (conditionCode <= SCATTERED_THUNDERSTORMS) {
+ return conditionCode + 1;
+ } else if (conditionCode <= SCATTERED_SNOW_SHOWERS) {
+ return conditionCode + 2;
+ } else if (conditionCode <= ISOLATED_THUNDERSHOWERS) {
+ return conditionCode + 3;
+ } else {
+ return NOT_AVAILABLE;
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java b/src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java
deleted file mode 100644
index a2ab385..0000000
--- a/src/com/cyanogenmod/lockclock/weather/WeatherContentProvider.java
+++ /dev/null
@@ -1,187 +0,0 @@
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.util.Log;
-
-import com.cyanogenmod.lockclock.misc.Preferences;
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-
-public class WeatherContentProvider extends ContentProvider {
-
- public static final String TAG = WeatherContentProvider.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- static WeatherInfo sCachedWeatherInfo;
-
- private static final int URI_TYPE_EVERYTHING = 1;
- private static final int URI_TYPE_CURRENT = 2;
- private static final int URI_TYPE_FORECAST = 3;
-
- private static final String COLUMN_CURRENT_CITY_ID = "city_id";
- private static final String COLUMN_CURRENT_CITY = "city";
- private static final String COLUMN_CURRENT_CONDITION = "condition";
- private static final String COLUMN_CURRENT_TEMPERATURE = "temperature";
- private static final String COLUMN_CURRENT_HUMIDITY = "humidity";
- private static final String COLUMN_CURRENT_WIND = "wind";
- private static final String COLUMN_CURRENT_TIME_STAMP = "time_stamp";
- private static final String COLUMN_CURRENT_CONDITION_CODE = "condition_code";
-
- private static final String COLUMN_FORECAST_LOW = "forecast_low";
- private static final String COLUMN_FORECAST_HIGH = "forecast_high";
- private static final String COLUMN_FORECAST_CONDITION = "forecast_condition";
- private static final String COLUMN_FORECAST_CONDITION_CODE = "forecast_condition_code";
-
- private static final String[] PROJECTION_DEFAULT_CURRENT = new String[] {
- COLUMN_CURRENT_CITY_ID,
- COLUMN_CURRENT_CITY,
- COLUMN_CURRENT_CONDITION,
- COLUMN_CURRENT_TEMPERATURE,
- COLUMN_CURRENT_HUMIDITY,
- COLUMN_CURRENT_WIND,
- COLUMN_CURRENT_TIME_STAMP,
- COLUMN_CURRENT_CONDITION_CODE
- };
-
- private static final String[] PROJECTION_DEFAULT_FORECAST = new String[] {
- COLUMN_FORECAST_LOW,
- COLUMN_FORECAST_HIGH,
- COLUMN_FORECAST_CONDITION,
- COLUMN_FORECAST_CONDITION_CODE
- };
-
- private static final String[] PROJECTION_DEFAULT_EVERYTHING = new String[] {
- COLUMN_CURRENT_CITY_ID,
- COLUMN_CURRENT_CITY,
- COLUMN_CURRENT_CONDITION,
- COLUMN_CURRENT_TEMPERATURE,
- COLUMN_CURRENT_HUMIDITY,
- COLUMN_CURRENT_WIND,
- COLUMN_CURRENT_TIME_STAMP,
- COLUMN_CURRENT_CONDITION_CODE,
-
- COLUMN_FORECAST_LOW,
- COLUMN_FORECAST_HIGH,
- COLUMN_FORECAST_CONDITION,
- COLUMN_FORECAST_CONDITION_CODE
- };
-
- public static final String AUTHORITY = "com.cyanogenmod.lockclock.weather.provider";
-
- private static final UriMatcher sUriMatcher;
- static {
- sUriMatcher = new UriMatcher(URI_TYPE_EVERYTHING);
- sUriMatcher.addURI(AUTHORITY, "weather", URI_TYPE_EVERYTHING);
- sUriMatcher.addURI(AUTHORITY, "weather/current", URI_TYPE_CURRENT);
- sUriMatcher.addURI(AUTHORITY, "weather/forecast", URI_TYPE_FORECAST);
- }
-
- private Context mContext;
-
- @Override
- public boolean onCreate() {
- mContext = getContext();
- sCachedWeatherInfo = Preferences.getCachedWeatherInfo(mContext);
- return true;
- }
-
- @Override
- public Cursor query(
- Uri uri,
- String[] projection,
- String selection,
- String[] selectionArgs,
- String sortOrder) {
-
- final int projectionType = sUriMatcher.match(uri);
- final MatrixCursor result = new MatrixCursor(resolveProjection(projection, projectionType));
-
- WeatherInfo weather = sCachedWeatherInfo;
- if (weather != null) {
- // current
- result.newRow()
- .add(COLUMN_CURRENT_CITY, weather.getCity())
- .add(COLUMN_CURRENT_CITY_ID, weather.getId())
- .add(COLUMN_CURRENT_CONDITION, weather.getCondition())
- .add(COLUMN_CURRENT_HUMIDITY, weather.getFormattedHumidity())
- .add(COLUMN_CURRENT_WIND, weather.getFormattedWindSpeed()
- + " " + weather.getWindDirection())
- .add(COLUMN_CURRENT_TEMPERATURE, weather.getFormattedTemperature())
- .add(COLUMN_CURRENT_TIME_STAMP, weather.getTimestamp().toString())
- .add(COLUMN_CURRENT_CONDITION_CODE, weather.getConditionCode());
-
- // forecast
- for (DayForecast day : weather.getForecasts()) {
- result.newRow()
- .add(COLUMN_FORECAST_CONDITION, day.getCondition(mContext))
- .add(COLUMN_FORECAST_LOW, day.getFormattedLow())
- .add(COLUMN_FORECAST_HIGH, day.getFormattedHigh())
- .add(COLUMN_FORECAST_CONDITION_CODE, day.getConditionCode());
- }
- return result;
- } else {
- if (DEBUG) Log.e(TAG, "sCachedWeatherInfo is null");
- Intent updateWeather = new Intent(WeatherUpdateService.ACTION_FORCE_UPDATE);
- updateWeather.setClass(mContext, WeatherUpdateService.class);
- mContext.startService(updateWeather);
- }
- return null;
- }
-
- private String[] resolveProjection(String[] projection, int uriType) {
- if (projection != null)
- return projection;
- switch (uriType) {
- default:
- case URI_TYPE_EVERYTHING:
- return PROJECTION_DEFAULT_EVERYTHING;
-
- case URI_TYPE_CURRENT:
- return PROJECTION_DEFAULT_CURRENT;
-
- case URI_TYPE_FORECAST:
- return PROJECTION_DEFAULT_FORECAST;
- }
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
-
- public static void updateCachedWeatherInfo(Context context, WeatherInfo info) {
- if (DEBUG) Log.e(TAG, "updateCachedWeatherInfo()");
- if(info != null) {
- if (DEBUG) Log.e(TAG, "set new weather info");
- sCachedWeatherInfo = WeatherInfo.fromSerializedString(context, info.toSerializedString());
- } else {
- if(DEBUG) Log.e(TAG, "nulled out cached weather info");
- sCachedWeatherInfo = null;
- }
- context.getContentResolver().notifyChange(
- Uri.parse("content://" + WeatherContentProvider.AUTHORITY + "/weather"), null);
- }
-
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java b/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
deleted file mode 100755
index 7ad4339..0000000
--- a/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2012 The AOKP Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-
-import com.cyanogenmod.lockclock.R;
-import com.cyanogenmod.lockclock.misc.IconUtils;
-
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Date;
-
-public class WeatherInfo {
- private static final DecimalFormat sNoDigitsFormat = new DecimalFormat("0");
-
- private Context mContext;
-
- private String id;
- private String city;
- private String condition;
- private int conditionCode;
- private float temperature;
- private String tempUnit;
- private float humidity;
- private float wind;
- private int windDirection;
- private String speedUnit;
- private long timestamp;
- private ArrayList<DayForecast> forecasts;
-
- public WeatherInfo(Context context, String id,
- String city, String condition, int conditionCode, float temp,
- String tempUnit, float humidity, float wind, int windDir,
- String speedUnit, ArrayList<DayForecast> forecasts, long timestamp) {
- this.mContext = context.getApplicationContext();
- this.id = id;
- this.city = city;
- this.condition = condition;
- this.conditionCode = conditionCode;
- this.humidity = humidity;
- this.wind = wind;
- this.windDirection = windDir;
- this.speedUnit = speedUnit;
- this.timestamp = timestamp;
- this.temperature = temp;
- this.tempUnit = tempUnit;
- this.forecasts = forecasts;
- }
-
- public static class DayForecast {
- public final float low, high;
- public final int conditionCode;
- public final String condition;
-
- public DayForecast(float low, float high, String condition, int conditionCode) {
- this.low = low;
- this.high = high;
- this.condition = condition;
- this.conditionCode = conditionCode;
- }
-
- public String getFormattedLow() {
- return getFormattedValue(low, "\u00b0");
- }
-
- public String getFormattedHigh() {
- return getFormattedValue(high, "\u00b0");
- }
-
- public int getConditionResource(Context context, String set) {
- return IconUtils.getWeatherIconResource(context, set, conditionCode);
- }
-
- public Bitmap getConditionBitmap(Context context, String set, int color) {
- return IconUtils.getWeatherIconBitmap(context, set, color, conditionCode);
- }
-
- public Bitmap getConditionBitmap(Context context, String set, int color, int density) {
- return IconUtils.getWeatherIconBitmap(context, set, color, conditionCode, density);
- }
-
- public String getCondition(Context context) {
- return WeatherInfo.getCondition(context, conditionCode, condition);
- }
-
- public int getConditionCode() {
- return conditionCode;
- }
- }
-
- public int getConditionResource(String set) {
- return IconUtils.getWeatherIconResource(mContext, set, conditionCode);
- }
-
- public Bitmap getConditionBitmap(String set, int color) {
- return IconUtils.getWeatherIconBitmap(mContext, set, color, conditionCode);
- }
-
- public Bitmap getConditionBitmap(String set, int color, int density) {
- return IconUtils.getWeatherIconBitmap(mContext, set, color, conditionCode, density);
- }
-
- public String getId() {
- return id;
- }
-
- public String getCity() {
- return city;
- }
-
- public String getCondition() {
- return getCondition(mContext, conditionCode, condition);
- }
-
- public int getConditionCode() {
- return conditionCode;
- }
-
- private static String getCondition(Context context, int conditionCode, String condition) {
- final Resources res = context.getResources();
- final int resId = res.getIdentifier("weather_" + conditionCode, "string", context.getPackageName());
- if (resId != 0) {
- return res.getString(resId);
- }
- return condition;
- }
-
- public Date getTimestamp() {
- return new Date(timestamp);
- }
-
- private static String getFormattedValue(float value, String unit) {
- if (Float.isNaN(value)) {
- return "-";
- }
- String formatted = sNoDigitsFormat.format(value);
- if (formatted.equals("-0")) {
- formatted = "0";
- }
- return formatted + unit;
- }
-
- public String getFormattedTemperature() {
- return getFormattedValue(temperature, "\u00b0" + tempUnit);
- }
-
- public String getFormattedLow() {
- return forecasts.get(0).getFormattedLow();
- }
-
- public String getFormattedHigh() {
- return forecasts.get(0).getFormattedHigh();
- }
-
- public String getFormattedHumidity() {
- return getFormattedValue(humidity, "%");
- }
-
- public String getFormattedWindSpeed() {
- if (wind < 0) {
- return mContext.getString(R.string.unknown);
- }
- return getFormattedValue(wind, speedUnit);
- }
-
- public String getWindDirection() {
- int resId;
-
- if (windDirection < 0) resId = R.string.unknown;
- else if (windDirection < 23) resId = R.string.weather_N;
- else if (windDirection < 68) resId = R.string.weather_NE;
- else if (windDirection < 113) resId = R.string.weather_E;
- else if (windDirection < 158) resId = R.string.weather_SE;
- else if (windDirection < 203) resId = R.string.weather_S;
- else if (windDirection < 248) resId = R.string.weather_SW;
- else if (windDirection < 293) resId = R.string.weather_W;
- else if (windDirection < 338) resId = R.string.weather_NW;
- else resId = R.string.weather_N;
-
- return mContext.getString(resId);
- }
-
- public ArrayList<DayForecast> getForecasts() {
- return forecasts;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("WeatherInfo for ");
- builder.append(city);
- builder.append(" (");
- builder.append(id);
- builder.append(") @ ");
- builder.append(getTimestamp());
- builder.append(": ");
- builder.append(getCondition());
- builder.append("(");
- builder.append(conditionCode);
- builder.append("), temperature ");
- builder.append(getFormattedTemperature());
- builder.append(", low ");
- builder.append(getFormattedLow());
- builder.append(", high ");
- builder.append(getFormattedHigh());
- builder.append(", humidity ");
- builder.append(getFormattedHumidity());
- builder.append(", wind ");
- builder.append(getFormattedWindSpeed());
- builder.append(" at ");
- builder.append(getWindDirection());
- if (forecasts.size() > 0) {
- builder.append(", forecasts:");
- }
- for (int i = 0; i < forecasts.size(); i++) {
- DayForecast d = forecasts.get(i);
- if (i != 0) {
- builder.append(";");
- }
- builder.append(" day ").append(i + 1).append(": ");
- builder.append("high ").append(d.getFormattedHigh());
- builder.append(", low ").append(d.getFormattedLow());
- builder.append(", ").append(d.condition);
- builder.append("(").append(d.conditionCode).append(")");
- }
- return builder.toString();
- }
-
- public String toSerializedString() {
- StringBuilder builder = new StringBuilder();
- builder.append(id).append('|');
- builder.append(city).append('|');
- builder.append(condition).append('|');
- builder.append(conditionCode).append('|');
- builder.append(temperature).append('|');
- builder.append(tempUnit).append('|');
- builder.append(humidity).append('|');
- builder.append(wind).append('|');
- builder.append(windDirection).append('|');
- builder.append(speedUnit).append('|');
- builder.append(timestamp).append('|');
- serializeForecasts(builder);
- return builder.toString();
- }
-
- private void serializeForecasts(StringBuilder builder) {
- builder.append(forecasts.size());
- for (DayForecast d : forecasts) {
- builder.append(';');
- builder.append(d.high).append(';');
- builder.append(d.low).append(';');
- builder.append(d.condition).append(';');
- builder.append(d.conditionCode);
- }
- }
-
- public static WeatherInfo fromSerializedString(Context context, String input) {
- if (input == null) {
- return null;
- }
-
- String[] parts = input.split("\\|");
- if (parts == null || parts.length != 12) {
- return null;
- }
-
- int conditionCode, windDirection;
- long timestamp;
- float temperature, humidity, wind;
- String[] forecastParts = parts[11].split(";");
- int forecastItems;
- ArrayList<DayForecast> forecasts = new ArrayList<DayForecast>();
-
- // Parse the core data
- try {
- conditionCode = Integer.parseInt(parts[3]);
- temperature = Float.parseFloat(parts[4]);
- humidity = Float.parseFloat(parts[6]);
- wind = Float.parseFloat(parts[7]);
- windDirection = Integer.parseInt(parts[8]);
- timestamp = Long.parseLong(parts[10]);
- forecastItems = forecastParts == null ? 0 : Integer.parseInt(forecastParts[0]);
- } catch (NumberFormatException e) {
- return null;
- }
-
- if (forecastItems == 0 || forecastParts.length != 4 * forecastItems + 1) {
- return null;
- }
-
- // Parse the forecast data
- try {
- for (int item = 0; item < forecastItems; item ++) {
- int offset = item * 4 + 1;
- DayForecast day = new DayForecast(
- /* low */ Float.parseFloat(forecastParts[offset + 1]),
- /* high */ Float.parseFloat(forecastParts[offset]),
- /* condition */ forecastParts[offset + 2],
- /* conditionCode */ Integer.parseInt(forecastParts[offset + 3]));
- if (!Float.isNaN(day.low) && !Float.isNaN(day.high) && day.conditionCode >= 0) {
- forecasts.add(day);
- }
- }
- } catch (NumberFormatException ignored) {
- }
-
- if (forecasts.isEmpty()) {
- return null;
- }
-
- return new WeatherInfo(context,
- /* id */ parts[0], /* city */ parts[1], /* condition */ parts[2],
- conditionCode, temperature, /* tempUnit */ parts[5],
- humidity, wind, windDirection, /* speedUnit */ parts[9],
- /* forecasts */ forecasts, timestamp);
- }
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java b/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java
deleted file mode 100644
index 70fbf42..0000000
--- a/src/com/cyanogenmod/lockclock/weather/WeatherProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2013 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.location.Location;
-
-import java.util.List;
-
-public interface WeatherProvider {
- public class LocationResult {
- public String id;
- public String city;
- public String postal;
- public String countryId;
- public String country;
- }
-
- List<LocationResult> getLocations(String input);
-
- WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metricUnits);
-
- WeatherInfo getWeatherInfo(Location location, boolean metricUnits);
-
- int getNameResourceId();
-}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java b/src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java
new file mode 100644
index 0000000..4bc816a
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherSourceListenerService.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.lockclock.weather;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import com.cyanogenmod.lockclock.ClockWidgetService;
+import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.misc.Preferences;
+import cyanogenmod.weather.CMWeatherManager;
+
+public class WeatherSourceListenerService extends Service
+ implements CMWeatherManager.WeatherServiceProviderChangeListener {
+
+ private static final String TAG = WeatherSourceListenerService.class.getSimpleName();
+ private static final boolean D = Constants.DEBUG;
+ private Context mContext;
+
+ @Override
+ public void onWeatherServiceProviderChanged(String providerLabel) {
+ if (D) Log.d(TAG, "Weather Source changed " + providerLabel);
+ Preferences.setWeatherSource(mContext, providerLabel);
+ Preferences.setCachedWeatherInfo(mContext, 0, null);
+ //The data contained in WeatherLocation is tightly coupled to the weather provider
+ //that generated that data, so we need to clear the cached weather location and let the new
+ //weather provider regenerate the data if the user decides to use custom location again
+ Preferences.setCustomWeatherLocationCity(mContext, null);
+ Preferences.setCustomWeatherLocation(mContext, null);
+ Preferences.setUseCustomWeatherLocation(mContext, false);
+
+ //Refresh the widget
+ mContext.startService(new Intent(mContext, ClockWidgetService.class)
+ .setAction(ClockWidgetService.ACTION_REFRESH));
+
+ if (providerLabel != null) {
+ mContext.startService(new Intent(mContext, WeatherUpdateService.class)
+ .putExtra(WeatherUpdateService.ACTION_FORCE_UPDATE, true));
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ mContext = getApplicationContext();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ final CMWeatherManager weatherManager
+ = CMWeatherManager.getInstance(mContext);
+ weatherManager.registerWeatherServiceProviderChangeListener(this);
+ if (D) Log.d(TAG, "Listener registered");
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ final CMWeatherManager weatherManager = CMWeatherManager.getInstance(mContext);
+ weatherManager.unregisterWeatherServiceProviderChangeListener(this);
+ if (D) Log.d(TAG, "Listener unregistered");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
index ea0b89c..fc652b1 100644
--- a/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
@@ -26,24 +26,31 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
-import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
-
+import android.widget.Toast;
import com.cyanogenmod.lockclock.ClockWidgetProvider;
+import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Constants;
import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
import com.cyanogenmod.lockclock.preference.WeatherPreferences;
-
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.WeatherInfo;
+import cyanogenmod.weather.WeatherLocation;
+import java.lang.ref.WeakReference;
import java.util.Date;
public class WeatherUpdateService extends Service {
@@ -54,6 +61,10 @@ public class WeatherUpdateService extends Service {
private static final String ACTION_CANCEL_LOCATION_UPDATE =
"com.cyanogenmod.lockclock.action.CANCEL_LOCATION_UPDATE";
+ private static final String ACTION_CANCEL_UPDATE_WEATHER_REQUEST =
+ "com.cyanogenmod.lockclock.action.CANCEL_UPDATE_WEATHER_REQUEST";
+ private static final long WEATHER_UPDATE_REQUEST_TIMEOUT_MS = 30L * 1000L;
+
// Broadcast action for end of update
public static final String ACTION_UPDATE_FINISHED = "com.cyanogenmod.lockclock.action.WEATHER_UPDATE_FINISHED";
public static final String EXTRA_UPDATE_CANCELLED = "update_cancelled";
@@ -62,7 +73,8 @@ public class WeatherUpdateService extends Service {
private static final long OUTDATED_LOCATION_THRESHOLD_MILLIS = 10L * 60L * 1000L; // 10 minutes
private static final float LOCATION_ACCURACY_THRESHOLD_METERS = 50000;
- private WeatherUpdateTask mTask;
+ private WorkerThread mWorkerThread;
+ private Handler mHandler;
private static final Criteria sLocationCriteria;
static {
@@ -73,107 +85,243 @@ public class WeatherUpdateService extends Service {
}
@Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate");
+ mWorkerThread = new WorkerThread(getApplicationContext());
+ mWorkerThread.start();
+ mWorkerThread.prepareHandler();
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (D) Log.v(TAG, "Got intent " + intent);
- boolean active = mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED;
-
if (ACTION_CANCEL_LOCATION_UPDATE.equals(intent.getAction())) {
WeatherLocationListener.cancel(this);
- if (!active) {
+ if (!mWorkerThread.isProcessing()) {
stopSelf();
}
return START_NOT_STICKY;
}
- if (active) {
- if (D) Log.v(TAG, "Weather update is still active, not starting new update");
- return START_REDELIVER_INTENT;
+ if (ACTION_CANCEL_UPDATE_WEATHER_REQUEST.equals(intent.getAction())) {
+ if (mWorkerThread.isProcessing()) {
+ mWorkerThread.getHandler().obtainMessage(
+ WorkerThread.MSG_CANCEL_UPDATE_WEATHER_REQUEST).sendToTarget();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final Context context = getApplicationContext();
+ final CMWeatherManager weatherManager
+ = CMWeatherManager.getInstance(context);
+ final String activeProviderLabel
+ = weatherManager.getActiveWeatherServiceProviderLabel();
+ final String noData
+ = getString(R.string.weather_cannot_reach_provider,
+ activeProviderLabel);
+ Toast.makeText(context, noData, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ stopSelf();
+ return START_NOT_STICKY;
}
boolean force = ACTION_FORCE_UPDATE.equals(intent.getAction());
if (!shouldUpdate(force)) {
Log.d(TAG, "Service started, but shouldn't update ... stopping");
- stopSelf();
sendCancelledBroadcast();
+ stopSelf();
return START_NOT_STICKY;
}
- mTask = new WeatherUpdateTask();
- mTask.execute();
+ mWorkerThread.getHandler().obtainMessage(WorkerThread.MSG_ON_NEW_WEATHER_REQUEST)
+ .sendToTarget();
return START_REDELIVER_INTENT;
}
- private void sendCancelledBroadcast() {
- Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
- finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, true);
- sendBroadcast(finishedIntent);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onDestroy() {
- if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) {
- mTask.cancel(true);
- mTask = null;
+ private boolean shouldUpdate(boolean force) {
+ final CMWeatherManager weatherManager
+ = CMWeatherManager.getInstance(getApplicationContext());
+ if (weatherManager.getActiveWeatherServiceProviderLabel() == null) {
+ //Why bother if we don't even have an active provider
+ if (D) Log.d(TAG, "No active weather service provider found, skip");
+ return false;
}
- }
- private boolean shouldUpdate(boolean force) {
- long interval = Preferences.weatherRefreshIntervalInMs(this);
+ final long interval = Preferences.weatherRefreshIntervalInMs(this);
if (interval == 0 && !force) {
- if (D) Log.v(TAG, "Interval set to manual and update not forced, skip update");
+ if (D) Log.v(TAG, "Interval set to manual and update not forced, skip");
return false;
}
if (!WeatherPreferences.hasLocationPermission(this)) {
- if (D) Log.v(TAG, "Application does not have the location permission");
+ if (D) Log.v(TAG, "Application does not have the location permission, skip");
return false;
}
- if (force) {
- Preferences.setCachedWeatherInfo(this, 0, null);
- }
-
- long now = System.currentTimeMillis();
- long lastUpdate = Preferences.lastWeatherUpdateTimestamp(this);
- long due = lastUpdate + interval;
-
- if (D) Log.d(TAG, "Now " + now + " due " + due + "(" + new Date(due) + ")");
+ if (WidgetUtils.isNetworkAvailable(this)) {
+ if (force) {
+ if (D) Log.d(TAG, "Forcing weather update");
+ return true;
+ } else {
+ final long now = SystemClock.elapsedRealtime();
+ final long lastUpdate = Preferences.lastWeatherUpdateTimestamp(this);
+ final long due = lastUpdate + interval;
+ if (D) {
+ Log.d(TAG, "Now " + now + " Last update " + lastUpdate
+ + " interval " + interval);
+ }
- if (lastUpdate != 0 && now < due) {
- if (D) Log.v(TAG, "Weather update is not due yet");
+ if (lastUpdate == 0 || due - now < 0) {
+ if (D) Log.d(TAG, "Should update");
+ return true;
+ } else {
+ if (D) Log.v(TAG, "Next weather update due in " + (due - now) + " ms, skip");
+ return false;
+ }
+ }
+ } else {
+ if (D) Log.d(TAG, "Network is not available, skip");
return false;
}
-
- return WidgetUtils.isNetworkAvailable(this);
}
- private class WeatherUpdateTask extends AsyncTask<Void, Void, WeatherInfo> {
+ private static class WorkerThread extends HandlerThread
+ implements CMWeatherManager.WeatherUpdateRequestListener {
+
+ public static final int MSG_ON_NEW_WEATHER_REQUEST = 1;
+ public static final int MSG_ON_WEATHER_REQUEST_COMPLETED = 2;
+ public static final int MSG_WEATHER_REQUEST_FAILED = 3;
+ public static final int MSG_CANCEL_UPDATE_WEATHER_REQUEST = 4;
+
+ private Handler mHandler;
+ private boolean mIsProcessingWeatherUpdate = false;
private WakeLock mWakeLock;
- private Context mContext;
+ private PendingIntent mTimeoutPendingIntent;
+ private int mRequestId;
+ private final CMWeatherManager mWeatherManager;
+ final private Context mContext;
+
+ public WorkerThread(Context context) {
+ super("weather-service-worker");
+ mContext = context;
+ mWeatherManager = CMWeatherManager.getInstance(mContext);
+ }
+
+ public synchronized void prepareHandler() {
+ mHandler = new Handler(getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (D) Log.d(TAG, "Msg " + msg.what);
+ switch (msg.what) {
+ case MSG_ON_NEW_WEATHER_REQUEST:
+ onNewWeatherRequest();
+ break;
+ case MSG_ON_WEATHER_REQUEST_COMPLETED:
+ WeatherInfo info = (WeatherInfo) msg.obj;
+ onWeatherRequestCompleted(info);
+ break;
+ case MSG_WEATHER_REQUEST_FAILED:
+ int status = msg.arg1;
+ onWeatherRequestFailed(status);
+ break;
+ case MSG_CANCEL_UPDATE_WEATHER_REQUEST:
+ onCancelUpdateWeatherRequest();
+ break;
+ default:
+ //Unknown message, pass it on...
+ super.handleMessage(msg);
+ }
+ }
+ };
+ }
+
+ private void startTimeoutAlarm() {
+ Intent intent = new Intent(mContext, WeatherUpdateService.class);
+ intent.setAction(ACTION_CANCEL_UPDATE_WEATHER_REQUEST);
+
+ mTimeoutPendingIntent = PendingIntent.getService(mContext, 0, intent,
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT);
+
+ AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE);
+ long elapseTime = SystemClock.elapsedRealtime() + WEATHER_UPDATE_REQUEST_TIMEOUT_MS;
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapseTime, mTimeoutPendingIntent);
+ if (D) Log.v(TAG, "Timeout alarm set to expire in " + elapseTime + " ms");
+ }
+
+ private void cancelTimeoutAlarm() {
+ if (mTimeoutPendingIntent != null) {
+ AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE);
+ am.cancel(mTimeoutPendingIntent);
+ mTimeoutPendingIntent = null;
+ if (D) Log.v(TAG, "Timeout alarm cancelled");
+ }
+ }
- public WeatherUpdateTask() {
- if (D) Log.d(TAG, "Starting weather update task");
- PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ public synchronized Handler getHandler() {
+ return mHandler;
+ }
+
+ private void onNewWeatherRequest() {
+ if (mIsProcessingWeatherUpdate) {
+ Log.d(TAG, "Already processing weather update, discarding request...");
+ return;
+ }
+
+ mIsProcessingWeatherUpdate = true;
+ final PowerManager pm
+ = (PowerManager) mContext.getSystemService(POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mWakeLock.setReferenceCounted(false);
- mContext = WeatherUpdateService.this;
+ if (D) Log.v(TAG, "ACQUIRING WAKELOCK");
+ mWakeLock.acquire();
+
+ WeatherLocation customWeatherLocation = null;
+ if (Preferences.useCustomWeatherLocation(mContext)) {
+ customWeatherLocation = Preferences.getCustomWeatherLocation(mContext);
+ }
+ if (customWeatherLocation != null) {
+ mRequestId = mWeatherManager.requestWeatherUpdate(customWeatherLocation, this);
+ if (D) Log.d(TAG, "Request submitted using WeatherLocation");
+ startTimeoutAlarm();
+ } else {
+ final Location location = getCurrentLocation();
+ if (location != null) {
+ mRequestId = mWeatherManager.requestWeatherUpdate(location, this);
+ if (D) Log.d(TAG, "Request submitted using Location");
+ startTimeoutAlarm();
+ } else {
+ // work with cached location from last request for now
+ // a listener to update it is already scheduled if possible
+ WeatherInfo cachedInfo = Preferences.getCachedWeatherInfo(mContext);
+ if (cachedInfo != null) {
+ mHandler.obtainMessage(MSG_ON_WEATHER_REQUEST_COMPLETED,
+ cachedInfo).sendToTarget();
+ if (D) Log.d(TAG, "Returning cached weather data [ "
+ + cachedInfo.toString()+ " ]");
+ } else {
+ mHandler.obtainMessage(MSG_WEATHER_REQUEST_FAILED).sendToTarget();
+ }
+ }
+ }
}
- @Override
- protected void onPreExecute() {
- if (D) Log.d(TAG, "ACQUIRING WAKELOCK");
- mWakeLock.acquire();
+ public void tearDown() {
+ if (D) Log.d(TAG, "Tearing down worker thread");
+ if (isProcessing()) mWeatherManager.cancelRequest(mRequestId);
+ quit();
+ }
+
+ public boolean isProcessing() {
+ return mIsProcessingWeatherUpdate;
}
private Location getCurrentLocation() {
- LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+ final LocationManager lm
+ = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
Location location = lm.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
if (D) Log.v(TAG, "Current location is " + location);
@@ -204,7 +352,6 @@ public class WeatherUpdateService extends Service {
WeatherLocationListener.registerIfNeeded(mContext, locationProvider);
}
}
-
return location;
}
@@ -214,77 +361,81 @@ public class WeatherUpdateService extends Service {
|| result == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED;
}
- @Override
- protected WeatherInfo doInBackground(Void... params) {
- WeatherProvider provider = Preferences.weatherProvider(mContext);
- boolean metric = Preferences.useMetricUnits(mContext);
- String customLocationId = null, customLocationName = null;
-
- if (Preferences.useCustomWeatherLocation(mContext)) {
- customLocationId = Preferences.customWeatherLocationId(mContext);
- customLocationName = Preferences.customWeatherLocationCity(mContext);
- }
+ private void onWeatherRequestCompleted(WeatherInfo result) {
+ if (D) Log.d(TAG, "Weather update received, caching data and updating widget");
+ cancelTimeoutAlarm();
+ long now = SystemClock.elapsedRealtime();
+ Preferences.setCachedWeatherInfo(mContext, now, result);
+ scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false);
- if (customLocationId != null) {
- return provider.getWeatherInfo(customLocationId, customLocationName, metric);
- }
+ Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
+ mContext.sendBroadcast(updateIntent);
+ broadcastAndCleanUp(false);
+ }
- Location location = getCurrentLocation();
- if (location != null) {
- WeatherInfo info = provider.getWeatherInfo(location, metric);
- if (info != null) {
- return info;
- }
+ private void onWeatherRequestFailed(int status) {
+ if (D) Log.d(TAG, "Weather refresh failed ["+status+"]");
+ cancelTimeoutAlarm();
+ if (status == CMWeatherManager.RequestStatus.ALREADY_IN_PROGRESS) {
+ if (D) Log.d(TAG, "A request is already in progress, no need to schedule again");
+ } else if (status == CMWeatherManager.RequestStatus.FAILED) {
+ //Something went wrong, let's schedule an update at the next interval from now
+ //A force update might happen earlier anyway
+ scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false);
+ } else {
+ //Wait until the next update is due
+ scheduleNextUpdate(mContext, false);
}
+ broadcastAndCleanUp(true);
+ }
- // work with cached location from last request for now
- // a listener to update it is already scheduled if possible
- WeatherInfo cachedInfo = Preferences.getCachedWeatherInfo(mContext);
- if (cachedInfo != null) {
- return provider.getWeatherInfo(cachedInfo.getId(), cachedInfo.getCity(), metric);
+ private void onCancelUpdateWeatherRequest() {
+ if (D) Log.d(TAG, "Cancelling active weather request");
+ if (mIsProcessingWeatherUpdate) {
+ cancelTimeoutAlarm();
+ mWeatherManager.cancelRequest(mRequestId);
+ broadcastAndCleanUp(true);
}
-
- return null;
}
- @Override
- protected void onPostExecute(WeatherInfo result) {
- finish(result);
- }
+ private void broadcastAndCleanUp(boolean updateCancelled) {
+ Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
+ finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, updateCancelled);
+ mContext.sendBroadcast(finishedIntent);
- @Override
- protected void onCancelled() {
- finish(null);
+ if (D) Log.d(TAG, "RELEASING WAKELOCK");
+ mWakeLock.release();
+ mIsProcessingWeatherUpdate = false;
+ mContext.stopService(new Intent(mContext, WeatherUpdateService.class));
}
- private void finish(WeatherInfo result) {
- if (result != null) {
- if (D) Log.d(TAG, "Weather update received, caching data and updating widget");
- long now = System.currentTimeMillis();
- Preferences.setCachedWeatherInfo(mContext, now, result);
- scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false);
-
- Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
- sendBroadcast(updateIntent);
- } else if (isCancelled()) {
- // cancelled, likely due to lost network - we'll get restarted
- // when network comes back
+ @Override
+ public void onWeatherRequestCompleted(int state, WeatherInfo weatherInfo) {
+ if (state == CMWeatherManager.RequestStatus.COMPLETED) {
+ mHandler.obtainMessage(WorkerThread.MSG_ON_WEATHER_REQUEST_COMPLETED, weatherInfo)
+ .sendToTarget();
} else {
- // failure, schedule next download in 30 minutes
- if (D) Log.d(TAG, "Weather refresh failed, scheduling update in 30 minutes");
- long interval = 30 * 60 * 1000;
- scheduleUpdate(mContext, interval, false);
+ mHandler.obtainMessage(WorkerThread.MSG_WEATHER_REQUEST_FAILED, state, 0)
+ .sendToTarget();
}
- WeatherContentProvider.updateCachedWeatherInfo(mContext, result);
+ }
+ }
- Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
- finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, result == null);
- sendBroadcast(finishedIntent);
+ private void sendCancelledBroadcast() {
+ Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED);
+ finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, true);
+ sendBroadcast(finishedIntent);
+ }
- if (D) Log.d(TAG, "RELEASING WAKELOCK");
- mWakeLock.release();
- stopSelf();
- }
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ mWorkerThread.tearDown();
}
private static class WeatherLocationListener implements LocationListener {
@@ -362,7 +513,7 @@ public class WeatherUpdateService extends Service {
// Now, we have a location to use. Schedule a weather update right now.
if (D) Log.d(TAG, "The location has changed, schedule an update ");
synchronized (WeatherLocationListener.class) {
- WeatherUpdateService.scheduleUpdate(mContext, 0, true);
+ scheduleUpdate(mContext, 0, true);
cancelTimeoutAlarm();
sInstance = null;
}
@@ -374,7 +525,7 @@ public class WeatherUpdateService extends Service {
if (D) Log.d(TAG, "The location service has become available, schedule an update ");
if (status == LocationProvider.AVAILABLE) {
synchronized (WeatherLocationListener.class) {
- WeatherUpdateService.scheduleUpdate(mContext, 0, true);
+ scheduleUpdate(mContext, 0, true);
cancelTimeoutAlarm();
sInstance = null;
}
@@ -392,21 +543,26 @@ public class WeatherUpdateService extends Service {
}
}
- private static void scheduleUpdate(Context context, long timeFromNow, boolean force) {
+ private static void scheduleUpdate(Context context, long millisFromNow, boolean force) {
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- long due = System.currentTimeMillis() + timeFromNow;
-
- if (D) Log.d(TAG, "Scheduling next update at " + new Date(due));
- am.set(AlarmManager.RTC_WAKEUP, due, getUpdateIntent(context, force));
+ long due = SystemClock.elapsedRealtime() + millisFromNow;
+ if (D) Log.d(TAG, "Next update scheduled at "
+ + new Date(System.currentTimeMillis() + millisFromNow));
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, due, getUpdateIntent(context, force));
}
public static void scheduleNextUpdate(Context context, boolean force) {
- long lastUpdate = Preferences.lastWeatherUpdateTimestamp(context);
- if (lastUpdate == 0 || force) {
+ if (force) {
+ if (D) Log.d(TAG, "Scheduling next update immediately");
scheduleUpdate(context, 0, true);
} else {
- long interval = Preferences.weatherRefreshIntervalInMs(context);
- scheduleUpdate(context, lastUpdate + interval - System.currentTimeMillis(), false);
+ final long lastUpdate = Preferences.lastWeatherUpdateTimestamp(context);
+ final long interval = Preferences.weatherRefreshIntervalInMs(context);
+ final long now = SystemClock.elapsedRealtime();
+ long due = (interval + lastUpdate) - now;
+ if (due < 0) due = 0;
+ if (D) Log.d(TAG, "Scheduling in " + due + " ms");
+ scheduleUpdate(context, due, false);
}
}
diff --git a/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java b/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java
deleted file mode 100644
index 21bc9e4..0000000
--- a/src/com/cyanogenmod/lockclock/weather/YahooWeatherProvider.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2013 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.cyanogenmod.lockclock.weather;
-
-import android.content.Context;
-import android.location.Location;
-import android.net.Uri;
-import android.text.Html;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.cyanogenmod.lockclock.weather.WeatherInfo.DayForecast;
-import com.cyanogenmod.lockclock.R;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-public class YahooWeatherProvider implements WeatherProvider {
- private static final String TAG = "YahooWeatherProvider";
-
- private static final String URL_WEATHER =
- "https://weather.yahooapis.com/forecastrss?w=%s&u=%s";
- private static final String URL_LOCATION =
- "https://query.yahooapis.com/v1/public/yql?format=json&q=" +
- Uri.encode("select woeid, postal, admin1, admin2, admin3, " +
- "locality1, locality2, country from geo.places where " +
- "(placetype = 7 or placetype = 8 or placetype = 9 " +
- "or placetype = 10 or placetype = 11 or placetype = 20) and text =");
- private static final String URL_PLACEFINDER =
- "https://query.yahooapis.com/v1/public/yql?format=json&q=" +
- Uri.encode("select * from geo.places where " +
- "text =");
-
- private static final String[] LOCALITY_NAMES = new String[] {
- "locality1", "locality2", "admin3", "admin2", "admin1"
- };
-
- private Context mContext;
-
- public YahooWeatherProvider(Context context) {
- mContext = context;
- }
-
- @Override
- public int getNameResourceId() {
- return R.string.weather_source_yahoo;
- }
-
- @Override
- public List<LocationResult> getLocations(String input) {
- String language = getLanguage();
- String params = "\"" + input + "\" and lang = \"" + language + "\"";
- String url = URL_LOCATION + Uri.encode(params);
- JSONObject jsonResults = fetchResults(url);
- if (jsonResults == null) {
- return null;
- }
-
- try {
- JSONArray places = jsonResults.optJSONArray("place");
- if (places == null) {
- // Yahoo returns an object instead of an array when there's only one result
- places = new JSONArray();
- places.put(jsonResults.getJSONObject("place"));
- }
-
- ArrayList<LocationResult> results = new ArrayList<LocationResult>();
- for (int i = 0; i < places.length(); i++) {
- LocationResult result = parsePlace(places.getJSONObject(i));
- if (result != null) {
- results.add(result);
- }
- }
- return results;
- } catch (JSONException e) {
- Log.e(TAG, "Received malformed places data (input=" + input + ", lang=" + language + ")", e);
- }
- return null;
- }
-
- @Override
- public WeatherInfo getWeatherInfo(String id, String localizedCityName, boolean metric) {
- String url = String.format(URL_WEATHER, id, metric ? "c" : "f");
- String response = HttpRetriever.retrieve(url);
-
- if (response == null) {
- return null;
- }
-
- SAXParserFactory factory = SAXParserFactory.newInstance();
- try {
- SAXParser parser = factory.newSAXParser();
- StringReader reader = new StringReader(response);
- WeatherHandler handler = new WeatherHandler();
- parser.parse(new InputSource(reader), handler);
-
- if (handler.isComplete()) {
- // There are cases where the current condition is unknown, but the forecast
- // is not - using the (inaccurate) forecast is probably better than showing
- // the question mark
- if (handler.conditionCode == 3200) {
- handler.condition = handler.forecasts.get(0).condition;
- handler.conditionCode = handler.forecasts.get(0).conditionCode;
- }
-
- WeatherInfo w = new WeatherInfo(mContext, id,
- localizedCityName != null ? localizedCityName : handler.city,
- handler.condition, handler.conditionCode, handler.temperature,
- handler.temperatureUnit, handler.humidity, handler.windSpeed,
- handler.windDirection, handler.speedUnit, handler.forecasts,
- System.currentTimeMillis());
- Log.d(TAG, "Weather updated: " + w);
- return w;
- } else {
- Log.w(TAG, "Received incomplete weather XML (id=" + id + ")");
- }
- } catch (ParserConfigurationException e) {
- Log.e(TAG, "Could not create XML parser", e);
- } catch (SAXException e) {
- Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
- } catch (IOException e) {
- Log.e(TAG, "Could not parse weather XML (id=" + id + ")", e);
- }
-
- return null;
- }
-
- private static class WeatherHandler extends DefaultHandler {
- String city;
- String temperatureUnit, speedUnit;
- int windDirection, conditionCode;
- float humidity, temperature, windSpeed;
- String condition;
- ArrayList<DayForecast> forecasts = new ArrayList<DayForecast>();
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
- if (qName.equals("yweather:location")) {
- city = attributes.getValue("city");
- } else if (qName.equals("yweather:units")) {
- temperatureUnit = attributes.getValue("temperature");
- speedUnit = attributes.getValue("speed");
- } else if (qName.equals("yweather:wind")) {
- windDirection = (int) stringToFloat(attributes.getValue("direction"), -1);
- windSpeed = stringToFloat(attributes.getValue("speed"), -1);
- } else if (qName.equals("yweather:atmosphere")) {
- humidity = stringToFloat(attributes.getValue("humidity"), -1);
- } else if (qName.equals("yweather:condition")) {
- condition = attributes.getValue("text");
- conditionCode = (int) stringToFloat(attributes.getValue("code"), -1);
- temperature = stringToFloat(attributes.getValue("temp"), Float.NaN);
- } else if (qName.equals("yweather:forecast")) {
- DayForecast day = new DayForecast(
- /* low */ stringToFloat(attributes.getValue("low"), Float.NaN),
- /* high */ stringToFloat(attributes.getValue("high"), Float.NaN),
- /* condition */ attributes.getValue("text"),
- /* conditionCode */ (int) stringToFloat(attributes.getValue("code"), -1));
- if (!Float.isNaN(day.low) && !Float.isNaN(day.high) && day.conditionCode >= 0) {
- forecasts.add(day);
- }
- }
- }
- public boolean isComplete() {
- return temperatureUnit != null && speedUnit != null && conditionCode >= 0
- && !Float.isNaN(temperature) && !forecasts.isEmpty();
- }
- private float stringToFloat(String value, float defaultValue) {
- try {
- if (value != null) {
- return Float.parseFloat(value);
- }
- } catch (NumberFormatException e) {
- // fall through to the return line below
- }
- return defaultValue;
- }
- }
-
- @Override
- public WeatherInfo getWeatherInfo(Location location, boolean metric) {
- String language = getLanguage();
- String params = String.format(Locale.US, "\"(%f,%f)\" and lang=\"%s\"",
- location.getLatitude(), location.getLongitude(), language);
- String url = URL_PLACEFINDER + Uri.encode(params);
- JSONObject results = fetchResults(url);
- if (results == null) {
- return null;
- }
- try {
- JSONObject place = results.getJSONObject("place");
- LocationResult result = parsePlace(place);
- String woeid = null;
- String city = null;
- if (result != null) {
- woeid = result.id;
- city = result.city;
- }
- // The city name in the placefinder result is HTML encoded :-(
- if (city != null) {
- city = Html.fromHtml(city).toString();
- } else {
- Log.w(TAG, "Can not resolve place name for " + location);
- }
-
- Log.d(TAG, "Resolved location " + location + " to " + city + " (" + woeid + ")");
-
- WeatherInfo info = getWeatherInfo(woeid, city, metric);
- if (info != null) {
- return info;
- }
- } catch (JSONException e) {
- Log.e(TAG, "Received malformed placefinder data (location="
- + location + ", lang=" + language + ")", e);
- }
-
- return null;
- }
-
- private LocationResult parsePlace(JSONObject place) throws JSONException {
- LocationResult result = new LocationResult();
- JSONObject country = place.getJSONObject("country");
-
- result.id = place.getString("woeid");
- result.country = country.getString("content");
- result.countryId = country.getString("code");
- if (!place.isNull("postal")) {
- result.postal = place.getJSONObject("postal").getString("content");
- }
-
- for (String name : LOCALITY_NAMES) {
- if (!place.isNull(name)) {
- JSONObject localeObject = place.getJSONObject(name);
- result.city = localeObject.getString("content");
- if (localeObject.optString("woeid") != null) {
- result.id = localeObject.getString("woeid");
- }
- break;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "JSON data " + place.toString() + " -> id=" + result.id
- + ", city=" + result.city + ", country=" + result.countryId);
- }
-
- if (result.id == null || result.city == null || result.countryId == null) {
- return null;
- }
-
- return result;
- }
-
- private JSONObject fetchResults(String url) {
- String response = HttpRetriever.retrieve(url);
- if (response == null) {
- return null;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Request URL is " + url + ", response is " + response);
- }
-
- try {
- JSONObject rootObject = new JSONObject(response);
- return rootObject.getJSONObject("query").getJSONObject("results");
- } catch (JSONException e) {
- Log.w(TAG, "Received malformed places data (url=" + url + ")", e);
- }
-
- return null;
- }
-
- private String getLanguage() {
- Locale locale = mContext.getResources().getConfiguration().locale;
- String country = locale.getCountry();
- String language = locale.getLanguage();
-
- if (TextUtils.isEmpty(country)) {
- return language;
- }
- return language + "-" + country;
- }
-}