/* * Android Weather Notification. * Copyright (C) 2010 Denis Nelubin aka Gelin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * http://gelin.ru * mailto:den@gelin.ru */ package ru.gelin.android.weather.notification.app; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; import ru.gelin.android.weather.Location; import ru.gelin.android.weather.Weather; import ru.gelin.android.weather.WeatherSource; import ru.gelin.android.weather.notification.R; import ru.gelin.android.weather.notification.WeatherStorage; import ru.gelin.android.weather.notification.skin.WeatherNotificationManager; import ru.gelin.android.weather.openweathermap.AndroidOpenWeatherMapLocation; import ru.gelin.android.weather.openweathermap.NameOpenWeatherMapLocation; import ru.gelin.android.weather.openweathermap.OpenWeatherMapSource; import java.util.Date; import static ru.gelin.android.weather.notification.AppUtils.EXTRA_FORCE; import static ru.gelin.android.weather.notification.AppUtils.EXTRA_VERBOSE; import static ru.gelin.android.weather.notification.PreferenceKeys.ENABLE_NOTIFICATION; import static ru.gelin.android.weather.notification.PreferenceKeys.ENABLE_NOTIFICATION_DEFAULT; import static ru.gelin.android.weather.notification.app.PreferenceKeys.*; import static ru.gelin.android.weather.notification.app.Tag.TAG; /** * Service to update weather. * Just start it. The new weather values will be wrote to SharedPreferences * (use {@link ru.gelin.android.weather.notification.WeatherStorage} to extract them). */ public class UpdateService extends Service implements Runnable { /** Success update message */ static final int SUCCESS = 0; /** Failure update message */ static final int FAILURE = 1; /** Update message when location is unknown */ static final int UNKNOWN_LOCATION = 2; /** Update message when querying new location */ static final int QUERY_LOCATION = 3; /** * Lock used when maintaining update thread. */ private static Object staticLock = new Object(); /** * Flag if there is an update thread already running. We only launch a new * thread if one isn't already running. */ static boolean threadRunning = false; /** Verbose flag */ boolean verbose = false; /** Force flag */ boolean force = false; /** Queried location */ Location location; /** Updated weather */ Weather weather; /** Weather update error */ Exception updateError; /** Intent which starts the service */ Intent startIntent; @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); synchronized(this) { this.startIntent = intent; if (intent != null) { this.verbose = intent.getBooleanExtra(EXTRA_VERBOSE, false); this.force = intent.getBooleanExtra(EXTRA_FORCE, false); } } removeLocationUpdates(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); WeatherStorage storage = new WeatherStorage(UpdateService.this); Weather weather = storage.load(); long lastUpdate = weather.getTime().getTime(); boolean notificationEnabled = preferences.getBoolean( ENABLE_NOTIFICATION, ENABLE_NOTIFICATION_DEFAULT); scheduleNextRun(lastUpdate); synchronized(staticLock) { if (threadRunning) { return; // only start processing thread if not already running } if (!force && !notificationEnabled) { skipUpdate(storage, "skipping update, notification disabled"); return; } if (!force && !isExpired(lastUpdate)) { skipUpdate(storage, "skipping update, not expired"); return; } if (!isNetworkAvailable()) { //no network skipUpdate(storage, "skipping update, no network"); if (verbose) { Toast.makeText(UpdateService.this, getString(R.string.weather_update_no_network), Toast.LENGTH_LONG).show(); } return; } if (!threadRunning) { threadRunning = true; new Thread(this).start(); } } } void skipUpdate(WeatherStorage storage, String logMessage) { stopSelf(); Log.d(TAG, logMessage); storage.updateTime(); WeatherNotificationManager.update(this); } //@Override public void run() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); Location location = null; LocationType locationType = getLocationType(); if (LocationType.LOCATION_MANUAL.equals(locationType)) { location = createSearchLocation(preferences.getString(LOCATION, LOCATION_DEFAULT)); } else { location = queryLocation(locationType.getLocationProvider()); if (location == null) { internalHandler.sendEmptyMessage(QUERY_LOCATION); return; } } synchronized(this) { this.location = location; } if (location == null || location.isEmpty()) { internalHandler.sendEmptyMessage(UNKNOWN_LOCATION); return; } WeatherSource source = new OpenWeatherMapSource(this); try { Weather weather = source.query(location); synchronized(this) { this.weather = weather; } internalHandler.sendEmptyMessage(SUCCESS); } catch (Exception e) { synchronized(this) { this.updateError = e; } internalHandler.sendEmptyMessage(FAILURE); } } /** * Handles weather update result. */ final Handler internalHandler = new Handler() { @Override public void handleMessage(Message msg) { synchronized(staticLock) { threadRunning = false; } WeatherStorage storage = new WeatherStorage(UpdateService.this); switch (msg.what) { case SUCCESS: synchronized(UpdateService.this) { Log.i(TAG, "received weather: " + weather.getLocation().getText() + " " + weather.getTime()); if (weather.isEmpty()) { storage.updateTime(); } else { storage.save(weather); //saving only non-empty weather } scheduleNextRun(weather.getTime().getTime()); if (verbose && weather.isEmpty()) { Toast.makeText(UpdateService.this, getString(R.string.weather_update_empty, location.getText()), Toast.LENGTH_LONG).show(); } } break; case FAILURE: synchronized(UpdateService.this) { Log.w(TAG, "failed to update weather", updateError); storage.updateTime(); if (verbose) { Toast.makeText(UpdateService.this, getString(R.string.weather_update_failed, updateError.getMessage()), Toast.LENGTH_LONG).show(); } } break; case UNKNOWN_LOCATION: synchronized(UpdateService.this) { Log.w(TAG, "failed to get location"); storage.updateTime(); if (verbose) { Toast.makeText(UpdateService.this, getString(R.string.weather_update_unknown_location), Toast.LENGTH_LONG).show(); } } break; case QUERY_LOCATION: synchronized(UpdateService.this) { Log.d(TAG, "quering new location"); //storage.updateTime(); //don't signal about update } break; } WeatherNotificationManager.update(UpdateService.this); stopSelf(); } }; /** * Check availability of network connections. * Returns true if any network connection is available. */ boolean isNetworkAvailable() { ConnectivityManager manager = (ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE); NetworkInfo info = manager.getActiveNetworkInfo(); if (info == null) { return false; } return info.isAvailable(); } /** * Returns true if the last update time is expired. * @param timestamp timestamp value to check */ boolean isExpired(long timestamp) { long now = System.currentTimeMillis(); RefreshInterval interval = getRefreshInterval(); return timestamp + interval.getInterval() < now; } void scheduleNextRun(long lastUpdate) { long now = System.currentTimeMillis(); RefreshInterval interval = getRefreshInterval(); long nextUpdate = lastUpdate + interval.getInterval(); if (nextUpdate <= now) { nextUpdate = now + interval.getInterval(); } PendingIntent pendingIntent = getPendingIntent(null); //don't inherit extra flags boolean notificationEnabled = PreferenceManager.getDefaultSharedPreferences(this). getBoolean(ENABLE_NOTIFICATION, ENABLE_NOTIFICATION_DEFAULT); AlarmManager alarmManager = (AlarmManager)getSystemService( Context.ALARM_SERVICE); if (notificationEnabled) { Log.d(TAG, "scheduling update to " + new Date(nextUpdate)); alarmManager.set(AlarmManager.RTC, nextUpdate, pendingIntent); } else { Log.d(TAG, "cancelling update schedule"); alarmManager.cancel(pendingIntent); } } RefreshInterval getRefreshInterval() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); return RefreshInterval.valueOf(preferences.getString( REFRESH_INTERVAL, REFRESH_INTERVAL_DEFAULT)); } LocationType getLocationType() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); return LocationType.valueOf(preferences.getString( LOCATION_TYPE, LOCATION_TYPE_DEFAULT)); } /** * Queries current location using android services. */ Location queryLocation(String locationProvider) { LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); if (manager == null) { return null; } android.location.Location androidLocation = manager.getLastKnownLocation(locationProvider); if (androidLocation == null || isExpired(androidLocation.getTime())) { try { Log.d(TAG, "requested location update from " + locationProvider); manager.requestLocationUpdates(locationProvider, 0, 0, getPendingIntent(this.startIntent)); //try to update immediately return null; } catch (IllegalArgumentException e) { return null; //no location provider } } return new AndroidOpenWeatherMapLocation(androidLocation); } /** * Creates the location to search the location by the entered string. */ Location createSearchLocation(String query) { LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); if (manager == null) { return new NameOpenWeatherMapLocation(query, null); } android.location.Location androidLocation = manager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); return new NameOpenWeatherMapLocation(query, androidLocation); } /** * Unsubscribes from location updates. */ void removeLocationUpdates() { if (this.startIntent != null && this.startIntent.hasExtra(LocationManager.KEY_LOCATION_CHANGED)) { Log.d(TAG, "location updated"); } LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); manager.removeUpdates(getPendingIntent(this.startIntent)); } /** * Returns pending intent to start the service. * @param intent to wrap into the pending intent or null */ PendingIntent getPendingIntent(Intent intent) { Intent serviceIntent; if (intent == null) { serviceIntent = new Intent(this, UpdateService.class); } else { serviceIntent = new Intent(intent); } return PendingIntent.getService(this, 0, serviceIntent, PendingIntent.FLAG_UPDATE_CURRENT); } @Override public IBinder onBind(Intent intent) { return null; } }