summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDvTonder <david.vantonder@gmail.com>2013-01-08 16:25:09 -0500
committerDanny Baumann <dannybaumann@web.de>2013-01-18 09:27:54 +0100
commit1dc51e521b868dcda27fad1d93a041913b0efa38 (patch)
tree84a66c94e1f372990be3de05af2d831f0db2a3f9
parentf9c9c2850954bca9023993bf22a1355a40779835 (diff)
downloadpackages_apps_LockClock-1dc51e521b868dcda27fad1d93a041913b0efa38.zip
packages_apps_LockClock-1dc51e521b868dcda27fad1d93a041913b0efa38.tar.gz
packages_apps_LockClock-1dc51e521b868dcda27fad1d93a041913b0efa38.tar.bz2
Chronus: Optimize widget loading/display
Completely refactors the Calendar handling and changes the refresh interval from every minute to the minimum of either the next weather refresh or the next event start/end. It also now detects more events (new calendar entry, deleted calendar entry, location change, timezone change etc) Patch set 7 : Change the startup of the provider to only update the required panels on enabling the appwigdet and updates This further reduces the number of times the widget refreshes itself, now limited only to actual events Patch set 8 : Change the weather refresh time to be absolute, not relative to current time Patch set 9 : Refactor the calendar to use a CalendarInfo class that maintains a static list of events. This allows the refreshing of the lock screen widget without querying the calendar provider every time the screen turns on Patch set 10: Simplified onReceive loading/updating of widget with additional tweaks to handling deleted widgets and a fix to the refresh timer calculations including a check of the lookahead window. Patch set 11: Change the alarmservice to wake the CPU and do the update if the device is sleeping. This way, since the updates are so infrequent now, it makes sense to ensure things are updated (if needed) when the user turns the screen on after a while. Patch set 12: Change application ID to an ID registered for Chronus Patch set 13: Store the returned WOEID in sharedPreferences it if its valid and retrieve the previously stored one if not. This allows for the querying of weather data even though the Yahoo Placefinder service API limit has been exceeded and it returns an invalid XML result on geocode query. Patch set 14: Add a flag to determine when we want the real placefinder result or the cached result is also OK Patch set 15: Final comments and code formatting cleanup Patch set 16: Factor out weather fetching to separate service to decouple weather fetching from widget update. Also optimize a lot of code. Patch set 17: Fix alarms refresh and optimize calendar query and weather refresh behaviour. Patch set 18: Unify debug flags Change-Id: I0496dad356c92fb26ad7289268327b27b365b6cd
-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;
}
-
}