aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml22
-rw-r--r--src/com/cyanogenmod/lockclock/ClockWidgetProvider.java91
-rw-r--r--src/com/cyanogenmod/lockclock/ClockWidgetService.java662
-rw-r--r--src/com/cyanogenmod/lockclock/misc/CalendarInfo.java134
-rw-r--r--src/com/cyanogenmod/lockclock/misc/Constants.java11
-rw-r--r--src/com/cyanogenmod/lockclock/misc/Preferences.java128
-rw-r--r--src/com/cyanogenmod/lockclock/misc/WidgetUtils.java19
-rw-r--r--src/com/cyanogenmod/lockclock/preference/CalendarPreferences.java35
-rw-r--r--src/com/cyanogenmod/lockclock/preference/ClockPreferences.java22
-rw-r--r--src/com/cyanogenmod/lockclock/preference/Preferences.java34
-rw-r--r--src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java112
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherInfo.java261
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java276
-rw-r--r--src/com/cyanogenmod/lockclock/weather/WeatherXmlParser.java113
-rw-r--r--src/com/cyanogenmod/lockclock/weather/YahooPlaceFinder.java35
15 files changed, 1321 insertions, 634 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9d7d2ad..8f49700 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -25,8 +25,11 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application
@@ -49,13 +52,26 @@
<!-- The Widget receiver -->
<receiver android:name="com.cyanogenmod.lockclock.ClockWidgetProvider" >
+ <meta-data android:name="android.appwidget.provider" android:resource="@xml/lock_clock" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider" android:resource="@xml/lock_clock" />
+ <action android:name="android.intent.action.TIMEZONE_CHANGED"/>
+ <action android:name="android.intent.action.DATE_CHANGED"/>
+ <action android:name="android.intent.action.TIME_SET"/>
+ <action android:name="android.intent.action.LOCALE_CHANGED"/>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
+ <action android:name="com.android.deskclock.NEXT_ALARM_TIME_SET"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.PROVIDER_CHANGED"/>
+ <data android:scheme="content"/>
+ <data android:host="com.android.calendar"/>
+ </intent-filter>
</receiver>
- <service android:name="com.cyanogenmod.lockclock.ClockWidgetService"></service>
+ <service android:name=".ClockWidgetService"></service>
+ <service android:name=".weather.WeatherUpdateService"></service>
</application>
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
index 1456ac3..d69b7ff 100644
--- a/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetProvider.java
@@ -22,37 +22,96 @@ import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.util.Log;
import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
public class ClockWidgetProvider extends AppWidgetProvider {
private static final String TAG = "ClockWidgetProvider";
+ private static boolean D = Constants.DEBUG;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- updateWidgets(context, null);
+ // Default handling, triggered via the super class
+ if (D) Log.v(TAG, "Updating widgets, default handling.");
+ updateWidgets(context, false);
}
@Override
public void onReceive(Context context, Intent intent) {
- super.onReceive(context, intent);
- updateWidgets(context, intent);
+
+ // Deal with received broadcasts that force a refresh
+ String action = intent.getAction();
+ if (D) Log.v(TAG, "Received intent " + intent);
+
+ // Network connection has changed, make sure the weather update service knows about it
+ if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
+ boolean hasConnection =
+ !intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+
+ if (D) Log.d(TAG, "Got connectivity change, has connection: " + hasConnection);
+
+ Intent i = new Intent(context, WeatherUpdateService.class);
+ if (hasConnection) {
+ context.startService(i);
+ } else {
+ context.stopService(i);
+ }
+
+ // Boot completed, schedule next weather update
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ WeatherUpdateService.scheduleNextUpdate(context);
+
+ // 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)) {
+ super.onReceive(context, intent);
+
+ // Calendar, Time or a settings change, force a calendar refresh
+ } else if (Intent.ACTION_PROVIDER_CHANGED.equals(action)
+ || Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+ || Intent.ACTION_DATE_CHANGED.equals(action)
+ || Intent.ACTION_LOCALE_CHANGED.equals(action)
+ || ClockWidgetService.ACTION_REFRESH_CALENDAR.equals(action)) {
+ updateWidgets(context, true);
+
+ // Something we did not handle, let the super class deal with it.
+ // This includes the REFRESH_CLOCK intent from Clock settings
+ } else {
+ if (D) Log.v(TAG, "We did not handle the intent, trigger normal handling");
+ super.onReceive(context, intent);
+ updateWidgets(context, false);
+ }
}
- private void updateWidgets(Context context, Intent intent) {
- // Update the widget via the service. Build the intent to call the service on a timer
+ /**
+ * Update the widget via the service.
+ */
+ private void updateWidgets(Context context, boolean refreshCalendar) {
+ // Build the intent and pass on the weather and calendar refresh triggers
Intent i = new Intent(context.getApplicationContext(), ClockWidgetService.class);
- PendingIntent pi = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ i.setAction(refreshCalendar
+ ? ClockWidgetService.ACTION_REFRESH_CALENDAR
+ : ClockWidgetService.ACTION_REFRESH);
- // See if we are forcing a full refresh and trigger a single update
- if (intent != null && intent.getBooleanExtra(Constants.FORCE_REFRESH, false)) {
- i.putExtra(Constants.FORCE_REFRESH, true);
- context.startService(i);
- }
+ // Start the service. The service itself will take care of scheduling refreshes if needed
+ if (D) Log.d(TAG, "Starting the service to update the widgets...");
+ context.startService(i);
+ }
- // Clear any old alarms and schedule the new alarm that only triggers if the device is ON (RTC)
- AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- am.cancel(pi);
- am.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), 60000, pi);
+ @Override
+ public void onEnabled(Context context) {
+ if (D) Log.d(TAG, "Scheduling next weather update");
+ WeatherUpdateService.scheduleNextUpdate(context);
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ if (D) Log.d(TAG, "Cleaning up: Clearing all pending alarms");
+ ClockWidgetService.cancelUpdates(context);
+ WeatherUpdateService.cancelUpdates(context);
}
-} \ No newline at end of file
+}
diff --git a/src/com/cyanogenmod/lockclock/ClockWidgetService.java b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
index 95a5338..2367f57 100644
--- a/src/com/cyanogenmod/lockclock/ClockWidgetService.java
+++ b/src/com/cyanogenmod/lockclock/ClockWidgetService.java
@@ -16,166 +16,147 @@
package com.cyanogenmod.lockclock;
+import android.app.AlarmManager;
+import android.app.IntentService;
import android.app.PendingIntent;
-import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
-import android.location.Criteria;
-import android.location.Location;
-import android.location.LocationManager;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.IBinder;
import android.provider.CalendarContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
-import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.RemoteViews;
+import com.cyanogenmod.lockclock.misc.CalendarInfo;
+import com.cyanogenmod.lockclock.misc.CalendarInfo.EventInfo;
import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.misc.Preferences;
import com.cyanogenmod.lockclock.misc.WidgetUtils;
-
-import static com.cyanogenmod.lockclock.misc.Constants.PREF_NAME;
-import static com.cyanogenmod.lockclock.misc.Constants.MAX_CALENDAR_ITEMS;
-import com.cyanogenmod.lockclock.weather.HttpRetriever;
import com.cyanogenmod.lockclock.weather.WeatherInfo;
-import com.cyanogenmod.lockclock.weather.WeatherXmlParser;
-import com.cyanogenmod.lockclock.weather.YahooPlaceFinder;
+import com.cyanogenmod.lockclock.weather.WeatherUpdateService;
import org.w3c.dom.Document;
-import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import java.util.TimeZone;
-public class ClockWidgetService extends Service {
+public class ClockWidgetService extends IntentService {
private static final String TAG = "ClockWidgetService";
- private static final boolean DEBUG = false;
+ private static final boolean D = Constants.DEBUG;
+
+ public static final String ACTION_REFRESH = "com.cyanogenmod.lockclock.action.REFRESH_WIDGET";
+ public static final String ACTION_REFRESH_CALENDAR = "com.cyanogenmod.lockclock.action.REFRESH_CALENDAR";
- private Context mContext;
private int[] mWidgetIds;
private AppWidgetManager mAppWidgetManager;
- private SharedPreferences mSharedPrefs;
- private boolean mForceRefresh;
+ private boolean mRefreshCalendar;
+
+ public ClockWidgetService() {
+ super("ClockWidgetService");
+ }
@Override
public void onCreate() {
- mContext = getApplicationContext();
- mAppWidgetManager = AppWidgetManager.getInstance(mContext);
- ComponentName thisWidget = new ComponentName(mContext, ClockWidgetProvider.class);
+ super.onCreate();
+
+ ComponentName thisWidget = new ComponentName(this, ClockWidgetProvider.class);
+ mAppWidgetManager = AppWidgetManager.getInstance(this);
mWidgetIds = mAppWidgetManager.getAppWidgetIds(thisWidget);
- mSharedPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- mForceRefresh = false;
+
+ mRefreshCalendar = false;
}
@Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // See if we are forcing a full weather refresh
- if (intent != null && intent.getBooleanExtra(Constants.FORCE_REFRESH, false)) {
- if (DEBUG) Log.d(TAG, "Forcing a weather refresh");
- mForceRefresh = true;
+ protected void onHandleIntent(Intent intent) {
+ if (D) Log.d(TAG, "Got intent " + intent);
+ if (intent != null && ACTION_REFRESH_CALENDAR.equals(intent.getAction())) {
+ if (D) Log.v(TAG, "Forcing a calendar refresh");
+ mRefreshCalendar = true;
}
- // Refresh the widgets
if (mWidgetIds != null && mWidgetIds.length != 0) {
refreshWidget();
- } else {
- stopSelf();
}
- return START_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
}
/**
* Reload the widget including the Weather forecast, Alarm, Clock font and Calendar
*/
private void refreshWidget() {
- // If we need to show the weather, do so
- boolean showWeather = mSharedPrefs.getBoolean(Constants.SHOW_WEATHER, false);
+ // Get things ready
+ RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.appwidget);
+ boolean digitalClock = Preferences.showDigitalClock(this);
+ boolean showWeather = Preferences.showWeather(this);
+ boolean showCalendar = Preferences.showCalendar(this);
+
+ // Always Refresh the Clock widget
+ refreshClock(remoteViews, digitalClock);
+ refreshAlarmStatus(remoteViews);
- if (showWeather) {
- // Load the required settings from preferences
- final long interval = Long.parseLong(mSharedPrefs.getString(Constants.WEATHER_REFRESH_INTERVAL, "60"));
- boolean manualSync = (interval == 0);
- if (mForceRefresh || (!manualSync && (((System.currentTimeMillis() - mWeatherInfo.last_sync) / 60000) >= interval))) {
- if (mWeatherQueryTask == null || mWeatherQueryTask.getStatus() == AsyncTask.Status.FINISHED) {
- mWeatherQueryTask = new WeatherQueryTask();
- mWeatherQueryTask.execute();
- mForceRefresh = false;
- }
- } else if (manualSync && mWeatherInfo.last_sync == 0) {
- setNoWeatherData();
- } else {
- setWeatherData(mWeatherInfo);
- }
- } else {
- updateAndExit();
+ // Don't bother with Calendar if its not enabled
+ if (showCalendar) {
+ showCalendar &= refreshCalendar(remoteViews);
}
- }
-
- private void updateAndExit() {
- RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.appwidget);
- updateAndExit(remoteViews);
- }
-
- /**
- * Refresh Alarm and Calendar (if visible) and update the widget views
- */
- private void updateAndExit(RemoteViews remoteViews) {
- // Refresh the remaining widget panels.
- //NOTE: Weather is updated prior to this method being called
- refreshClock(remoteViews);
- refreshAlarmStatus(remoteViews);
- boolean hasCalEvents = refreshCalendar(remoteViews);
// Hide the Loading indicator
remoteViews.setViewVisibility(R.id.loading_indicator, View.GONE);
+ // Now, if we need to show the actual weather, do so
+ if (showWeather) {
+ WeatherInfo weatherInfo = Preferences.getCachedWeatherInfo(this);
+
+ if (weatherInfo != null) {
+ setWeatherData(remoteViews, weatherInfo);
+ } else {
+ setNoWeatherData(remoteViews);
+ }
+ }
+
// Update the widgets
- boolean showWeather = mSharedPrefs.getBoolean(Constants.SHOW_WEATHER, false);
- boolean showCalendar = hasCalEvents && mSharedPrefs.getBoolean(Constants.SHOW_CALENDAR, false);
- boolean digitalClock = mSharedPrefs.getBoolean(Constants.CLOCK_DIGITAL, true);
for (int id : mWidgetIds) {
// Resize the clock font if needed
if (digitalClock) {
- float ratio = WidgetUtils.getScaleRatio(mContext, id);
+ float ratio = WidgetUtils.getScaleRatio(this, id);
setClockSize(remoteViews, ratio);
}
- // Hide the panels if there is no space for them
- boolean canFitWeather = WidgetUtils.canFitWeather(mContext, id, digitalClock);
- boolean canFitCalendar = WidgetUtils.canFitCalendar(mContext, id, digitalClock);
- remoteViews.setViewVisibility(R.id.weather_panel, canFitWeather && showWeather ? View.VISIBLE : View.GONE);
- remoteViews.setViewVisibility(R.id.calendar_panel, canFitCalendar && showCalendar ? View.VISIBLE : View.GONE);
+ if (showWeather) {
+ boolean canFitWeather = WidgetUtils.canFitWeather(this, id, digitalClock);
+ remoteViews.setViewVisibility(R.id.weather_panel, canFitWeather ? View.VISIBLE : View.GONE);
+ }
+
+ // Hide the calendar panel if there is no space for it
+ if (showCalendar) {
+ boolean canFitCalendar = WidgetUtils.canFitCalendar(this, id, digitalClock);
+ remoteViews.setViewVisibility(R.id.calendar_panel, canFitCalendar ? View.VISIBLE : View.GONE);
+ }
// Do the update
mAppWidgetManager.updateAppWidget(id, remoteViews);
}
- stopSelf();
+
+ if (showCalendar) {
+ scheduleCalendarUpdate();
+ }
}
//===============================================================================================
// Clock related functionality
//===============================================================================================
- private void refreshClock(RemoteViews clockViews) {
+ private void refreshClock(RemoteViews clockViews, boolean digitalClock) {
// Analog or Digital clock
- if (mSharedPrefs.getBoolean(Constants.CLOCK_DIGITAL, true)) {
+ if (digitalClock) {
// Hours/Minutes is specific to Didital, set it's size
refreshClockFont(clockViews);
clockViews.setViewVisibility(R.id.digital_clock, View.VISIBLE);
@@ -191,13 +172,13 @@ public class ClockWidgetService extends Service {
// Register an onClickListener on Clock, starting DeskClock
ComponentName clk = new ComponentName("com.android.deskclock", "com.android.deskclock.DeskClock");
Intent i = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER).setComponent(clk);
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
clockViews.setOnClickPendingIntent(R.id.clock_panel, pi);
}
private void refreshClockFont(RemoteViews clockViews) {
// Hours
- if (mSharedPrefs.getBoolean(Constants.CLOCK_FONT, true)) {
+ if (Preferences.useBoldFontForHours(this)) {
clockViews.setViewVisibility(R.id.clock1_bold, View.VISIBLE);
clockViews.setViewVisibility(R.id.clock1_regular, View.GONE);
} else {
@@ -206,7 +187,7 @@ public class ClockWidgetService extends Service {
}
// Minutes
- if (mSharedPrefs.getBoolean(Constants.CLOCK_FONT_MINUTES, false)) {
+ if (Preferences.useBoldFontForMinutes(this)) {
clockViews.setViewVisibility(R.id.clock2_bold, View.VISIBLE);
clockViews.setViewVisibility(R.id.clock2_regular, View.GONE);
} else {
@@ -217,7 +198,7 @@ public class ClockWidgetService extends Service {
private void refreshDateAlarmFont(RemoteViews clockViews) {
// Date and Alarm font
- if (mSharedPrefs.getBoolean(Constants.CLOCK_FONT_DATE, true)) {
+ if (Preferences.useBoldFontForDateAndAlarms(this)) {
clockViews.setViewVisibility(R.id.date_bold, View.VISIBLE);
clockViews.setViewVisibility(R.id.date_regular, View.GONE);
} else {
@@ -230,7 +211,7 @@ public class ClockWidgetService extends Service {
}
private void setClockSize(RemoteViews clockViews, float scale) {
- float fontSize = mContext.getResources().getDimension(R.dimen.widget_big_font_size);
+ float fontSize = getResources().getDimension(R.dimen.widget_big_font_size);
clockViews.setTextViewTextSize(R.id.clock1_bold, TypedValue.COMPLEX_UNIT_PX, fontSize * scale);
clockViews.setTextViewTextSize(R.id.clock1_regular, TypedValue.COMPLEX_UNIT_PX, fontSize * scale);
clockViews.setTextViewTextSize(R.id.clock2_bold, TypedValue.COMPLEX_UNIT_PX, fontSize * scale);
@@ -241,14 +222,11 @@ public class ClockWidgetService extends Service {
// Alarm related functionality
//===============================================================================================
private void refreshAlarmStatus(RemoteViews alarmViews) {
- boolean showAlarm = mSharedPrefs.getBoolean(Constants.CLOCK_SHOW_ALARM, true);
- boolean isBold = mSharedPrefs.getBoolean(Constants.CLOCK_FONT_DATE, true);
-
- // Update Alarm status
- if (showAlarm) {
+ if (Preferences.showAlarm(this)) {
String nextAlarm = getNextAlarm();
if (!TextUtils.isEmpty(nextAlarm)) {
// An alarm is set, deal with displaying it
+ boolean isBold = Preferences.useBoldFontForDateAndAlarms(this);
alarmViews.setTextViewText(isBold ? R.id.nextAlarm_bold : R.id.nextAlarm_regular,
nextAlarm.toString().toUpperCase());
alarmViews.setViewVisibility(R.id.nextAlarm_bold, isBold ? View.VISIBLE : View.GONE);
@@ -266,8 +244,8 @@ public class ClockWidgetService extends Service {
* @return A formatted string of the next alarm or null if there is no next alarm.
*/
private String getNextAlarm() {
- String nextAlarm = Settings.System.getString(mContext.getContentResolver(),
- Settings.System.NEXT_ALARM_FORMATTED);
+ String nextAlarm = Settings.System.getString(
+ getContentResolver(), Settings.System.NEXT_ALARM_FORMATTED);
if (nextAlarm == null || TextUtils.isEmpty(nextAlarm)) {
return null;
}
@@ -277,149 +255,57 @@ public class ClockWidgetService extends Service {
//===============================================================================================
// Weather related functionality
//===============================================================================================
- private static final String URL_YAHOO_API_WEATHER = "http://weather.yahooapis.com/forecastrss?w=%s&u=";
- private static WeatherInfo mWeatherInfo = new WeatherInfo();
- private WeatherQueryTask mWeatherQueryTask;
-
- private class WeatherQueryTask extends AsyncTask<Void, Void, WeatherInfo> {
- @Override
- protected WeatherInfo doInBackground(Void... params) {
- // Load the preferences
- boolean useCustomLoc = mSharedPrefs.getBoolean(Constants.WEATHER_USE_CUSTOM_LOCATION, false);
- String customLoc = mSharedPrefs.getString(Constants.WEATHER_CUSTOM_LOCATION_STRING, null);
-
- // Get location related stuff ready
- LocationManager locationManager =
- (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
- String woeid = null;
-
- if (customLoc != null && useCustomLoc) {
- // custom location
- try {
- woeid = YahooPlaceFinder.GeoCode(mContext, customLoc);
- if (DEBUG)
- Log.d(TAG, "Yahoo location code for " + customLoc + " is " + woeid);
- } catch (Exception e) {
- Log.e(TAG, "ERROR: Could not get Location code", e);
- }
- } else {
- // network location
- Location loc = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
-
- if (loc != null) {
- try {
- woeid = YahooPlaceFinder.reverseGeoCode(mContext,
- loc.getLatitude(), loc.getLongitude());
- if (DEBUG)
- Log.d(TAG, "Yahoo location code for current geolocation is " + woeid);
- } catch (Exception e) {
- Log.e(TAG, "ERROR: Could not get Location code", e);
- }
- } else {
- Log.e(TAG, "ERROR: Location returned null");
- }
- if (DEBUG) {
- Log.d(TAG, "Location code is " + woeid);
- }
- }
-
- if (woeid != null) {
- try {
- return parseXml(getDocument(woeid));
- } catch (Exception e) {
- Log.e(TAG, "ERROR: Could not parse weather return info", e);
- }
- }
-
- return null;
- }
-
- @Override
- protected void onPostExecute(WeatherInfo info) {
- if (info != null) {
- setWeatherData(info);
- mWeatherInfo = info;
- } else if (mWeatherInfo.temp.equals(WeatherInfo.NODATA)) {
- setNoWeatherData();
- } else {
- setWeatherData(mWeatherInfo);
- }
- }
- }
-
/**
* Display the weather information
- * @param w
*/
- private void setWeatherData(WeatherInfo w) {
+ private void setWeatherData(RemoteViews weatherViews, WeatherInfo w) {
// Load the preferences
- boolean showLocation = mSharedPrefs.getBoolean(Constants.WEATHER_SHOW_LOCATION, true);
- boolean showTimestamp = mSharedPrefs.getBoolean(Constants.WEATHER_SHOW_TIMESTAMP, true);
- boolean invertLowhigh = mSharedPrefs.getBoolean(Constants.WEATHER_INVERT_LOWHIGH, false);
- boolean defaultIcons = !mSharedPrefs.getBoolean(Constants.WEATHER_USE_ALTERNATE_ICONS, false);
+ boolean showLocation = Preferences.showWeatherLocation(this);
+ boolean showTimestamp = Preferences.showWeatherTimestamp(this);
+ boolean invertLowhigh = Preferences.invertLowHighTemperature(this);
- // Get the views ready
- RemoteViews weatherViews = new RemoteViews(mContext.getPackageName(), R.layout.appwidget);
-
- // Weather Image - Either the default or alternate set
- String prefix = defaultIcons ? "weather_" : "weather2_";
- String conditionCode = w.condition_code;
- String condition_filename = prefix + conditionCode;
-
- // Get the resource id based on the constructed name
- final Resources res = getBaseContext().getResources();
- int resID = res.getIdentifier(condition_filename, "drawable",
- getBaseContext().getPackageName());
-
- if (DEBUG)
- Log.d("Weather", "Condition:" + conditionCode + " ID:" + resID);
-
- if (resID != 0) {
- weatherViews.setImageViewResource(R.id.weather_image, resID);
- } else {
- weatherViews.setImageViewResource(R.id.weather_image,
- defaultIcons ? R.drawable.weather_na : R.drawable.weather2_na);
- }
+ // Weather Image
+ weatherViews.setImageViewResource(R.id.weather_image, w.getConditionResource());
// City
- weatherViews.setTextViewText(R.id.weather_city, w.city);
+ weatherViews.setTextViewText(R.id.weather_city, w.getCity());
weatherViews.setViewVisibility(R.id.weather_city, showLocation ? View.VISIBLE : View.GONE);
// Weather Condition
- weatherViews.setTextViewText(R.id.weather_condition, w.condition);
+ weatherViews.setTextViewText(R.id.weather_condition, w.getCondition());
weatherViews.setViewVisibility(R.id.weather_condition, View.VISIBLE);
// Weather Update Time
- long now = System.currentTimeMillis();
- if (now - w.last_sync < 60000) {
- weatherViews.setTextViewText(R.id.update_time, res.getString(R.string.weather_last_sync_just_now));
+ if (showTimestamp) {
+ Date updateTime = w.getTimestamp();
+ StringBuilder sb = new StringBuilder();
+ sb.append(DateFormat.format("E", updateTime));
+ sb.append(" ");
+ sb.append(DateFormat.getTimeFormat(this).format(updateTime));
+ weatherViews.setTextViewText(R.id.update_time, sb.toString());
+ weatherViews.setViewVisibility(R.id.update_time, View.VISIBLE);
} else {
- weatherViews.setTextViewText(R.id.update_time, DateUtils.getRelativeTimeSpanString(
- w.last_sync, now, DateUtils.MINUTE_IN_MILLIS));
+ weatherViews.setViewVisibility(R.id.update_time, View.GONE);
}
- weatherViews.setViewVisibility(R.id.update_time, showTimestamp ? View.VISIBLE : View.GONE);
// Weather Temps Panel
- weatherViews.setTextViewText(R.id.weather_temp, w.temp);
- weatherViews.setTextViewText(R.id.weather_low_high, invertLowhigh ? w.high + " | " + w.low : w.low + " | " + w.high);
+ final String low = w.getFormattedLow();
+ final String high = w.getFormattedHigh();
+
+ weatherViews.setTextViewText(R.id.weather_temp, w.getFormattedTemperature());
+ weatherViews.setTextViewText(R.id.weather_low_high, invertLowhigh ? high + " | " + low : low + " | " + high);
weatherViews.setViewVisibility(R.id.weather_temps_panel, View.VISIBLE);
// Register an onClickListener on Weather
setWeatherClickListener(weatherViews);
-
- // Update the rest of the widget and stop
- updateAndExit(weatherViews);
}
/**
- * There is no data to display, display 'empty' fields and the
- * 'Tap to reload' message
+ * There is no data to display, display 'empty' fields and the 'Tap to reload' message
*/
- private void setNoWeatherData() {
- boolean defaultIcons = !mSharedPrefs.getBoolean(Constants.WEATHER_USE_ALTERNATE_ICONS, false);
-
+ private void setNoWeatherData(RemoteViews weatherViews) {
+ boolean defaultIcons = !Preferences.useAlternateWeatherIcons(this);
final Resources res = getBaseContext().getResources();
- RemoteViews weatherViews = new RemoteViews(mContext.getPackageName(), R.layout.appwidget);
// Weather Image - Either the default or alternate set
weatherViews.setImageViewResource(R.id.weather_image,
@@ -428,120 +314,77 @@ public class ClockWidgetService extends Service {
// Rest of the data
weatherViews.setTextViewText(R.id.weather_city, res.getString(R.string.weather_no_data));
weatherViews.setViewVisibility(R.id.weather_city, View.VISIBLE);
- weatherViews.setTextViewText(R.id.weather_condition, res.getString(R.string.weather_tap_to_refresh));
weatherViews.setViewVisibility(R.id.update_time, View.GONE);
weatherViews.setViewVisibility(R.id.weather_temps_panel, View.GONE);
+ weatherViews.setTextViewText(R.id.weather_condition, res.getString(R.string.weather_tap_to_refresh));
// Register an onClickListener on Weather
setWeatherClickListener(weatherViews);
-
- // Update the rest of the widget and stop
- updateAndExit(weatherViews);
}
private void setWeatherClickListener(RemoteViews weatherViews) {
- Intent weatherClickIntent = new Intent(mContext, ClockWidgetProvider.class);
- weatherClickIntent.putExtra(Constants.FORCE_REFRESH, true);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, weatherClickIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- weatherViews.setOnClickPendingIntent(R.id.weather_panel, pendingIntent);
- }
-
- /**
- * Get the weather forecast XML document for a specific location
- * @param woeid
- * @return
- */
- private Document getDocument(String woeid) {
- try {
- boolean celcius = mSharedPrefs.getBoolean(Constants.WEATHER_USE_METRIC, true);
- String urlWithDegreeUnit;
- if (celcius) {
- urlWithDegreeUnit = URL_YAHOO_API_WEATHER + "c";
- } else {
- urlWithDegreeUnit = URL_YAHOO_API_WEATHER + "f";
- }
- return new HttpRetriever().getDocumentFromURL(String.format(urlWithDegreeUnit, woeid));
- } catch (IOException e) {
- Log.e(TAG, "Error querying Yahoo weather");
- }
- return null;
- }
-
- /**
- * Parse the weather XML document
- * @param wDoc
- * @return
- */
- private WeatherInfo parseXml(Document wDoc) {
- try {
- return new WeatherXmlParser(getBaseContext()).parseWeatherResponse(wDoc);
- } catch (Exception e) {
- Log.e(TAG, "Error parsing Yahoo weather XML document", e);
- }
- return null;
+ weatherViews.setOnClickPendingIntent(R.id.weather_panel,
+ WeatherUpdateService.getUpdateIntent(this, true));
}
//===============================================================================================
// Calendar related functionality
//===============================================================================================
+ private static CalendarInfo mCalendarInfo = new CalendarInfo();
+
private boolean refreshCalendar(RemoteViews calendarViews) {
// Load the settings
- boolean showCalendar = mSharedPrefs.getBoolean(Constants.SHOW_CALENDAR, false);
- Set<String> calendarList = mSharedPrefs.getStringSet(Constants.CALENDAR_LIST, null);
- boolean remindersOnly = mSharedPrefs.getBoolean(Constants.CALENDAR_REMINDERS_ONLY, false);
- boolean hideAllDay = mSharedPrefs.getBoolean(Constants.CALENDAR_HIDE_ALLDAY, false);
- long lookAhead = Long.parseLong(mSharedPrefs.getString(Constants.CALENDAR_LOOKAHEAD, "10800000"));
+ Set<String> calendarList = Preferences.calendarsToDisplay(this);
+ boolean remindersOnly = Preferences.showEventsWithRemindersOnly(this);
+ boolean hideAllDay = !Preferences.showAllDayEvents(this);
+ long lookAhead = Preferences.lookAheadTimeInMs(this);
boolean hasEvents = false;
- if (showCalendar) {
- String[][] nextCalendar = null;
- nextCalendar = getNextCalendarAlarm(lookAhead, calendarList, remindersOnly, hideAllDay);
+ // Remove all the views to start
+ calendarViews.removeAllViews(R.id.calendar_panel);
- // Remove all the views to start
- calendarViews.removeAllViews(R.id.calendar_panel);
+ // If we don't have any events yet or forcing a refresh, get the next batch of events
+ if (!mCalendarInfo.hasEvents() || mRefreshCalendar) {
+ if (D) Log.d(TAG, "Checking for calendar events...");
+ getCalendarEvents(lookAhead, calendarList, remindersOnly, hideAllDay);
+ mRefreshCalendar = false;
+ }
- // Iterate through the calendars, up to the maximum
- for (int i = 0; i < MAX_CALENDAR_ITEMS; i++) {
- if (nextCalendar[i][0] != null) {
- final RemoteViews itemViews = new RemoteViews(mContext.getPackageName(),
- R.layout.calendar_item);
+ // Iterate through the events and add them to the views
+ for (EventInfo event : mCalendarInfo.getEvents()) {
+ final RemoteViews itemViews = new RemoteViews(getPackageName(), R.layout.calendar_item);
- // Only set the icon on the first event
- if (!hasEvents) {
- itemViews.setImageViewResource(R.id.calendar_icon, R.drawable.ic_lock_idle_calendar);
- }
+ // Only set the icon on the first event
+ if (!hasEvents) {
+ itemViews.setImageViewResource(R.id.calendar_icon, R.drawable.ic_lock_idle_calendar);
+ }
- // Add the event text fields
- itemViews.setTextViewText(R.id.calendar_event_title, nextCalendar[i][0]);
- if (nextCalendar[i][1] != null) {
- itemViews.setTextViewText(R.id.calendar_event_details, nextCalendar[i][1]);
- }
+ // Add the event text fields
+ itemViews.setTextViewText(R.id.calendar_event_title, event.title);
+ itemViews.setTextViewText(R.id.calendar_event_details, event.description);
- // Add the view to the panel
- calendarViews.addView(R.id.calendar_panel, itemViews);
- hasEvents = true;
- }
- }
+ if (D) Log.v(TAG, "Showing event: " + event.title);
+
+ // Add the view to the panel
+ calendarViews.addView(R.id.calendar_panel, itemViews);
+ hasEvents = true;
}
// Register an onClickListener on Calendar if it contains any events, starting the Calendar app
if (hasEvents) {
ComponentName cal = new ComponentName("com.android.calendar", "com.android.calendar.AllInOneActivity");
Intent i = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER).setComponent(cal);
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
calendarViews.setOnClickPendingIntent(R.id.calendar_panel, pi);
}
-
return hasEvents;
}
/**
- * @return A formatted string of the next calendar event with a reminder
- * (for showing on the lock screen), or null if there is no next event
- * within a certain look-ahead time.
+ * Get the next set of calendar events (up to MAX_CALENDAR_ITEMS) within a certain look-ahead time.
+ * Result is stored in the CalendarInfo object
*/
- private String[][] getNextCalendarAlarm(long lookahead, Set<String> calendars,
+ private void getCalendarEvents(long lookahead, Set<String> calendars,
boolean remindersOnly, boolean hideAllDay) {
long now = System.currentTimeMillis();
long later = now + lookahead;
@@ -551,10 +394,16 @@ public class ClockWidgetService extends Service {
if (remindersOnly) {
where.append(CalendarContract.Events.HAS_ALARM + "=1");
}
- if (calendars != null && calendars.size() > 0) {
+ if (hideAllDay) {
if (remindersOnly) {
where.append(" AND ");
}
+ where.append(CalendarContract.Events.ALL_DAY + "!=1");
+ }
+ if (calendars != null && calendars.size() > 0) {
+ if (remindersOnly || hideAllDay) {
+ where.append(" AND ");
+ }
where.append(CalendarContract.Events.CALENDAR_ID + " in (");
int i = 0;
for (String s : calendars) {
@@ -569,50 +418,45 @@ public class ClockWidgetService extends Service {
// Projection array
String[] projection = new String[] {
+ CalendarContract.Events._ID,
CalendarContract.Events.TITLE,
CalendarContract.Instances.BEGIN,
+ CalendarContract.Instances.END,
CalendarContract.Events.DESCRIPTION,
CalendarContract.Events.EVENT_LOCATION,
CalendarContract.Events.ALL_DAY,
- CalendarContract.Events.CALENDAR_ID
};
// The indices for the projection array
- int TITLE_INDEX = 0;
- int BEGIN_TIME_INDEX = 1;
- int DESCRIPTION_INDEX = 2;
- int LOCATION_INDEX = 3;
- int ALL_DAY_INDEX = 4;
- int CALENDAR_ID_INDEX = 5;
+ int EVENT_ID_INDEX = 0;
+ int TITLE_INDEX = 1;
+ int BEGIN_TIME_INDEX = 2;
+ int END_TIME_INDEX = 3;
+ int DESCRIPTION_INDEX = 4;
+ int LOCATION_INDEX = 5;
+ int ALL_DAY_INDEX = 6;
Uri uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI,
String.format("%d/%d", now, later));
- String[][] nextCalendarAlarm = new String[MAX_CALENDAR_ITEMS][2];
Cursor cursor = null;
+ mCalendarInfo.clearEvents();
try {
- cursor = mContext.getContentResolver().query(uri, projection,
- where.toString(), null, "begin ASC");
+ cursor = getContentResolver().query(uri, projection,
+ where.toString(), null, CalendarContract.Instances.BEGIN + " ASC");
if (cursor != null) {
cursor.moveToFirst();
// Iterate through returned rows to a maximum number of calendar events
- for (int i = 0, eventCount = 0; i < cursor.getCount() && eventCount < MAX_CALENDAR_ITEMS; i++) {
+ for (int i = 0, eventCount = 0; i < cursor.getCount() && eventCount < Constants.MAX_CALENDAR_ITEMS; i++) {
+ long eventId = cursor.getLong(EVENT_ID_INDEX);
String title = cursor.getString(TITLE_INDEX);
long begin = cursor.getLong(BEGIN_TIME_INDEX);
+ long end = cursor.getLong(END_TIME_INDEX);
String description = cursor.getString(DESCRIPTION_INDEX);
String location = cursor.getString(LOCATION_INDEX);
boolean allDay = cursor.getInt(ALL_DAY_INDEX) != 0;
- int calendarId = cursor.getInt(CALENDAR_ID_INDEX);
- if (DEBUG) {
- Log.d(TAG, "Event: " + title + " from calendar with id: " + calendarId);
- }
-
- // If skipping all day events, continue the loop without incementing eventCount
- if (allDay && hideAllDay) {
- cursor.moveToNext();
- continue;
- }
+ if (D) Log.v(TAG, "Adding event: " + title + " with id: " + eventId);
// Check the next event in the case of all day event. As UTC is used for all day
// events, the next event may be the one that actually starts sooner
@@ -620,8 +464,10 @@ public class ClockWidgetService extends Service {
cursor.moveToNext();
long nextBegin = cursor.getLong(BEGIN_TIME_INDEX);
if (nextBegin < begin + TimeZone.getDefault().getOffset(begin)) {
+ eventId = cursor.getLong(EVENT_ID_INDEX);
title = cursor.getString(TITLE_INDEX);
begin = nextBegin;
+ end = cursor.getLong(END_TIME_INDEX);
description = cursor.getString(DESCRIPTION_INDEX);
location = cursor.getString(LOCATION_INDEX);
allDay = cursor.getInt(ALL_DAY_INDEX) != 0;
@@ -630,81 +476,75 @@ public class ClockWidgetService extends Service {
cursor.moveToPrevious();
}
- // Set the event title as the first array item
- nextCalendarAlarm[eventCount][0] = title.toString();
-
// Start building the event details string
// Starting with the date
- Date start = new Date(begin);
+ Date startDate = new Date(begin);
+ Date endDate = new Date(end);
StringBuilder sb = new StringBuilder();
if (allDay) {
SimpleDateFormat sdf = new SimpleDateFormat(
- mContext.getString(R.string.abbrev_wday_month_day_no_year));
+ getString(R.string.abbrev_wday_month_day_no_year));
// Calendar stores all-day events in UTC -- setting the time zone ensures
// the correct date is shown.
sdf.setTimeZone(TimeZone.getTimeZone(Time.TIMEZONE_UTC));
- sb.append(sdf.format(start));
+ sb.append(sdf.format(startDate));
} else {
- sb.append(DateFormat.format("E", start));
+ sb.append(DateFormat.format("E", startDate));
sb.append(" ");
- sb.append(DateFormat.getTimeFormat(mContext).format(start));
+ sb.append(DateFormat.getTimeFormat(this).format(startDate));
+ sb.append(" - ");
+ sb.append(DateFormat.getTimeFormat(this).format(endDate));
}
// Add the event location if it should be shown
- int showLocation = Integer.parseInt(mSharedPrefs.getString(Constants.CALENDAR_SHOW_LOCATION, "0"));
- if (showLocation != 0 && !TextUtils.isEmpty(location)) {
- switch(showLocation) {
- case 1:
- // Show first line
- int end = location.indexOf('\n');
- if(end == -1) {
+ int showLocation = Preferences.calendarLocationMode(this);
+ if (showLocation != Preferences.SHOW_NEVER && !TextUtils.isEmpty(location)) {
+ switch (showLocation) {
+ case Preferences.SHOW_FIRST_LINE:
+ int stringEnd = location.indexOf('\n');
+ if(stringEnd == -1) {
sb.append(": " + location);
} else {
- sb.append(": " + location.substring(0, end));
+ sb.append(": " + location.substring(0, stringEnd));
}
break;
- case 2:
- // Show all
+ case Preferences.SHOW_ALWAYS:
sb.append(": " + location);
break;
}
}
// Add the event description if it should be shown
- int showDescription = Integer.parseInt(mSharedPrefs.getString(Constants.CALENDAR_SHOW_DESCRIPTION, "0"));
- if (showDescription != 0 && !TextUtils.isEmpty(description)) {
+ int showDescription = Preferences.calendarDescriptionMode(this);
+ if (showDescription != Preferences.SHOW_NEVER && !TextUtils.isEmpty(description)) {
// Show the appropriate separator
- if (showLocation == 0) {
+ if (showLocation == Preferences.SHOW_NEVER) {
sb.append(": ");
} else {
sb.append(" - ");
}
- switch(showDescription) {
- case 1:
- // Show first line
- int end = description.indexOf('\n');
- if(end == -1) {
+ switch (showDescription) {
+ case Preferences.SHOW_FIRST_LINE:
+ int stringEnd = description.indexOf('\n');
+ if(stringEnd == -1) {
sb.append(description);
} else {
- sb.append(description.substring(0, end));
+ sb.append(description.substring(0, stringEnd));
}
break;
- case 2:
- // Show all
+ case Preferences.SHOW_ALWAYS:
sb.append(description);
break;
}
}
- // Set the time, location and description as the second array item
- nextCalendarAlarm[eventCount][1] = sb.toString();
- cursor.moveToNext();
-
- // Increment the event counter
+ // Add the event details to the CalendarInfo object and move to next record
+ mCalendarInfo.addEvent(populateEventInfo(eventId, title, sb.toString(), begin, end, allDay));
eventCount++;
+ cursor.moveToNext();
}
}
} catch (Exception e) {
@@ -714,6 +554,126 @@ public class ClockWidgetService extends Service {
cursor.close();
}
}
- return nextCalendarAlarm;
+
+ // check for first event outside of lookahead window
+ long endOfLookahead = now + lookahead;
+ long minUpdateTime = getMinUpdateFromNow(endOfLookahead);
+
+ // don't bother with querying if the end result is later than the
+ // minimum update time anyway
+ if (endOfLookahead < minUpdateTime) {
+ if (where.length() > 0) {
+ where.append(" AND ");
+ }
+ where.append(CalendarContract.Instances.BEGIN);
+ where.append(" > ");
+ where.append(endOfLookahead);
+
+ uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI,
+ String.format("%d/%d", endOfLookahead, minUpdateTime));
+ projection = new String[] { CalendarContract.Instances.BEGIN };
+ cursor = getContentResolver().query(uri, projection, where.toString(), null,
+ CalendarContract.Instances.BEGIN + " ASC limit 1");
+
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ mCalendarInfo.setFollowingEventStart(cursor.getLong(0));
+ }
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * Construct the EventInfo object
+ */
+ private EventInfo populateEventInfo(long eventId, String title, String description,
+ long begin, long end, boolean allDay) {
+ EventInfo eventInfo = new EventInfo();
+
+ // Populate
+ eventInfo.id = eventId;
+ eventInfo.title = title;
+ eventInfo.description = description;
+ eventInfo.start = begin;
+ eventInfo.end = end;
+ eventInfo.allDay = allDay;
+
+ return eventInfo;
+ }
+
+ //===============================================================================================
+ // Update timer related functionality
+ //===============================================================================================
+ /**
+ * Calculates and returns the next time we should push widget updates.
+ */
+ private long calculateUpdateTime() {
+ final long now = System.currentTimeMillis();
+ long lookAhead = Preferences.lookAheadTimeInMs(this);
+ long minUpdateTime = getMinUpdateFromNow(now);
+
+ // Check if there is a calendar event earlier
+ for (EventInfo event : mCalendarInfo.getEvents()) {
+ final long end = event.end;
+ final long start = event.start;
+ if (now < start) {
+ minUpdateTime = Math.min(minUpdateTime, start);
+ }
+ if (now < end) {
+ minUpdateTime = Math.min(minUpdateTime, end);
+ }
+ }
+
+ if (mCalendarInfo.getFollowingEventStart() > 0) {
+ // Make sure to update when the next event gets into the lookahead window
+ minUpdateTime = Math.min(minUpdateTime, mCalendarInfo.getFollowingEventStart() - lookAhead);
+ }
+
+ // Construct a log entry in human readable form
+ if (D) {
+ Date date1 = new Date(now);
+ Date date2 = new Date(minUpdateTime);
+ Log.i(TAG, "Chronus: It is now " + DateFormat.getTimeFormat(this).format(date1)
+ + ", next widget update at " + DateFormat.getTimeFormat(this).format(date2));
+ }
+
+ // Return the next update time
+ return minUpdateTime;
+ }
+
+ private long getMinUpdateFromNow(long now) {
+ /* we update at least once a day */
+ final long millisPerDay = 24L * 60L * 60L * 1000L;
+ return now + millisPerDay;
+ }
+
+ private static PendingIntent getRefreshIntent(Context context) {
+ Intent i = new Intent(context, ClockWidgetService.class);
+ i.setAction(ACTION_REFRESH_CALENDAR);
+ return PendingIntent.getService(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ /**
+ * Schedule an alarm to trigger an update at the next weather refresh or at the next event
+ * time boundary (start/end).
+ */
+ private void scheduleCalendarUpdate() {
+ PendingIntent pi = getRefreshIntent(this);
+ long updateTime = calculateUpdateTime();
+
+ // Clear any old alarms and schedule the new alarm
+ // Since the updates are now only done very infrequently, it can wake the device to ensure the
+ // latest date is available when the user turns the screen on after a few hours sleep
+ AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ am.cancel(pi);
+ if (updateTime > 0) {
+ am.set(AlarmManager.RTC_WAKEUP, updateTime, pi);
+ }
+ }
+
+ public static void cancelUpdates(Context context) {
+ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ am.cancel(getRefreshIntent(context));
}
}
diff --git a/src/com/cyanogenmod/lockclock/misc/CalendarInfo.java b/src/com/cyanogenmod/lockclock/misc/CalendarInfo.java
new file mode 100644
index 0000000..3832476
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/misc/CalendarInfo.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project (DvTonder)
+ * Portions Copyright (C) 2012 The Android Open Source 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.misc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CalendarInfo {
+ private static List<EventInfo> mEventsList;
+ private static long mFollowingEventStart;
+
+ public CalendarInfo() {
+ if (mEventsList == null) {
+ mEventsList = new ArrayList<EventInfo>(Constants.MAX_CALENDAR_ITEMS);
+ }
+ mFollowingEventStart = 0;
+ }
+
+ public List<EventInfo> getEvents() {
+ return mEventsList;
+ }
+
+ public boolean hasEvents() {
+ return !mEventsList.isEmpty();
+ }
+
+ public void clearEvents() {
+ mEventsList.clear();
+ mFollowingEventStart = 0;
+ }
+
+ public void addEvent(EventInfo event) {
+ mEventsList.add(event);
+ }
+
+ public void setFollowingEventStart(long start) {
+ mFollowingEventStart = start;
+ }
+
+ public long getFollowingEventStart() {
+ return mFollowingEventStart;
+ }
+
+ //===============================================================================================
+ // Calendar event information class
+ //===============================================================================================
+ /**
+ * EventInfo is a class that represents an event in the widget
+ */
+ public static class EventInfo {
+ public String description;
+ public String title;
+
+ public long id;
+ public long start;
+ public long end;
+ public boolean allDay;
+
+ public EventInfo() {
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("EventInfo [Title=");
+ builder.append(title);
+ builder.append(", id=");
+ builder.append(id);
+ builder.append(", description=");
+ builder.append(description);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (allDay ? 1231 : 1237);
+ result = prime * result + (int) (id ^ (id >>> 32));
+ result = prime * result + (int) (end ^ (end >>> 32));
+ result = prime * result + (int) (start ^ (start >>> 32));
+ result = prime * result + ((title == null) ? 0 : title.hashCode());
+ result = prime * result + ((description == null) ? 0 : description.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ EventInfo other = (EventInfo) obj;
+ if (id != other.id)
+ return false;
+ if (allDay != other.allDay)
+ return false;
+ if (end != other.end)
+ return false;
+ if (start != other.start)
+ return false;
+ if (title == null) {
+ if (other.title != null)
+ return false;
+ } else if (!title.equals(other.title))
+ return false;
+ if (description == null) {
+ if (other.description != null)
+ return false;
+ } else if (!description.equals(other.description)) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/misc/Constants.java b/src/com/cyanogenmod/lockclock/misc/Constants.java
index 4834d07..e7a2c0a 100644
--- a/src/com/cyanogenmod/lockclock/misc/Constants.java
+++ b/src/com/cyanogenmod/lockclock/misc/Constants.java
@@ -17,11 +17,9 @@
package com.cyanogenmod.lockclock.misc;
public class Constants {
- public static final String PREF_NAME = "LockClock";
- public static final String PREFERENCES_CHANGED = "preferences_changed";
+ public static final boolean DEBUG = false;
- // Activity start commands
- public static final String FORCE_REFRESH = "force_refresh";
+ public static final String PREF_NAME = "LockClock";
// Widget Settings
public static final String CLOCK_DIGITAL = "clock_digital";
@@ -39,6 +37,7 @@ public class Constants {
public static final String WEATHER_INVERT_LOWHIGH = "weather_invert_lowhigh";
public static final String WEATHER_REFRESH_INTERVAL = "weather_refresh_interval";
public static final String WEATHER_USE_ALTERNATE_ICONS = "weather_use_alternate_icons";
+ public static final String WEATHER_WOEID = "weather_woeid";
public static final String SHOW_CALENDAR = "show_calendar";
public static final String CALENDAR_LIST = "calendar_list";
@@ -48,5 +47,9 @@ public class Constants {
public static final String CALENDAR_SHOW_LOCATION = "calendar_show_location";
public static final String CALENDAR_SHOW_DESCRIPTION = "calendar_show_description";
+ // other shared pref entries
+ public static final String WEATHER_LAST_UPDATE = "last_weather_update";
+ public static final String WEATHER_DATA = "weather_data";
+
public static final int MAX_CALENDAR_ITEMS = 3;
}
diff --git a/src/com/cyanogenmod/lockclock/misc/Preferences.java b/src/com/cyanogenmod/lockclock/misc/Preferences.java
new file mode 100644
index 0000000..d58fcef
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/misc/Preferences.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 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.misc;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.cyanogenmod.lockclock.weather.WeatherInfo;
+
+import java.util.Set;
+
+public class Preferences {
+ private Preferences() {
+ }
+
+ public static boolean showDigitalClock(Context context) {
+ return getPrefs(context).getBoolean(Constants.CLOCK_DIGITAL, true);
+ }
+ public static boolean showAlarm(Context context) {
+ return getPrefs(context).getBoolean(Constants.CLOCK_SHOW_ALARM, true);
+ }
+ public static boolean showWeather(Context context) {
+ return getPrefs(context).getBoolean(Constants.SHOW_WEATHER, false);
+ }
+ public static boolean showCalendar(Context context) {
+ return getPrefs(context).getBoolean(Constants.SHOW_CALENDAR, false);
+ }
+
+ public static boolean useBoldFontForHours(Context context) {
+ return getPrefs(context).getBoolean(Constants.CLOCK_FONT, true);
+ }
+ public static boolean useBoldFontForMinutes(Context context) {
+ return getPrefs(context).getBoolean(Constants.CLOCK_FONT_MINUTES, false);
+ }
+ public static boolean useBoldFontForDateAndAlarms(Context context) {
+ return getPrefs(context).getBoolean(Constants.CLOCK_FONT_DATE, true);
+ }
+
+ public static boolean showWeatherLocation(Context context) {
+ return getPrefs(context).getBoolean(Constants.WEATHER_SHOW_LOCATION, true);
+ }
+ public static boolean showWeatherTimestamp(Context context) {
+ return getPrefs(context).getBoolean(Constants.WEATHER_SHOW_TIMESTAMP, true);
+ }
+ public static boolean invertLowHighTemperature(Context context) {
+ return getPrefs(context).getBoolean(Constants.WEATHER_INVERT_LOWHIGH, false);
+ }
+ public static boolean useAlternateWeatherIcons(Context context) {
+ return getPrefs(context).getBoolean(Constants.WEATHER_USE_ALTERNATE_ICONS, false);
+ }
+ public static boolean useMetricUnits(Context context) {
+ return getPrefs(context).getBoolean(Constants.WEATHER_USE_METRIC, true);
+ }
+ public static long weatherRefreshIntervalInMs(Context context) {
+ String value = getPrefs(context).getString(Constants.WEATHER_REFRESH_INTERVAL, "60");
+ return Long.parseLong(value) * 60 * 1000;
+ }
+ public static boolean useCustomWeatherLocation(Context context) {
+ return getPrefs(context).getBoolean(Constants.WEATHER_USE_CUSTOM_LOCATION, false);
+ }
+ public static String customWeatherLocation(Context context) {
+ return getPrefs(context).getString(Constants.WEATHER_CUSTOM_LOCATION_STRING, null);
+ }
+
+ public static void setCachedWeatherInfo(Context context, long timestamp, WeatherInfo data) {
+ SharedPreferences.Editor editor = getPrefs(context).edit();
+ editor.putLong(Constants.WEATHER_LAST_UPDATE, timestamp);
+ if (data != null) {
+ editor.putString(Constants.WEATHER_DATA, data.toSerializedString());
+ }
+ editor.apply();
+ }
+ public static long lastWeatherUpdateTimestamp(Context context) {
+ return getPrefs(context).getLong(Constants.WEATHER_LAST_UPDATE, 0);
+ }
+ public static WeatherInfo getCachedWeatherInfo(Context context) {
+ return WeatherInfo.fromSerializedString(context,
+ getPrefs(context).getString(Constants.WEATHER_DATA, null));
+ }
+ public static String getCachedWoeid(Context context) {
+ return getPrefs(context).getString(Constants.WEATHER_WOEID, null);
+ }
+ public static void setCachedWoeid(Context context, String woeid) {
+ getPrefs(context).edit().putString(Constants.WEATHER_WOEID, woeid).apply();
+ }
+
+ public static Set<String> calendarsToDisplay(Context context) {
+ return getPrefs(context).getStringSet(Constants.CALENDAR_LIST, null);
+ }
+ public static boolean showEventsWithRemindersOnly(Context context) {
+ return getPrefs(context).getBoolean(Constants.CALENDAR_REMINDERS_ONLY, false);
+ }
+ public static boolean showAllDayEvents(Context context) {
+ return !getPrefs(context).getBoolean(Constants.CALENDAR_HIDE_ALLDAY, false);
+ }
+ public static long lookAheadTimeInMs(Context context) {
+ return Long.parseLong(getPrefs(context).getString(Constants.CALENDAR_LOOKAHEAD, "10800000"));
+ }
+
+ public static final int SHOW_NEVER = 0;
+ public static final int SHOW_FIRST_LINE = 1;
+ public static final int SHOW_ALWAYS = 2;
+
+ public static int calendarLocationMode(Context context) {
+ return Integer.parseInt(getPrefs(context).getString(Constants.CALENDAR_SHOW_LOCATION, "0"));
+ }
+ public static int calendarDescriptionMode(Context context) {
+ return Integer.parseInt(getPrefs(context).getString(Constants.CALENDAR_SHOW_DESCRIPTION, "0"));
+ }
+
+ public static SharedPreferences getPrefs(Context context) {
+ return context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java b/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java
index d6fc1c6..3cecfc7 100644
--- a/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java
+++ b/src/com/cyanogenmod/lockclock/misc/WidgetUtils.java
@@ -26,9 +26,12 @@ import android.util.TypedValue;
import com.cyanogenmod.lockclock.R;
public class WidgetUtils {
- static final String TAG = "WidgetUtils";
-
- // Decide whether to show the Weather panel
+ //===============================================================================================
+ // Widget display and resizing related functionality
+ //===============================================================================================
+ /**
+ * Decide whether to show the Weather panel
+ */
public static boolean canFitWeather(Context context, int id, boolean digitalClock) {
Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(id);
if (options == null) {
@@ -46,7 +49,9 @@ public class WidgetUtils {
return (minHeightPx > neededSize);
}
- // Decide whether to show the Calendar panel
+ /**
+ * Decide whether to show the Calendar panel
+ */
public static boolean canFitCalendar(Context context, int id, boolean digitalClock) {
Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(id);
if (options == null) {
@@ -64,7 +69,9 @@ public class WidgetUtils {
return (minHeightPx > neededSize);
}
- // Calculate the scale factor of the fonts in the widget
+ /**
+ * Calculate the scale factor of the fonts in the widget
+ */
public static float getScaleRatio(Context context, int id) {
Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(id);
if (options != null) {
@@ -79,4 +86,4 @@ public class WidgetUtils {
}
return 1f;
}
-} \ No newline at end of file
+}
diff --git a/src/com/cyanogenmod/lockclock/preference/CalendarPreferences.java b/src/com/cyanogenmod/lockclock/preference/CalendarPreferences.java
index 884bce9..5ce25a8 100644
--- a/src/com/cyanogenmod/lockclock/preference/CalendarPreferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/CalendarPreferences.java
@@ -16,9 +16,6 @@
package com.cyanogenmod.lockclock.preference;
-import static com.cyanogenmod.lockclock.misc.Constants.PREF_NAME;
-
-import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -34,6 +31,7 @@ import android.preference.PreferenceFragment;
import android.provider.CalendarContract;
import com.cyanogenmod.lockclock.ClockWidgetProvider;
+import com.cyanogenmod.lockclock.ClockWidgetService;
import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Constants;
@@ -42,40 +40,45 @@ import java.util.List;
public class CalendarPreferences extends PreferenceFragment implements
OnSharedPreferenceChangeListener {
- private static final String TAG = "Calendar Preferences";
- private MultiSelectListPreference mCalendarList;
private Context mContext;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getPreferenceManager().setSharedPreferencesName(PREF_NAME);
+ getPreferenceManager().setSharedPreferencesName(Constants.PREF_NAME);
addPreferencesFromResource(R.xml.preferences_calendar);
mContext = getActivity();
- // Load the required settings from preferences
- SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
-
// The calendar list entries and values are determined at run time, not in XML
- mCalendarList = (MultiSelectListPreference) findPreference(Constants.CALENDAR_LIST);
+ MultiSelectListPreference calendarList =
+ (MultiSelectListPreference) findPreference(Constants.CALENDAR_LIST);
CalendarEntries calEntries = CalendarEntries.findCalendars(getActivity());
- mCalendarList.setEntries(calEntries.getEntries());
- mCalendarList.setEntryValues(calEntries.getEntryValues());
+ calendarList.setEntries(calEntries.getEntries());
+ calendarList.setEntryValues(calEntries.getEntryValues());
+ }
- prefs.registerOnSharedPreferenceChangeListener(this);
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
Preference pref = findPreference(key);
if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
}
Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
- updateIntent.putExtra(Constants.FORCE_REFRESH, true);
+ updateIntent.setAction(ClockWidgetService.ACTION_REFRESH_CALENDAR);
mContext.sendBroadcast(updateIntent);
}
diff --git a/src/com/cyanogenmod/lockclock/preference/ClockPreferences.java b/src/com/cyanogenmod/lockclock/preference/ClockPreferences.java
index e8d9f1c..d638beb 100644
--- a/src/com/cyanogenmod/lockclock/preference/ClockPreferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/ClockPreferences.java
@@ -16,8 +16,6 @@
package com.cyanogenmod.lockclock.preference;
-import static com.cyanogenmod.lockclock.misc.Constants.PREF_NAME;
-
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -33,31 +31,37 @@ import com.cyanogenmod.lockclock.misc.Constants;
public class ClockPreferences extends PreferenceFragment implements
OnSharedPreferenceChangeListener {
- private static final String TAG = "Clock Preferences";
private Context mContext;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getPreferenceManager().setSharedPreferencesName(PREF_NAME);
+ getPreferenceManager().setSharedPreferencesName(Constants.PREF_NAME);
addPreferencesFromResource(R.xml.preferences_clock);
mContext = getActivity();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
- // Load the required settings from preferences
- SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- prefs.registerOnSharedPreferenceChangeListener(this);
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
Preference pref = findPreference(key);
if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
}
Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
- updateIntent.putExtra(Constants.FORCE_REFRESH, true);
mContext.sendBroadcast(updateIntent);
}
}
diff --git a/src/com/cyanogenmod/lockclock/preference/Preferences.java b/src/com/cyanogenmod/lockclock/preference/Preferences.java
index 0f5d64d..4d55f28 100644
--- a/src/com/cyanogenmod/lockclock/preference/Preferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/Preferences.java
@@ -17,7 +17,6 @@
package com.cyanogenmod.lockclock.preference;
import android.content.Context;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
@@ -32,34 +31,10 @@ import android.widget.TextView;
import com.cyanogenmod.lockclock.R;
import com.cyanogenmod.lockclock.misc.Constants;
-import static com.cyanogenmod.lockclock.misc.Constants.PREF_NAME;
import java.util.List;
-public class Preferences extends PreferenceActivity
- implements SharedPreferences.OnSharedPreferenceChangeListener {
-
- private static final String TAG = "LockClock Preferences";
-
- private SharedPreferences mPreferences;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPreferences = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mPreferences.registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mPreferences.unregisterOnSharedPreferenceChangeListener(this);
- }
+public class Preferences extends PreferenceActivity {
@Override
public void onBuildHeaders(List<Header> target) {
@@ -85,13 +60,6 @@ public class Preferences extends PreferenceActivity
}
}
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- SharedPreferences.Editor editor = mPreferences.edit();
- editor.putBoolean(Constants.PREFERENCES_CHANGED, true);
- editor.commit();
- }
-
public static class ClockFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
diff --git a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
index c63ba81..6c2895e 100644
--- a/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
+++ b/src/com/cyanogenmod/lockclock/preference/WeatherPreferences.java
@@ -16,8 +16,6 @@
package com.cyanogenmod.lockclock.preference;
-import static com.cyanogenmod.lockclock.misc.Constants.PREF_NAME;
-
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
@@ -38,6 +36,7 @@ import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@@ -45,17 +44,24 @@ 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.weather.WeatherUpdateService;
import com.cyanogenmod.lockclock.weather.YahooPlaceFinder;
public class WeatherPreferences extends PreferenceFragment implements
- OnPreferenceClickListener, OnSharedPreferenceChangeListener {
- private static final String TAG = "Weather Preferences";
+ OnPreferenceClickListener, OnSharedPreferenceChangeListener {
+ private static final String TAG = "WeatherPreferences";
+
+ private static final String[] LOCATION_PREF_KEYS = new String[] {
+ Constants.WEATHER_USE_CUSTOM_LOCATION,
+ Constants.WEATHER_CUSTOM_LOCATION_STRING
+ };
+ private static final String[] WEATHER_REFRESH_KEYS = new String[] {
+ Constants.SHOW_WEATHER,
+ Constants.WEATHER_REFRESH_INTERVAL
+ };
private CheckBoxPreference mUseCustomLoc;
- private CheckBoxPreference mUseMetric;
- private CheckBoxPreference mShowLocation;
- private CheckBoxPreference mShowTimestamp;
- private CheckBoxPreference mUseAlternateIcons;
private EditTextPreference mCustomWeatherLoc;
private Context mContext;
@@ -64,27 +70,13 @@ public class WeatherPreferences extends PreferenceFragment implements
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getPreferenceManager().setSharedPreferencesName(PREF_NAME);
+ getPreferenceManager().setSharedPreferencesName(Constants.PREF_NAME);
addPreferencesFromResource(R.xml.preferences_weather);
mContext = getActivity();
mResolver = mContext.getContentResolver();
- // Load the required settings from preferences
- SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
-
- // Some preferences need to be set to a default value in code since we cannot do them in XML
- mUseMetric = (CheckBoxPreference) findPreference(Constants.WEATHER_USE_METRIC);
- mUseMetric.setChecked(prefs.getBoolean(Constants.WEATHER_USE_METRIC, true));
- mShowLocation = (CheckBoxPreference) findPreference(Constants.WEATHER_SHOW_LOCATION);
- mShowLocation.setChecked(prefs.getBoolean(Constants.WEATHER_SHOW_LOCATION, true));
- mShowTimestamp = (CheckBoxPreference) findPreference(Constants.WEATHER_SHOW_TIMESTAMP);
- mShowTimestamp.setChecked(prefs.getBoolean(Constants.WEATHER_SHOW_TIMESTAMP, true));
- mUseAlternateIcons = (CheckBoxPreference) findPreference(Constants.WEATHER_USE_ALTERNATE_ICONS);
- mUseAlternateIcons.setChecked(prefs.getBoolean(Constants.WEATHER_USE_ALTERNATE_ICONS, false));
-
// Load items that need custom summaries etc.
mUseCustomLoc = (CheckBoxPreference) findPreference(Constants.WEATHER_USE_CUSTOM_LOCATION);
- mUseCustomLoc.setChecked(prefs.getBoolean(Constants.WEATHER_USE_CUSTOM_LOCATION, false));
mUseCustomLoc.setOnPreferenceClickListener(this);
mCustomWeatherLoc = (EditTextPreference) findPreference(Constants.WEATHER_CUSTOM_LOCATION_STRING);
mCustomWeatherLoc.setOnPreferenceClickListener(this);
@@ -96,8 +88,18 @@ public class WeatherPreferences extends PreferenceFragment implements
&& !mUseCustomLoc.isChecked()) {
showDialog();
}
+ }
- prefs.registerOnSharedPreferenceChangeListener(this);
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
@Override
@@ -107,27 +109,51 @@ public class WeatherPreferences extends PreferenceFragment implements
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
}
- Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
- updateIntent.putExtra(Constants.FORCE_REFRESH, true);
- mContext.sendBroadcast(updateIntent);
- }
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (preference == mUseCustomLoc) {
- SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- prefs.edit().putBoolean(Constants.WEATHER_USE_CUSTOM_LOCATION, mUseCustomLoc.isChecked()).apply();
+ if (pref == mUseCustomLoc) {
updateLocationSummary();
- return true;
}
- return super.onPreferenceTreeClick(preferenceScreen, preference);
+
+ boolean needWeatherUpdate = false;
+ boolean forceWeatherUpdate = false;
+
+ for (String k : LOCATION_PREF_KEYS) {
+ if (TextUtils.equals(key, k)) {
+ // location pref has changed -> clear out woeid cache
+ Preferences.setCachedWoeid(mContext, null);
+ forceWeatherUpdate = true;
+ break;
+ }
+ }
+
+ for (String k : WEATHER_REFRESH_KEYS) {
+ if (TextUtils.equals(key, k)) {
+ needWeatherUpdate = true;
+ break;
+ }
+ }
+
+ if (Constants.DEBUG) {
+ Log.v(TAG, "Preference " + key + " changed, need update " +
+ needWeatherUpdate + " force update " + forceWeatherUpdate);
+ }
+
+ if (Preferences.showWeather(mContext) && (needWeatherUpdate || forceWeatherUpdate)) {
+ Intent updateIntent = new Intent(mContext, WeatherUpdateService.class);
+ if (forceWeatherUpdate) {
+ updateIntent.setAction(WeatherUpdateService.ACTION_FORCE_UPDATE);
+ }
+ mContext.startService(updateIntent);
+ }
+
+ Intent updateIntent = new Intent(mContext, ClockWidgetProvider.class);
+ mContext.sendBroadcast(updateIntent);
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == mCustomWeatherLoc) {
- SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- String location = prefs.getString(Constants.WEATHER_CUSTOM_LOCATION_STRING, null);
+ String location = com.cyanogenmod.lockclock.misc.Preferences.customWeatherLocation(mContext);
if (location != null) {
mCustomWeatherLoc.getEditText().setText(location);
mCustomWeatherLoc.getEditText().setSelection(location.length());
@@ -155,9 +181,6 @@ public class WeatherPreferences extends PreferenceFragment implements
mCustomWeatherLoc.setText(location);
mCustomWeatherLoc.setSummary(location);
mCustomWeatherLoc.getDialog().dismiss();
-
- SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- prefs.edit().putString(Constants.WEATHER_CUSTOM_LOCATION_STRING, location).apply();
}
d.dismiss();
}
@@ -180,7 +203,7 @@ public class WeatherPreferences extends PreferenceFragment implements
String woeid = null;
try {
- woeid = YahooPlaceFinder.GeoCode(mContext, input[0]);
+ woeid = YahooPlaceFinder.geoCode(mContext, input[0]);
} catch (Exception e) {
Log.e(TAG, "Could not resolve location", e);
}
@@ -191,9 +214,10 @@ public class WeatherPreferences extends PreferenceFragment implements
private void updateLocationSummary() {
if (mUseCustomLoc.isChecked()) {
- SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- String location = prefs.getString(Constants.WEATHER_CUSTOM_LOCATION_STRING,
- getResources().getString(R.string.unknown));
+ String location = com.cyanogenmod.lockclock.misc.Preferences.customWeatherLocation(mContext);
+ if (location == null) {
+ location = getResources().getString(R.string.unknown);
+ }
mCustomWeatherLoc.setSummary(location);
} else {
mCustomWeatherLoc.setSummary(R.string.weather_geolocated);
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java b/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
index bb4a5fa..420900d 100644
--- a/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherInfo.java
@@ -17,99 +17,208 @@
package com.cyanogenmod.lockclock.weather;
import android.content.Context;
+import android.content.res.Resources;
+
import com.cyanogenmod.lockclock.R;
+import com.cyanogenmod.lockclock.misc.Preferences;
+
+import java.text.DecimalFormat;
+import java.util.Date;
public class WeatherInfo {
+ private static final long serialVersionUID = 1L;
- public static final String NODATA = "-";
-
- public String city, forecast_date, condition, condition_code, temp, temp_unit,
- humidity, wind, wind_dir, speed_unit, low, high;
- public long last_sync;
-
- public WeatherInfo() {
- this.city = NODATA;
- this.forecast_date = NODATA;
- this.condition = NODATA;
- this.condition_code = NODATA;
- this.temp = NODATA;
- this.temp_unit = NODATA;
- this.humidity = NODATA;
- this.wind = NODATA;
- this.wind_dir = NODATA;
- this.speed_unit = NODATA;
- this.low = NODATA;
- this.high = NODATA;
- this.last_sync = 0;
- }
+ private static final DecimalFormat sNoDigitsFormat = new DecimalFormat("0");
- public WeatherInfo(Context context, String city, String fdate, String condition, String condition_code,
- String temp, String temp_unit, String humidity,
- String wind, String wind_dir, String speed_unit,
- String low, String high, long last_sync) {
+ private Context mContext;
+
+ private String city;
+ private String forecastDate;
+ private String condition;
+ private int conditionCode;
+ private float temperature;
+ private float lowTemperature;
+ private float highTemperature;
+ private String tempUnit;
+ private float humidity;
+ private float wind;
+ private int windDirection;
+ private String speedUnit;
+ private long timestamp;
+
+ public WeatherInfo(Context context,
+ String city, String fdate, String condition, int conditionCode,
+ float temp, float low, float high, String tempUnit, float humidity,
+ float wind, int windDir, String speedUnit, long timestamp) {
+ this.mContext = context;
this.city = city;
- this.forecast_date = fdate;
+ this.forecastDate = fdate;
this.condition = condition;
- this.condition_code = condition_code;
- this.humidity = humidity + "%";
- this.wind = calcDirection(context, wind_dir) + " " + trimSpeed(wind) + speed_unit;
- this.speed_unit = speed_unit;
- this.last_sync = last_sync;
- // Only the current temperature gets the temp_unit added.
- this.temp_unit = temp_unit;
- this.temp = temp + "°" + temp_unit;
- this.low = low + "°";
- this.high = high + "°";
+ this.conditionCode = conditionCode;
+ this.humidity = humidity;
+ this.wind = wind;
+ this.windDirection = windDir;
+ this.speedUnit = speedUnit;
+ this.timestamp = timestamp;
+ this.temperature = temp;
+ this.lowTemperature = low;
+ this.highTemperature = high;
+ this.tempUnit = tempUnit;
+ }
+
+ public int getConditionResource() {
+ boolean alternativeIcons = Preferences.useAlternateWeatherIcons(mContext);
+ final String prefix = alternativeIcons ? "weather2_" : "weather_";
+ final Resources res = mContext.getResources();
+ final int resId = res.getIdentifier(prefix + conditionCode, "drawable", mContext.getPackageName());
+
+ if (resId != 0) {
+ return resId;
+ }
+
+ return alternativeIcons ? R.drawable.weather2_na : R.drawable.weather_na;
}
- /**
- * find the optimal weather string (helper function for translation)
- *
- * @param conditionCode condition code from Yahoo (this is the main
- * identifier which will be used to find a matching translation
- * in the project's resources
- * @param providedString
- * @return either the defaultString (which should be Yahoo's weather
- * condition text), or the translated version from resources
- */
- public static String getTranslatedConditionString(Context context, int conditionCode,
- String providedString) {
- int resID = context.getResources().getIdentifier("weather_" + conditionCode, "string",
- context.getPackageName());
- return (resID != 0) ? context.getResources().getString(resID) : providedString;
+ public String getCity() {
+ return city;
}
- private String calcDirection(Context context, String degrees) {
- try {
- int deg = Integer.parseInt(degrees);
- if (deg >= 338 || deg <= 22)
- return context.getResources().getString(R.string.weather_N);
- else if (deg < 68)
- return context.getResources().getString(R.string.weather_NE);
- else if (deg < 113)
- return context.getResources().getString(R.string.weather_E);
- else if (deg < 158)
- return context.getResources().getString(R.string.weather_SE);
- else if (deg < 203)
- return context.getResources().getString(R.string.weather_S);
- else if (deg < 248)
- return context.getResources().getString(R.string.weather_SW);
- else if (deg < 293)
- return context.getResources().getString(R.string.weather_W);
- else if (deg < 338)
- return context.getResources().getString(R.string.weather_NW);
- else
- return "";
- } catch (NumberFormatException e) {
+ public String getCondition() {
+ final Resources res = mContext.getResources();
+ final int resId = res.getIdentifier("weather_" + conditionCode, "string", mContext.getPackageName());
+
+ if (resId != 0) {
+ return res.getString(resId);
+ }
+
+ return condition;
+ }
+
+ public Date getTimestamp() {
+ return new Date(timestamp);
+ }
+
+ private String getFormattedValue(float value, String unit) {
+ if (Float.isNaN(highTemperature)) {
+ return "-";
+ }
+ return sNoDigitsFormat.format(value) + unit;
+ }
+
+ public String getFormattedTemperature() {
+ return getFormattedValue(temperature, "°" + tempUnit);
+ }
+
+ public String getFormattedLow() {
+ return getFormattedValue(lowTemperature, "°");
+ }
+
+ public String getFormattedHigh() {
+ return getFormattedValue(highTemperature, "°");
+ }
+
+ public String getFormattedHumidity() {
+ return getFormattedValue(humidity, "%");
+ }
+
+ public String getFormattedWindSpeed() {
+ return getFormattedValue(wind, speedUnit);
+ }
+
+ public String getWindDirection() {
+ int resId;
+
+ if (windDirection < 0) {
return "";
}
+
+ 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);
}
- private String trimSpeed(String speed) {
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WeatherInfo for ");
+ builder.append(city);
+ 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());
+ return builder.toString();
+ }
+
+ public String toSerializedString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(city).append('|');
+ builder.append(forecastDate).append('|');
+ builder.append(condition).append('|');
+ builder.append(conditionCode).append('|');
+ builder.append(temperature).append('|');
+ builder.append(lowTemperature).append('|');
+ builder.append(highTemperature).append('|');
+ builder.append(tempUnit).append('|');
+ builder.append(humidity).append('|');
+ builder.append(wind).append('|');
+ builder.append(windDirection).append('|');
+ builder.append(speedUnit).append('|');
+ builder.append(timestamp);
+ return builder.toString();
+ }
+
+ public static WeatherInfo fromSerializedString(Context context, String input) {
+ if (input == null) {
+ return null;
+ }
+
+ String[] parts = input.split("\\|");
+ if (parts == null || parts.length != 13) {
+ return null;
+ }
+
+ int conditionCode, windDirection;
+ long timestamp;
+ float temperature, low, high, humidity, wind;
+
try {
- return String.valueOf(Math.round(Float.parseFloat(speed)));
+ conditionCode = Integer.parseInt(parts[3]);
+ temperature = Float.parseFloat(parts[4]);
+ low = Float.parseFloat(parts[5]);
+ high = Float.parseFloat(parts[6]);
+ humidity = Float.parseFloat(parts[8]);
+ wind = Float.parseFloat(parts[9]);
+ windDirection = Integer.parseInt(parts[10]);
+ timestamp = Long.parseLong(parts[12]);
} catch (NumberFormatException e) {
- return "";
+ return null;
}
+
+ return new WeatherInfo(context,
+ /* city */ parts[0], /* date */ parts[1], /* condition */ parts[2],
+ conditionCode, temperature, low, high, /* tempUnit */ parts[7],
+ humidity, wind, windDirection, /* speedUnit */ parts[11], timestamp);
}
}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
new file mode 100644
index 0000000..3e24dd7
--- /dev/null
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherUpdateService.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2012 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.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Location;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import com.cyanogenmod.lockclock.ClockWidgetProvider;
+import com.cyanogenmod.lockclock.misc.Constants;
+import com.cyanogenmod.lockclock.misc.Preferences;
+
+import java.io.IOException;
+import java.util.Date;
+
+import org.w3c.dom.Document;
+
+public class WeatherUpdateService extends Service {
+ private static final String TAG = "WeatherUpdateService";
+ private static final boolean D = Constants.DEBUG;
+
+ private static final String URL_YAHOO_API_WEATHER = "http://weather.yahooapis.com/forecastrss?w=%s&u=";
+
+ public static final String ACTION_FORCE_UPDATE = "com.cyanogenmod.lockclock.action.FORCE_WEATHER_UPDATE";
+
+ private WeatherUpdateTask mTask;
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (D) Log.v(TAG, "Got intent " + intent);
+
+ if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) {
+ if (D) Log.v(TAG, "Weather update is still active, not starting new update");
+ return START_REDELIVER_INTENT;
+ }
+
+ boolean force = ACTION_FORCE_UPDATE.equals(intent.getAction());
+ if (force) {
+ Preferences.setCachedWeatherInfo(this, 0, null);
+ }
+ if (!shouldUpdate(force)) {
+ Log.d(TAG, "Service started, but shouldn't update ... stopping");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ mTask = new WeatherUpdateTask();
+ mTask.execute();
+
+ return START_REDELIVER_INTENT;
+ }
+
+ @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) {
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ NetworkInfo info = cm.getActiveNetworkInfo();
+
+ if (info == null || !info.isConnected()) {
+ if (D) Log.d(TAG, "No network connection is available for weather update");
+ return false;
+ }
+
+ if (!Preferences.showWeather(this)) {
+ if (D) Log.v(TAG, "Weather isn't shown, skip update");
+ return false;
+ }
+
+ long interval = Preferences.weatherRefreshIntervalInMs(this);
+ if (interval == 0 && !force) {
+ if (D) Log.v(TAG, "Interval set to manual and update not forced, skip update");
+ return false;
+ }
+
+ 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 (lastUpdate != 0 && now < due) {
+ if (D) Log.v(TAG, "Weather update is not due yet");
+ return false;
+ }
+
+ return true;
+ }
+
+ private class WeatherUpdateTask extends AsyncTask<Void, Void, WeatherInfo> {
+ private WakeLock mWakeLock;
+ private Context mContext;
+
+ private static final int RESULT_SUCCESS = 0;
+ private static final int RESULT_FAILURE = 1;
+ private static final int RESULT_CANCELLED = 2;
+
+ public WeatherUpdateTask() {
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mContext = WeatherUpdateService.this;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mWakeLock.acquire();
+ }
+
+ private String getWoeidForCustomLocation(String location) {
+ // first try with the cached woeid, no need to constantly query constant information
+ String woeid = Preferences.getCachedWoeid(mContext);
+ if (woeid == null) {
+ woeid = YahooPlaceFinder.geoCode(mContext, location);
+ }
+ if (D) Log.v(TAG, "Yahoo location code for " + location + " is " + woeid);
+ return woeid;
+ }
+
+ private String getWoeidForCurrentLocation(Location location) {
+ String woeid = YahooPlaceFinder.reverseGeoCode(mContext,
+ location.getLatitude(), location.getLongitude());
+ if (woeid == null) {
+ // we couldn't fetch up-to-date information, fall back to cache
+ woeid = Preferences.getCachedWoeid(mContext);
+ }
+ if (D) Log.v(TAG, "Yahoo location code for current geolocation " + location + " is " + woeid);
+ return woeid;
+ }
+
+ private Location getCurrentLocation() {
+ LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+ Location location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
+ if (D) Log.v(TAG, "Current location is " + location);
+ return location;
+ }
+
+ private Document getDocument(String woeid) {
+ boolean celcius = Preferences.useMetricUnits(mContext);
+ String urlWithUnit = URL_YAHOO_API_WEATHER + (celcius ? "c" : "f");
+
+ try {
+ return new HttpRetriever().getDocumentFromURL(String.format(urlWithUnit, woeid));
+ } catch (IOException e) {
+ Log.e(TAG, "Couldn't fetch weather data", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected WeatherInfo doInBackground(Void... params) {
+ String customLocation = null;
+ String woeid;
+
+ if (Preferences.useCustomWeatherLocation(mContext)) {
+ customLocation = Preferences.customWeatherLocation(mContext);
+ }
+
+ if (customLocation != null) {
+ woeid = getWoeidForCustomLocation(customLocation);
+ } else {
+ Location location = getCurrentLocation();
+ woeid = getWoeidForCurrentLocation(location);
+ }
+
+ if (woeid == null || isCancelled()) {
+ return null;
+ }
+
+ Document doc = getDocument(woeid);
+ if (doc == null || isCancelled()) {
+ return null;
+ }
+
+ return new WeatherXmlParser(mContext).parseWeatherResponse(doc);
+ }
+
+ @Override
+ protected void onPostExecute(WeatherInfo result) {
+ finish(result);
+ }
+
+ @Override
+ protected void onCancelled() {
+ finish(null);
+ }
+
+ private void finish(WeatherInfo result) {
+ if (result != null) {
+ long now = System.currentTimeMillis();
+ Preferences.setCachedWeatherInfo(mContext, now, result);
+ scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext));
+
+ 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 */
+ } else {
+ /* failure, schedule next download in 30 minutes */
+ long interval = 30 * 60 * 1000;
+ scheduleUpdate(mContext, interval);
+ }
+
+ mWakeLock.release();
+ stopSelf();
+ }
+ }
+
+ private static void scheduleUpdate(Context context, long timeFromNow) {
+ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ long due = System.currentTimeMillis() + timeFromNow;
+
+ if (D) Log.v(TAG, "Scheduling next update at " + new Date(due));
+ am.set(AlarmManager.RTC_WAKEUP, due, getUpdateIntent(context, false));
+ }
+
+ public static void scheduleNextUpdate(Context context) {
+ long lastUpdate = Preferences.lastWeatherUpdateTimestamp(context);
+ if (lastUpdate == 0) {
+ scheduleUpdate(context, 0);
+ } else {
+ long interval = Preferences.weatherRefreshIntervalInMs(context);
+ scheduleUpdate(context, lastUpdate + interval - System.currentTimeMillis());
+ }
+ }
+
+ public static PendingIntent getUpdateIntent(Context context, boolean force) {
+ Intent i = new Intent(context, WeatherUpdateService.class);
+ if (force) {
+ i.setAction(ACTION_FORCE_UPDATE);
+ }
+ return PendingIntent.getService(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ public static void cancelUpdates(Context context) {
+ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ am.cancel(getUpdateIntent(context, true));
+ am.cancel(getUpdateIntent(context, false));
+ }
+}
diff --git a/src/com/cyanogenmod/lockclock/weather/WeatherXmlParser.java b/src/com/cyanogenmod/lockclock/weather/WeatherXmlParser.java
index 50c2c98..0a1643f 100644
--- a/src/com/cyanogenmod/lockclock/weather/WeatherXmlParser.java
+++ b/src/com/cyanogenmod/lockclock/weather/WeatherXmlParser.java
@@ -69,90 +69,67 @@ public class WeatherXmlParser {
mContext = context;
}
+ private String getValueForAttribute(Element root, String tagName, String attributeName) {
+ NamedNodeMap node = root.getElementsByTagName(tagName).item(0).getAttributes();
+ if (node == null) {
+ return null;
+ }
+ return node.getNamedItem(attributeName).getNodeValue();
+ }
+
+ private float getFloatForAttribute(Element root, String tagName, String attributeName)
+ throws NumberFormatException {
+ String value = getValueForAttribute(root, tagName, attributeName);
+ if (value == null) {
+ return Float.NaN;
+ }
+ return Float.parseFloat(value);
+ }
+
+ private int getIntForAttribute(Element root, String tagName, String attributeName)
+ throws NumberFormatException {
+ String value = getValueForAttribute(root, tagName, attributeName);
+ if (value == null) {
+ return -1;
+ }
+ return Integer.parseInt(value);
+ }
+
public WeatherInfo parseWeatherResponse(Document docWeather) {
if (docWeather == null) {
Log.e(TAG, "Invalid doc weather");
return null;
}
- String strCity = null;
- String strDate = null;
- String strCondition = null;
- String strCondition_code = null;
- String strTemp = null;
- String strTempUnit = null;
- String strHumidity = null;
- String strWindSpeed = null;
- String strWindDir = null;
- String strSpeedUnit = null;
- String strHigh = null;
- String strLow = null;
-
try {
Element root = docWeather.getDocumentElement();
root.normalize();
- NamedNodeMap locationNode = root.getElementsByTagName(PARAM_YAHOO_LOCATION).item(0)
- .getAttributes();
- if (locationNode != null) {
- strCity = locationNode.getNamedItem(ATT_YAHOO_CITY).getNodeValue();
- }
-
- NamedNodeMap unitNode = root.getElementsByTagName(PARAM_YAHOO_UNIT).item(0)
- .getAttributes();
-
- if (locationNode != null) {
- strTempUnit = unitNode.getNamedItem(ATT_YAHOO_TEMP_UNIT).getNodeValue();
- strSpeedUnit = unitNode.getNamedItem(ATT_YAHOO_SPEED).getNodeValue();
- }
-
- NamedNodeMap atmosNode = root.getElementsByTagName(PARAM_YAHOO_ATMOSPHERE).item(0)
- .getAttributes();
- if (atmosNode != null) {
- strHumidity = atmosNode.getNamedItem(ATT_YAHOO_HUMIDITY).getNodeValue();
- }
-
- NamedNodeMap conditionNode = root.getElementsByTagName(PARAM_YAHOO_CONDITION).item(0)
- .getAttributes();
- if (conditionNode != null) {
- strCondition = conditionNode.getNamedItem(ATT_YAHOO_TEXT).getNodeValue();
- strCondition_code = conditionNode.getNamedItem(ATT_YAHOO_CODE).getNodeValue();
- strCondition = WeatherInfo.getTranslatedConditionString(mContext, Integer.parseInt(strCondition_code), strCondition);
- strTemp = conditionNode.getNamedItem(ATT_YAHOO_TEMP).getNodeValue();
- strDate = conditionNode.getNamedItem(ATT_YAHOO_DATE).getNodeValue();
- }
-
- NamedNodeMap temNode = root.getElementsByTagName(PARAM_YAHOO_WIND).item(0)
- .getAttributes();
- if (temNode != null) {
- strWindSpeed = temNode.getNamedItem(ATT_YAHOO_SPEED).getNodeValue();
- strWindDir = temNode.getNamedItem(ATT_YAHOO_DIRECTION).getNodeValue();
- }
-
- NamedNodeMap fcNode = root.getElementsByTagName(PARAM_YAHOO_FORECAST).item(0).getAttributes();
- if (fcNode != null) {
- strHigh = fcNode.getNamedItem(ATT_YAHOO_TODAY_HIGH).getNodeValue();
- strLow = fcNode.getNamedItem(ATT_YAHOO_TODAY_LOW).getNodeValue();
- }
+ WeatherInfo w = new WeatherInfo(mContext,
+ /* city */ getValueForAttribute(root, PARAM_YAHOO_LOCATION, ATT_YAHOO_CITY),
+ /* forecastDate */ getValueForAttribute(root, PARAM_YAHOO_CONDITION, ATT_YAHOO_DATE),
+ /* condition */ getValueForAttribute(root, PARAM_YAHOO_CONDITION, ATT_YAHOO_TEXT),
+ /* conditionCode */ getIntForAttribute(root, PARAM_YAHOO_CONDITION, ATT_YAHOO_CODE),
+ /* temperature */ getFloatForAttribute(root, PARAM_YAHOO_CONDITION, ATT_YAHOO_TEMP),
+ /* low */ getFloatForAttribute(root, PARAM_YAHOO_FORECAST, ATT_YAHOO_TODAY_LOW),
+ /* high */ getFloatForAttribute(root, PARAM_YAHOO_FORECAST, ATT_YAHOO_TODAY_HIGH),
+ /* tempUnit */ getValueForAttribute(root, PARAM_YAHOO_UNIT, ATT_YAHOO_TEMP_UNIT),
+ /* humidity */ getFloatForAttribute(root, PARAM_YAHOO_ATMOSPHERE, ATT_YAHOO_HUMIDITY),
+ /* wind */ getFloatForAttribute(root, PARAM_YAHOO_WIND, ATT_YAHOO_SPEED),
+ /* windDir */ getIntForAttribute(root, PARAM_YAHOO_WIND, ATT_YAHOO_DIRECTION),
+ /* speedUnit */ getValueForAttribute(root, PARAM_YAHOO_UNIT, ATT_YAHOO_SPEED),
+ System.currentTimeMillis());
+
+ Log.d(TAG, "Weather updated: " + w);
+ return w;
} catch (Exception e) {
- Log.e(TAG, "Something wrong with parser data: " + e.toString());
+ Log.e(TAG, "Couldn't parse Yahoo weather XML", e);
return null;
}
-
- /* Weather info */
- WeatherInfo yahooWeatherInfo = new WeatherInfo(mContext, strCity, strDate, strCondition, strCondition_code, strTemp,
- strTempUnit, strHumidity, strWindSpeed, strWindDir, strSpeedUnit, strLow, strHigh, System.currentTimeMillis());
-
- Log.d(TAG, "Weather updated for " + strCity + ": " + strDate + ", " + strCondition + "(" + strCondition_code
- + "), " + strTemp + strTempUnit + ", " + strHumidity + "% humidity, " + ", wind: " + strWindDir + " at "
- + strWindSpeed + strSpeedUnit + ", low: " + strLow + strTempUnit + " high: " + strHigh + strTempUnit);
-
- return yahooWeatherInfo;
}
public String parsePlaceFinderResponse(String response) {
try {
-
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(response)));
@@ -170,7 +147,7 @@ public class WeatherXmlParser {
}
}
} catch (Exception e) {
- Log.e(TAG, e.toString());
+ Log.e(TAG, "Couldn't parse Yahoo place finder XML", e);
}
return null;
}
diff --git a/src/com/cyanogenmod/lockclock/weather/YahooPlaceFinder.java b/src/com/cyanogenmod/lockclock/weather/YahooPlaceFinder.java
index 569cef1..c2b3f0d 100644
--- a/src/com/cyanogenmod/lockclock/weather/YahooPlaceFinder.java
+++ b/src/com/cyanogenmod/lockclock/weather/YahooPlaceFinder.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2013 The CyanogenMod Project (DvTonder)
* Copyright (C) 2012 The AOKP Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,25 +18,43 @@
package com.cyanogenmod.lockclock.weather;
import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.cyanogenmod.lockclock.misc.Preferences;
public class YahooPlaceFinder {
- private static final String YAHOO_API_BASE_REV_URL = "http://where.yahooapis.com/geocode?appid=jYkTZp64&q=%1$s,+%2$s&gflags=R";
- private static final String YAHOO_API_BASE_URL = "http://where.yahooapis.com/geocode?appid=jYkTZp64&q=%1$s";
+ private static final String YAHOO_API_BASE_REV_URL = "http://where.yahooapis.com/geocode?appid=EKvCnl4k&q=%1$s,+%2$s&gflags=R";
+ private static final String YAHOO_API_BASE_URL = "http://where.yahooapis.com/geocode?appid=EKvCnl4k&q=%1$s";
public static String reverseGeoCode(Context c, double latitude, double longitude) {
-
String url = String.format(YAHOO_API_BASE_REV_URL, String.valueOf(latitude),
String.valueOf(longitude));
String response = new HttpRetriever().retrieve(url);
- return new WeatherXmlParser(c).parsePlaceFinderResponse(response);
-
+ if (response == null) {
+ return null;
+ }
+
+ String woeid = new WeatherXmlParser(c).parsePlaceFinderResponse(response);
+ if (woeid != null) {
+ // cache the result for potential reuse - the placefinder service API is rate limited
+ Preferences.setCachedWoeid(c, woeid);
+ }
+ return woeid;
}
- public static String GeoCode(Context c, String location) {
+ public static String geoCode(Context c, String location) {
String url = String.format(YAHOO_API_BASE_URL, location).replace(' ', '+');
String response = new HttpRetriever().retrieve(url);
- return new WeatherXmlParser(c).parsePlaceFinderResponse(response);
+ if (response == null) {
+ return null;
+ }
+
+ String woeid = new WeatherXmlParser(c).parsePlaceFinderResponse(response);
+ if (woeid != null) {
+ // cache the result for potential reuse - the placefinder service API is rate limited
+ Preferences.setCachedWoeid(c, woeid);
+ }
+ return woeid;
}
-
}