show weather icon
This commit is contained in:
@@ -18,7 +18,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildTypes.each {
|
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.android.support:recyclerview-v7:23.1.1'
|
||||||
compile 'com.google.android.gms:play-services-gcm:7.8.0'
|
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.apps.muzei:muzei-api:2.0'
|
||||||
|
compile 'com.google.android.gms:play-services-wearable:7.8.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,13 @@ import android.view.View;
|
|||||||
|
|
||||||
import com.example.android.sunshine.app.data.WeatherContract;
|
import com.example.android.sunshine.app.data.WeatherContract;
|
||||||
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
|
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.ConnectionResult;
|
||||||
import com.google.android.gms.common.GooglePlayServicesUtil;
|
import com.google.android.gms.common.GooglePlayServicesUtil;
|
||||||
import com.google.android.gms.gcm.GoogleCloudMessaging;
|
import com.google.android.gms.gcm.GoogleCloudMessaging;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements ForecastFragment.Callback {
|
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) {
|
if (id == R.id.action_settings) {
|
||||||
startActivity(new Intent(this, SettingsActivity.class));
|
startActivity(new Intent(this, SettingsActivity.class));
|
||||||
return true;
|
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);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
// Interval at which to sync with the weather, in seconds.
|
// Interval at which to sync with the weather, in seconds.
|
||||||
// 60 seconds (1 minute) * 180 = 3 hours
|
// 60 seconds (1 minute) * 180 = 3 hours
|
||||||
public static final int SYNC_INTERVAL = 60 * 180;
|
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 long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
|
||||||
private static final int WEATHER_NOTIFICATION_ID = 3004;
|
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_WEATHER_ID,
|
||||||
WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
|
WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
|
||||||
WeatherContract.WeatherEntry.COLUMN_MIN_TEMP,
|
WeatherContract.WeatherEntry.COLUMN_MIN_TEMP,
|
||||||
@@ -78,8 +78,9 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
private static final int INDEX_SHORT_DESC = 3;
|
private static final int INDEX_SHORT_DESC = 3;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({LOCATION_STATUS_OK, LOCATION_STATUS_SERVER_DOWN, LOCATION_STATUS_SERVER_INVALID, LOCATION_STATUS_UNKNOWN, LOCATION_STATUS_INVALID})
|
@IntDef({LOCATION_STATUS_OK, LOCATION_STATUS_SERVER_DOWN, LOCATION_STATUS_SERVER_INVALID, LOCATION_STATUS_UNKNOWN, LOCATION_STATUS_INVALID})
|
||||||
public @interface LocationStatus {}
|
public @interface LocationStatus {
|
||||||
|
}
|
||||||
|
|
||||||
public static final int LOCATION_STATUS_OK = 0;
|
public static final int LOCATION_STATUS_OK = 0;
|
||||||
public static final int LOCATION_STATUS_SERVER_DOWN = 1;
|
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
|
* 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.
|
* pull out the data we need to construct the Strings needed for the wireframes.
|
||||||
*
|
* <p/>
|
||||||
* Fortunately parsing is easy: constructor takes the JSON string and converts it
|
* Fortunately parsing is easy: constructor takes the JSON string and converts it
|
||||||
* into an Object hierarchy for us.
|
* into an Object hierarchy for us.
|
||||||
*/
|
*/
|
||||||
@@ -232,7 +233,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
JSONObject forecastJson = new JSONObject(forecastJsonStr);
|
JSONObject forecastJson = new JSONObject(forecastJsonStr);
|
||||||
|
|
||||||
// do we have an error?
|
// do we have an error?
|
||||||
if ( forecastJson.has(OWM_MESSAGE_CODE) ) {
|
if (forecastJson.has(OWM_MESSAGE_CODE)) {
|
||||||
int errorCode = forecastJson.getInt(OWM_MESSAGE_CODE);
|
int errorCode = forecastJson.getInt(OWM_MESSAGE_CODE);
|
||||||
|
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
@@ -278,7 +279,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
// now we work exclusively in UTC
|
// now we work exclusively in UTC
|
||||||
dayTime = new Time();
|
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.
|
// These are the values that will be collected.
|
||||||
long dateTime;
|
long dateTime;
|
||||||
double pressure;
|
double pressure;
|
||||||
@@ -296,7 +297,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
JSONObject dayForecast = weatherArray.getJSONObject(i);
|
JSONObject dayForecast = weatherArray.getJSONObject(i);
|
||||||
|
|
||||||
// Cheating to convert this to UTC time, which is what we want anyhow
|
// 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);
|
pressure = dayForecast.getDouble(OWM_PRESSURE);
|
||||||
humidity = dayForecast.getInt(OWM_HUMIDITY);
|
humidity = dayForecast.getInt(OWM_HUMIDITY);
|
||||||
@@ -334,7 +335,7 @@ public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||||||
|
|
||||||
int inserted = 0;
|
int inserted = 0;
|
||||||
// add to database
|
// add to database
|
||||||
if ( cVVector.size() > 0 ) {
|
if (cVVector.size() > 0) {
|
||||||
ContentValues[] cvArray = new ContentValues[cVVector.size()];
|
ContentValues[] cvArray = new ContentValues[cVVector.size()];
|
||||||
cVVector.toArray(cvArray);
|
cVVector.toArray(cvArray);
|
||||||
getContext().getContentResolver().bulkInsert(WeatherContract.WeatherEntry.CONTENT_URI, 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
|
// delete old data so we don't build up an endless history
|
||||||
getContext().getContentResolver().delete(WeatherContract.WeatherEntry.CONTENT_URI,
|
getContext().getContentResolver().delete(WeatherContract.WeatherEntry.CONTENT_URI,
|
||||||
WeatherContract.WeatherEntry.COLUMN_DATE + " <= ?",
|
WeatherContract.WeatherEntry.COLUMN_DATE + " <= ?",
|
||||||
new String[] {Long.toString(dayTime.setJulianDay(julianStartDay-1))});
|
new String[]{Long.toString(dayTime.setJulianDay(julianStartDay - 1))});
|
||||||
|
|
||||||
updateWidgets();
|
updateWidgets();
|
||||||
updateMuzei();
|
updateMuzei();
|
||||||
notifyWeather();
|
notifyWeather();
|
||||||
|
syncWearable();
|
||||||
}
|
}
|
||||||
Log.d(LOG_TAG, "Sync Complete. " + cVVector.size() + " Inserted");
|
Log.d(LOG_TAG, "Sync Complete. " + cVVector.size() + " Inserted");
|
||||||
setLocationStatus(getContext(), LOCATION_STATUS_OK);
|
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() {
|
private void notifyWeather() {
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
//checking the last update and notify if it' the first of the day
|
//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 displayNotifications = prefs.getBoolean(displayNotificationsKey,
|
||||||
Boolean.parseBoolean(context.getString(R.string.pref_enable_notifications_default)));
|
Boolean.parseBoolean(context.getString(R.string.pref_enable_notifications_default)));
|
||||||
|
|
||||||
if ( displayNotifications ) {
|
if (displayNotifications) {
|
||||||
|
|
||||||
String lastNotificationKey = context.getString(R.string.pref_last_notification);
|
String lastNotificationKey = context.getString(R.string.pref_last_notification);
|
||||||
long lastSync = prefs.getLong(lastNotificationKey, 0);
|
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.
|
* 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 locationSetting The location string used to request updates from the server.
|
||||||
* @param cityName A human-readable city name, e.g "Mountain View"
|
* @param cityName A human-readable city name, e.g "Mountain View"
|
||||||
* @param lat the latitude of the city
|
* @param lat the latitude of the city
|
||||||
* @param lon the longitude of the city
|
* @param lon the longitude of the city
|
||||||
* @return the row ID of the added location.
|
* @return the row ID of the added location.
|
||||||
*/
|
*/
|
||||||
long addLocation(String locationSetting, String cityName, double lat, double lon) {
|
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
|
* Helper method to have the sync adapter sync immediately
|
||||||
|
*
|
||||||
* @param context The context used to access the account service
|
* @param context The context used to access the account service
|
||||||
*/
|
*/
|
||||||
public static void syncImmediately(Context context) {
|
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));
|
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 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
|
* 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
|
* 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.
|
* 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
|
* @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 sp = PreferenceManager.getDefaultSharedPreferences(c);
|
||||||
SharedPreferences.Editor spe = sp.edit();
|
SharedPreferences.Editor spe = sp.edit();
|
||||||
spe.putInt(c.getString(R.string.pref_location_status_key), locationStatus);
|
spe.putInt(c.getString(R.string.pref_location_status_key), locationStatus);
|
||||||
|
|||||||
@@ -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<DataApi.DataItemResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(DataApi.DataItemResult dataItemResult) {
|
||||||
|
Log.d("SunshineWearSyncHelper", "Data item synced: " + dataItemResult.getDataItem().getUri());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionSuspended(int i) {
|
||||||
|
Log.d("SunshineWearSyncHelper", "Api connection suspended. " + i );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||||
|
Log.d("SunshineWearSyncHelper", "Api connection failed. " + connectionResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,4 +21,9 @@
|
|||||||
android:title="@string/action_settings"
|
android:title="@string/action_settings"
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
<item android:id="@+id/syncWear"
|
||||||
|
android:title="Sync wear"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
app:showAsAction="never"
|
||||||
|
/>
|
||||||
</menu>
|
</menu>
|
||||||
@@ -3,9 +3,14 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://repos.zeroturnaround.com/nexus/content/repositories/zt-public-releases'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:1.5.0'
|
classpath 'com.android.tools.build:gradle:1.5.0'
|
||||||
|
// This does not break the build when Android Studio is missing the JRebel for Android plugin.
|
||||||
|
classpath 'com.zeroturnaround.jrebel.android:jr-android-gradle:1.0.+'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
// This does not break the build when Android Studio is missing the JRebel for Android plugin.
|
||||||
|
apply plugin: 'com.zeroturnaround.jrebel.android'
|
||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -6,7 +8,7 @@ android {
|
|||||||
buildToolsVersion "23.0.2"
|
buildToolsVersion "23.0.2"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.example.android.sunshine.wear"
|
applicationId "com.example.android.sunshine.app"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
versionCode 1
|
versionCode 1
|
||||||
|
|||||||
@@ -21,17 +21,35 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.support.wearable.watchface.CanvasWatchFaceService;
|
import android.support.wearable.watchface.CanvasWatchFaceService;
|
||||||
import android.support.wearable.watchface.WatchFaceStyle;
|
import android.support.wearable.watchface.WatchFaceStyle;
|
||||||
import android.text.format.Time;
|
import android.text.format.Time;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
|
import com.google.android.gms.common.api.GoogleApiClient;
|
||||||
|
import com.google.android.gms.common.api.PendingResult;
|
||||||
|
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.DataEvent;
|
||||||
|
import com.google.android.gms.wearable.DataEventBuffer;
|
||||||
|
import com.google.android.gms.wearable.DataItem;
|
||||||
|
import com.google.android.gms.wearable.DataMap;
|
||||||
|
import com.google.android.gms.wearable.DataMapItem;
|
||||||
|
import com.google.android.gms.wearable.Wearable;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -78,13 +96,16 @@ public class SunshineWatchFaceService extends CanvasWatchFaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SunshineWatchFaceEngine extends CanvasWatchFaceService.Engine {
|
private class SunshineWatchFaceEngine extends CanvasWatchFaceService.Engine implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, DataApi.DataListener {
|
||||||
final Handler mUpdateTimeHandler = new EngineHandler(this);
|
final Handler mUpdateTimeHandler = new EngineHandler(this);
|
||||||
WatchFaceDrawHelper mDrawerHelper;
|
WatchFaceDrawHelper mDrawerHelper;
|
||||||
boolean mRegisteredTimeZoneReceiver = false;
|
boolean mRegisteredTimeZoneReceiver = false;
|
||||||
|
|
||||||
boolean mAmbient;
|
boolean mAmbient;
|
||||||
Time mTime;
|
Time mTime;
|
||||||
|
int tempHigh, tempLow;
|
||||||
|
Bitmap weatherIcon;
|
||||||
|
|
||||||
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
|
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@@ -99,6 +120,8 @@ public class SunshineWatchFaceService extends CanvasWatchFaceService {
|
|||||||
*/
|
*/
|
||||||
boolean mLowBitAmbient;
|
boolean mLowBitAmbient;
|
||||||
|
|
||||||
|
GoogleApiClient mApiClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(SurfaceHolder holder) {
|
public void onCreate(SurfaceHolder holder) {
|
||||||
super.onCreate(holder);
|
super.onCreate(holder);
|
||||||
@@ -111,6 +134,13 @@ public class SunshineWatchFaceService extends CanvasWatchFaceService {
|
|||||||
.build());
|
.build());
|
||||||
|
|
||||||
mTime = new Time();
|
mTime = new Time();
|
||||||
|
|
||||||
|
mApiClient = new GoogleApiClient.Builder(getApplicationContext())
|
||||||
|
.addApi(Wearable.API)
|
||||||
|
.addConnectionCallbacks(this)
|
||||||
|
.addOnConnectionFailedListener(this)
|
||||||
|
.build();
|
||||||
|
mApiClient.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -192,11 +222,10 @@ public class SunshineWatchFaceService extends CanvasWatchFaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDraw(Canvas canvas, Rect bounds) {
|
public void onDraw(Canvas canvas, Rect bounds) {
|
||||||
mTime.setToNow();
|
mTime.setToNow();
|
||||||
mDrawerHelper.draw(canvas, bounds, mTime, 25, 16);
|
mDrawerHelper.draw(canvas, bounds, mTime, tempHigh, tempLow, weatherIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,5 +259,62 @@ public class SunshineWatchFaceService extends CanvasWatchFaceService {
|
|||||||
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
|
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnected(Bundle bundle) {
|
||||||
|
Log.d("SunshineWatchFaceSync", "connected");
|
||||||
|
Wearable.DataApi.addListener(mApiClient, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionSuspended(int i) {
|
||||||
|
Log.d("SunshineWatchFaceSync", "connection suspended: " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||||
|
Log.d("SunshineWatchFaceSync",
|
||||||
|
"connection failed: " + connectionResult.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String KEY_HIGHEST_TEMP = "highestTemp";
|
||||||
|
public static final String KEY_LOWEST_TEMP = "lowestTemp";
|
||||||
|
public static final String KEY_WEATHER_ICON = "weatherIcon";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||||
|
Log.d("SunshineWatchFaceSync", "data changed");
|
||||||
|
for (DataEvent event : dataEvents) {
|
||||||
|
if (event.getType() == DataEvent.TYPE_CHANGED) {
|
||||||
|
// DataItem changed
|
||||||
|
DataItem item = event.getDataItem();
|
||||||
|
if (item.getUri().getPath().compareTo("/dailyTemp") == 0) {
|
||||||
|
DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
|
||||||
|
tempHigh = dataMap.getInt(KEY_HIGHEST_TEMP);
|
||||||
|
tempLow = dataMap.getInt(KEY_LOWEST_TEMP);
|
||||||
|
|
||||||
|
|
||||||
|
GoogleApiClient client = mApiClient;
|
||||||
|
Asset asset = dataMap.getAsset(KEY_WEATHER_ICON);
|
||||||
|
|
||||||
|
PendingResult<DataApi.GetFdForAssetResult> pendingResult = Wearable.DataApi.getFdForAsset(client, asset);
|
||||||
|
pendingResult.setResultCallback(new ResultCallback<DataApi.GetFdForAssetResult>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(DataApi.GetFdForAssetResult assetResult) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inSampleSize = 6; // TODO calculate this
|
||||||
|
InputStream assetInputStream = assetResult.getInputStream();
|
||||||
|
weatherIcon = BitmapFactory.decodeStream(assetInputStream, null, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Log.d("SunshineWatchFaceSync", "new dataitem: highest: " + tempHigh + " lowest: " + tempLow);
|
||||||
|
}
|
||||||
|
} else if (event.getType() == DataEvent.TYPE_DELETED) {
|
||||||
|
Log.d("SunshineWatchFaceSync", "dailyTemp deleted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.android.sunshine.wear;
|
package com.example.android.sunshine.wear;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@@ -23,23 +24,30 @@ public class WatchFaceDrawHelper {
|
|||||||
private Paint mDateTextPaint;
|
private Paint mDateTextPaint;
|
||||||
private Paint mHighestTempPaint;
|
private Paint mHighestTempPaint;
|
||||||
private Paint mLowestTempPaint;
|
private Paint mLowestTempPaint;
|
||||||
|
private Paint mIconPaint;
|
||||||
|
|
||||||
float timeXOffset;
|
float mTimeXOffset;
|
||||||
float timeYOffset;
|
float mTimeYOffset;
|
||||||
float mDateXOffset;
|
float mDateXOffset;
|
||||||
float mDateYOffset;
|
float mDateYOffset;
|
||||||
float mWeatherYOffset;
|
float mTempYOffset;
|
||||||
float mWeatherXOffset;
|
float mWeatherIconXOffset;
|
||||||
float mWeatherXOffset2;
|
float mWeatherIconYOffset;
|
||||||
|
float mTempHighXOffset;
|
||||||
|
float mTempLowXOffset;
|
||||||
|
|
||||||
public WatchFaceDrawHelper(Resources resources, boolean isRound) {
|
public WatchFaceDrawHelper(Resources resources, boolean isRound) {
|
||||||
timeYOffset = resources.getDimension(R.dimen.time_y_offset);
|
mTimeYOffset = resources.getDimension(R.dimen.time_y_offset);
|
||||||
mDateYOffset = resources.getDimension(R.dimen.date_y_offset);
|
mDateYOffset = resources.getDimension(R.dimen.date_y_offset);
|
||||||
mWeatherYOffset = resources.getDimension(R.dimen.weather_y_offset);
|
mTempYOffset = resources.getDimension(R.dimen.temp_y_offset);
|
||||||
|
mWeatherIconYOffset = resources.getDimension(R.dimen.weather_icon_y_offset);
|
||||||
|
|
||||||
mBackgroundPaint = new Paint();
|
mBackgroundPaint = new Paint();
|
||||||
mBackgroundPaint.setColor(resources.getColor(R.color.background));
|
mBackgroundPaint.setColor(resources.getColor(R.color.background));
|
||||||
|
|
||||||
|
mIconPaint = new Paint();
|
||||||
|
// mIconPaint.setColor(resources.getColor(android.R.color.transparent));
|
||||||
|
|
||||||
timeTextPaint = createTextPaint(resources.getColor(R.color.textcolor_primary));
|
timeTextPaint = createTextPaint(resources.getColor(R.color.textcolor_primary));
|
||||||
mDateTextPaint = createTextPaint(resources.getColor(R.color.textcolor_secondary));
|
mDateTextPaint = createTextPaint(resources.getColor(R.color.textcolor_secondary));
|
||||||
|
|
||||||
@@ -47,14 +55,16 @@ public class WatchFaceDrawHelper {
|
|||||||
mHighestTempPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
|
mHighestTempPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
|
||||||
mLowestTempPaint = createTextPaint(resources.getColor(R.color.textcolor_primary));
|
mLowestTempPaint = createTextPaint(resources.getColor(R.color.textcolor_primary));
|
||||||
|
|
||||||
timeXOffset = resources.getDimension(isRound
|
mTimeXOffset = resources.getDimension(isRound
|
||||||
? R.dimen.time_x_offset_round : R.dimen.time_x_offset);
|
? R.dimen.time_x_offset_round : R.dimen.time_x_offset);
|
||||||
mDateXOffset = resources.getDimension(isRound
|
mDateXOffset = resources.getDimension(isRound
|
||||||
? R.dimen.date_x_offset_round : R.dimen.date_x_offset);
|
? R.dimen.date_x_offset_round : R.dimen.date_x_offset);
|
||||||
mWeatherXOffset = resources.getDimension(isRound
|
mWeatherIconXOffset = resources.getDimension(isRound
|
||||||
? R.dimen.weather_x_offset_round : R.dimen.weather_x_offset);
|
? R.dimen.weather_icon_x_offset_round : R.dimen.weather_icon_x_offset);
|
||||||
mWeatherXOffset2 = resources.getDimension(isRound
|
mTempHighXOffset = resources.getDimension(isRound
|
||||||
? R.dimen.weather_x_offset_round_2 : R.dimen.weather_x_offset_2);
|
? R.dimen.temp_high_x_offset_round : R.dimen.temp_high_x_offset);
|
||||||
|
mTempLowXOffset = resources.getDimension(isRound
|
||||||
|
? R.dimen.temp_low_x_offset_round : R.dimen.temp_low_x_offset);
|
||||||
|
|
||||||
float timeTextSize = resources.getDimension(isRound
|
float timeTextSize = resources.getDimension(isRound
|
||||||
? R.dimen.time_text_size_round : R.dimen.time_text_size);
|
? R.dimen.time_text_size_round : R.dimen.time_text_size);
|
||||||
@@ -91,7 +101,7 @@ public class WatchFaceDrawHelper {
|
|||||||
this.isLowBitAmbient = isLowBitAmbient;
|
this.isLowBitAmbient = isLowBitAmbient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(Canvas canvas, Rect bounds, Time time, int highestTemp, int lowestTemp) {
|
public void draw(Canvas canvas, Rect bounds, Time time, int highestTemp, int lowestTemp, Bitmap weatherIcon) {
|
||||||
// Draw the background.
|
// Draw the background.
|
||||||
if (isInAmbientMode) {
|
if (isInAmbientMode) {
|
||||||
canvas.drawColor(Color.BLACK);
|
canvas.drawColor(Color.BLACK);
|
||||||
@@ -104,17 +114,21 @@ public class WatchFaceDrawHelper {
|
|||||||
String timeText = isInAmbientMode
|
String timeText = isInAmbientMode
|
||||||
? String.format(TIME_FORMAT, time.hour, time.minute)
|
? String.format(TIME_FORMAT, time.hour, time.minute)
|
||||||
: String.format(TIME_FORMAT_AMBIENT, time.hour, time.minute, time.second);
|
: String.format(TIME_FORMAT_AMBIENT, time.hour, time.minute, time.second);
|
||||||
canvas.drawText(timeText, timeXOffset, timeYOffset, timeTextPaint);
|
canvas.drawText(timeText, mTimeXOffset, mTimeYOffset, timeTextPaint);
|
||||||
|
|
||||||
// Draw current date
|
// Draw current date
|
||||||
String dateText = time.format(DATE_FORMAT);
|
String dateText = time.format(DATE_FORMAT);
|
||||||
canvas.drawText(dateText, mDateXOffset, mDateYOffset, mDateTextPaint);
|
canvas.drawText(dateText, mDateXOffset, mDateYOffset, mDateTextPaint);
|
||||||
|
|
||||||
// Draw weather
|
// Draw weather
|
||||||
String currentHigh = String.format(TEMP_FORMAT, highestTemp);
|
if(!(highestTemp == 0 && lowestTemp == 0)) {
|
||||||
canvas.drawText(currentHigh, mWeatherXOffset, mWeatherYOffset, mHighestTempPaint);
|
String currentHigh = String.format(TEMP_FORMAT, highestTemp);
|
||||||
String currentLow = String.format(TEMP_FORMAT, lowestTemp);
|
canvas.drawText(currentHigh, mTempHighXOffset, mTempYOffset, mHighestTempPaint);
|
||||||
canvas.drawText(currentLow, mWeatherXOffset + mWeatherXOffset2, mWeatherYOffset, mLowestTempPaint);
|
String currentLow = String.format(TEMP_FORMAT, lowestTemp);
|
||||||
|
canvas.drawText(currentLow, mTempLowXOffset, mTempYOffset, mLowestTempPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(weatherIcon != null)
|
||||||
|
canvas.drawBitmap(weatherIcon, mWeatherIconXOffset, mWeatherIconYOffset, mIconPaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,17 @@
|
|||||||
<dimen name="weather_text_size_round">25dp</dimen>
|
<dimen name="weather_text_size_round">25dp</dimen>
|
||||||
<dimen name="weather_text_size_2">15dp</dimen>
|
<dimen name="weather_text_size_2">15dp</dimen>
|
||||||
<dimen name="weather_text_size_round_2">20dp</dimen>
|
<dimen name="weather_text_size_round_2">20dp</dimen>
|
||||||
<dimen name="weather_x_offset">45dp</dimen>
|
|
||||||
<dimen name="weather_x_offset_round">55dp</dimen>
|
|
||||||
<dimen name="weather_x_offset_2">70dp</dimen>
|
|
||||||
<dimen name="weather_x_offset_round_2">100dp</dimen>
|
|
||||||
<dimen name="weather_y_offset">140dp</dimen>
|
|
||||||
|
|
||||||
<dimen name="weather_indicator_size">40dp</dimen>
|
<dimen name="weather_icon_x_offset">15dp</dimen>
|
||||||
|
<dimen name="weather_icon_x_offset_round">55dp</dimen>
|
||||||
|
<dimen name="weather_icon_y_offset">120dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="temp_high_x_offset">70dp</dimen>
|
||||||
|
<dimen name="temp_high_x_offset_round">100dp</dimen>
|
||||||
|
<dimen name="temp_low_x_offset">125dp</dimen>
|
||||||
|
<dimen name="temp_low_x_offset_round">140dp</dimen>
|
||||||
|
<dimen name="temp_y_offset">140dp</dimen>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user