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