diff --git a/app/build.gradle b/app/build.gradle index 23edbc7..80c4f79 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,7 +18,7 @@ android { } } buildTypes.each { - it.buildConfigField 'String', 'OPEN_WEATHER_MAP_API_KEY', '""' + it.buildConfigField 'String', 'OPEN_WEATHER_MAP_API_KEY', '"f230a5958a6234052143e3983e295d58"' } } @@ -33,4 +33,5 @@ dependencies { compile 'com.android.support:recyclerview-v7:23.1.1' compile 'com.google.android.gms:play-services-gcm:7.8.0' compile 'com.google.android.apps.muzei:muzei-api:2.0' + compile 'com.google.android.gms:play-services-wearable:7.8.0' } diff --git a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java index d90301c..2058138 100644 --- a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java +++ b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java @@ -36,11 +36,13 @@ import android.view.View; import com.example.android.sunshine.app.data.WeatherContract; import com.example.android.sunshine.app.sync.SunshineSyncAdapter; +import com.example.android.sunshine.app.sync.SunshineWearSyncHelper; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.gcm.GoogleCloudMessaging; import java.io.IOException; +import java.util.Random; public class MainActivity extends AppCompatActivity implements ForecastFragment.Callback { @@ -145,6 +147,11 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment. if (id == R.id.action_settings) { startActivity(new Intent(this, SettingsActivity.class)); return true; + } else if(id == R.id.syncWear) { + // TODO remove + SunshineWearSyncHelper helper = new SunshineWearSyncHelper(); + Random rnd = new Random(); + helper.updateWear(this, rnd.nextInt(100), rnd.nextInt(100), R.drawable.art_fog); } return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/com/example/android/sunshine/app/sync/SunshineSyncAdapter.java b/app/src/main/java/com/example/android/sunshine/app/sync/SunshineSyncAdapter.java index 7a4be1c..5651fb9 100644 --- a/app/src/main/java/com/example/android/sunshine/app/sync/SunshineSyncAdapter.java +++ b/app/src/main/java/com/example/android/sunshine/app/sync/SunshineSyncAdapter.java @@ -59,12 +59,12 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { // Interval at which to sync with the weather, in seconds. // 60 seconds (1 minute) * 180 = 3 hours public static final int SYNC_INTERVAL = 60 * 180; - public static final int SYNC_FLEXTIME = SYNC_INTERVAL/3; + 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[] { + private static final String[] NOTIFY_WEATHER_PROJECTION = new String[]{ WeatherContract.WeatherEntry.COLUMN_WEATHER_ID, WeatherContract.WeatherEntry.COLUMN_MAX_TEMP, WeatherContract.WeatherEntry.COLUMN_MIN_TEMP, @@ -78,8 +78,9 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { private static final int INDEX_SHORT_DESC = 3; @Retention(RetentionPolicy.SOURCE) - @IntDef({LOCATION_STATUS_OK, LOCATION_STATUS_SERVER_DOWN, LOCATION_STATUS_SERVER_INVALID, LOCATION_STATUS_UNKNOWN, LOCATION_STATUS_INVALID}) - public @interface LocationStatus {} + @IntDef({LOCATION_STATUS_OK, LOCATION_STATUS_SERVER_DOWN, LOCATION_STATUS_SERVER_INVALID, LOCATION_STATUS_UNKNOWN, LOCATION_STATUS_INVALID}) + public @interface LocationStatus { + } public static final int LOCATION_STATUS_OK = 0; public static final int LOCATION_STATUS_SERVER_DOWN = 1; @@ -186,7 +187,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { /** * Take the String representing the complete forecast in JSON Format and * pull out the data we need to construct the Strings needed for the wireframes. - * + *
* Fortunately parsing is easy: constructor takes the JSON string and converts it * into an Object hierarchy for us. */ @@ -232,7 +233,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { JSONObject forecastJson = new JSONObject(forecastJsonStr); // do we have an error? - if ( forecastJson.has(OWM_MESSAGE_CODE) ) { + if (forecastJson.has(OWM_MESSAGE_CODE)) { int errorCode = forecastJson.getInt(OWM_MESSAGE_CODE); switch (errorCode) { @@ -278,7 +279,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { // now we work exclusively in UTC dayTime = new Time(); - for(int i = 0; i < weatherArray.length(); i++) { + for (int i = 0; i < weatherArray.length(); i++) { // These are the values that will be collected. long dateTime; double pressure; @@ -296,7 +297,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { JSONObject dayForecast = weatherArray.getJSONObject(i); // Cheating to convert this to UTC time, which is what we want anyhow - dateTime = dayTime.setJulianDay(julianStartDay+i); + dateTime = dayTime.setJulianDay(julianStartDay + i); pressure = dayForecast.getDouble(OWM_PRESSURE); humidity = dayForecast.getInt(OWM_HUMIDITY); @@ -334,7 +335,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { int inserted = 0; // add to database - if ( cVVector.size() > 0 ) { + if (cVVector.size() > 0) { ContentValues[] cvArray = new ContentValues[cVVector.size()]; cVVector.toArray(cvArray); getContext().getContentResolver().bulkInsert(WeatherContract.WeatherEntry.CONTENT_URI, cvArray); @@ -342,11 +343,12 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { // delete old data so we don't build up an endless history getContext().getContentResolver().delete(WeatherContract.WeatherEntry.CONTENT_URI, WeatherContract.WeatherEntry.COLUMN_DATE + " <= ?", - new String[] {Long.toString(dayTime.setJulianDay(julianStartDay-1))}); + new String[]{Long.toString(dayTime.setJulianDay(julianStartDay - 1))}); updateWidgets(); updateMuzei(); notifyWeather(); + syncWearable(); } Log.d(LOG_TAG, "Sync Complete. " + cVVector.size() + " Inserted"); setLocationStatus(getContext(), LOCATION_STATUS_OK); @@ -376,6 +378,24 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { } } + private void syncWearable() { + String locationQuery = Utility.getPreferredLocation(getContext()); + Uri weatherUri = WeatherContract.WeatherEntry.buildWeatherLocationWithDate(locationQuery, System.currentTimeMillis()); + Cursor cursor = getContext().getContentResolver().query(weatherUri, NOTIFY_WEATHER_PROJECTION, null, null, null); + + try { + if (cursor.moveToFirst()) { + // we only show ints on the wear + int highestTemp = (int) cursor.getDouble(INDEX_MAX_TEMP); + int lowestTemp = (int) cursor.getDouble(INDEX_MIN_TEMP); + int weatherIconId = Utility.getArtResourceForWeatherCondition(cursor.getInt(INDEX_WEATHER_ID)); + new SunshineWearSyncHelper().updateWear(getContext(), highestTemp, lowestTemp, weatherIconId); + } + } finally { + cursor.close(); + } + } + private void notifyWeather() { Context context = getContext(); //checking the last update and notify if it' the first of the day @@ -384,7 +404,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { boolean displayNotifications = prefs.getBoolean(displayNotificationsKey, Boolean.parseBoolean(context.getString(R.string.pref_enable_notifications_default))); - if ( displayNotifications ) { + if (displayNotifications) { String lastNotificationKey = context.getString(R.string.pref_last_notification); long lastSync = prefs.getLong(lastNotificationKey, 0); @@ -487,9 +507,9 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { * Helper method to handle insertion of a new location in the weather database. * * @param locationSetting The location string used to request updates from the server. - * @param cityName A human-readable city name, e.g "Mountain View" - * @param lat the latitude of the city - * @param lon the longitude of the city + * @param cityName A human-readable city name, e.g "Mountain View" + * @param lat the latitude of the city + * @param lon the longitude of the city * @return the row ID of the added location. */ long addLocation(String locationSetting, String cityName, double lat, double lon) { @@ -554,6 +574,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { /** * 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) { @@ -582,7 +603,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { 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) ) { + if (null == accountManager.getPassword(newAccount)) { /* * Add the account and account type, no password or user data @@ -627,10 +648,11 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { /** * Sets the location status into shared preference. This function should not be called from * the UI thread because it uses commit to write to the shared preferences. - * @param c Context to get the PreferenceManager from. + * + * @param c Context to get the PreferenceManager from. * @param locationStatus The IntDef value to set */ - static private void setLocationStatus(Context c, @LocationStatus int locationStatus){ + static private void setLocationStatus(Context c, @LocationStatus int locationStatus) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(c); SharedPreferences.Editor spe = sp.edit(); spe.putInt(c.getString(R.string.pref_location_status_key), locationStatus); diff --git a/app/src/main/java/com/example/android/sunshine/app/sync/SunshineWearSyncHelper.java b/app/src/main/java/com/example/android/sunshine/app/sync/SunshineWearSyncHelper.java new file mode 100644 index 0000000..a077185 --- /dev/null +++ b/app/src/main/java/com/example/android/sunshine/app/sync/SunshineWearSyncHelper.java @@ -0,0 +1,89 @@ +package com.example.android.sunshine.app.sync; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.util.Log; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.wearable.Asset; +import com.google.android.gms.wearable.DataApi; +import com.google.android.gms.wearable.DataMap; +import com.google.android.gms.wearable.PutDataMapRequest; +import com.google.android.gms.wearable.PutDataRequest; +import com.google.android.gms.wearable.Wearable; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class SunshineWearSyncHelper implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + + public static final String KEY_HIGHEST_TEMP = "highestTemp"; + public static final String KEY_LOWEST_TEMP = "lowestTemp"; + public static final String KEY_WEATHER_ICON = "weatherIcon"; + + private GoogleApiClient mApiClient; + private PutDataRequest mDataRequest; + + public void updateWear(Context context, int highestTemp, int lowestTemp, int weatherIconResId) { + mDataRequest = buildRequest(context, highestTemp, lowestTemp, weatherIconResId); + mApiClient = new GoogleApiClient.Builder(context.getApplicationContext()) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .addApi(Wearable.API) + .build(); + mApiClient.connect(); + } + + /** + * Build the DataRequest + * @param highestTemp daily highest temperature + * @param lowestTemp daily lowest Temperature + * @return PutDataRequest with the given values in it + */ + private PutDataRequest buildRequest(Context context, int highestTemp, int lowestTemp, int weatherIconResId) { + PutDataMapRequest mapRequest = PutDataMapRequest.create("/dailyTemp"); + DataMap dMap = mapRequest.getDataMap(); + dMap.putInt(KEY_HIGHEST_TEMP, highestTemp); + dMap.putInt(KEY_LOWEST_TEMP, lowestTemp); + + try { + Bitmap weatherIcon = BitmapFactory.decodeResource(context.getResources(), weatherIconResId); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + weatherIcon.compress(Bitmap.CompressFormat.PNG, 100, stream); + stream.flush(); + byte[] byteArray = stream.toByteArray(); + Asset asset = Asset.createFromBytes(byteArray); + dMap.putAsset(KEY_WEATHER_ICON, asset); + } catch (IOException e) { + // should now happen + } + + return mapRequest.asPutDataRequest(); + } + + + @Override + public void onConnected(Bundle bundle) { + Log.d("SunshineWearSyncHelper", "Api connected."); + Wearable.DataApi.putDataItem(mApiClient, mDataRequest).setResultCallback(new ResultCallback