Back to project page Sunshine.
The source code is released under:
MIT License
If you think the Android project Sunshine listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package net.alteridem.sunshine.sync; // w ww.j a v a 2s. co m import android.accounts.Account; import android.accounts.AccountManager; import android.app.PendingIntent; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SyncRequest; import android.content.SyncResult; import android.database.Cursor; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.TaskStackBuilder; import android.util.Log; import net.alteridem.sunshine.MainActivity; import net.alteridem.sunshine.R; import net.alteridem.sunshine.Utility; import net.alteridem.sunshine.WeatherDataParser; import net.alteridem.sunshine.data.WeatherContract; import org.json.JSONException; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Date; public class WeatherSyncAdapter extends AbstractThreadedSyncAdapter { private static final String TAG = WeatherSyncAdapter.class.getSimpleName(); public static final int SYNC_INTERVAL = 60 * 180; public static final int SYNC_FLEXTIME = SYNC_INTERVAL / 3; private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24; private static final int WEATHER_NOTIFICATION_ID = 3004; private static final String[] NOTIFY_WEATHER_PROJECTION = new String[]{ WeatherContract.WeatherEntry.COLUMN_WEATHER_ID, WeatherContract.WeatherEntry.COLUMN_MAX_TEMP, WeatherContract.WeatherEntry.COLUMN_MIN_TEMP, WeatherContract.WeatherEntry.COLUMN_SHORT_DESC }; // these indices must match the projection private static final int INDEX_WEATHER_ID = 0; private static final int INDEX_MAX_TEMP = 1; private static final int INDEX_MIN_TEMP = 2; private static final int INDEX_SHORT_DESC = 3; public WeatherSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { Log.d(TAG, "onPerformSync started"); String location = Utility.getPreferredLocation(getContext()); int days = 14; // These two need to be declared outside the try/catch // so that they can be closed in the finally block. HttpURLConnection urlConnection = null; BufferedReader reader = null; // Will contain the raw JSON response as a string. String forecastJsonStr = null; try { // Construct the URL for the OpenWeatherMap query // Possible parameters are avaiable at OWM's forecast API page, at // http://openweathermap.org/API#forecast Uri.Builder builder = new Uri.Builder(); builder.scheme("http") .authority("api.openweathermap.org") .appendPath("data") .appendPath("2.5") .appendPath("forecast") .appendPath("daily") .appendQueryParameter("q", location) .appendQueryParameter("mode", "json") .appendQueryParameter("units", "metric") .appendQueryParameter("cnt", Integer.toString(days)); URL url = new URL(builder.build().toString()); // Create the request to OpenWeatherMap, and open the connection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET"); urlConnection.connect(); // Read the input stream into a String InputStream inputStream = urlConnection.getInputStream(); StringBuilder buffer = new StringBuilder(); if (inputStream == null) { // Nothing to do. return; } reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { // Since it's JSON, adding a newline isn't necessary (it won't affect parsing) // But it does make debugging a *lot* easier if you print out the completed // buffer for debugging. buffer.append(line); buffer.append("\n"); } if (buffer.length() == 0) { // Stream was empty. No point in parsing. return; } forecastJsonStr = buffer.toString(); } catch (IOException e) { Log.e(TAG, "Error ", e); // If the code didn't successfully get the weather data, there's no point in attempting // to parse it. return; } finally { if (urlConnection != null) { urlConnection.disconnect(); } if (reader != null) { try { reader.close(); } catch (final IOException e) { Log.e(TAG, "Error closing stream", e); } } } try { WeatherDataParser parser = new WeatherDataParser(getContext(), location); parser.getWeatherDataFromJson(forecastJsonStr, days); notifyWeather(); } catch (JSONException e) { Log.e(TAG, "Error, failed to parse JSON ", e); e.printStackTrace(); } } /** * Helper method to have the sync adapter sync immediately * * @param context The context used to access the account service */ public static void syncImmediately(Context context) { Bundle bundle = new Bundle(); bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); ContentResolver.requestSync(getSyncAccount(context), context.getString(R.string.content_authority), bundle); } /** * Helper method to get the fake account to be used with SyncAdapter, or make a new one * if the fake account doesn't exist yet. If we make a new account, we call the * onAccountCreated method so we can initialize things. * * @param context The context used to access the account service * @return a fake account. */ public static Account getSyncAccount(Context context) { // Get an instance of the Android account manager AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); // Create the account type and default account Account newAccount = new Account(context.getString(R.string.app_name), context.getString(R.string.sync_account_type)); // If the password doesn't exist, the account doesn't exist if (null == accountManager.getPassword(newAccount)) { /* * Add the account and account type, no password or user data * If successful, return the Account object, otherwise report an error. */ if (!accountManager.addAccountExplicitly(newAccount, "", null)) { return null; } /* * If you don't set android:syncable="true" in * in your <provider> element in the manifest, * then call ContentResolver.setIsSyncable(account, AUTHORITY, 1) * here. */ onAccountCreated(newAccount, context); } return newAccount; } /** * Helper method to schedule the sync adapter periodic execution */ public static void configurePeriodicSync(Context context, int syncInterval, int flexTime) { Account account = getSyncAccount(context); String authority = context.getString(R.string.content_authority); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // we can enable inexact timers in our periodic sync SyncRequest request = new SyncRequest.Builder() .syncPeriodic(syncInterval, flexTime) .setSyncAdapter(account, authority) .build(); ContentResolver.requestSync(request); } else { ContentResolver.addPeriodicSync(account, authority, new Bundle(), syncInterval); } } private static void onAccountCreated(Account newAccount, Context context) { /* * Since we've created an account */ WeatherSyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME); /* * Without calling setSyncAutomatically, our periodic sync will not be enabled. */ ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true); /* * Finally, let's do a sync to get things started */ syncImmediately(context); } public static void initializeSyncAdapter(Context context) { getSyncAccount(context); } private void notifyWeather() { Context context = getContext(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); // check if the user wants to see notifications String showNotificationsKey = context.getString(R.string.pref_notifications_key); boolean showNotifications = prefs.getBoolean(showNotificationsKey, true); if (!showNotifications) return; //checking the last update and notify if it' the first of the day String lastNotificationKey = context.getString(R.string.pref_last_notification); long lastSync = prefs.getLong(lastNotificationKey, 0); if (System.currentTimeMillis() - lastSync >= DAY_IN_MILLIS) { // Last sync was more than 1 day ago, let's send a notification with the weather. String locationQuery = Utility.getPreferredLocation(context); Uri weatherUri = WeatherContract.WeatherEntry.buildWeatherLocationWithDate(locationQuery, WeatherContract.getDbDateString(new Date())); // we'll query our contentProvider, as always Cursor cursor = context.getContentResolver().query(weatherUri, NOTIFY_WEATHER_PROJECTION, null, null, null); if (cursor.moveToFirst()) { int weatherId = cursor.getInt(INDEX_WEATHER_ID); double high = cursor.getDouble(INDEX_MAX_TEMP); double low = cursor.getDouble(INDEX_MIN_TEMP); String desc = cursor.getString(INDEX_SHORT_DESC); int iconId = Utility.getIconResourceForWeatherCondition(weatherId); int backgroundId = Utility.getBackgroundResourceForWeatherCondition(weatherId); String title = context.getString(R.string.app_name); boolean isMetric = Utility.isMetric(context); // Define the text of the forecast. String contentText = String.format(context.getString(R.string.format_notification), desc, Utility.formatTemperature(context, high, isMetric), Utility.formatTemperature(context, low, isMetric)); //build your notification here. // Creates an explicit intent for an Activity in your app Intent resultIntent = new Intent(context, MainActivity.class); // The stack builder object will contain an artificial back stack for the // started Activity. // This ensures that navigating backward from the Activity leads out of // your application to the Home screen. TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); // Adds the back stack for the Intent (but not the Intent itself) stackBuilder.addParentStack(MainActivity.class); // Adds the Intent that starts the Activity to the top of the stack stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent = stackBuilder.getPendingIntent( 0, PendingIntent.FLAG_UPDATE_CURRENT ); NotificationCompat.WearableExtender extender = new NotificationCompat.WearableExtender() .setHintHideIcon(true) .setBackground(BitmapFactory.decodeResource(context.getResources(), backgroundId)); NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(iconId) .setContentTitle(title) .setContentText(contentText) .setContentIntent(resultPendingIntent) .extend(extender); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); // WEATHER_NOTIFICATION_ID allows you to update the notification later on. notificationManager.notify(WEATHER_NOTIFICATION_ID, builder.build()); //refreshing last sync SharedPreferences.Editor editor = prefs.edit(); editor.putLong(lastNotificationKey, System.currentTimeMillis()); editor.commit(); } } } }