diff options
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; } - } |