From a0d0c451cee866149283d18cd62a487201759d4a Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Wed, 8 Dec 2021 17:53:20 -0500 Subject: [PATCH 01/11] Start converting API calls to OpenWeatherMap Add new classes based on WUnderground and ForecastIO Add new constants Add new method to build request URLs Begin converting existing methods to use OpenWeatherMap Add new relevant Javadoc comments --- .../OpenWeatherMapLocationJsonProcessor.java | 40 +++ .../openweathermap/OpenWeatherMapService.java | 174 +++++++++ .../OpenWeatherMapWeatherJsonProcessor.java | 340 ++++++++++++++++++ 3 files changed, 554 insertions(+) create mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapLocationJsonProcessor.java create mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java create mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapLocationJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapLocationJsonProcessor.java new file mode 100644 index 0000000..4b5e7ff --- /dev/null +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapLocationJsonProcessor.java @@ -0,0 +1,40 @@ +package com.sdl.mobileweather.openweathermap; + +import com.sdl.mobileweather.artifact.GPSLocation; +import com.sdl.mobileweather.artifact.WeatherLocation; +import com.sdl.mobileweather.location.LocationJsonProcessor; + +import org.json.JSONException; +import org.json.JSONObject; + +//TODO - Convert WUnderground specific code to OpenWeatherMap specific code +public class OpenWeatherMapLocationJsonProcessor implements LocationJsonProcessor { + private static final String LOCATION = "location"; + private static final String STATE = "state"; + private static final String CITY = "city"; + private static final String LATITUDE = "lat"; + private static final String LONGITUDE = "lon"; + private static final String ZIPCODE = "zip"; + + @Override + public WeatherLocation getLocation(JSONObject jsonRoot) { + WeatherLocation location = null; + + if (jsonRoot != null) { + location = new WeatherLocation(); + try { + JSONObject jsonLocation = jsonRoot.getJSONObject(LOCATION); + location.state = jsonLocation.getString(STATE); + location.city = jsonLocation.getString(CITY); + location.zipCode = jsonLocation.getString(ZIPCODE); + location.gpsLocation = new GPSLocation(); + location.gpsLocation.latitude = jsonLocation.getString(LATITUDE); + location.gpsLocation.longitude = jsonLocation.getString(LONGITUDE); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return location; + } +} diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java new file mode 100644 index 0000000..f7a42c5 --- /dev/null +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java @@ -0,0 +1,174 @@ +package com.sdl.mobileweather.openweathermap; + +import android.content.Intent; +import android.net.Uri; +import android.util.Log; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import com.sdl.mobileweather.artifact.WeatherLocation; +import com.sdl.mobileweather.connection.HttpConnection; +import com.sdl.mobileweather.processor.JsonProcessor; +import com.sdl.mobileweather.smartdevicelink.SdlApplication; +import com.sdl.mobileweather.weather.Forecast; +import com.sdl.mobileweather.weather.WeatherAlert; +import com.sdl.mobileweather.weather.WeatherConditions; +import com.sdl.mobileweather.weather.WeatherService; + +import org.json.JSONObject; + +import java.net.MalformedURLException; +import java.net.URL; + +//TODO - Convert WUnderground and ForecastIo specific code to OpenWeatherMap specific code +public class OpenWeatherMapService extends WeatherService { + + /** + * API key used for OpenWeatherMap API + *

+ * I acquired this free one for testing. We may want to use a different one for broader use. + *

+ * -Noah Stanford + */ + private static final String API_KEY = "e81bf4160a279541cdaff8a8fc4cdda1"; + private static final String URI_SCHEME = "https"; + /** + * Base request URL for OpenWeatherMap + */ + private static final String BASE_URL = "api.openweathermap.org"; + private static final String DATA_PATH = "data"; + /** + * A number value in the suggested API call to OpenWeatherMap + *

+ * I believe this value in the API call represents the version number of the API. + * It may need to change if OpenWeatherMap updates their API + *

+ * - Noah Stanford (noah@livio.io) + */ + private static final String VERSION = "2.5"; + /** + * This API call type provides current weather, minute forecast for 1 hour, hourly forecast for 48 hours, + * daily forecast for 7 days, global weather alerts and historical data for 5 previous day for any location + */ + private static final String ONE_CALL_API_CALL_TYPE = "onecall"; + private static final String LATITUDE_PARAMETER = "lat"; + private static final String LONGITUDE_PARAMETER = "lon"; + /** + * This parameter is used to specify the API key for the API call + */ + private static final String APP_ID_PARAMETER = "appid"; + /** + * This is 0 because it's the first index and only one URL is used for weather data at a time + */ + private static final int ONLY_URL_INDEX = 0; + private static final int HTTP_OK = 200; + + + public OpenWeatherMapService() { + super(); + mWeatherProcessor = new OpenWeatherMapWeatherJsonProcessor(); + } + + @Override + protected void updateWeatherData(URL... urls) { + HttpConnection httpRequest = new HttpConnection(); + JsonProcessor jsonProcessor = new JsonProcessor(); + WeatherConditions conditions = null; + Forecast[] forecast = null; + Forecast[] hourlyForecast = null; + WeatherAlert[] alerts = null; + String httpResult = null; + + Log.d("MobileWeather", "updateWeatherData"); + if (urls.length == 1 && mDataManager != null && mWeatherProcessor != null) { + LocalBroadcastManager lbManager = LocalBroadcastManager.getInstance(this); + Log.d("MobileWeather", "updateWeatherData - valid urls"); + if (urls[ONLY_URL_INDEX] != null) { + Log.d("MobileWeather", "updateWeatherData - getting json"); + httpResult = httpRequest.sendRequest(urls[ONLY_URL_INDEX], HttpConnection.RequestMethod.GET, null, "application/json"); + int statusCode = httpRequest.getStatusCode(httpResult); + if (statusCode == HTTP_OK) { + Log.d("MobileWeather", "updateWeatherData - parsing conditions json"); + JSONObject jsonRoot = jsonProcessor.getJsonFromString(httpResult); + if (jsonRoot != null) { + Log.d("MobileWeather", "updateWeatherData - parsing conditions"); + conditions = mWeatherProcessor.getWeatherConditions(jsonRoot); + mDataManager.setWeatherConditions(conditions); + if (conditions != null) { + Log.d("MobileWeather", "updateWeatherData - new conditions"); + Intent intent = new Intent("com.sdl.mobileweather.WeatherConditions"); + lbManager.sendBroadcast(intent); + } + + Log.d("MobileWeather", "updateWeatherData - parsing forecast"); + forecast = mWeatherProcessor.getForecast(jsonRoot); + mDataManager.setForecast(forecast); + if (forecast != null) { + Log.d("MobileWeather", "updateWeatherData - new forecast"); + Intent intent = new Intent("com.sdl.mobileweather.Forecast"); + lbManager.sendBroadcast(intent); + } + + Log.d("MobileWeather", "updateWeatherData - parsing hourly forecast"); + hourlyForecast = mWeatherProcessor.getHourlyForecast(jsonRoot); + mDataManager.setHourlyForecast(hourlyForecast); + if (hourlyForecast != null) { + Intent intent = new Intent("com.sdl.mobileweather.HourlyForecast"); + lbManager.sendBroadcast(intent); + } + + alerts = mWeatherProcessor.getAlerts(jsonRoot); + mDataManager.setAlerts(alerts); + Log.d("MobileWeather", "updateWeatherData - new Alerts"); + Intent intent = new Intent("com.sdl.mobileweather.Alerts"); + lbManager.sendBroadcast(intent); + } + + reportApiAvail(true); + + } else if (statusCode == -2){ + reportConnectionAvail(false); + }else{ + reportApiAvail(false); + } + } + + + WeatherLocation loc = mDataManager.getCurrentLocation(); + if (loc != null) { + mDataManager.setLastCity(loc.city); + mDataManager.setLastState(loc.state); + } + } + } + + /** + * Creates service specific weather data URLs. + */ + @Override + protected URL[] getURLs(WeatherLocation currentLocation) { + URL oneCallURL = null; + try { + oneCallURL = buildOneCallURL(currentLocation); + Log.d(SdlApplication.TAG, currentLocation.gpsLocation.latitude + "," + currentLocation.gpsLocation.longitude); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return new URL[] { oneCallURL }; + } + + private URL buildOneCallURL(WeatherLocation currentLocation) throws MalformedURLException { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(URI_SCHEME) + .authority(BASE_URL) + .appendPath(DATA_PATH) + .appendPath(VERSION) + .appendPath(ONE_CALL_API_CALL_TYPE) + .appendQueryParameter(LATITUDE_PARAMETER, currentLocation.gpsLocation.latitude) + .appendQueryParameter(LONGITUDE_PARAMETER, currentLocation.gpsLocation.longitude) + .appendQueryParameter(APP_ID_PARAMETER, API_KEY); + + Uri everythingUri = builder.build(); + return new URL(everythingUri.toString()); + } +} diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java new file mode 100644 index 0000000..f71292a --- /dev/null +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java @@ -0,0 +1,340 @@ +package com.sdl.mobileweather.openweathermap; + +import com.sdl.mobileweather.weather.Forecast; +import com.sdl.mobileweather.weather.WeatherAlert; +import com.sdl.mobileweather.weather.WeatherConditions; +import com.sdl.mobileweather.weather.WeatherJsonProcessor; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Calendar; +import java.util.Vector; + +//TODO - Convert WUnderground specific code to OpenWeatherMap specific code +public class OpenWeatherMapWeatherJsonProcessor implements WeatherJsonProcessor { + private static final String CURRENT_OBSERVATION = "current_observation"; + private static final String ICON_URL = "icon_url"; + private static final String WEATHER = "weather"; + private static final String TEMP_C = "temp_c"; + private static final String RELATIVE_HUMIDITY = "relative_humidity"; + private static final String WIND_KPH = "wind_kph"; + private static final String WIND_GUST_KPH = "wind_gust_kph"; + private static final String VISIBILITY_KM = "visibility_km"; + private static final String HEAT_INDEX_C = "heat_index_c"; + private static final String WINDCHILL_C = "windchill_c"; + private static final String PRECIP_1HR_METRIC = "precip_1hr_metric"; + private static final String FORECAST = "forecast"; + private static final String FORECASTDAY = "forecastday"; + private static final String SIMPLEFORECAST = "simpleforecast"; + private static final String DATE = "date"; + private static final String EPOCH = "epoch"; + private static final String HIGH = "high"; + private static final String CELSIUS = "celsius"; + private static final String LOW = "low"; + private static final String CONDITIONS = "conditions"; + private static final String QPF_ALLDAY = "qpf_allday"; + private static final String MM = "mm"; + private static final String SNOW_ALLDAY = "snow_allday"; + private static final String AVEHUMIDITY = "avehumidity"; + private static final String AVEWIND = "avewind"; + private static final String KPH = "kph"; + private static final String CM = "cm"; + private static final String POP = "pop"; + + @Override + public Forecast[] getForecast(JSONObject forecastJson) { + Vector forecastVector = new Vector(); + JSONArray forecastDays = null; + JSONObject simpleForecast = null; + JSONObject forecastObj = null; + + try { + forecastObj = forecastJson.getJSONObject(FORECAST); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (forecastObj != null) { + try { + simpleForecast = forecastObj.getJSONObject(SIMPLEFORECAST); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (simpleForecast != null) { + try { + forecastDays = simpleForecast.getJSONArray(FORECASTDAY); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (forecastDays != null) { + int numberOfDays = forecastDays.length(); + for (int dayCounter = 0; dayCounter < numberOfDays; dayCounter++) { + JSONObject day = null; + try { + day = forecastDays.getJSONObject(dayCounter); + } catch (JSONException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + Forecast currentForecast = new Forecast(); + if (day != null && currentForecast != null) { + JSONObject date = null; + try { + date = day.getJSONObject(DATE); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (date != null) { + String epoch = null; + try { + epoch = date.getString(EPOCH); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (epoch != null) { + long epochLong = Long.parseLong(epoch, 10); + Calendar forecastDate = Calendar.getInstance(); + forecastDate.setTimeInMillis(epochLong); + currentForecast.date = forecastDate; + } + } + + JSONObject high = null; + try { + high = day.getJSONObject(HIGH); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (high != null) { + try { + currentForecast.highTemperature = Float.valueOf(high.getInt(CELSIUS)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + JSONObject low = null; + try { + low = day.getJSONObject(LOW); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (low != null) { + try { + currentForecast.lowTemperature = Float.valueOf(low.getInt(CELSIUS)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + try { + currentForecast.precipitationChance = Integer.valueOf(day.getInt(POP)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + currentForecast.conditionTitle = day.getString(CONDITIONS); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + currentForecast.conditionIcon = new URL(day.getString(ICON_URL)); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + JSONObject qpf = null; + try { + qpf = day.getJSONObject(QPF_ALLDAY); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (qpf != null) { + try { + currentForecast.precipitation = Float.valueOf((float) qpf.getDouble(MM)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + JSONObject snow = null; + try { + snow = day.getJSONObject(SNOW_ALLDAY); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (snow != null) { + try { + currentForecast.snow = Float.valueOf((float) snow.getDouble(CM)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + try { + currentForecast.humidity = Float.valueOf((float) day.getDouble(AVEHUMIDITY)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + JSONObject wind = null; + try { + wind = day.getJSONObject(AVEWIND); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (wind != null) { + try { + currentForecast.windSpeed = Float.valueOf((float) wind.getDouble(KPH)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + forecastVector.add(currentForecast); + } + } + } + } + if (forecastVector.size() > 0) { + Forecast[] forecastArray = forecastVector.toArray(new Forecast[forecastVector.size()]); + return forecastArray; + } + else { + return null; + } + } + + @Override + public Forecast[] getHourlyForecast(JSONObject forecast) { + // TODO Auto-generated method stub + return null; + } + + @Override + public WeatherConditions getWeatherConditions(JSONObject conditions) { + WeatherConditions weatherConditions = null; + if (conditions != null) { + weatherConditions = new WeatherConditions(); + JSONObject currentObservation = null; + // Parse JSON + // Individual try/catch blocks used such that one failure will not abort the whole thing + try { + currentObservation = conditions.getJSONObject(CURRENT_OBSERVATION); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (currentObservation != null) { + try { + weatherConditions.conditionIcon = new URL(currentObservation.getString(ICON_URL)); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + weatherConditions.conditionTitle = currentObservation.getString(WEATHER); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + weatherConditions.temperature = Float.valueOf((float) currentObservation.getDouble(TEMP_C)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + String humidity = null; + try { + humidity = currentObservation.getString(RELATIVE_HUMIDITY); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (humidity != null) { + StringBuilder humidityBuilder = new StringBuilder(humidity); + int percentLocation = humidityBuilder.lastIndexOf("%"); + if (percentLocation > 0) { + humidityBuilder = humidityBuilder.deleteCharAt(percentLocation); + } + weatherConditions.humidity = Float.valueOf(humidityBuilder.toString()); + } + try { + weatherConditions.windSpeed = Float.valueOf((float) currentObservation.getDouble(WIND_KPH)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + weatherConditions.windSpeedGust = Float.valueOf((float) currentObservation.getDouble(WIND_GUST_KPH)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + weatherConditions.visibility = Float.valueOf((float) currentObservation.getDouble(VISIBILITY_KM)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + weatherConditions.feelsLikeTemperature = Float.valueOf((float) currentObservation.getDouble(HEAT_INDEX_C)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + weatherConditions.feelsLikeTemperature = Float.valueOf((float) currentObservation.getDouble(WINDCHILL_C)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + weatherConditions.precipitation = Float.valueOf((float) currentObservation.getDouble(PRECIP_1HR_METRIC)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + return weatherConditions; + } + + @Override + public WeatherAlert[] getAlerts(JSONObject alerts) { + // TODO Auto-generated method stub + return null; + } +} From aaf51f62273d3ac2500a739b3cf677a02366289b Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Thu, 9 Dec 2021 15:43:46 -0500 Subject: [PATCH 02/11] Continue conversion to OpenWeatherMap Move private static constants in OpenWeatherMapJsonProcessor to private static nested class Change constants and JSON parsing in OpenWeatherMapJsonProcessor to match the API Start moving try-catch structures to individual private functions to make code easier to read and collapse in Android Studio Add null check to alerts in OpenWeatherMapService Add HTTP request status constants to OpenWeatherMapService Add public function to get a request status from OpenWeatherMapService Add instrumented test for successful HTTP request status --- .../ExampleInstrumentedTest.java | 9 + .../openweathermap/OpenWeatherMapService.java | 31 +- .../OpenWeatherMapWeatherJsonProcessor.java | 431 ++++++++++++------ 3 files changed, 327 insertions(+), 144 deletions(-) diff --git a/MobileWeather/app/src/androidTest/java/com/sdl/mobileweather/ExampleInstrumentedTest.java b/MobileWeather/app/src/androidTest/java/com/sdl/mobileweather/ExampleInstrumentedTest.java index 2dc11a0..33af981 100644 --- a/MobileWeather/app/src/androidTest/java/com/sdl/mobileweather/ExampleInstrumentedTest.java +++ b/MobileWeather/app/src/androidTest/java/com/sdl/mobileweather/ExampleInstrumentedTest.java @@ -9,6 +9,8 @@ import static org.junit.Assert.*; +import com.sdl.mobileweather.openweathermap.OpenWeatherMapService; + /** * Instrumented test, which will execute on an Android device. * @@ -23,4 +25,11 @@ public void useAppContext() { assertEquals("com.sdl.mobileweather", appContext.getPackageName()); } + + + @Test + public void openWeatherAPIRequestSuccessful() { + OpenWeatherMapService openWeatherMapService = new OpenWeatherMapService(); + assertEquals(OpenWeatherMapService.REQUEST_SUCCESS, openWeatherMapService.getRequestStatus()); + } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java index f7a42c5..7de13dd 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java @@ -6,6 +6,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.sdl.mobileweather.artifact.GPSLocation; import com.sdl.mobileweather.artifact.WeatherLocation; import com.sdl.mobileweather.connection.HttpConnection; import com.sdl.mobileweather.processor.JsonProcessor; @@ -61,7 +62,8 @@ public class OpenWeatherMapService extends WeatherService { * This is 0 because it's the first index and only one URL is used for weather data at a time */ private static final int ONLY_URL_INDEX = 0; - private static final int HTTP_OK = 200; + public static final int REQUEST_SUCCESS = -1; + public static final int REQUEST_FAILURE = -2; public OpenWeatherMapService() { @@ -87,7 +89,7 @@ protected void updateWeatherData(URL... urls) { Log.d("MobileWeather", "updateWeatherData - getting json"); httpResult = httpRequest.sendRequest(urls[ONLY_URL_INDEX], HttpConnection.RequestMethod.GET, null, "application/json"); int statusCode = httpRequest.getStatusCode(httpResult); - if (statusCode == HTTP_OK) { + if (statusCode == REQUEST_SUCCESS) { Log.d("MobileWeather", "updateWeatherData - parsing conditions json"); JSONObject jsonRoot = jsonProcessor.getJsonFromString(httpResult); if (jsonRoot != null) { @@ -119,16 +121,18 @@ protected void updateWeatherData(URL... urls) { alerts = mWeatherProcessor.getAlerts(jsonRoot); mDataManager.setAlerts(alerts); - Log.d("MobileWeather", "updateWeatherData - new Alerts"); - Intent intent = new Intent("com.sdl.mobileweather.Alerts"); - lbManager.sendBroadcast(intent); + if (alerts != null) { + Log.d("MobileWeather", "updateWeatherData - new Alerts"); + Intent intent = new Intent("com.sdl.mobileweather.Alerts"); + lbManager.sendBroadcast(intent); + } } reportApiAvail(true); - } else if (statusCode == -2){ + } else if (statusCode == REQUEST_FAILURE){ reportConnectionAvail(false); - }else{ + } else { reportApiAvail(false); } } @@ -171,4 +175,17 @@ private URL buildOneCallURL(WeatherLocation currentLocation) throws MalformedURL Uri everythingUri = builder.build(); return new URL(everythingUri.toString()); } + + public int getRequestStatus() { + String httpResult = null; + HttpConnection httpRequest = new HttpConnection(); + WeatherLocation location = new WeatherLocation(); + location.gpsLocation = new GPSLocation(); + location.gpsLocation.latitude = "0.0"; + location.gpsLocation.longitude = "0.0"; + + URL[] urls = getURLs(location); + httpResult = httpRequest.sendRequest(urls[ONLY_URL_INDEX], HttpConnection.RequestMethod.GET, null, "application/json"); + return httpRequest.getStatusCode(httpResult); + } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java index f71292a..9c9a68b 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java @@ -1,5 +1,9 @@ package com.sdl.mobileweather.openweathermap; +import static net.hockeyapp.android.Constants.BASE_URL; + +import android.net.Uri; + import com.sdl.mobileweather.weather.Forecast; import com.sdl.mobileweather.weather.WeatherAlert; import com.sdl.mobileweather.weather.WeatherConditions; @@ -16,58 +20,76 @@ //TODO - Convert WUnderground specific code to OpenWeatherMap specific code public class OpenWeatherMapWeatherJsonProcessor implements WeatherJsonProcessor { - private static final String CURRENT_OBSERVATION = "current_observation"; - private static final String ICON_URL = "icon_url"; - private static final String WEATHER = "weather"; - private static final String TEMP_C = "temp_c"; - private static final String RELATIVE_HUMIDITY = "relative_humidity"; - private static final String WIND_KPH = "wind_kph"; - private static final String WIND_GUST_KPH = "wind_gust_kph"; - private static final String VISIBILITY_KM = "visibility_km"; - private static final String HEAT_INDEX_C = "heat_index_c"; - private static final String WINDCHILL_C = "windchill_c"; - private static final String PRECIP_1HR_METRIC = "precip_1hr_metric"; - private static final String FORECAST = "forecast"; - private static final String FORECASTDAY = "forecastday"; - private static final String SIMPLEFORECAST = "simpleforecast"; - private static final String DATE = "date"; - private static final String EPOCH = "epoch"; - private static final String HIGH = "high"; - private static final String CELSIUS = "celsius"; - private static final String LOW = "low"; - private static final String CONDITIONS = "conditions"; - private static final String QPF_ALLDAY = "qpf_allday"; - private static final String MM = "mm"; - private static final String SNOW_ALLDAY = "snow_allday"; - private static final String AVEHUMIDITY = "avehumidity"; - private static final String AVEWIND = "avewind"; - private static final String KPH = "kph"; - private static final String CM = "cm"; - private static final String POP = "pop"; - + private static class Constants { + private static final String CURRENT = "current"; + private static final String ICON_URL = "icon_url"; + private static final String WEATHER = "weather"; + private static final String TEMP = "temp"; + private static final String HUMIDITY = "humidity"; + private static final String WIND_SPEED = "wind_speed"; + private static final String WIND_GUST = "wind_gust"; + private static final String VISIBILITY = "visibility"; + private static final String HEAT_INDEX_C = "heat_index_c"; + private static final String WINDCHILL_C = "windchill_c"; + private static final String RAIN = "rain"; + private static final String SNOW = "snow"; + private static final String TIME_1_HR = "1h"; + private static final String FORECAST = "forecast"; + private static final String FORECASTDAY = "forecastday"; + private static final String SIMPLEFORECAST = "simpleforecast"; + private static final String DATE = "date"; + private static final String EPOCH = "epoch"; + private static final String HIGH = "high"; + private static final String CELSIUS = "celsius"; + private static final String LOW = "low"; + private static final String CONDITIONS = "conditions"; + private static final String QPF_ALLDAY = "qpf_allday"; + private static final String MM = "mm"; + private static final String SNOW_ALLDAY = "snow_allday"; + private static final String AVEHUMIDITY = "avehumidity"; + private static final String AVEWIND = "avewind"; + private static final String KPH = "kph"; + private static final String CM = "cm"; + private static final String POP = "pop"; + private static final String URI_SCHEME = "https"; + private static final String BASE_URL = "openweathermap.org"; + private static final String IMAGE_PATH = "img"; + private static final String WN_PATH = "wn"; + private static final String ICON_FILE_FORMAT = "%s@2x.png"; + private static final String ICON_PATH = "icon"; + private static final String MAIN = "main"; + private static final int ONLY_INDEX = 0; + private static final String ALERTS = "alerts"; + private static final String EVENT = "event"; + private static final String DESCRIPTION = "description"; + private static final String START_TIME = "start"; + private static final String END_TIME = "end"; + } + + @Override - public Forecast[] getForecast(JSONObject forecastJson) { + public Forecast[] getForecast(JSONObject jsonRoot) { Vector forecastVector = new Vector(); JSONArray forecastDays = null; JSONObject simpleForecast = null; JSONObject forecastObj = null; try { - forecastObj = forecastJson.getJSONObject(FORECAST); + forecastObj = jsonRoot.getJSONObject(Constants.FORECAST); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (forecastObj != null) { try { - simpleForecast = forecastObj.getJSONObject(SIMPLEFORECAST); + simpleForecast = forecastObj.getJSONObject(Constants.SIMPLEFORECAST); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (simpleForecast != null) { try { - forecastDays = simpleForecast.getJSONArray(FORECASTDAY); + forecastDays = simpleForecast.getJSONArray(Constants.FORECASTDAY); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -87,7 +109,7 @@ public Forecast[] getForecast(JSONObject forecastJson) { if (day != null && currentForecast != null) { JSONObject date = null; try { - date = day.getJSONObject(DATE); + date = day.getJSONObject(Constants.DATE); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -95,7 +117,7 @@ public Forecast[] getForecast(JSONObject forecastJson) { if (date != null) { String epoch = null; try { - epoch = date.getString(EPOCH); + epoch = date.getString(Constants.EPOCH); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -110,14 +132,14 @@ public Forecast[] getForecast(JSONObject forecastJson) { JSONObject high = null; try { - high = day.getJSONObject(HIGH); + high = day.getJSONObject(Constants.HIGH); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (high != null) { try { - currentForecast.highTemperature = Float.valueOf(high.getInt(CELSIUS)); + currentForecast.highTemperature = Float.valueOf(high.getInt(Constants.CELSIUS)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -126,14 +148,14 @@ public Forecast[] getForecast(JSONObject forecastJson) { JSONObject low = null; try { - low = day.getJSONObject(LOW); + low = day.getJSONObject(Constants.LOW); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (low != null) { try { - currentForecast.lowTemperature = Float.valueOf(low.getInt(CELSIUS)); + currentForecast.lowTemperature = Float.valueOf(low.getInt(Constants.CELSIUS)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -141,21 +163,21 @@ public Forecast[] getForecast(JSONObject forecastJson) { } try { - currentForecast.precipitationChance = Integer.valueOf(day.getInt(POP)); + currentForecast.precipitationChance = Integer.valueOf(day.getInt(Constants.POP)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { - currentForecast.conditionTitle = day.getString(CONDITIONS); + currentForecast.conditionTitle = day.getString(Constants.CONDITIONS); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { - currentForecast.conditionIcon = new URL(day.getString(ICON_URL)); + currentForecast.conditionIcon = new URL(day.getString(Constants.ICON_URL)); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -166,14 +188,14 @@ public Forecast[] getForecast(JSONObject forecastJson) { JSONObject qpf = null; try { - qpf = day.getJSONObject(QPF_ALLDAY); + qpf = day.getJSONObject(Constants.QPF_ALLDAY); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (qpf != null) { try { - currentForecast.precipitation = Float.valueOf((float) qpf.getDouble(MM)); + currentForecast.precipitation = Float.valueOf((float) qpf.getDouble(Constants.MM)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -182,14 +204,14 @@ public Forecast[] getForecast(JSONObject forecastJson) { JSONObject snow = null; try { - snow = day.getJSONObject(SNOW_ALLDAY); + snow = day.getJSONObject(Constants.SNOW_ALLDAY); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (snow != null) { try { - currentForecast.snow = Float.valueOf((float) snow.getDouble(CM)); + currentForecast.snow = Float.valueOf((float) snow.getDouble(Constants.CM)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -197,7 +219,7 @@ public Forecast[] getForecast(JSONObject forecastJson) { } try { - currentForecast.humidity = Float.valueOf((float) day.getDouble(AVEHUMIDITY)); + currentForecast.humidity = Float.valueOf((float) day.getDouble(Constants.AVEHUMIDITY)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -205,14 +227,14 @@ public Forecast[] getForecast(JSONObject forecastJson) { JSONObject wind = null; try { - wind = day.getJSONObject(AVEWIND); + wind = day.getJSONObject(Constants.AVEWIND); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (wind != null) { try { - currentForecast.windSpeed = Float.valueOf((float) wind.getDouble(KPH)); + currentForecast.windSpeed = Float.valueOf((float) wind.getDouble(Constants.KPH)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -234,107 +256,242 @@ public Forecast[] getForecast(JSONObject forecastJson) { } @Override - public Forecast[] getHourlyForecast(JSONObject forecast) { + public Forecast[] getHourlyForecast(JSONObject jsonRoot) { // TODO Auto-generated method stub return null; } - + + private JSONObject getCurrent(JSONObject conditions) { + JSONObject current = null; + try { + current = conditions.getJSONObject(Constants.CURRENT); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return current; + } + private JSONObject getWeather(JSONObject conditionAtTime) { + JSONObject weather = null; + try { + weather = conditionAtTime.getJSONArray(Constants.WEATHER).getJSONObject(Constants.ONLY_INDEX); + } catch (JSONException e) { + e.printStackTrace(); + } + return weather; + } + private URL getWeatherIconURL(String iconId) throws MalformedURLException { + String filename = String.format(Constants.ICON_FILE_FORMAT, iconId); + + Uri.Builder builder = new Uri.Builder(); + builder.scheme(Constants.URI_SCHEME) + .authority(BASE_URL) + .appendPath(Constants.IMAGE_PATH) + .appendPath(Constants.WN_PATH) + .appendPath(filename); + + String uriString = builder.build().toString(); + + return new URL(uriString); + } + private void tryToSetConditionIconFromCurrent(WeatherConditions weatherConditions, JSONObject weather) { + try { + String iconId = weather.getString(Constants.ICON_PATH); + + weatherConditions.conditionIcon = getWeatherIconURL(iconId); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionTitleFromCurrent(WeatherConditions weatherConditions, JSONObject weather) { + try { + weatherConditions.conditionTitle = weather.getString(Constants.MAIN); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionTemperatureFromCurrent(WeatherConditions weatherConditions, JSONObject current) { + try { + weatherConditions.temperature = (float) current.getDouble(Constants.TEMP); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionHumidityFromCurrent(WeatherConditions weatherConditions, JSONObject current) { + try { + weatherConditions.humidity = Float.valueOf(current.getString(Constants.HUMIDITY)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionWindSpeedFromCurrent(WeatherConditions weatherConditions, JSONObject current) { + try { + weatherConditions.windSpeed = (float) current.getDouble(Constants.WIND_SPEED); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionWindSpeedGustFromCurrent(WeatherConditions weatherConditions, JSONObject current) { + try { + weatherConditions.windSpeedGust = (float) current.getDouble(Constants.WIND_GUST); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionVisibilityFromCurrent(WeatherConditions weatherConditions, JSONObject current) { + try { + weatherConditions.visibility = (float) current.getDouble(Constants.VISIBILITY); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionFeelsLikeTempFromCurrent(WeatherConditions weatherConditions, JSONObject current) { + try { + weatherConditions.feelsLikeTemperature = (float) current.getDouble(Constants.HEAT_INDEX_C); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetConditionPrecipitationFromCurrent(WeatherConditions weatherConditions, JSONObject current) { + weatherConditions.precipitation = 0.0F; + try { + weatherConditions.precipitation += (float) current.getJSONObject(Constants.RAIN).getDouble(Constants.TIME_1_HR); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + weatherConditions.precipitation += (float) current.getJSONObject(Constants.SNOW).getDouble(Constants.TIME_1_HR); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + @Override - public WeatherConditions getWeatherConditions(JSONObject conditions) { + public WeatherConditions getWeatherConditions(JSONObject jsonRoot) { WeatherConditions weatherConditions = null; - if (conditions != null) { + if (jsonRoot != null) { weatherConditions = new WeatherConditions(); - JSONObject currentObservation = null; + JSONObject current = getCurrent(jsonRoot); // Parse JSON - // Individual try/catch blocks used such that one failure will not abort the whole thing - try { - currentObservation = conditions.getJSONObject(CURRENT_OBSERVATION); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (currentObservation != null) { - try { - weatherConditions.conditionIcon = new URL(currentObservation.getString(ICON_URL)); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.conditionTitle = currentObservation.getString(WEATHER); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.temperature = Float.valueOf((float) currentObservation.getDouble(TEMP_C)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - String humidity = null; - try { - humidity = currentObservation.getString(RELATIVE_HUMIDITY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (humidity != null) { - StringBuilder humidityBuilder = new StringBuilder(humidity); - int percentLocation = humidityBuilder.lastIndexOf("%"); - if (percentLocation > 0) { - humidityBuilder = humidityBuilder.deleteCharAt(percentLocation); - } - weatherConditions.humidity = Float.valueOf(humidityBuilder.toString()); - } - try { - weatherConditions.windSpeed = Float.valueOf((float) currentObservation.getDouble(WIND_KPH)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.windSpeedGust = Float.valueOf((float) currentObservation.getDouble(WIND_GUST_KPH)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.visibility = Float.valueOf((float) currentObservation.getDouble(VISIBILITY_KM)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.feelsLikeTemperature = Float.valueOf((float) currentObservation.getDouble(HEAT_INDEX_C)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.feelsLikeTemperature = Float.valueOf((float) currentObservation.getDouble(WINDCHILL_C)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.precipitation = Float.valueOf((float) currentObservation.getDouble(PRECIP_1HR_METRIC)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (current != null) { + JSONObject weather = getWeather(current); + if (weather != null) { + tryToSetConditionIconFromCurrent(weatherConditions, weather); + tryToSetConditionTitleFromCurrent(weatherConditions, weather); } + tryToSetConditionTemperatureFromCurrent(weatherConditions, current); + tryToSetConditionHumidityFromCurrent(weatherConditions, current); + tryToSetConditionWindSpeedFromCurrent(weatherConditions, current); + tryToSetConditionWindSpeedGustFromCurrent(weatherConditions, current); + tryToSetConditionVisibilityFromCurrent(weatherConditions, current); + tryToSetConditionFeelsLikeTempFromCurrent(weatherConditions, current); + tryToSetConditionPrecipitationFromCurrent(weatherConditions, current); } } return weatherConditions; } - + + private JSONArray getAlertArray(JSONObject jsonRoot) { + JSONArray alertArray = null; + + try { + alertArray = jsonRoot.getJSONArray(Constants.ALERTS); + } + catch (JSONException e) { + e.printStackTrace(); + } + + return alertArray; + } + + private void tryToSetAlertMessage(WeatherAlert weatherAlert, JSONObject alert) { + try { + weatherAlert.message = alert.getString(Constants.EVENT); + } + catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetAlertDescription(WeatherAlert weatherAlert, JSONObject alert) { + try { + weatherAlert.description = alert.getString(Constants.DESCRIPTION); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetAlertDateIssued(WeatherAlert weatherAlert, JSONObject alert) { + try { + Calendar dateIssued = Calendar.getInstance(); + dateIssued.setTimeInMillis(alert.getLong(Constants.START_TIME)); + weatherAlert.dateIssued = dateIssued; + } + catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetAlertDateExpires(WeatherAlert weatherAlert, JSONObject alert) { + try { + Calendar dateExpires = Calendar.getInstance(); + dateExpires.setTimeInMillis(alert.getLong(Constants.END_TIME)); + weatherAlert.dateExpires = dateExpires; + } + catch (JSONException e) { + e.printStackTrace(); + } + } + + private WeatherAlert getAlert(JSONArray alertArray, int index) { + WeatherAlert weatherAlert = null; + JSONObject alert = null; + + try { + alert = alertArray.getJSONObject(index); + } catch (JSONException e) { + e.printStackTrace(); + } + + if (alert != null) { + weatherAlert = new WeatherAlert(); + tryToSetAlertMessage(weatherAlert, alert); + tryToSetAlertDescription(weatherAlert, alert); + tryToSetAlertDateIssued(weatherAlert, alert); + tryToSetAlertDateExpires(weatherAlert, alert); + //TODO - set alert type somehow + } + + + return weatherAlert; + } + @Override - public WeatherAlert[] getAlerts(JSONObject alerts) { - // TODO Auto-generated method stub - return null; + public WeatherAlert[] getAlerts(JSONObject jsonRoot) { + WeatherAlert[] alerts = null; + JSONArray alertArray = getAlertArray(jsonRoot); + if (alertArray != null) { + int arrayLength = alertArray.length(); + alerts = new WeatherAlert[arrayLength]; + + for (int i = 0; i < arrayLength; i++) { + alerts[i] = getAlert(alertArray, i); + } + } + + return alerts; } } From 4b48a6336f39cdddd421f89775bb3e5293a0e93d Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Mon, 13 Dec 2021 09:07:38 -0500 Subject: [PATCH 03/11] Continue transition to OpenWeatherMap Switch usages of ForecastIoService to OpenWeatherMapService Make ImageProcessor pull icons directly from OpenWeatherMap website Change ForecastListAdpater to take an Activity in the constructor Change ImageProcessor.setConditionsImage() to take an activity Add constants to OpenWeatherMapService in place of literal values Make API call use metric units to match ForecastIO implementation Update logic and add methods in OpenWeatherMapWeatherJsonProcessor to handle forecasts and alerts Use ExecutorService in ImageProcessor to download icon images in the background --- .../app/src/main/AndroidManifest.xml | 3 +- .../adapter/ForecastListAdapter.java | 6 +- .../fragments/ConditionsFragment.java | 2 +- .../fragments/ForecastFragment.java | 4 +- .../localization/LocalizationUtil.java | 6 +- .../openweathermap/OpenWeatherMapService.java | 25 +- .../OpenWeatherMapWeatherJsonProcessor.java | 450 ++++++++++-------- .../processor/ImageProcessor.java | 44 +- .../smartdevicelink/SdlApplication.java | 11 +- 9 files changed, 332 insertions(+), 219 deletions(-) diff --git a/MobileWeather/app/src/main/AndroidManifest.xml b/MobileWeather/app/src/main/AndroidManifest.xml index 70f6b8f..9431a81 100644 --- a/MobileWeather/app/src/main/AndroidManifest.xml +++ b/MobileWeather/app/src/main/AndroidManifest.xml @@ -50,7 +50,8 @@ - + + { private WeatherDataManager mDataManager; private boolean[] mForecastType; private LayoutInflater mInflater; + private Activity parentActivity; - public ForecastListAdapter(Context context, Forecast[] forecast) { + public ForecastListAdapter(Context context, Forecast[] forecast, Activity parentActivity) { super(context, R.layout.forecast_list_item, forecast); this.mContext = context; this.mForecast = forecast; this.mDataManager = WeatherDataManager.getInstance(); this.mForecastType = new boolean[forecast.length]; + this.parentActivity = parentActivity; Arrays.fill(this.mForecastType, false); mInflater = ((Activity) this.mContext).getLayoutInflater(); } @@ -119,7 +121,7 @@ else if((day.conditionIcon != null) && lowTempTextView.setText(lowTemp); highTempTextView.setText(highTemp); if (conditionURL != null) - ImageProcessor.setConditionsImage(forecastImageView, conditionURL, true); + ImageProcessor.setConditionsImage(forecastImageView, conditionURL, parentActivity/*, true*/); } /*else { forecastImageView.setImageBitmap(null); diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java index 27edfc1..442d140 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java @@ -148,7 +148,7 @@ public void setConditions(WeatherConditions conditions, String units) { mHumidityView.setText(humid); if (conditions.conditionIcon != null) - ImageProcessor.setConditionsImage(mConditionsIconView, conditions.conditionIcon, false); + ImageProcessor.setConditionsImage(mConditionsIconView, conditions.conditionIcon, getActivity()/*, false*/); } } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ForecastFragment.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ForecastFragment.java index 2a4d441..710bd3e 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ForecastFragment.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ForecastFragment.java @@ -7,6 +7,7 @@ import com.sdl.mobileweather.weather.Forecast; import com.sdl.mobileweather.weather.WeatherDataManager; +import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -96,7 +97,8 @@ public void updateForecast() { */ public void setForecast(Forecast[] forecast, String units) { if (forecast != null) { - mAdapter = new ForecastListAdapter(getActivity(), forecast); + Activity context = getActivity(); + mAdapter = new ForecastListAdapter(context, forecast, context); mForecastListView.setAdapter(mAdapter); mForecastListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/localization/LocalizationUtil.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/localization/LocalizationUtil.java index f590ad5..9bde53f 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/localization/LocalizationUtil.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/localization/LocalizationUtil.java @@ -7,8 +7,9 @@ import android.content.res.Configuration; import com.sdl.mobileweather.R; +import com.sdl.mobileweather.openweathermap.OpenWeatherMapService; import com.smartdevicelink.proxy.rpc.enums.Language; -import com.sdl.mobileweather.forecastio.ForecastIoService; +//import com.sdl.mobileweather.forecastio.ForecastIoService; import com.sdl.mobileweather.smartdevicelink.SdlApplication; import com.sdl.mobileweather.weather.WeatherDataManager; import com.sdl.mobileweather.weather.WeatherUpdateWakefulReceiver; @@ -224,7 +225,8 @@ public void changeLocale(String language, String country, Context context){ } Context mAppContext = SdlApplication.getInstance().getApplicationContext(); Intent mUpdateIntent = new Intent(mAppContext, WeatherUpdateWakefulReceiver.class); - mUpdateIntent.putExtra("weather_update_service", ForecastIoService.class.getName()); + //mUpdateIntent.putExtra("weather_update_service", ForecastIoService.class.getName()); + mUpdateIntent.putExtra("weather_update_service", OpenWeatherMapService.class.getName()); mAppContext.sendBroadcast(mUpdateIntent); return; } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java index 7de13dd..0d158ca 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java @@ -54,6 +54,8 @@ public class OpenWeatherMapService extends WeatherService { private static final String ONE_CALL_API_CALL_TYPE = "onecall"; private static final String LATITUDE_PARAMETER = "lat"; private static final String LONGITUDE_PARAMETER = "lon"; + private static final String UNITS_PARAMETER = "units"; + private static final String METRIC = "metric"; /** * This parameter is used to specify the API key for the API call */ @@ -64,6 +66,7 @@ public class OpenWeatherMapService extends WeatherService { private static final int ONLY_URL_INDEX = 0; public static final int REQUEST_SUCCESS = -1; public static final int REQUEST_FAILURE = -2; + public static final String TAG = "MobileWeather"; public OpenWeatherMapService() { @@ -81,37 +84,38 @@ protected void updateWeatherData(URL... urls) { WeatherAlert[] alerts = null; String httpResult = null; - Log.d("MobileWeather", "updateWeatherData"); + Log.d(TAG, "updateWeatherData"); if (urls.length == 1 && mDataManager != null && mWeatherProcessor != null) { LocalBroadcastManager lbManager = LocalBroadcastManager.getInstance(this); - Log.d("MobileWeather", "updateWeatherData - valid urls"); + Log.d(TAG, "updateWeatherData - valid urls"); if (urls[ONLY_URL_INDEX] != null) { - Log.d("MobileWeather", "updateWeatherData - getting json"); + Log.d(TAG, urls[ONLY_URL_INDEX].toString()); + Log.d(TAG, "updateWeatherData - getting json"); httpResult = httpRequest.sendRequest(urls[ONLY_URL_INDEX], HttpConnection.RequestMethod.GET, null, "application/json"); int statusCode = httpRequest.getStatusCode(httpResult); if (statusCode == REQUEST_SUCCESS) { - Log.d("MobileWeather", "updateWeatherData - parsing conditions json"); + Log.d(TAG, "updateWeatherData - parsing conditions json"); JSONObject jsonRoot = jsonProcessor.getJsonFromString(httpResult); if (jsonRoot != null) { - Log.d("MobileWeather", "updateWeatherData - parsing conditions"); + Log.d(TAG, "updateWeatherData - parsing conditions"); conditions = mWeatherProcessor.getWeatherConditions(jsonRoot); mDataManager.setWeatherConditions(conditions); if (conditions != null) { - Log.d("MobileWeather", "updateWeatherData - new conditions"); + Log.d(TAG, "updateWeatherData - new conditions"); Intent intent = new Intent("com.sdl.mobileweather.WeatherConditions"); lbManager.sendBroadcast(intent); } - Log.d("MobileWeather", "updateWeatherData - parsing forecast"); + Log.d(TAG, "updateWeatherData - parsing forecast"); forecast = mWeatherProcessor.getForecast(jsonRoot); mDataManager.setForecast(forecast); if (forecast != null) { - Log.d("MobileWeather", "updateWeatherData - new forecast"); + Log.d(TAG, "updateWeatherData - new forecast"); Intent intent = new Intent("com.sdl.mobileweather.Forecast"); lbManager.sendBroadcast(intent); } - Log.d("MobileWeather", "updateWeatherData - parsing hourly forecast"); + Log.d(TAG, "updateWeatherData - parsing hourly forecast"); hourlyForecast = mWeatherProcessor.getHourlyForecast(jsonRoot); mDataManager.setHourlyForecast(hourlyForecast); if (hourlyForecast != null) { @@ -122,7 +126,7 @@ protected void updateWeatherData(URL... urls) { alerts = mWeatherProcessor.getAlerts(jsonRoot); mDataManager.setAlerts(alerts); if (alerts != null) { - Log.d("MobileWeather", "updateWeatherData - new Alerts"); + Log.d(TAG, "updateWeatherData - new Alerts"); Intent intent = new Intent("com.sdl.mobileweather.Alerts"); lbManager.sendBroadcast(intent); } @@ -170,6 +174,7 @@ private URL buildOneCallURL(WeatherLocation currentLocation) throws MalformedURL .appendPath(ONE_CALL_API_CALL_TYPE) .appendQueryParameter(LATITUDE_PARAMETER, currentLocation.gpsLocation.latitude) .appendQueryParameter(LONGITUDE_PARAMETER, currentLocation.gpsLocation.longitude) + .appendQueryParameter(UNITS_PARAMETER, METRIC) .appendQueryParameter(APP_ID_PARAMETER, API_KEY); Uri everythingUri = builder.build(); diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java index 9c9a68b..71c691f 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java @@ -1,8 +1,7 @@ package com.sdl.mobileweather.openweathermap; -import static net.hockeyapp.android.Constants.BASE_URL; - import android.net.Uri; +import android.util.Log; import com.sdl.mobileweather.weather.Forecast; import com.sdl.mobileweather.weather.WeatherAlert; @@ -16,11 +15,11 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Calendar; -import java.util.Vector; //TODO - Convert WUnderground specific code to OpenWeatherMap specific code public class OpenWeatherMapWeatherJsonProcessor implements WeatherJsonProcessor { private static class Constants { + public static final String TAG = "MobileWeather"; private static final String CURRENT = "current"; private static final String ICON_URL = "icon_url"; private static final String WEATHER = "weather"; @@ -34,10 +33,11 @@ private static class Constants { private static final String RAIN = "rain"; private static final String SNOW = "snow"; private static final String TIME_1_HR = "1h"; - private static final String FORECAST = "forecast"; + private static final String DAILY_FORECAST = "daily"; + private static final String HOURLY_FORECAST = "hourly"; private static final String FORECASTDAY = "forecastday"; private static final String SIMPLEFORECAST = "simpleforecast"; - private static final String DATE = "date"; + private static final String DATE = "dt"; private static final String EPOCH = "epoch"; private static final String HIGH = "high"; private static final String CELSIUS = "celsius"; @@ -64,201 +64,265 @@ private static class Constants { private static final String DESCRIPTION = "description"; private static final String START_TIME = "start"; private static final String END_TIME = "end"; + private static final String PRECIPITATION_CHANCE = "pop"; + private static final float METERS_PER_SEC_TO_KPH = 3.6F; + private static final String MIN = "min"; + private static final String MAX = "max"; + private static final String DAY = "day"; + private static final String FEELS_LIKE = "feels_like"; } + private JSONArray getForecastArray(JSONObject jsonRoot) { + JSONArray forecastObj = null; - @Override - public Forecast[] getForecast(JSONObject jsonRoot) { - Vector forecastVector = new Vector(); - JSONArray forecastDays = null; - JSONObject simpleForecast = null; - JSONObject forecastObj = null; + try { + forecastObj = jsonRoot.getJSONArray(Constants.DAILY_FORECAST); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return forecastObj; + } + + private JSONArray getHourForecastArray(JSONObject jsonRoot) { + JSONArray forecastObj = null; + + try { + forecastObj = jsonRoot.getJSONArray(Constants.HOURLY_FORECAST); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return forecastObj; + } + + private void tryToSetForecastDate(Forecast forecast, JSONObject forecastObj) { + try { + Calendar date = Calendar.getInstance(); + date.setTimeInMillis(forecastObj.getLong(Constants.DATE)); + forecast.date = date; + } + catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetForecastHumidity(Forecast forecast, JSONObject forecastObj) { + try { + forecast.humidity = Float.valueOf(forecastObj.getString(Constants.HUMIDITY)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetDayForecastPrecipitation(Forecast forecast, JSONObject forecastObj) { + forecast.precipitation = 0.0F; + try { + forecast.precipitation += (float) forecastObj.getDouble(Constants.RAIN); + } + catch (JSONException e) { + e.printStackTrace(); + } + try { + forecast.precipitation += (float) forecastObj.getDouble(Constants.SNOW); + } + catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetHourForecastPrecipitation(Forecast forecast, JSONObject forecastObj) { + forecast.precipitation = 0.0F; + try { + forecast.precipitation += (float) forecastObj.getJSONObject(Constants.RAIN).getDouble(Constants.TIME_1_HR); + //forecast.precipitation += (float) forecastObj.getDouble(Constants.RAIN); + } + catch (JSONException e) { + e.printStackTrace(); + } + try { + forecast.precipitation += (float) forecastObj.getJSONObject(Constants.SNOW).getDouble(Constants.TIME_1_HR); + //forecast.precipitation += (float) forecastObj.getDouble(Constants.SNOW); + } + catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetDayForecastSnow(Forecast forecast, JSONObject forecastObj) { + try { + forecast.precipitation = (float) forecastObj.getDouble(Constants.SNOW); + } + catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetHourForecastSnow(Forecast forecast, JSONObject forecastObj) { + try { + forecast.precipitation = (float) forecastObj.getJSONObject(Constants.SNOW).getDouble(Constants.TIME_1_HR); + } + catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetForecastPrecipitationChance(Forecast forecast, JSONObject forecastObj) { + try { + //The API returns the probability as a float between 0 and 1, therefore it must be mapped to an integer between 0 and 100 + forecast.precipitationChance = (int)(forecastObj.getDouble(Constants.PRECIPITATION_CHANCE) * 100.0); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetForecastWindSpeed(Forecast forecast, JSONObject forecastObj) { + try { + forecast.windSpeed = (float) forecastObj.getDouble(Constants.WIND_SPEED) * Constants.METERS_PER_SEC_TO_KPH; + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetForecastHighTemperature(Forecast forecast, JSONObject forecastObj) { + try { + forecast.highTemperature = (float) forecastObj.getJSONObject(Constants.TEMP).getDouble(Constants.MAX); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetForecastLowTemperature(Forecast forecast, JSONObject forecastObj) { + try { + forecast.lowTemperature = (float) forecastObj.getJSONObject(Constants.TEMP).getDouble(Constants.MIN); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetForecastDayTemperature(Forecast forecast, JSONObject forecastObj) { + try { + forecast.temperature = (float) forecastObj.getJSONObject(Constants.TEMP).getDouble(Constants.DAY); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetForecastHourTemperature(Forecast forecast, JSONObject forecastObj) { + try { + forecast.temperature = (float) forecastObj.getDouble(Constants.TEMP); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void tryToSetForecastIcon(Forecast forecast, JSONObject weather) { + try { + String iconId = weather.getString(Constants.ICON_PATH); + forecast.conditionIcon = getWeatherIconURL(iconId); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + private void tryToSetForecastTitle(Forecast forecast, JSONObject weather) { try { - forecastObj = jsonRoot.getJSONObject(Constants.FORECAST); + forecast.conditionTitle = weather.getString(Constants.MAIN); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } - if (forecastObj != null) { - try { - simpleForecast = forecastObj.getJSONObject(Constants.SIMPLEFORECAST); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } + + private Forecast getSingleDayForecast(JSONArray forecastArray, int index) { + Forecast singleDayForecast = null; + JSONObject forecast = null; + + try { + forecast = forecastArray.getJSONObject(index); + } + catch (JSONException e) { + e.printStackTrace(); + } + + if (forecast != null) { + singleDayForecast = new Forecast(); + tryToSetForecastDate(singleDayForecast, forecast); + tryToSetForecastHumidity(singleDayForecast, forecast); + tryToSetDayForecastPrecipitation(singleDayForecast, forecast); + tryToSetDayForecastSnow(singleDayForecast, forecast); + tryToSetForecastPrecipitationChance(singleDayForecast, forecast); + tryToSetForecastWindSpeed(singleDayForecast, forecast); + tryToSetForecastHighTemperature(singleDayForecast, forecast); + tryToSetForecastLowTemperature(singleDayForecast, forecast); + tryToSetForecastDayTemperature(singleDayForecast, forecast); + JSONObject weather = getWeather(forecast); + if (weather != null) { + tryToSetForecastIcon(singleDayForecast, weather); + tryToSetForecastTitle(singleDayForecast, weather); } - if (simpleForecast != null) { - try { - forecastDays = simpleForecast.getJSONArray(Constants.FORECASTDAY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (forecastDays != null) { - int numberOfDays = forecastDays.length(); - for (int dayCounter = 0; dayCounter < numberOfDays; dayCounter++) { - JSONObject day = null; - try { - day = forecastDays.getJSONObject(dayCounter); - } catch (JSONException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - Forecast currentForecast = new Forecast(); - if (day != null && currentForecast != null) { - JSONObject date = null; - try { - date = day.getJSONObject(Constants.DATE); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (date != null) { - String epoch = null; - try { - epoch = date.getString(Constants.EPOCH); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (epoch != null) { - long epochLong = Long.parseLong(epoch, 10); - Calendar forecastDate = Calendar.getInstance(); - forecastDate.setTimeInMillis(epochLong); - currentForecast.date = forecastDate; - } - } - - JSONObject high = null; - try { - high = day.getJSONObject(Constants.HIGH); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (high != null) { - try { - currentForecast.highTemperature = Float.valueOf(high.getInt(Constants.CELSIUS)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - JSONObject low = null; - try { - low = day.getJSONObject(Constants.LOW); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (low != null) { - try { - currentForecast.lowTemperature = Float.valueOf(low.getInt(Constants.CELSIUS)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - try { - currentForecast.precipitationChance = Integer.valueOf(day.getInt(Constants.POP)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - currentForecast.conditionTitle = day.getString(Constants.CONDITIONS); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - currentForecast.conditionIcon = new URL(day.getString(Constants.ICON_URL)); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - JSONObject qpf = null; - try { - qpf = day.getJSONObject(Constants.QPF_ALLDAY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (qpf != null) { - try { - currentForecast.precipitation = Float.valueOf((float) qpf.getDouble(Constants.MM)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - JSONObject snow = null; - try { - snow = day.getJSONObject(Constants.SNOW_ALLDAY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (snow != null) { - try { - currentForecast.snow = Float.valueOf((float) snow.getDouble(Constants.CM)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - try { - currentForecast.humidity = Float.valueOf((float) day.getDouble(Constants.AVEHUMIDITY)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - JSONObject wind = null; - try { - wind = day.getJSONObject(Constants.AVEWIND); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (wind != null) { - try { - currentForecast.windSpeed = Float.valueOf((float) wind.getDouble(Constants.KPH)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - forecastVector.add(currentForecast); - } - } + } + + return singleDayForecast; + } + + @Override + public Forecast[] getForecast(JSONObject jsonRoot) { + Forecast[] forecasts = null; + JSONArray forecastArray = getForecastArray(jsonRoot); + + if (forecastArray != null) { + int forecastLength = forecastArray.length(); + forecasts = new Forecast[forecastLength]; + for (int i = 0; i < forecastLength; i++) { + forecasts[i] = getSingleDayForecast(forecastArray, i); } } - if (forecastVector.size() > 0) { - Forecast[] forecastArray = forecastVector.toArray(new Forecast[forecastVector.size()]); - return forecastArray; + + return forecasts; + } + + private Forecast getSingleHourForecast(JSONArray forecastArray, int index) { + Forecast singleHourForecast = null; + JSONObject forecast = null; + + try { + forecast = forecastArray.getJSONObject(index); } - else { - return null; + catch (JSONException e) { + e.printStackTrace(); } + + if (forecast != null) { + singleHourForecast = new Forecast(); + tryToSetForecastDate(singleHourForecast, forecast); + tryToSetForecastHumidity(singleHourForecast, forecast); + tryToSetHourForecastPrecipitation(singleHourForecast, forecast); + tryToSetHourForecastSnow(singleHourForecast, forecast); + tryToSetForecastPrecipitationChance(singleHourForecast, forecast); + tryToSetForecastWindSpeed(singleHourForecast, forecast); + //tryToSetForecastHighTemperature(singleHourForecast, forecast); + //tryToSetForecastLowTemperature(singleHourForecast, forecast); + tryToSetForecastHourTemperature(singleHourForecast, forecast); + JSONObject weather = getWeather(forecast); + if (weather != null) { + tryToSetForecastIcon(singleHourForecast, weather); + tryToSetForecastTitle(singleHourForecast, weather); + } + } + + return singleHourForecast; } - + @Override public Forecast[] getHourlyForecast(JSONObject jsonRoot) { - // TODO Auto-generated method stub - return null; + Forecast[] forecasts = null; + JSONArray forecastArray = getHourForecastArray(jsonRoot); + + if (forecastArray != null) { + int forecastLength = forecastArray.length(); + forecasts = new Forecast[forecastLength]; + for (int i = 0; i < forecastLength; i++) { + forecasts[i] = getSingleHourForecast(forecastArray, i); + } + } + + return forecasts; } private JSONObject getCurrent(JSONObject conditions) { @@ -286,14 +350,16 @@ private URL getWeatherIconURL(String iconId) throws MalformedURLException { Uri.Builder builder = new Uri.Builder(); builder.scheme(Constants.URI_SCHEME) - .authority(BASE_URL) + .authority(Constants.BASE_URL) .appendPath(Constants.IMAGE_PATH) .appendPath(Constants.WN_PATH) .appendPath(filename); String uriString = builder.build().toString(); - - return new URL(uriString); + URL url = new URL(uriString); + String urlString = url.toString(); + Log.d(Constants.TAG, urlString); + return url; } private void tryToSetConditionIconFromCurrent(WeatherConditions weatherConditions, JSONObject weather) { try { @@ -334,7 +400,7 @@ private void tryToSetConditionHumidityFromCurrent(WeatherConditions weatherCondi } private void tryToSetConditionWindSpeedFromCurrent(WeatherConditions weatherConditions, JSONObject current) { try { - weatherConditions.windSpeed = (float) current.getDouble(Constants.WIND_SPEED); + weatherConditions.windSpeed = (float) current.getDouble(Constants.WIND_SPEED) * Constants.METERS_PER_SEC_TO_KPH; } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -342,7 +408,7 @@ private void tryToSetConditionWindSpeedFromCurrent(WeatherConditions weatherCond } private void tryToSetConditionWindSpeedGustFromCurrent(WeatherConditions weatherConditions, JSONObject current) { try { - weatherConditions.windSpeedGust = (float) current.getDouble(Constants.WIND_GUST); + weatherConditions.windSpeedGust = (float) current.getDouble(Constants.WIND_GUST) * Constants.METERS_PER_SEC_TO_KPH; } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -358,7 +424,7 @@ private void tryToSetConditionVisibilityFromCurrent(WeatherConditions weatherCon } private void tryToSetConditionFeelsLikeTempFromCurrent(WeatherConditions weatherConditions, JSONObject current) { try { - weatherConditions.feelsLikeTemperature = (float) current.getDouble(Constants.HEAT_INDEX_C); + weatherConditions.feelsLikeTemperature = (float) current.getDouble(Constants.FEELS_LIKE); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -472,10 +538,12 @@ private WeatherAlert getAlert(JSONArray alertArray, int index) { tryToSetAlertDescription(weatherAlert, alert); tryToSetAlertDateIssued(weatherAlert, alert); tryToSetAlertDateExpires(weatherAlert, alert); - //TODO - set alert type somehow + /*TODO - Set alert type somehow + * This could be difficult because the current implementation uses enum types, + * but the API provides more verbose strings + */ } - return weatherAlert; } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java index 386fda1..3da8023 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java @@ -1,11 +1,16 @@ package com.sdl.mobileweather.processor; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import android.app.Activity; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -13,7 +18,10 @@ import com.sdl.mobileweather.smartdevicelink.SdlApplication; +import javax.net.ssl.HttpsURLConnection; + public class ImageProcessor { + private static final ExecutorService executorService = Executors.newCachedThreadPool(); private static final Map mConditionsImageMap = Collections.unmodifiableMap(new HashMap() { @@ -100,19 +108,41 @@ public static String getFileFromURL(URL url) { } } - public static void setConditionsImage(ImageView imageView, URL conditionsImageURL, boolean small) { - String conditionsImageName = getFileFromURL(conditionsImageURL); + public static void setConditionsImage(final ImageView imageView, final URL conditionsImageURL, final Activity activity/*, boolean small*/) { + + /*String conditionsImageName = getFileFromURL(conditionsImageURL); String mappedName = getMappedConditionsImageName(conditionsImageName, small); if (mappedName != null) { Bitmap mappedImage = getBitmapFromResources(mappedName); imageView.setImageBitmap(mappedImage); } else { - // TODO - /*final DownloadTask downloadTask = new DownloadTask(context); - downloadTask.execute(conditionsImageURL.toString()); - //imageView.setImageBitmap(bm);*/ - } + TODO + final SetImageFromURLTask downloadTask = new SetImageFromURLTask(conditionsImageURL, imageView); + + }*/ + + executorService.execute(new Runnable() { + @Override + public void run() { + try { + HttpsURLConnection connection = (HttpsURLConnection) conditionsImageURL.openConnection(); + connection.setDoInput(true); + connection.connect(); + InputStream inputStream = connection.getInputStream(); + final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + imageView.setImageBitmap(bitmap); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); } public static byte[] getConditionsImageBytes(URL conditionsImageURL) { diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlApplication.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlApplication.java index 24b6e66..269ded1 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlApplication.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlApplication.java @@ -10,10 +10,11 @@ import android.util.Log; import com.sdl.mobileweather.R; -import com.sdl.mobileweather.forecastio.ForecastIoService; +//import com.sdl.mobileweather.forecastio.ForecastIoService; import com.sdl.mobileweather.localization.LocalizationUtil; import com.sdl.mobileweather.location.PlayServicesConnectionChecker; import com.sdl.mobileweather.location.WeatherLocationServices; +import com.sdl.mobileweather.openweathermap.OpenWeatherMapService; import com.sdl.mobileweather.weather.WeatherAlarmManager; import com.sdl.mobileweather.weather.WeatherDataManager; import com.sdl.mobileweather.weather.WeatherUpdateWakefulReceiver; @@ -63,7 +64,8 @@ public void onCreate() { mDataManager.setUpdateInterval(5); mDataManager.setUnits(getResources().getString(R.string.units_default)); mLocalizationUtil = new LocalizationUtil(); - mWeatherAlarm = new WeatherAlarmManager(ForecastIoService.class); + //mWeatherAlarm = new WeatherAlarmManager(ForecastIoService.class); + mWeatherAlarm = new WeatherAlarmManager(OpenWeatherMapService.class); mLocationServices = null; if (PlayServicesConnectionChecker.servicesConnected()) { mLocationServices = new WeatherLocationServices(); @@ -75,8 +77,9 @@ public void onConfigurationChanged(Configuration newConfig) { Log.d(TAG, "onConfigurationChanged received"); Context mAppContext = SdlApplication.getInstance().getApplicationContext(); Intent mUpdateIntent = new Intent(mAppContext, WeatherUpdateWakefulReceiver.class); - mUpdateIntent.putExtra("weather_update_service", ForecastIoService.class.getName()); - mAppContext.sendBroadcast(mUpdateIntent); + //mUpdateIntent.putExtra("weather_update_service", ForecastIoService.class.getName()); + mUpdateIntent.putExtra("weather_update_service", OpenWeatherMapService.class.getName()); + mAppContext.sendBroadcast(mUpdateIntent); if (mDataManager != null) { mDataManager.setUnits(getResources().getString(R.string.units_default)); } From 72f5e795643ad6c861429590f493932ca6135aaf Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Wed, 15 Dec 2021 13:20:13 -0500 Subject: [PATCH 04/11] Attempt fixes for icons on the HMI Change executorService in ImageProcessor to a single thread executor Add constants to ImageProcessor Add getImageUriFromURL() to ImageProcessor Remove commented code Reomove getConditionsImageBytes() Make forecast_items and forecast_item_counter volatile to try eliminating stale value errors in multithreading Add executorService to SdlService Add setWeatherGraphic() as a convenience function for setting primary graphics off the main thread Comment out various sections of SdlService to isolate behavior without full removal Tweak logic for setting icons in the HMI Update com.smartdevicelink:sdl_android dependency to 5.3.1 --- MobileWeather/app/build.gradle | 2 +- .../processor/ImageProcessor.java | 72 ++++---- .../smartdevicelink/SdlService.java | 164 ++++++++++++------ 3 files changed, 153 insertions(+), 85 deletions(-) diff --git a/MobileWeather/app/build.gradle b/MobileWeather/app/build.gradle index e24f53f..baa3961 100644 --- a/MobileWeather/app/build.gradle +++ b/MobileWeather/app/build.gradle @@ -38,7 +38,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' // implementation 'com.smartdevicelink:sdl_android:4.+' - implementation 'com.smartdevicelink:sdl_android:5.0.0' + implementation 'com.smartdevicelink:sdl_android:5.3.1' implementation 'net.hockeyapp.android:HockeySDK:5.1.0' implementation 'com.google.android.gms:play-services-location:16.0.0' } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java index 3da8023..353b3c9 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java @@ -1,9 +1,12 @@ package com.sdl.mobileweather.processor; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -11,9 +14,13 @@ import java.util.concurrent.Executors; import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.net.Uri; +import android.util.Log; import android.widget.ImageView; import com.sdl.mobileweather.smartdevicelink.SdlApplication; @@ -21,7 +28,11 @@ import javax.net.ssl.HttpsURLConnection; public class ImageProcessor { - private static final ExecutorService executorService = Executors.newCachedThreadPool(); + private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private static final String IMAGE_DIR = "imageDir"; + private static final String PNG_EXTENSION = ".png"; + private static final int BITMAP_QUALITY = 100; + private static final String TAG = "ImageProcessor"; private static final Map mConditionsImageMap = Collections.unmodifiableMap(new HashMap() { @@ -97,11 +108,39 @@ public static Bitmap getBitmapFromResources(String imageName) { int resId = resources.getIdentifier(imageName, "drawable", "com.sdl.mobileweather"); return BitmapFactory.decodeResource(resources, resId); } + + public static Uri getImageUriFromURL(String imageName, URL url, Context context) { + imageName = imageName.replaceAll("\\s", ""); + Uri fileUri = null; + HttpsURLConnection connection; + try { + connection = (HttpsURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.connect(); + InputStream inputStream = connection.getInputStream(); + final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + + ContextWrapper contextWrapper = new ContextWrapper(context.getApplicationContext()); + File directory = contextWrapper.getDir(IMAGE_DIR, Context.MODE_PRIVATE); + File file = new File(directory, bitmap.hashCode() + PNG_EXTENSION); + if (!file.exists()) { + Log.d(TAG, imageName); + FileOutputStream outputStream = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.PNG, BITMAP_QUALITY, outputStream); + outputStream.flush(); + outputStream.close(); + } + fileUri = Uri.fromFile(file); + Log.d(TAG, imageName); + } catch (IOException e) { + e.printStackTrace(); + } + return fileUri; + } public static String getFileFromURL(URL url) { if (url != null) { - String urlPath = url.getFile(); - return urlPath.substring(urlPath.lastIndexOf('/') + 1, urlPath.length()); + return url.getFile(); } else { return null; @@ -110,18 +149,6 @@ public static String getFileFromURL(URL url) { public static void setConditionsImage(final ImageView imageView, final URL conditionsImageURL, final Activity activity/*, boolean small*/) { - /*String conditionsImageName = getFileFromURL(conditionsImageURL); - String mappedName = getMappedConditionsImageName(conditionsImageName, small); - if (mappedName != null) { - Bitmap mappedImage = getBitmapFromResources(mappedName); - imageView.setImageBitmap(mappedImage); - } - else { - TODO - final SetImageFromURLTask downloadTask = new SetImageFromURLTask(conditionsImageURL, imageView); - - }*/ - executorService.execute(new Runnable() { @Override public void run() { @@ -144,21 +171,6 @@ public void run() { } }); } - - public static byte[] getConditionsImageBytes(URL conditionsImageURL) { - String conditionsImageName = getFileFromURL(conditionsImageURL); - String mappedName = getMappedConditionsImageName(conditionsImageName, false); - Bitmap bm = null; - if (mappedName != null) { - bm = getBitmapFromResources(mappedName); - } - else { - // TODO - } - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bm.compress(Bitmap.CompressFormat.PNG, 100, stream); - return stream.toByteArray(); - } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java index ab95715..ddee145 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java @@ -10,6 +10,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -74,6 +75,7 @@ import com.smartdevicelink.transport.MultiplexTransportConfig; import com.smartdevicelink.transport.TCPTransportConfig; import com.smartdevicelink.util.DebugTool; +import com.smartdevicelink.util.SystemInfo; import java.net.URL; import java.text.SimpleDateFormat; @@ -84,6 +86,10 @@ import java.util.List; import java.util.Locale; import java.util.Vector; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static com.smartdevicelink.trace.enums.Mod.proxy; @@ -141,8 +147,8 @@ public class SdlService extends Service { private SoftButtonObject mShowNextItem = null; private SoftButtonObject mShowListItems = null; private SoftButtonObject mShowBack = null; - private ForecastItem[] forecast_items = null; - private static int forecast_item_counter = 0; + volatile private ForecastItem[] forecast_items = null; + volatile private static int forecast_item_counter = 0; private Handler mTimedShowHandler = null; private String mConditionIconFileName = null; private WeatherDataManager mDataManager = null; @@ -177,6 +183,7 @@ public class SdlService extends Service { private MenuCell mainCell4 = null; private MenuCell mainCell5 = null; + private ExecutorService executorService = Executors.newSingleThreadExecutor(); // TCP/IP transport config // The default port is 12345 // The IP is of the machine that is running SDL Core @@ -737,6 +744,12 @@ public LifecycleConfigurationUpdate managerShouldUpdateLifecycle(Language langua return null; } } + + @Override + public boolean onSystemInfoReceived(SystemInfo systemInfo) { + return true; + } + }; // Create App Icon, this is set in the SdlManager builder @@ -989,7 +1002,7 @@ private void showWeatherConditions(boolean includeSpeak) { windSpeed = UnitConverter.convertSpeedToImperial(windSpeed); precipitation = UnitConverter.convertLengthToImperial(precipitation); } - if (mWeatherConditions.conditionIcon != null) { + /*if (mWeatherConditions.conditionIcon != null) { String imageName = ImageProcessor.getFileFromURL(mWeatherConditions.conditionIcon); mappedName = ImageProcessor.getMappedConditionsImageName(imageName, false); @@ -998,7 +1011,7 @@ private void showWeatherConditions(boolean includeSpeak) { conditionsID = getResources().getIdentifier(mappedName, "drawable", getPackageName()); Log.i(SdlApplication.TAG, "Conditions file: " + mConditionIconFileName); } - } + }*/ String field1; String field2 = ""; @@ -1029,14 +1042,16 @@ private void showWeatherConditions(boolean includeSpeak) { sdlManager.getScreenManager().setTextField4(field4); sdlManager.getScreenManager().setMediaTrackTextField(mediatrack); sdlManager.getScreenManager().setTextAlignment(TextAlignment.LEFT_ALIGNED); - sdlManager.getScreenManager().setPrimaryGraphic(new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionsID, true)); + //sdlManager.getScreenManager().setPrimaryGraphic(new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionsID, true)); sdlManager.getScreenManager().setSoftButtonObjects(getSoftButtonsForMainScreens()); sdlManager.getScreenManager().commit(new CompletionListener() { @Override public void onComplete(boolean success) { Log.i(TAG, "ScreenManager update complete: " + success); - + if (mWeatherConditions.conditionIcon != null) { + setWeatherGraphic(mWeatherConditions.conditionTitle + " 1057", mWeatherConditions.conditionIcon); + } } }); if (includeSpeak) { @@ -1164,6 +1179,7 @@ private void writeDisplay(boolean includeSpeak) { field1 = forecast_items[forecast_item_counter].showString_field1; field2 = forecast_items[forecast_item_counter].showString_field2; mediatrack = forecast_items[forecast_item_counter].precipitationChance.toString() + "%"; + setWeatherGraphic(forecast_items[forecast_item_counter].conditionTitle + " 1217", forecast_items[forecast_item_counter].conditionIcon); } sdlManager.getScreenManager().setTextField1(field1); @@ -1172,7 +1188,7 @@ private void writeDisplay(boolean includeSpeak) { sdlManager.getScreenManager().setTextField4(field4); sdlManager.getScreenManager().setMediaTrackTextField(mediatrack); - String mappedName = null; + /* String mappedName = null; conditionsID = 0; if (mWeatherConditions.conditionIcon != null && forecast_items != null) { String imageName = ImageProcessor.getFileFromURL(forecast_items[forecast_item_counter].conditionIcon); @@ -1181,10 +1197,14 @@ private void writeDisplay(boolean includeSpeak) { mConditionIconFileName = mappedName + ".png"; conditionsID = getResources().getIdentifier(mappedName, "drawable", getPackageName()); } - } + }*/ + + sdlManager.getScreenManager().setTextAlignment(TextAlignment.LEFT_ALIGNED); - sdlManager.getScreenManager().setPrimaryGraphic(new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionsID, true)); + //sdlManager.getScreenManager().setPrimaryGraphic(new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionsID, true)); + //setWeatherGraphic(mWeatherConditions.conditionTitle + " 1205", mWeatherConditions.conditionIcon); + if(softButtonObjects.size() > 0){ sdlManager.getScreenManager().setSoftButtonObjects(softButtonObjects); } @@ -1411,66 +1431,79 @@ private void showForecast(boolean includeSpeak, int numberOfForecasts) { * @param icon_url * @return sdlArtwork for Forecast choiceset */ - private SdlArtwork getArtWork(URL icon_url) { - String mappedName = null; + private SdlArtwork getArtWork(String title, URL icon_url) { + /* String mappedName = null; int conditionID = 0; if (mWeatherConditions.conditionIcon != null) { String imageName = ImageProcessor.getFileFromURL(icon_url); - mappedName = ImageProcessor.getMappedConditionsImageName(imageName, false); + //mappedName = ImageProcessor.getMappedConditionsImageName(imageName, false); - if (mappedName != null) { + /*if (mappedName != null) { mConditionIconFileName = mappedName + ".png"; conditionID = getResources().getIdentifier(mappedName, "drawable", getPackageName()); - } + }* / } - SdlArtwork tempArtworkName = new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionID, true); - return tempArtworkName; + */ + + //SdlArtwork tempArtworkName = new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionID, true); + //return tempArtworkName; + + SdlArtwork artwork; + + Uri graphicUri = ImageProcessor.getImageUriFromURL(title, icon_url, getApplicationContext()); + artwork = new SdlArtwork(title, FileType.GRAPHIC_PNG, graphicUri,true); + return artwork; } /** * pre loads choiceset for Hourly and Daily forecast */ private void createForecastChoiceSet() { - /* Choices for Hourly Forecast to be created */ - if (choiceCellList != null) { - sdlManager.getScreenManager().deleteChoices(choiceCellList); - } - choiceCellList = null; + executorService.execute(new Runnable() { + @Override + public void run() { + /* Choices for Hourly Forecast to be created */ + if (choiceCellList != null) { + sdlManager.getScreenManager().deleteChoices(choiceCellList); + } + choiceCellList = null; - if (mActiveInfoType == InfoType.HOURLY_FORECAST) { - ChoiceCell cell1 = new ChoiceCell(forecast_items[0].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[0].timeString})), getArtWork(forecast_items[0].conditionIcon)); - ChoiceCell cell2 = new ChoiceCell(forecast_items[1].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[1].timeString})), getArtWork(forecast_items[1].conditionIcon)); - ChoiceCell cell3 = new ChoiceCell(forecast_items[2].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[2].timeString})), getArtWork(forecast_items[2].conditionIcon)); - ChoiceCell cell4 = new ChoiceCell(forecast_items[3].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[3].timeString})), getArtWork(forecast_items[3].conditionIcon)); - ChoiceCell cell5 = new ChoiceCell(forecast_items[4].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[4].timeString})), getArtWork(forecast_items[4].conditionIcon)); - ChoiceCell cell6 = new ChoiceCell(forecast_items[5].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[5].timeString})), getArtWork(forecast_items[5].conditionIcon)); - ChoiceCell cell7 = new ChoiceCell(forecast_items[6].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[6].timeString})), getArtWork(forecast_items[6].conditionIcon)); - ChoiceCell cell8 = new ChoiceCell(forecast_items[7].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[7].timeString})), getArtWork(forecast_items[7].conditionIcon)); - ChoiceCell cell9 = new ChoiceCell(forecast_items[8].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[8].timeString})), getArtWork(forecast_items[8].conditionIcon)); - ChoiceCell cell10 = new ChoiceCell(forecast_items[9].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[9].timeString})), getArtWork(forecast_items[9].conditionIcon)); - ChoiceCell cell11 = new ChoiceCell(forecast_items[10].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[10].timeString})), getArtWork(forecast_items[10].conditionIcon)); - ChoiceCell cell12 = new ChoiceCell(forecast_items[11].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[11].timeString})), getArtWork(forecast_items[11].conditionIcon)); - forecast_item_counter = 0; - choiceCellList = Arrays.asList(cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8, cell9, cell10, cell11, cell12); - sdlManager.getScreenManager().preloadChoices(choiceCellList, null); - } + if (mActiveInfoType == InfoType.HOURLY_FORECAST) { + ChoiceCell cell1 = new ChoiceCell(forecast_items[0].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[0].timeString})), getArtWork(forecast_items[0].title, forecast_items[0].conditionIcon)); + ChoiceCell cell2 = new ChoiceCell(forecast_items[1].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[1].timeString})), getArtWork(forecast_items[1].title, forecast_items[1].conditionIcon)); + ChoiceCell cell3 = new ChoiceCell(forecast_items[2].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[2].timeString})), getArtWork(forecast_items[2].title, forecast_items[2].conditionIcon)); + ChoiceCell cell4 = new ChoiceCell(forecast_items[3].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[3].timeString})), getArtWork(forecast_items[3].title, forecast_items[3].conditionIcon)); + ChoiceCell cell5 = new ChoiceCell(forecast_items[4].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[4].timeString})), getArtWork(forecast_items[4].title, forecast_items[4].conditionIcon)); + ChoiceCell cell6 = new ChoiceCell(forecast_items[5].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[5].timeString})), getArtWork(forecast_items[5].title, forecast_items[5].conditionIcon)); + ChoiceCell cell7 = new ChoiceCell(forecast_items[6].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[6].timeString})), getArtWork(forecast_items[6].title, forecast_items[6].conditionIcon)); + ChoiceCell cell8 = new ChoiceCell(forecast_items[7].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[7].timeString})), getArtWork(forecast_items[7].title, forecast_items[7].conditionIcon)); + ChoiceCell cell9 = new ChoiceCell(forecast_items[8].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[8].timeString})), getArtWork(forecast_items[8].title, forecast_items[8].conditionIcon)); + ChoiceCell cell10 = new ChoiceCell(forecast_items[9].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[9].timeString})), getArtWork(forecast_items[9].title, forecast_items[9].conditionIcon)); + ChoiceCell cell11 = new ChoiceCell(forecast_items[10].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[10].timeString})), getArtWork(forecast_items[10].title, forecast_items[10].conditionIcon)); + ChoiceCell cell12 = new ChoiceCell(forecast_items[11].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[11].timeString})), getArtWork(forecast_items[11].title, forecast_items[11].conditionIcon)); + forecast_item_counter = 0; + choiceCellList = Arrays.asList(cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8, cell9, cell10, cell11, cell12); + sdlManager.getScreenManager().preloadChoices(choiceCellList, null); + } - /* Choices for Daily Forecast to be created */ - else if (mActiveInfoType == InfoType.DAILY_FORECAST) { - ChoiceCell cell1 = new ChoiceCell(getResources().getString(R.string.cmd_today), new Vector<>(Arrays.asList(new String[]{getResources().getString(R.string.cmd_today)})), getArtWork(forecast_items[0].conditionIcon)); - ChoiceCell cell2 = new ChoiceCell(getResources().getString(R.string.cmd_tomorrow), new Vector<>(Arrays.asList(new String[]{getResources().getString(R.string.cmd_tomorrow)})), getArtWork(forecast_items[1].conditionIcon)); - ChoiceCell cell3 = new ChoiceCell(forecast_items[2].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[2].fullDateString})), getArtWork(forecast_items[2].conditionIcon)); - ChoiceCell cell4 = new ChoiceCell(forecast_items[3].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[3].fullDateString})), getArtWork(forecast_items[3].conditionIcon)); - ChoiceCell cell5 = new ChoiceCell(forecast_items[4].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[4].fullDateString})), getArtWork(forecast_items[4].conditionIcon)); - ChoiceCell cell6 = new ChoiceCell(forecast_items[5].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[5].fullDateString})), getArtWork(forecast_items[5].conditionIcon)); - ChoiceCell cell7 = new ChoiceCell(forecast_items[6].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[6].fullDateString})), getArtWork(forecast_items[6].conditionIcon)); - ChoiceCell cell8 = new ChoiceCell(forecast_items[7].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[7].fullDateString})), getArtWork(forecast_items[7].conditionIcon)); - forecast_item_counter = 0; - choiceCellList = Arrays.asList(cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8); - sdlManager.getScreenManager().preloadChoices(choiceCellList, null); - } else { - Log.d(SdlApplication.TAG, "CreateInteractioinChoiceSet requested for something else than hourly or daily forecast"); - } + /* Choices for Daily Forecast to be created */ + else if (mActiveInfoType == InfoType.DAILY_FORECAST) { + ChoiceCell cell1 = new ChoiceCell(getResources().getString(R.string.cmd_today), new Vector<>(Arrays.asList(new String[]{getResources().getString(R.string.cmd_today)})), getArtWork(forecast_items[0].title, forecast_items[0].conditionIcon)); + ChoiceCell cell2 = new ChoiceCell(getResources().getString(R.string.cmd_tomorrow), new Vector<>(Arrays.asList(new String[]{getResources().getString(R.string.cmd_tomorrow)})), getArtWork(forecast_items[0].title, forecast_items[1].conditionIcon)); + ChoiceCell cell3 = new ChoiceCell(forecast_items[2].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[2].fullDateString})), getArtWork(forecast_items[2].title, forecast_items[2].conditionIcon)); + ChoiceCell cell4 = new ChoiceCell(forecast_items[3].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[3].fullDateString})), getArtWork(forecast_items[3].title, forecast_items[3].conditionIcon)); + ChoiceCell cell5 = new ChoiceCell(forecast_items[4].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[4].fullDateString})), getArtWork(forecast_items[4].title, forecast_items[4].conditionIcon)); + ChoiceCell cell6 = new ChoiceCell(forecast_items[5].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[5].fullDateString})), getArtWork(forecast_items[5].title, forecast_items[5].conditionIcon)); + ChoiceCell cell7 = new ChoiceCell(forecast_items[6].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[6].fullDateString})), getArtWork(forecast_items[6].title, forecast_items[6].conditionIcon)); + ChoiceCell cell8 = new ChoiceCell(forecast_items[7].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[7].fullDateString})), getArtWork(forecast_items[7].title, forecast_items[7].conditionIcon)); + forecast_item_counter = 0; + choiceCellList = Arrays.asList(cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8); + sdlManager.getScreenManager().preloadChoices(choiceCellList, null); + } else { + Log.d(SdlApplication.TAG, "CreateInteractioinChoiceSet requested for something else than hourly or daily forecast"); + } + } + }); } private void showStandardForecast(boolean includeSpeak) { @@ -1677,4 +1710,27 @@ public void setGlobalProperties(String helpPrompt, String timeoutPrompt, Integer req.setTimeoutPrompt(Arrays.asList(new TTSChunk(timeoutPrompt, SpeechCapabilities.TEXT))); sdlManager.sendRPC(req); } + + private void setWeatherGraphic(final String title, final URL iconURL) { + + Callable callable = new Callable() { + @Override + public SdlArtwork call() { + final SdlArtwork artwork = getArtWork(title, iconURL); + sdlManager.getScreenManager().setPrimaryGraphic(artwork); + + return artwork; + } + }; + + ExecutorCompletionService completionService = new ExecutorCompletionService<>(executorService); + completionService.submit(callable); + + try { + completionService.take(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } } From 4d9b7df88abeb3d9d0983b8a5026002f082369f8 Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Mon, 20 Dec 2021 09:25:53 -0500 Subject: [PATCH 05/11] Add local copies of weather icons to project --- .../app/src/main/res/drawable/ic_01d.png | Bin 0 -> 948 bytes .../app/src/main/res/drawable/ic_01n.png | Bin 0 -> 945 bytes .../app/src/main/res/drawable/ic_02d.png | Bin 0 -> 1628 bytes .../app/src/main/res/drawable/ic_02n.png | Bin 0 -> 1666 bytes .../app/src/main/res/drawable/ic_03d.png | Bin 0 -> 837 bytes .../app/src/main/res/drawable/ic_03n.png | Bin 0 -> 837 bytes .../app/src/main/res/drawable/ic_04d.png | Bin 0 -> 1869 bytes .../app/src/main/res/drawable/ic_04n.png | Bin 0 -> 1869 bytes .../app/src/main/res/drawable/ic_09d.png | Bin 0 -> 2697 bytes .../app/src/main/res/drawable/ic_09n.png | Bin 0 -> 2697 bytes .../app/src/main/res/drawable/ic_10d.png | Bin 0 -> 2584 bytes .../app/src/main/res/drawable/ic_10n.png | Bin 0 -> 2584 bytes .../app/src/main/res/drawable/ic_11d.png | Bin 0 -> 2844 bytes .../app/src/main/res/drawable/ic_11n.png | Bin 0 -> 2844 bytes .../app/src/main/res/drawable/ic_13d.png | Bin 0 -> 1703 bytes .../app/src/main/res/drawable/ic_13n.png | Bin 0 -> 1703 bytes .../app/src/main/res/drawable/ic_50d.png | Bin 0 -> 650 bytes .../app/src/main/res/drawable/ic_50n.png | Bin 0 -> 650 bytes .../app/src/main/res/drawable/ic_none.png | Bin 0 -> 5266 bytes 19 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 MobileWeather/app/src/main/res/drawable/ic_01d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_01n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_02d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_02n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_03d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_03n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_04d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_04n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_09d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_09n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_10d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_10n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_11d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_11n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_13d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_13n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_50d.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_50n.png create mode 100644 MobileWeather/app/src/main/res/drawable/ic_none.png diff --git a/MobileWeather/app/src/main/res/drawable/ic_01d.png b/MobileWeather/app/src/main/res/drawable/ic_01d.png new file mode 100644 index 0000000000000000000000000000000000000000..ed42ad9e01a8ac5c6a724cce862c79efe64c5113 GIT binary patch literal 948 zcmV;l155mgP)9LMpmDT&B6hN8p^g$x<)gDLX^g`^~+JSjz#2gRE=JSbASFP@ZSmXLYOOdd>E zNt7WZ6s;_}#O=6eum6AT{62NMx9)BK);Yg(_PF*zL_|bHL_|bHL_|bHL_|bHL_|cy zOf##Xm&<1qwWu1P78nW)1bP4+Y2ZT6Pc-*J`T*;J1ywF*Y)Rk<~a@u=mOLM z4+<#bcjJJwz%gKW!ON~%j)lwv?!+QeZsWr0?;@}&Va7kpt&nZNuCV!DybIYLH?BG9 z4qO6Sd5p=kkXgVI$Na_&1~zyEdlk|i*y)sAsns5osB&#s%LZ(*mKRKFaH74sqrWofHWxmzH zqGciNtT&Jas)?uVld~*jY>BQ={)x$_+p>_6hFK{Lu?Vp&WPo8-3SBHhEDPyrn3X~$ z6Dz7_Sx5)NtQ1;Xgjg2x)iA5HEDOnXhD%l~U!?aXcs&~P#UjMAkY|QjDZH}?u`J}C zVO9#SEkY~{xont~!X1kc%R-JBW~Fe{BE+(g=W+IV8GVWq`I58V72R9XLUsdhErP5I zG5Odhu-_`kx{zyex@MVt1`b$_*W>7Uu|sAx!tDyLtin7Bxm}`N;19qi%RsO5ON*Rx z|J8EqK(9h>0c)JHE43%i$ui}626kh-*Dpum*6L**o$|wR#vscBz`x1q zvTE4p9~r;7!BSnkW9o+Q)Bi5F>)hcq#kx34@JiyW`gokN5O|U}t75rMJP3E@4g+Qb z_X}QjIBUBS=nHHqc-d9UeXi~9_?QVyh(CKZVL9*OfnL~PAIxm8?iXd5{JbYVRy6pU zrXg{9XP8yrCjMXeZj_I4d-O71c0C!dR%Tm*h=_=Yh=_=Yh=_=Yh=_=Yh=|Dl0r&=| WM07SpB(5j`0000?ZnwBHJP2x_#6c?paQ-fS~2He>>nLAap@S&IIG)Jdzrq(+*Om~TNa=LhO zq2e*?d(Z#;e}3|PaQ zEk!Qb$YrQo_AmCo)4FroBxgnT*K-*wU-QiQdZ)Hhe3PYIOz|br9SmDONK8&wyxiE@ z_mfwWW6f8NZ(2)qH=Nbn9i(kjwluCar*r9(bAS4eEuO%4z^DCAz2LbW%c9t-L^fSi z+$0Jt@Vq}#odp0>m6`eZSdMjE&PM6rB;s7^ZYg3D^1U7pUPR??RwhjR)o9) zcZhKQZT0lX6ElR)2K?(gx%>8}2@l&MHF-91Y}8pYKQWAJu8xxq_shH)Q=L?~wO6G@ zdRQM`BN7$W5+U~KsKM$jI@~8qTfM_{>OJ3kefXVmBE4JSw9`DPw51=eDIM7~!5V1R z9*|j276HxL4l>I!X~xt;iC|?%uRc%xVyO4T``oXP5`Rzbo2+{_a%|E&@+5cu0lU=- zubtK%c{(fg6Q{@j7a>myzwS8z^x(t7*6Cr1a;HnRuAxQVgbT$a^#4IYZIRqr2t9m&TNx;=eA*tfSBHUuAjb z)q->OHg!eKOW%I%T$#Pk{-62Iv&HSVMOHu7I_TZGS?>7jp2`D@k3@=|E`8`&VN}jx zz`P^+!?we*w^JSR6Pyp;UKjN)JGcDkb(4geKerysZ~JT(_o9{gTjQB?uj+!P9X-GM zadzh3{!A&6>E&&qU)MhR-g@5cr$E2E{-Z6gKUzf3%;i+V1pnC|X!Dtsm}T9n6#`~O N22WQ%mvv4FO#qL;wORlG literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_02d.png b/MobileWeather/app/src/main/res/drawable/ic_02d.png new file mode 100644 index 0000000000000000000000000000000000000000..fabd9c3b31a1032119075253795e9b7ed4116bcd GIT binary patch literal 1628 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 E0C6^bEC2ui literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_03n.png b/MobileWeather/app/src/main/res/drawable/ic_03n.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2e9f7f955fe1b8da7f30e9ae15b2f707387c48 GIT binary patch literal 837 zcmeAS@N?(olHy`uVBq!ia0vp^DIm>P?`AWc7~S-m-ejAoHrg_yf=SDPl?b_^@-?U)%;(kqaiJ3;*q@k2$SkU4Af3?c`uJQKgmd|Y;e_wg?%-r95&Rg!ce!l0q?QOP%hekm$Ah;>xlaY^4@g z&4Aq34Kw?`PQUUawzzBZtPrWFcQN$|TzfcnFqIr%{pb4u=Lbv%$9J~-xGlTDVsLwB z9jgV8%-WB)N)kjj$lj2dp2#)XOX2aH*V~WIuUdDM^FZYDI<~2&6klgE+45L0uhGf+ zpu{{gr1Z|eumt(7Ioeqd_K1rg-t<#1f!jd8PT^N${DF-AIH@nfGgxcZegC#ePp^;F z@0jyt!vx2xH+(x=1$!h@R=v^dpIo(LXUYDS)$4U#d0xqSa6A?@xLPU0|GrD&it&U3 z)jOe=4D60XA9;AsOaFt>yzo1fY@664W*m33n89Jw`CRDH-Q&>*Tpw(Fejsl1n!p3m z32p9wn_}0^6PunoZENX_Hpe^L^w!rt7xma3d^cnbgI~p$N1`5z#Xsh}?N+Z1j%(bq zYzMpSo9%itUnM)FPE2L3^w`7nGicS_TRSwbvF>GhS+;7n)aSp?MNh;o-?j4P=6A=n zC#*goz5JHu&y>5}&pr2E-dLt37y136LrJZa?O#b-2fG*)luK`cd=M%d6g* zf2sbGs8Eye|H$&BcSrOE|9(EJ^ZIGIAa*cuA;UkWQ;RcxC35Xd17;5fPgg&ebxsLQ E0C6^bEC2ui literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_04d.png b/MobileWeather/app/src/main/res/drawable/ic_04d.png new file mode 100644 index 0000000000000000000000000000000000000000..9c64ea86b6a93c7fd61543c696956b0657454fe9 GIT binary patch literal 1869 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 D(B1}6 literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_09n.png b/MobileWeather/app/src/main/res/drawable/ic_09n.png new file mode 100644 index 0000000000000000000000000000000000000000..0f14cb61a53463cf6ca803561716b21a41cd3c36 GIT binary patch literal 2697 zcmV;43U>90P)U4DF_S2r1S7%mZ*7z+!h=I(8NED^y%*(I1(Nnk(ZN`onBwx z@bt*Yh<)hNf)T)Lllp|?php|BiVrLIx%h6H&1NgKSS*DbHf-p(T5TvUK7iEJRD_3z z(-5ZRK3{Vi`Ltmd)tu>Vny9 z*4k5y+?JdN@OJx(+O!rwOJhX9^;F&PsM zi84GqjIb~uBjZa@Rh4}_^uD(8IGfM$mmrIHgf-Jo_j8H8@uyCm3Zv2JjUZnTRY!ja zk~bOd@$vC!Z*Rwq z8#fRT5Qv0?__3jE0(@fkTp`-+E#+}4-9SSWE&tMxMQ(3bS2sTWv<`dszJ-eyFCr~1 zjX4Dqh*DfuR{A3m&m_{znyoi62%3Y0g@v(34+=6cd-CX`kD#PvKb9_i64$R^pQwmV z02@WsPvnrbPGH#{Lp{VEY8hpai8N(O6pD&|&D`cQXDTsmS_(Vw1YP)b0H2AfpU58K zbx6ct*w_FGggF#Peck5EwF5#Gdg?kLvnHowr}6M%xbkh z(AL&A(_k=U@Wrg3aACyOLtxoffn^JU^1b@|`;ediOyd6i@75WO#@fclrhgd>2ERRv zNI@nwS-=yhmyie6+iT99J$n|4i{E97UKGv`3JUU19R!wrLSWg#AfvCU>L9u8MXE>`5@p7W88~v}5Yp2hz=aE!*aBEvTbsBSLR7(gVz`Q@ z^|%d9$jG~)4@GY$i75NaWOs?Xx;n9AM-lq^dfB9M=guM_HS@;;)dv8caOWC&coM>7 z3csTq4+DUnLP5~=&h;Wu91&F~f2@Z*>8?Eq4i08wP#9y$k|$7Ab^Z=<{6ML*7~sa9 z7QCo;(3%Q)VxsWZ0@c%eVW(!ie;5sE7}46=g5A6KpuD^sd-wjHH3kVK7?rPaEVsUwq?=%9p-{+K07v-R{-mReEp618k&#pI;fG~dzy5_+@4x?x+sL4Yg@w)B zwd*&vl*;1_4IlKT(zW*SoraV1=c||qxw-Skk{u{4;=_za2Gv&tlbQzP{8oSR$z^KH zUl|OBPDN2ZYieq#wKrr%MFneY+EX}xel8X+TsT%vcXv0A9XpPbCr{zZmFk%QLT1gH zc_A+^4+RDJOt9WwGaGXJ>>y}nQhEpp5fT!DkPzAhWyQxIm*eo^qqutY8iIp^*xO8v zN4?~wmo}iWu@QUryv6c(M@Ce(^`NjYpXKwC7@If0#>9+`jm?RVk7GNk+S~v0@a@~T z&Qhkyjvc>5cJ^#0iiTCeyCSNG_*D>UyjBn8=JQ<@3_Ds;XX8RW%2r_NGWtlua~|qg2xa z2liv$ym>xzqwhIEXpFZL8XAItfYH&B@}b^;{{x&peHv%ZoJC-u0ZB(3u$xcv@Q50n{<)`t}yA?%QPi0Z#(t;Bg@vl6!h@kz4O*F(cH8o-S^y%2Q?~jOy ziFPL^YeMch8)7%8;6~MK?qxmU;K4(vtUQOv$VjB5q%fO6jj5_CWxyu`_yupw7P{79 zANuPK#Rt&PL&*u?J&*Gp{(qPFHaI3I-KW0(DypliL3YK5+tq!JFhMqM+%&c}-Q3)a zj*c!i$Dko8D=U-P0{V_OW)c?zS%gmf+~LpT_TJ&ZQitPm8WFYDXLJqSKOuB)5GF`cQj*K_ zF1(yjr*Oq~7nhylv;BA;Mb?bPGBh^Ck+VQqjCyo|^^j~$afto=xO$u)0;m8mi~kzK zpS9kA>`-_(MQDGE%F0R>q9LQ~W)7BL(Ssnvd^arFh;O<0K86X!KKuCNuG&^J!Q5L);K|L;xG3Nx&!h+6 zyRmJ&n^5D^P*qn~$97m9J$i`ku<~SXBFgR6`%h~;#>D{N0^r< zWo2c-UEQG+|6jOGQP+S*%&}9NP{@Gk%FgB9TZW z5{X12kw_#Gi9{liNF)-8L?V$$Boc{4B9TZWq5$w;?BvCS0QD>{00000NkvXXu0mjf D(B1}6 literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_10d.png b/MobileWeather/app/src/main/res/drawable/ic_10d.png new file mode 100644 index 0000000000000000000000000000000000000000..62304fded6be1c5cea4a6a353cf336a98bec4c3f GIT binary patch literal 2584 zcmaKuXFL=R1IN!6iDbs*jL@IUR%e~losm5f*_7>MCmC0fGrMdK;bd=4*(+y-dt_zD zW!xb<`|0_7p105Q;`jT$`@a67jSRF{m~Juw000&pZMex_KK;)a>Hq%opDAVlz?B6Z zILzz?b`u-&+-w|3fUeGmLfJI{eCn}yBe#lwVM@ki-7AcUT)GugvQ9)Lyg7gDR3O&8 z!n`8<<9DVu7Zl!J_4DOjo@EPrwquf6(|rzu;ljecA6eTsJl`LITEs|O-Bz->+GpJ(UyMg3DKPgY}y5vw$I5>>+VVH(i0gORp0^Q@Xex**{b=E^+ z`a^NW_ythU&GWtGG~UhZZqKsY)&aK^iok6~@vr$ATIjHl(W_m32KY}lr*Hb`7C`N- zSMr*}EL{K#e$AsMW(6Q$`H7Sy*i*T_0JwEybVq+~_tZEgQoKE&xx>UP3v@)iJTP47 z<~o;+FJZD(Vvxw{9^gYZdTBuF0;|oEK(%0~z$+vRIIWZa9(r`ePdc!X|B3F3VbDZM z*{nDYTG~JOL3yJJYZ&#atYlz^KZ%II@qjC7Mi3}jYuVH z4&tt%9f~sW_eAw*_Rgkh#X^B~Zr+gBFWRiCR<0hi|JJux0X?1@`D{{3u^#Nfn-(fP z8DpEi3l~8L_Hxvc6_e{e3-}ktpBr6)t;FY-p|;IFZc%GY;R#AU)F)S1-uJ7E$iBzU zv>-DVM9Hijdz_pFjF{pF-&u~Qz_$Q~!E>Tem%dl_m5B!jv0lW?1+k-Dt`G}%;2pSZ zRB=T^qRqn>whY*2bk5H-&{zaHwA8~sJ!%t!2$R1d(?V4iEY`aP18c1|^OYczM|mCyM_#)Hb~;?zX`K+xh}+ zN=b4}p;wn*we+&W=66f@<_NML(!0jWzsio5Wx^PDKj((7$ugYfEQK-FGQF65_p(Ku z-a0+Zd~MQlVw~h$Ey=7u#u%x=7_?kI;DBD>Jh`py1aXl6!Dd}OL!giidXo0FJoVNlDzeolz+KMUa_1Zlf_(q{m z`3$?=*t9_Hj%Cy5<>5}%3XzrJsUJy*E7VOpSK?=ikJBoMWK|83EM_Fvgnb=v^GbEZ-?{*;!;nf0M$g|BwKjy|>)=PFr>-bg-U zyLL%$9?z+Iw~6Db_Idx49(1laPB}?14ZQClc}wggnd0W|xYHf0Q)sHEc4YlYPj$q= zu+Rh_&FRmcp#2D*J>~ol*KHn2m9_fg@oy`>$E25_YF*bSrJqHeV!d=vL}XG>j( zG*qJcnLYLE-0T6!c4t{$F6eJIiQM3*RxhujH`AIi99^l24 zi!)42P5u0VzO?@cMa%P{pTeRK@rvfEtugHtb|cDGjqF$6>4>r)de&gsZs|`#Mu{-B z+#=D8tMqR=tqL1tcaKR7N~TscvT(B)m(1@2(#C>Z4wg{A(h&2p`t|H}`P0Mg)hP0+ zESZp&WKcwZU_Bxur9Ce`>ysq3mU*#LJlpOqpnEI)HTPusvmo8rP0R7|i2!s(~=WRn_b^a9QP;k_^E5* zD)@98HLnK7>g(`TK?>$%p+3kUKjL=Kec_h$48%y{J*7lw8XClZEhMN@3%&ZfC!Z#U zkwW^AzmS%qohB$?=a++Mcaz?%dHSl#oQI9*`u6TR@e#2N_XE45oSj`;e|mWbb0PTq zLCBI6|HT0*dQuqpZd~dtvd~Zx!+Lirw%$3!wN1b_759S{Gf&(Joxs>eQP-VhjPKIP zDWk$6JKeX@1Sfx-$&g+3`&g-_bOIMtLj?okU`kbq*~xOBt$f&6UNjOUJOagkTHSIE(TVQYnI;TqWRiF2IQ`^d`mo zyX-qWOV6XNyqmLal!J_|tgMWU6F6akG&iyzBg-W*7nytc>8%LmeJVla@Tt+fdVHgt r%Wyz_1O7*d$p6Wv|0WfZ=8V;WO01z+^n~=c_y9Tx19+u|{p)`Nw}AnJ literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_10n.png b/MobileWeather/app/src/main/res/drawable/ic_10n.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e5d101266c3a54b4893e8e38e3c0014f3fdfa2 GIT binary patch literal 2584 zcmaKuc{me}AIGO5b8v_l*R z1Wy{2KE~S!f3P#jEW0VnBCCoQOUhum{DyLYVgS+S;FmX8<+HSmm%fk@7#|p+P8fF? zgsDf4$OPudM>833L-kZmQ|~d0VyCs3FJjl4mUKB-Puzz~@c(bD`xy=Yo}QSP2+Iz( z7xncI^O#E#z~f_PtCys;80P`10BvqpwM6~HX&t~H;H)Cx*NMF)#>C|DwC?RSsdNYX z<@|R-q+LMYs(#i5Kr0Em!=-K_o7kxOrm3(jHN2C3RsnF*T1ZM*mE|WZoGPJr=5c}u zk4iqF7n6cwb@4 z@`dK7W-!>J1xSi4ag}NGwGfTkyQD-G#rgT~l&Oh>8mhOA+SnWr5Dj|iE3Ym)zEZnJ z%N$&l#prXdaj+*8L&*0z>75M!jVKUuY#}FIWM|VGr>_f~l}{`q!W^DTKQJh!E#?0C zxti5E{p1`LtCVl*xTzyzvs>|pLM-=a*Pho@81hx^|^NqSe>1;=*a$FLWlxr{pcM%MO7l=NDx?YTP`#Y!X! z*Llx<;-t?8`3>0-%wLQld?m*{Gnl_na1D@qg3mxfk2@W1eDa#}$GHf4jE9t}z>yK1 zw?vC9f?a!`-YE6FeLDCTDzUE%n~7Vy7OTY3QS3VWy+%kkP9rp+zCQe8*aBB2fhwf! z%WqoCjY5t1Nmf;9PNxT|bQ2vptVV@erlBCSqQ_5t1l_*=c-{eps<|E&ghvrt3fTaH zbuclUuTED}N+9`{bvbRz5ih*P;YOJquzfbeG{*3)Kb8Mf7G_S%J&qA3&*&u{-uGh* zdvU~MoO_j$u9Ioz(JiC!fHE0WxAclg9du5;X@GyzX)B0LkviS7H>`qb3pQ;|PJo{h zv&lr}hU*QU!4~I=kbLU-O@I;hPp+$k)cN+!q_?!(0)fGE+r|kN#w{-YhB=cd69`U= za35BOoL2@N^CvG!B~aiG+TCs{#)^YsRuhq$9cmZ$@0 z2Ut20B}K69%w4xmydOl=@!>>)Lq0d`nR!Dh?43}nd(^Dox5Svnv}A&RHYMfxaa=Q?Tri0!O22Hh?V++ zZDF<@0ToQ|!fU0YrZ3)EX3+XC9La~d_LyxgX{7%%uQE&25I6`{MJ-Ba%z)E`Xm9p69?3q ziB999p9e_WFARBPgTZO*^CK2+^QD=8e}BbS+p%{ht%II;ZpaXk;!ZiJUk#c6VYN32 zG*z~%epTK_)rNX#Ub)?xWRsx9^Rlg|sY3KUk5nq(=QCT2wX~I;sylG%&Z-(awkNdd z&$T-b+UIgb3JP*yV!VC%I_JnhTS+aT!k@`~C6Ld|JHB%||W z8XtWsriSU548~$0^xoG(& zmh$Ed^+w~Ih4DMO;spiIYqrN_@_r{@n%&C$ZE_rcK-8BU8UU-SBq5>U#O!`EGst8u zm?>TXH@l~Uu3%rRG3np19A$3FjP%}xakeJtCaUibf-_2JzS8Y6-2J!rY~oo+T=Beb zjtA@TF|k^c2Mu#iR+ZvuQG0RA_B%iB@Kd`oQqS=e@#OcdM-x9r&7Bx1ge8}sxpcSb zZO(KQx19V>OrECrqBr0Si?(<|-bWbYsGfD>>~cXRkLJsk&9=GT&Tq7Db#i(`{uS2Z|LH;)F@};v$14EW@=?R|F#LfTG>y(Z~iiy4=3$ zejMM7Sn+5VbKNWtg-oCM6%PsdP6*a(xTBEF^@wL!1x?|$aK0!r$!#z+v>rsiE_&eF zHw7Zb2qnq^bj?uToX(Ef=WxdSo5m(}uvnP^kdu>UsUz(#YAE5Oih`0iu2WKzAbkhiGH1TJfD_G0N zn}v^o(C6UZoHopR`$|bgMMa#^vLbugoXdU_IzhKg>m5Lpwkj-Vjcyi>*~7EoKQTIK q9eQ4|{C?@u|3{hrt5X4hL-Ds+h8Bvjpyz*$4}iR8ZdhsHn)DxmH|971 literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_11d.png b/MobileWeather/app/src/main/res/drawable/ic_11d.png new file mode 100644 index 0000000000000000000000000000000000000000..4a885cfa9e79bc113e3d2dc7c7ad7d425e97e1fc GIT binary patch literal 2844 zcmV+%3*+>OP)n5 z+o=j#Q99aKYSoGwt&TccS`-GY1qB64AS8i+B%6@u-rM=^o=sL*0-HU%n=t1$lS%gO z?%8wif6wfEU46dC>-FBz+}w=MmvCHilb)&TVJHc*K-u*bz)YYL7zGrm-<6hS z&Ft#xeDaJl&e(R>U3X6H?d_$pu`y9mz5`@NJuU`H5<`$sLqkJDBBF@k1;E+Bt7D^WZiB!+%Lh0d2KpXFC8^iCDUeNX2}4|sm5`kE_qlk7Q)cUENHq2jwThBXIt7^nR1fyea^Y>;c14*! zeL9O4Ep$y+68U2?2ak|0K?;H8i4GDGMY!UM%c-cSprs{gb+yZZlae)fgmejVi_{8r z3?!n+D#^{weYXB zSIL0`Uy+fKf#2`Pacos?@ljS*>PBRd_r%F|o%4=Gx&%2>=U@@jRarS6pD%+02ljLG z%{MWA{ApxmWx3C_wgy?ZZXIjZyh>(fCS_%1NFEahVhcyBDC|r1SUc(150Q$icJC`8 zuL!epfmOVmK(_Y4o9UG4*9Z@-n6w+Q-xzl_7V<)~Jk`6)6 z0zT34xzUJ^kd;taSmYiJ23y@@5q~1j1p)y#XK89`WX_yxX=yp=t`zYz2_SnbKSh%u z>2T^hfP}6$=lbJsKyX@V&SI0;HMy>pWNwy%;Q|E!R@FAd<Y&z)z-c?r~+9ux>m?F!%YlgG~ES*LHBV%K_N?) zJjlL%&1~4Pf%5Y5fwFn2y<9ED03CDEm52O5(>Q5GR*PixQc@8ffBaEOOH0|aXODY4 z2LA-Len_?#M+8=NJ&esTzsj|<>wbLZ{Cd?7A{=$QC3z~ps1+G6AFcT zRTIP}wHL1yU4rN_S5o$ns%`Gk_pY4wHjoN!>~E}A+S=N<^2#e4eZGvF7caiYjm%0) zO5!cs$AvDZ{&@l|o&9)y5Xi)S=SA$gFR^lzFfz;TE*v|A?~>~vzX-Y#E(#tK@h9Qr zF>}nPpMKgffBpj3;mcOBh;ukY>2`#z2@B%bSlQXw^*iZVem8V=LRL=rIgb~oejDBy zv$1k>b=97ZiTISRt}a;-Z0J0c_>7Me)pFD>KBcOviYK33L2+>r_4V~S z+E|Z3mmtzb-ZtoW%SKX84)&H$2)+3{)~J)*71Q_hS55e?_zBjiQk-BgCH^CdPRp`3 zi7>LyGHKEzYHFTyi;|l+f7X}F=t9?-^{P9Q28SoyaTt1k4IBzWK<$)@scP7XXUYtG zm(0PDvbm!r+>ZxY{aq-ZBBDqK{;E|^yP5vhtzS@Ax6AFE)Pc?|+<@Be7#Y`_#dqWv zVmH;(v-rC3i~`x=u(!Jtr@0aT-7E2&c{YyZ2dP~hs5aMEqYua?m?V!vP0jPX|NaMV zGsV)S4<-Czy7$z_s*R}QziM8e>&$x|T}s;)X12}C#cg0xZ)Ftd4Twhf@d~h@|P|2h0$6HqT_BvgWUotkR)~Z{Q zrB(F$0bi-Q)kE*T1Z^!)II7=Ah##>$^enprN1`g}KHR+x->nbetDcRc8X1y=^zqI; zo}onuorz+y+km9ba~^Q15-IFo{<#MC3ZP3#VK0ow!r*`ToB6V4o8x~q&NeV`M-Cy+c%flYu9%*K_{8_GJccUwR zW~}#~5Tr&BiK?>muyV2qZCa1L^<%93Bbpa)Q+LEhco_J7-=hpaPFssBN@{Y#^pM4~#!XLoO z44W2b*EV`qJ%qjfU98b3xvRwuW&4>D)$c2%my2VLEB>QtS5l@tRL~-bm6w0y!H`Yp z<9}j*_!gm$-gPr^tGqIt@Ez$tBEF+hwW7vGs=pm7T@bsK`pR$8(IQCi%TM<|kW#v1 zV}G_D`#=A3Gi__s7$`08D;gfgX~1Rb&}`M?6dN&BU21bGMZ1QnMvoxRKK%6qa8<`{eRXjvZAAzgE9RjDg!}{C;R|43|`-%6bBZzIsen*|?)WIj~uge=751;AvIr z9{6*za>npPUz6rU@#aA27|$1Fc=I5 ugTY`h7z_r3!C){L3OP)n5 z+o=j#Q99aKYSoGwt&TccS`-GY1qB64AS8i+B%6@u-rM=^o=sL*0-HU%n=t1$lS%gO z?%8wif6wfEU46dC>-FBz+}w=MmvCHilb)&TVJHc*K-u*bz)YYL7zGrm-<6hS z&Ft#xeDaJl&e(R>U3X6H?d_$pu`y9mz5`@NJuU`H5<`$sLqkJDBBF@k1;E+Bt7D^WZiB!+%Lh0d2KpXFC8^iCDUeNX2}4|sm5`kE_qlk7Q)cUENHq2jwThBXIt7^nR1fyea^Y>;c14*! zeL9O4Ep$y+68U2?2ak|0K?;H8i4GDGMY!UM%c-cSprs{gb+yZZlae)fgmejVi_{8r z3?!n+D#^{weYXB zSIL0`Uy+fKf#2`Pacos?@ljS*>PBRd_r%F|o%4=Gx&%2>=U@@jRarS6pD%+02ljLG z%{MWA{ApxmWx3C_wgy?ZZXIjZyh>(fCS_%1NFEahVhcyBDC|r1SUc(150Q$icJC`8 zuL!epfmOVmK(_Y4o9UG4*9Z@-n6w+Q-xzl_7V<)~Jk`6)6 z0zT34xzUJ^kd;taSmYiJ23y@@5q~1j1p)y#XK89`WX_yxX=yp=t`zYz2_SnbKSh%u z>2T^hfP}6$=lbJsKyX@V&SI0;HMy>pWNwy%;Q|E!R@FAd<Y&z)z-c?r~+9ux>m?F!%YlgG~ES*LHBV%K_N?) zJjlL%&1~4Pf%5Y5fwFn2y<9ED03CDEm52O5(>Q5GR*PixQc@8ffBaEOOH0|aXODY4 z2LA-Len_?#M+8=NJ&esTzsj|<>wbLZ{Cd?7A{=$QC3z~ps1+G6AFcT zRTIP}wHL1yU4rN_S5o$ns%`Gk_pY4wHjoN!>~E}A+S=N<^2#e4eZGvF7caiYjm%0) zO5!cs$AvDZ{&@l|o&9)y5Xi)S=SA$gFR^lzFfz;TE*v|A?~>~vzX-Y#E(#tK@h9Qr zF>}nPpMKgffBpj3;mcOBh;ukY>2`#z2@B%bSlQXw^*iZVem8V=LRL=rIgb~oejDBy zv$1k>b=97ZiTISRt}a;-Z0J0c_>7Me)pFD>KBcOviYK33L2+>r_4V~S z+E|Z3mmtzb-ZtoW%SKX84)&H$2)+3{)~J)*71Q_hS55e?_zBjiQk-BgCH^CdPRp`3 zi7>LyGHKEzYHFTyi;|l+f7X}F=t9?-^{P9Q28SoyaTt1k4IBzWK<$)@scP7XXUYtG zm(0PDvbm!r+>ZxY{aq-ZBBDqK{;E|^yP5vhtzS@Ax6AFE)Pc?|+<@Be7#Y`_#dqWv zVmH;(v-rC3i~`x=u(!Jtr@0aT-7E2&c{YyZ2dP~hs5aMEqYua?m?V!vP0jPX|NaMV zGsV)S4<-Czy7$z_s*R}QziM8e>&$x|T}s;)X12}C#cg0xZ)Ftd4Twhf@d~h@|P|2h0$6HqT_BvgWUotkR)~Z{Q zrB(F$0bi-Q)kE*T1Z^!)II7=Ah##>$^enprN1`g}KHR+x->nbetDcRc8X1y=^zqI; zo}onuorz+y+km9ba~^Q15-IFo{<#MC3ZP3#VK0ow!r*`ToB6V4o8x~q&NeV`M-Cy+c%flYu9%*K_{8_GJccUwR zW~}#~5Tr&BiK?>muyV2qZCa1L^<%93Bbpa)Q+LEhco_J7-=hpaPFssBN@{Y#^pM4~#!XLoO z44W2b*EV`qJ%qjfU98b3xvRwuW&4>D)$c2%my2VLEB>QtS5l@tRL~-bm6w0y!H`Yp z<9}j*_!gm$-gPr^tGqIt@Ez$tBEF+hwW7vGs=pm7T@bsK`pR$8(IQCi%TM<|kW#v1 zV}G_D`#=A3Gi__s7$`08D;gfgX~1Rb&}`M?6dN&BU21bGMZ1QnMvoxRKK%6qa8<`{eRXjvZAAzgE9RjDg!}{C;R|43|`-%6bBZzIsen*|?)WIj~uge=751;AvIr z9{6*za>npPUz6rU@#aA27|$1Fc=I5 ugTY`h7z_r3!C){L3wQiuOoSRvxy z3mevUck$V`cHQGwcf;eq$IqN7{WsON`qk-!UB1j3S#`mrjaN72t9a|V-Q8-G#gG7= z--l}IOi}=9s!{A#BW@vw@f%xMUFI3U--0$C<_O>$mYShdgcZ;Y=mv;oY%1bpIKe$Y zxZLyW-Z3BJMLcK$m@POgH+p)qO>%E=AtfR=ZZ*76W{jxPhU@CKEyILcA^aKTp`)J$qi419|C2ltig6tC=kAO_uY_aTJc6Nq-%T zF_}6Uy@X1bIX@na`b1bG^ciieZs4A4JdYzM{#m+a>Ck&^K+4x=sTeVk!$(H?{kW z(zv(D>0FeH`Z=YYI?p6W&ZBv`?4p?-52oFVZ+sV(A1C+i0Kru=bsk?^<;sd9MI1X% z3mR|Bvv9Ef?Su^~8c4K8LQ6$^tQ@tNet^V$Q@QV61`n2>xn9unhfL(#ADH9=bvd6Q z2U}ZT+ID$gUBQdSZ*T`RIVdo%;6HH(pi=kG=TjuHq<$?HVNy&?(zp>Z`UhxwGtK>+ zcE0-=+~SkTpSDHp+OeN1c-ohHe=p0e-EPW9yaa68mV)4{AW*-CV`KHfFulZ=plESB zX{t*daFkra>VI8^x8=qdelY9ssmuUaRT)Y_(p=Leq)O|VEI}RIPiLmb25W75aNavu zu*8iXcS1R&1adtSj$VWrO_XfSe_=N%$W|IqU_&*so;li0=df#?0w!B<#JV@{acHO& z2nYI1!&&QycA$i1BL{oy;qE6{>l`DAU$q(2eAvm@SS@>bBW}1i{!r=JrkbJS&2DEA zkYu2K#e{MtRErKk`;Z>tg!V8($ra1TpLisu!r^z%-)Mha(N7)31_?;Yg6yEtzG8gE zdn)OqRoooSs;IOLvxf$7I(#BFH(NZ=X4rrbO1xSoFR;UnBkKbC_e0F~*zRODlr5nX zh0~$F*Cez4{tv;L9YU)1C(>-*9h5}%>v`cjQd*RXhHa|MnLlQ`5aM&fnBE1yX&KsR zAWqB6$GX{HzW-!5#vrjk0k!~%u#`3v3WU|YfzGK8l;Q;JTT`29>hHCWH;;Vx8dAcr zLpndQ6($q^tO-TxFqvMIi}a<)<@Btjc*-#g++6HUx}@6I?pRiuM{=m2&3%4*`6%+f z8)Ju+DbZ{l)T$~QqAY+o{n+eUEdM0~aV=o3QUGCS-9lJTlsE};z6q~wKW98}_!MSx zGMLIruG(_>4O1FCm9M*&#$GAD1>3N(B<`c#CBzYEqx9Pw6s&EPI%U*^K+Sv=J*wvP z^j9H#2q_AdY)<3Op#59x#hAqyzoP9)Na%=~bJmS^TmZga)Kh=8a8NqJAg1YUS1aS| zxzoBP?R5Rw`f}xmje!PBw>992`&v`oy=~*P_qH=aIIip(!*W~&yWtv=j0b-CvwJbF rvX&B;oBPkRzMBmmxU?r41+b$gHHVs93caDScR&DduTw}iA|m%M@vA#( literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_13n.png b/MobileWeather/app/src/main/res/drawable/ic_13n.png new file mode 100644 index 0000000000000000000000000000000000000000..7867322e900068a96547ec05d5e1a7b841d4ab64 GIT binary patch literal 1703 zcmai#30D$`0);`%-O87m1~!R`3$EmnwQiuOoSRvxy z3mevUck$V`cHQGwcf;eq$IqN7{WsON`qk-!UB1j3S#`mrjaN72t9a|V-Q8-G#gG7= z--l}IOi}=9s!{A#BW@vw@f%xMUFI3U--0$C<_O>$mYShdgcZ;Y=mv;oY%1bpIKe$Y zxZLyW-Z3BJMLcK$m@POgH+p)qO>%E=AtfR=ZZ*76W{jxPhU@CKEyILcA^aKTp`)J$qi419|C2ltig6tC=kAO_uY_aTJc6Nq-%T zF_}6Uy@X1bIX@na`b1bG^ciieZs4A4JdYzM{#m+a>Ck&^K+4x=sTeVk!$(H?{kW z(zv(D>0FeH`Z=YYI?p6W&ZBv`?4p?-52oFVZ+sV(A1C+i0Kru=bsk?^<;sd9MI1X% z3mR|Bvv9Ef?Su^~8c4K8LQ6$^tQ@tNet^V$Q@QV61`n2>xn9unhfL(#ADH9=bvd6Q z2U}ZT+ID$gUBQdSZ*T`RIVdo%;6HH(pi=kG=TjuHq<$?HVNy&?(zp>Z`UhxwGtK>+ zcE0-=+~SkTpSDHp+OeN1c-ohHe=p0e-EPW9yaa68mV)4{AW*-CV`KHfFulZ=plESB zX{t*daFkra>VI8^x8=qdelY9ssmuUaRT)Y_(p=Leq)O|VEI}RIPiLmb25W75aNavu zu*8iXcS1R&1adtSj$VWrO_XfSe_=N%$W|IqU_&*so;li0=df#?0w!B<#JV@{acHO& z2nYI1!&&QycA$i1BL{oy;qE6{>l`DAU$q(2eAvm@SS@>bBW}1i{!r=JrkbJS&2DEA zkYu2K#e{MtRErKk`;Z>tg!V8($ra1TpLisu!r^z%-)Mha(N7)31_?;Yg6yEtzG8gE zdn)OqRoooSs;IOLvxf$7I(#BFH(NZ=X4rrbO1xSoFR;UnBkKbC_e0F~*zRODlr5nX zh0~$F*Cez4{tv;L9YU)1C(>-*9h5}%>v`cjQd*RXhHa|MnLlQ`5aM&fnBE1yX&KsR zAWqB6$GX{HzW-!5#vrjk0k!~%u#`3v3WU|YfzGK8l;Q;JTT`29>hHCWH;;Vx8dAcr zLpndQ6($q^tO-TxFqvMIi}a<)<@Btjc*-#g++6HUx}@6I?pRiuM{=m2&3%4*`6%+f z8)Ju+DbZ{l)T$~QqAY+o{n+eUEdM0~aV=o3QUGCS-9lJTlsE};z6q~wKW98}_!MSx zGMLIruG(_>4O1FCm9M*&#$GAD1>3N(B<`c#CBzYEqx9Pw6s&EPI%U*^K+Sv=J*wvP z^j9H#2q_AdY)<3Op#59x#hAqyzoP9)Na%=~bJmS^TmZga)Kh=8a8NqJAg1YUS1aS| zxzoBP?R5Rw`f}xmje!PBw>992`&v`oy=~*P_qH=aIIip(!*W~&yWtv=j0b-CvwJbF rvX&B;oBPkRzMBmmxU?r41+b$gHHVs93caDScR&DduTw}iA|m%M@vA#( literal 0 HcmV?d00001 diff --git a/MobileWeather/app/src/main/res/drawable/ic_50d.png b/MobileWeather/app/src/main/res/drawable/ic_50d.png new file mode 100644 index 0000000000000000000000000000000000000000..f04122b30fdcde958c90ca339bb990fb5508ffef GIT binary patch literal 650 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 literal 0 HcmV?d00001 From d7d127a8e9b05e533e62c10cb0648fe23ab048ce Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Mon, 20 Dec 2021 10:45:13 -0500 Subject: [PATCH 06/11] Restructure and clean up code Change URL conditionIcon in Forecast and WeatherConditions to String conditionId Restructure usages of conditionIcon to use an id instead Add new logic and format strings to get drawables from condition id strings Convert some long redundant code in SdlService to use for loops Fix HMI primary graphic not displaying by setting SdlArtwork filename to null for condition icons Add a commit() call missing from display update logic Remove classes relating to ForecastIo and WUnderground Remove some more unused functions Remove sections of commented code Optimize imports for modified files --- .../adapter/ForecastListAdapter.java | 35 +- .../forecastio/ForecastIoService.java | 119 ------ .../ForecastIoWeatherJsonProcessor.java | 294 --------------- .../fragments/ConditionsFragment.java | 4 +- .../OpenWeatherMapWeatherJsonProcessor.java | 37 +- .../processor/ImageProcessor.java | 92 ++--- .../smartdevicelink/SdlService.java | 195 ++++------ .../sdl/mobileweather/weather/Forecast.java | 3 +- .../weather/WeatherConditions.java | 4 +- .../WUndergroundLocationJsonProcessor.java | 39 -- .../wunderground/WUndergroundService.java | 46 --- .../WUndergroundWeatherJsonProcessor.java | 339 ------------------ 12 files changed, 115 insertions(+), 1092 deletions(-) delete mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoService.java delete mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoWeatherJsonProcessor.java delete mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundLocationJsonProcessor.java delete mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundService.java delete mode 100644 MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundWeatherJsonProcessor.java diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/adapter/ForecastListAdapter.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/adapter/ForecastListAdapter.java index de4648e..833ddaf 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/adapter/ForecastListAdapter.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/adapter/ForecastListAdapter.java @@ -1,11 +1,5 @@ package com.sdl.mobileweather.adapter; -import java.net.URL; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Locale; - import android.app.Activity; import android.content.Context; import android.view.Gravity; @@ -24,6 +18,11 @@ import com.sdl.mobileweather.weather.UnitConverter; import com.sdl.mobileweather.weather.WeatherDataManager; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Locale; + public class ForecastListAdapter extends ArrayAdapter { private final Context mContext; private final Forecast[] mForecast; @@ -82,12 +81,12 @@ public View getView(int position, View view, ViewGroup parent) { precipTextView.setText(day.conditionTitle); forecastImageView.setImageBitmap(null); } - else if((day.conditionIcon != null) && + else if((day.conditionId != null) && (day.precipitationChance != null) && (day.highTemperature != null) && (day.lowTemperature != null) && (day.date != null)) { - URL conditionURL = day.conditionIcon; + String conditionId = day.conditionId; Integer precip = day.precipitationChance; Float highTemperature = day.highTemperature; Float lowTemperature = day.lowTemperature; @@ -120,25 +119,11 @@ else if((day.conditionIcon != null) && precipTextView.setText(precipChance); lowTempTextView.setText(lowTemp); highTempTextView.setText(highTemp); - if (conditionURL != null) - ImageProcessor.setConditionsImage(forecastImageView, conditionURL, parentActivity/*, true*/); + if (conditionId != null) + ImageProcessor.setConditionsImage(forecastImageView, conditionId); } - /*else { - forecastImageView.setImageBitmap(null); - shortDayTextView.setText(""); - precipTextView.setText(""); - lowTempTextView.setText(""); - highTempTextView.setText(""); - }*/ } - /* - else { - forecastImageView.setImageBitmap(null); - shortDayTextView.setText(""); - precipTextView.setText(""); - lowTempTextView.setText(""); - highTempTextView.setText(""); - }*/ + } else { rowView = mInflater.inflate(R.layout.forecast_list_item, null, true); diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoService.java deleted file mode 100644 index 8c80fb0..0000000 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoService.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.sdl.mobileweather.forecastio; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Locale; - -import org.json.JSONObject; - -import android.content.Intent; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import android.util.Log; - -import com.sdl.mobileweather.artifact.WeatherLocation; -import com.sdl.mobileweather.connection.HttpConnection; -import com.sdl.mobileweather.connection.HttpConnection.RequestMethod; -import com.sdl.mobileweather.processor.JsonProcessor; -import com.sdl.mobileweather.smartdevicelink.SdlApplication; -import com.sdl.mobileweather.weather.Forecast; -import com.sdl.mobileweather.weather.WeatherAlert; -import com.sdl.mobileweather.weather.WeatherConditions; -import com.sdl.mobileweather.weather.WeatherService; - - -public class ForecastIoService extends WeatherService { - - private static final String API_KEY = "8ca293f8b0e23c52ffdf7d69cbd4dbfd"; // API key used for API - private static final String BASE_URL = "https://api.forecast.io/forecast/"; // Base request URL - - public ForecastIoService() { - super(); - mWeatherProcessor = new ForecastIoWeatherJsonProcessor(); - } - - @Override - protected URL[] getURLs(WeatherLocation currentLocation) { - URL everythingURL = null; - try { - everythingURL = new URL(BASE_URL + API_KEY +"/" + currentLocation.gpsLocation.latitude + "," + - currentLocation.gpsLocation.longitude + "?" + "lang=" + (Locale.getDefault()).getLanguage()); - Log.d(SdlApplication.TAG, currentLocation.gpsLocation.latitude + "," + currentLocation.gpsLocation.longitude); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - return new URL[] { everythingURL }; - } - - @Override - protected void updateWeatherData(URL... urls) { - HttpConnection httpRequest = new HttpConnection(); - JsonProcessor jsonProcessor = new JsonProcessor(); - WeatherConditions conditions = null; - Forecast[] forecast = null; - Forecast[] hourlyForecast = null; - WeatherAlert[] alerts = null; - String httpResult = null; - - Log.d("MobileWeather", "updateWeatherData"); - if (urls.length == 1 && mDataManager != null && mWeatherProcessor != null) { - LocalBroadcastManager lbManager = LocalBroadcastManager.getInstance(this); - Log.d("MobileWeather", "updateWeatherData - valid urls"); - if (urls[0] != null) { - Log.d("MobileWeather", "updateWeatherData - getting json"); - httpResult = httpRequest.sendRequest(urls[0], RequestMethod.GET, null, "application/json"); - int statusCode = httpRequest.getStatusCode(httpResult); - if (statusCode == -1) { - Log.d("MobileWeather", "updateWeatherData - parsing conditions json"); - JSONObject jsonRoot = jsonProcessor.getJsonFromString(httpResult); - if (jsonRoot != null) { - Log.d("MobileWeather", "updateWeatherData - parsing conditions"); - conditions = mWeatherProcessor.getWeatherConditions(jsonRoot); - mDataManager.setWeatherConditions(conditions); - if (conditions != null) { - Log.d("MobileWeather", "updateWeatherData - new conditions"); - Intent intent = new Intent("com.sdl.mobileweather.WeatherConditions"); - lbManager.sendBroadcast(intent); - } - - Log.d("MobileWeather", "updateWeatherData - parsing forecast"); - forecast = mWeatherProcessor.getForecast(jsonRoot); - mDataManager.setForecast(forecast); - if (forecast != null) { - Log.d("MobileWeather", "updateWeatherData - new forecast"); - Intent intent = new Intent("com.sdl.mobileweather.Forecast"); - lbManager.sendBroadcast(intent); - } - - Log.d("MobileWeather", "updateWeatherData - parsing hourly forecast"); - hourlyForecast = mWeatherProcessor.getHourlyForecast(jsonRoot); - mDataManager.setHourlyForecast(hourlyForecast); - if (hourlyForecast != null) { - Intent intent = new Intent("com.sdl.mobileweather.HourlyForecast"); - lbManager.sendBroadcast(intent); - } - - alerts = mWeatherProcessor.getAlerts(jsonRoot); - mDataManager.setAlerts(alerts); - Log.d("MobileWeather", "updateWeatherData - new Alerts"); - Intent intent = new Intent("com.sdl.mobileweather.Alerts"); - lbManager.sendBroadcast(intent); - } - - reportApiAvail(true); - - } else if (statusCode == -2){ - reportConnectionAvail(false); - }else{ - reportApiAvail(false); - } - } - - - WeatherLocation loc = mDataManager.getCurrentLocation(); - if (loc != null) { - mDataManager.setLastCity(loc.city); - mDataManager.setLastState(loc.state); - } - } - } -} diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoWeatherJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoWeatherJsonProcessor.java deleted file mode 100644 index 3247ea1..0000000 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/forecastio/ForecastIoWeatherJsonProcessor.java +++ /dev/null @@ -1,294 +0,0 @@ -package com.sdl.mobileweather.forecastio; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Calendar; -import java.util.Vector; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.util.Log; - -import com.sdl.mobileweather.smartdevicelink.SdlApplication; -import com.sdl.mobileweather.weather.Forecast; -import com.sdl.mobileweather.weather.UnitConverter; -import com.sdl.mobileweather.weather.WeatherAlert; -import com.sdl.mobileweather.weather.WeatherConditions; -import com.sdl.mobileweather.weather.WeatherJsonProcessor; - -public class ForecastIoWeatherJsonProcessor implements WeatherJsonProcessor { - - private static final String CURRENTLY = "currently"; - private static final String HOURLY = "hourly"; - private static final String DAILY = "daily"; - private static final String ALERTS = "alerts"; - private static final String ICON = "icon"; - private static final String SUMMARY = "summary"; - private static final String TEMPERATURE = "temperature"; - private static final String HUMIDITY = "humidity"; - private static final String WIND_SPEED = "windSpeed"; - private static final String VISIBILITY = "visibility"; - private static final String APPARENT_TEMPERATURE = "apparentTemperature"; - private static final String PRECIP_PROBABILITY = "precipProbability"; - private static final String DATA = "data"; - private static final String TIME = "time"; - private static final String TEMPERATURE_MAX = "temperatureMax"; - private static final String TEMPERATURE_MIN = "temperatureMin"; - private static final String PRECIP_ACCUMULATION = "precipAccumulation"; - private static final String EXPIRES = "expires"; - private static final String TITLE = "title"; - private static final String DESCRIPTION = "description"; - - private Forecast[] processForecast(JSONObject forecastJson, String type) { - Vector forecastVector = new Vector(); - JSONArray data = null; - JSONObject section = null; - - try { - section = forecastJson.getJSONObject(type); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (section != null) { - try { - data = section.getJSONArray(DATA); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (data != null) { - int numberOfDays = data.length(); - for (int dayCounter = 0; dayCounter < numberOfDays; dayCounter++) { - JSONObject day = null; - try { - day = data.getJSONObject(dayCounter); - } catch (JSONException e1) { - Log.v(SdlApplication.TAG, "No data in JSONArray for day " + dayCounter); - } - - Forecast currentForecast = new Forecast(); - if (day != null && currentForecast != null) { - long time = 0; - try { - time = day.getLong(TIME); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No TIME in JSON for day " + dayCounter); - e.printStackTrace(); - } - if (time != 0) { - Calendar forecastDate = Calendar.getInstance(); - forecastDate.setTimeInMillis(time); - currentForecast.date = forecastDate; - } - - try { - currentForecast.conditionTitle = day.getString(SUMMARY); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No SUMMARY in JSON for day " + dayCounter); - } - - try { - currentForecast.conditionIcon = new URL("http://localhost/" + day.getString(ICON) + ".gif"); - } catch (MalformedURLException e) { - Log.v(SdlApplication.TAG, "Bad icon URL for day " + dayCounter); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "Unable to get condition icon for JSON for day " + dayCounter); - } - - try { - currentForecast.temperature = UnitConverter.convertTemperatureToMetric(Float.valueOf((float) day.getDouble(TEMPERATURE))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No TEMPERTATURE in JSON for day " + dayCounter); - } - - try { - currentForecast.highTemperature = UnitConverter.convertTemperatureToMetric(Float.valueOf((float) day.getDouble(TEMPERATURE_MAX))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No TEMPERATURE_MAX in JSON for day " + dayCounter); - } - - try { - currentForecast.lowTemperature = UnitConverter.convertTemperatureToMetric(Float.valueOf((float) day.getDouble(TEMPERATURE_MIN))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No TEMPERATURE_MIN in JSON for day " + dayCounter); - } - - try { - currentForecast.humidity = Float.valueOf((float) day.getDouble(HUMIDITY)) * 100.0f; - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No HUMIDITY in JSON for day " + dayCounter); - } - - try { - currentForecast.windSpeed = UnitConverter.convertSpeedToMetric(Float.valueOf((float) day.getDouble(WIND_SPEED))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No WIND_SPEED in JSON for day " + dayCounter); - } - - try { - currentForecast.precipitationChance = (int) (Float.valueOf((float) day.getDouble(PRECIP_PROBABILITY)) * 100.0); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No PERCIP_PROBABILITY in JSON for day " + dayCounter); - } - - try { - currentForecast.snow = UnitConverter.convertLengthToMetric(Float.valueOf((float) day.getDouble(PRECIP_ACCUMULATION))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No PERCIP_ACCUMULATION in JSON for day " + dayCounter); - } - - forecastVector.add(currentForecast); - } - } - } - } - if (forecastVector.size() > 0) { - Forecast[] forecastArray = forecastVector.toArray(new Forecast[forecastVector.size()]); - return forecastArray; - } - else { - return null; - } - } - - @Override - public Forecast[] getForecast(JSONObject forecastJson) { - return processForecast(forecastJson, DAILY); - } - - @Override - public Forecast[] getHourlyForecast(JSONObject forecastJson) { - return processForecast(forecastJson, HOURLY); - } - - @Override - public WeatherConditions getWeatherConditions(JSONObject conditions) { - WeatherConditions weatherConditions = null; - if (conditions != null) { - weatherConditions = new WeatherConditions(); - JSONObject currently = null; - // Parse JSON - // Individual try/catch blocks used such that one failure will not abort the whole thing - try { - currently = conditions.getJSONObject(CURRENTLY); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No JSONObject available for CURRENTLY (Current conditions)"); - } - if (currently != null) { - // Parse JSON - // Individual try/catch blocks used such that one failure will not abort the whole thing - try { - weatherConditions.conditionIcon = new URL("http://localhost/" + currently.getString(ICON) + ".gif"); - } catch (MalformedURLException e) { - Log.d(SdlApplication.TAG, "Bad icon URL for currently"); - } catch (JSONException e) { - Log.d(SdlApplication.TAG, "Unable to get condition icon for JSON for currently"); - } - - try { - weatherConditions.conditionTitle = currently.getString(SUMMARY); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No SUMMARY in JSON for currently."); - } - - try { - weatherConditions.temperature = UnitConverter.convertTemperatureToMetric(Float.valueOf((float) currently.getDouble(TEMPERATURE))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No TEMPERATURE in JSON for currently."); - } - - try { - weatherConditions.humidity = Float.valueOf((float) currently.getDouble(HUMIDITY)) * 100.0f; - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No HUMIDITY in JSON for currently."); - } - - try { - weatherConditions.windSpeed = UnitConverter.convertSpeedToMetric(Float.valueOf((float) currently.getDouble(WIND_SPEED))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No WIND_SPEED in JSON for currently."); - } - - try { - weatherConditions.visibility = UnitConverter.convertLengthToMetric(Float.valueOf((float) currently.getDouble(VISIBILITY))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No VISIBILITY in JSON for currently."); - } - - try { - weatherConditions.feelsLikeTemperature = UnitConverter.convertTemperatureToMetric(Float.valueOf((float) currently.getDouble(APPARENT_TEMPERATURE))); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No APPARENT_TEMPERATURE in JSON for currently."); - } - - try { - weatherConditions.precipitation = Float.valueOf((float) currently.getDouble(PRECIP_PROBABILITY)) * 100.0f; - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No PRECIP_PROBABILITY in JSON for currently."); - } - } - } - - return weatherConditions; - } - - @Override - public WeatherAlert[] getAlerts(JSONObject alertsJson) { - Vector alertVector = new Vector(); - JSONArray data = null; - - try { - data = alertsJson.getJSONArray(ALERTS); - } catch (JSONException e) { - Log.d(SdlApplication.TAG, "No ALERTS JSONArray available."); - } - if (data != null) { - int numberOfAlerts = data.length(); - for (int alertCounter = 0; alertCounter < numberOfAlerts; alertCounter++) { - JSONObject alert = null; - try { - alert = data.getJSONObject(alertCounter); - } catch (JSONException e1) { - Log.d(SdlApplication.TAG, "No JSON available for alert " + alertCounter); - } - - WeatherAlert currentAlert = new WeatherAlert(); - if (alert != null && currentAlert != null) { - long time = 0; - try { - time = alert.getLong(EXPIRES); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No EXPIRES available for alert " + alertCounter); - } - if (time != 0) { - Calendar expiresDate = Calendar.getInstance(); - expiresDate.setTimeInMillis(time); - currentAlert.dateExpires = expiresDate; - } - try { - currentAlert.message = alert.getString(TITLE); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No TITLE available for alert " + alertCounter); - } - try { - currentAlert.description = alert.getString(DESCRIPTION); - } catch (JSONException e) { - Log.v(SdlApplication.TAG, "No DESCRIPTION available for alert " + alertCounter); - } - - alertVector.add(currentAlert); - } - } - } - if (alertVector.size() > 0) { - WeatherAlert[] alertArray = alertVector.toArray(new WeatherAlert[alertVector.size()]); - return alertArray; - } - else { - return null; - } - } -} diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java index 442d140..69324f4 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/fragments/ConditionsFragment.java @@ -147,8 +147,8 @@ public void setConditions(WeatherConditions conditions, String units) { mWindSpeedView.setText(wind); mHumidityView.setText(humid); - if (conditions.conditionIcon != null) - ImageProcessor.setConditionsImage(mConditionsIconView, conditions.conditionIcon, getActivity()/*, false*/); + if (conditions.conditionId != null) + ImageProcessor.setConditionsImage(mConditionsIconView, conditions.conditionId); } } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java index 71c691f..136f0b8 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapWeatherJsonProcessor.java @@ -1,8 +1,5 @@ package com.sdl.mobileweather.openweathermap; -import android.net.Uri; -import android.util.Log; - import com.sdl.mobileweather.weather.Forecast; import com.sdl.mobileweather.weather.WeatherAlert; import com.sdl.mobileweather.weather.WeatherConditions; @@ -12,8 +9,6 @@ import org.json.JSONException; import org.json.JSONObject; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Calendar; //TODO - Convert WUnderground specific code to OpenWeatherMap specific code @@ -70,6 +65,8 @@ private static class Constants { private static final String MAX = "max"; private static final String DAY = "day"; private static final String FEELS_LIKE = "feels_like"; + + private static final String ICON_RESOURCE_TEMPLATE = "ic_%s"; } private JSONArray getForecastArray(JSONObject jsonRoot) { @@ -209,12 +206,7 @@ private void tryToSetForecastHourTemperature(Forecast forecast, JSONObject forec } private void tryToSetForecastIcon(Forecast forecast, JSONObject weather) { try { - String iconId = weather.getString(Constants.ICON_PATH); - - forecast.conditionIcon = getWeatherIconURL(iconId); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + forecast.conditionId = weather.getString(Constants.ICON_PATH); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -345,30 +337,9 @@ private JSONObject getWeather(JSONObject conditionAtTime) { } return weather; } - private URL getWeatherIconURL(String iconId) throws MalformedURLException { - String filename = String.format(Constants.ICON_FILE_FORMAT, iconId); - - Uri.Builder builder = new Uri.Builder(); - builder.scheme(Constants.URI_SCHEME) - .authority(Constants.BASE_URL) - .appendPath(Constants.IMAGE_PATH) - .appendPath(Constants.WN_PATH) - .appendPath(filename); - - String uriString = builder.build().toString(); - URL url = new URL(uriString); - String urlString = url.toString(); - Log.d(Constants.TAG, urlString); - return url; - } private void tryToSetConditionIconFromCurrent(WeatherConditions weatherConditions, JSONObject weather) { try { - String iconId = weather.getString(Constants.ICON_PATH); - - weatherConditions.conditionIcon = getWeatherIconURL(iconId); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + weatherConditions.conditionId = weather.getString(Constants.ICON_PATH); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java index 353b3c9..a6697fe 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/processor/ImageProcessor.java @@ -1,8 +1,15 @@ package com.sdl.mobileweather.processor; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.widget.ImageView; + +import com.sdl.mobileweather.BuildConfig; +import com.sdl.mobileweather.smartdevicelink.SdlApplication; + import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -13,18 +20,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import android.app.Activity; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.util.Log; -import android.widget.ImageView; - -import com.sdl.mobileweather.smartdevicelink.SdlApplication; - import javax.net.ssl.HttpsURLConnection; public class ImageProcessor { @@ -33,6 +28,9 @@ public class ImageProcessor { private static final String PNG_EXTENSION = ".png"; private static final int BITMAP_QUALITY = 100; private static final String TAG = "ImageProcessor"; + private static final String RESOURCE_SCHEME = "android.resource://"; + private static final String DRAWABLE_FOLDER = "drawable"; + private static final String ICON_RESOURCE_TEMPLATE = "ic_%s"; private static final Map mConditionsImageMap = Collections.unmodifiableMap(new HashMap() { @@ -96,46 +94,42 @@ public static String getMappedConditionsImageName(String conditionsImage, boolea suffix = "_50"; } if (mConditionsImageMap.containsKey(conditionsImage)) { - return mConditionsImageMap.get(conditionsImage) + suffix; + return mConditionsImageMap.get(conditionsImage); } else { return null; } } - public static Bitmap getBitmapFromResources(String imageName) { + public static Bitmap getBitmapFromResources(String conditionId) { + String imageName = String.format(ICON_RESOURCE_TEMPLATE, conditionId); Resources resources = SdlApplication.getInstance().getResources(); int resId = resources.getIdentifier(imageName, "drawable", "com.sdl.mobileweather"); return BitmapFactory.decodeResource(resources, resId); } - public static Uri getImageUriFromURL(String imageName, URL url, Context context) { - imageName = imageName.replaceAll("\\s", ""); - Uri fileUri = null; - HttpsURLConnection connection; + public static byte[] getBytesFromURL(URL url) { + byte[] bytes = null; + HttpsURLConnection connection = null; try { connection = (HttpsURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream inputStream = connection.getInputStream(); final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + int size = bitmap.getHeight() * bitmap.getRowBytes(); + ByteBuffer buffer = ByteBuffer.allocate(size); + bitmap.copyPixelsToBuffer(buffer); + bytes = buffer.array(); - ContextWrapper contextWrapper = new ContextWrapper(context.getApplicationContext()); - File directory = contextWrapper.getDir(IMAGE_DIR, Context.MODE_PRIVATE); - File file = new File(directory, bitmap.hashCode() + PNG_EXTENSION); - if (!file.exists()) { - Log.d(TAG, imageName); - FileOutputStream outputStream = new FileOutputStream(file); - bitmap.compress(Bitmap.CompressFormat.PNG, BITMAP_QUALITY, outputStream); - outputStream.flush(); - outputStream.close(); - } - fileUri = Uri.fromFile(file); - Log.d(TAG, imageName); } catch (IOException e) { e.printStackTrace(); } - return fileUri; + return bytes; + } + + public static Uri getImageUriFromURL(String imageName, URL url, Context context) { + return Uri.parse(url.toString()); } public static String getFileFromURL(URL url) { @@ -147,29 +141,17 @@ public static String getFileFromURL(URL url) { } } - public static void setConditionsImage(final ImageView imageView, final URL conditionsImageURL, final Activity activity/*, boolean small*/) { + public static void setConditionsImage(ImageView imageView, String conditionId) { + + Bitmap bitmap = getBitmapFromResources(conditionId); + + imageView.setImageBitmap(bitmap); + } - executorService.execute(new Runnable() { - @Override - public void run() { - try { - HttpsURLConnection connection = (HttpsURLConnection) conditionsImageURL.openConnection(); - connection.setDoInput(true); - connection.connect(); - InputStream inputStream = connection.getInputStream(); - final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + public static Uri getWeatherIconUri(String iconId) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - imageView.setImageBitmap(bitmap); - } - }); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); + String iconFileString = String.format(ICON_RESOURCE_TEMPLATE, iconId); + return Uri.parse(RESOURCE_SCHEME + BuildConfig.APPLICATION_ID + "/" + DRAWABLE_FOLDER + "/" + iconFileString); } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java index ddee145..ebd52eb 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlService.java @@ -1,5 +1,7 @@ package com.sdl.mobileweather.smartdevicelink; +import static com.smartdevicelink.trace.enums.Mod.proxy; + import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; @@ -14,10 +16,11 @@ import android.os.Build; import android.os.Handler; import android.os.IBinder; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import android.util.Log; import com.sdl.mobileweather.BuildConfig; import com.sdl.mobileweather.R; @@ -77,7 +80,6 @@ import com.smartdevicelink.util.DebugTool; import com.smartdevicelink.util.SystemInfo; -import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -86,12 +88,6 @@ import java.util.List; import java.util.Locale; import java.util.Vector; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static com.smartdevicelink.trace.enums.Mod.proxy; public class SdlService extends Service { @@ -108,6 +104,7 @@ public class SdlService extends Service { private static final String APP_NAME = "MobileWeather"; private static final String APP_NAME_ES = "Clima móvil"; private static final String APP_NAME_FR = "Météo mobile"; + private static final String NONE = "none"; // Service shutdown timing constants private int conditionsID = 0; @@ -183,7 +180,6 @@ public class SdlService extends Service { private MenuCell mainCell4 = null; private MenuCell mainCell5 = null; - private ExecutorService executorService = Executors.newSingleThreadExecutor(); // TCP/IP transport config // The default port is 12345 // The IP is of the machine that is running SDL Core @@ -982,7 +978,6 @@ private void showWeatherConditions(boolean includeSpeak) { float humidity = mWeatherConditions.humidity; String title = mWeatherConditions.conditionTitle; String locationString = ""; - String mappedName = null; conditionsID = 0; if (mCurrentLocation != null) { if (mCurrentLocation.city != null) { @@ -1002,16 +997,6 @@ private void showWeatherConditions(boolean includeSpeak) { windSpeed = UnitConverter.convertSpeedToImperial(windSpeed); precipitation = UnitConverter.convertLengthToImperial(precipitation); } - /*if (mWeatherConditions.conditionIcon != null) { - String imageName = ImageProcessor.getFileFromURL(mWeatherConditions.conditionIcon); - mappedName = ImageProcessor.getMappedConditionsImageName(imageName, false); - - if (mappedName != null) { - mConditionIconFileName = mappedName + ".png"; - conditionsID = getResources().getIdentifier(mappedName, "drawable", getPackageName()); - Log.i(SdlApplication.TAG, "Conditions file: " + mConditionIconFileName); - } - }*/ String field1; String field2 = ""; @@ -1042,18 +1027,9 @@ private void showWeatherConditions(boolean includeSpeak) { sdlManager.getScreenManager().setTextField4(field4); sdlManager.getScreenManager().setMediaTrackTextField(mediatrack); sdlManager.getScreenManager().setTextAlignment(TextAlignment.LEFT_ALIGNED); - //sdlManager.getScreenManager().setPrimaryGraphic(new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionsID, true)); + setWeatherGraphic(mWeatherConditions.conditionTitle, mWeatherConditions.conditionId); sdlManager.getScreenManager().setSoftButtonObjects(getSoftButtonsForMainScreens()); - sdlManager.getScreenManager().commit(new CompletionListener() { - @Override - public void onComplete(boolean success) { - Log.i(TAG, "ScreenManager update complete: " + success); - if (mWeatherConditions.conditionIcon != null) { - setWeatherGraphic(mWeatherConditions.conditionTitle + " 1057", mWeatherConditions.conditionIcon); - } - } - }); if (includeSpeak) { String speakString; Vector chunks = new Vector(); @@ -1070,14 +1046,20 @@ public void onComplete(boolean success) { chunk.setText(speakString); chunk.setType(SpeechCapabilities.TEXT); chunks.add(chunk); - Speak speakRequest = new Speak(); + final Speak speakRequest = new Speak(); speakRequest.setTtsChunks(chunks); speakRequest.setCorrelationID(autoIncCorrId++); - sdlManager.sendRPC(speakRequest); + sdlManager.getScreenManager().commit(new CompletionListener() { + @Override + public void onComplete(boolean b) { + sdlManager.sendRPC(speakRequest); + } + }); } } else { showNoConditionsAvail(); } + } private void resetFirstErrorFlags() { @@ -1093,6 +1075,7 @@ private void showNoConditionsAvail() { sdlManager.getScreenManager().setTextField2(getResources().getString(R.string.conditions_txt_field2)); sdlManager.getScreenManager().setTextField3(getResources().getString(R.string.conditions_txt_field3)); sdlManager.getScreenManager().setTextField4(getResources().getString(R.string.conditions_txt_field4)); + setWeatherGraphic(NONE, NONE); sdlManager.getScreenManager().setTextAlignment(TextAlignment.CENTERED); sdlManager.getScreenManager().commit(new CompletionListener() { @Override @@ -1173,13 +1156,11 @@ private void writeDisplay(boolean includeSpeak) { sdlManager.getScreenManager().beginTransaction(); - - if (forecast_items != null) { field1 = forecast_items[forecast_item_counter].showString_field1; field2 = forecast_items[forecast_item_counter].showString_field2; mediatrack = forecast_items[forecast_item_counter].precipitationChance.toString() + "%"; - setWeatherGraphic(forecast_items[forecast_item_counter].conditionTitle + " 1217", forecast_items[forecast_item_counter].conditionIcon); + setWeatherGraphic(forecast_items[forecast_item_counter].conditionTitle + " 1217", forecast_items[forecast_item_counter].conditionId); } sdlManager.getScreenManager().setTextField1(field1); @@ -1188,22 +1169,7 @@ private void writeDisplay(boolean includeSpeak) { sdlManager.getScreenManager().setTextField4(field4); sdlManager.getScreenManager().setMediaTrackTextField(mediatrack); - /* String mappedName = null; - conditionsID = 0; - if (mWeatherConditions.conditionIcon != null && forecast_items != null) { - String imageName = ImageProcessor.getFileFromURL(forecast_items[forecast_item_counter].conditionIcon); - mappedName = ImageProcessor.getMappedConditionsImageName(imageName, false); - if (mappedName != null) { - mConditionIconFileName = mappedName + ".png"; - conditionsID = getResources().getIdentifier(mappedName, "drawable", getPackageName()); - } - }*/ - - - sdlManager.getScreenManager().setTextAlignment(TextAlignment.LEFT_ALIGNED); - //sdlManager.getScreenManager().setPrimaryGraphic(new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionsID, true)); - //setWeatherGraphic(mWeatherConditions.conditionTitle + " 1205", mWeatherConditions.conditionIcon); if(softButtonObjects.size() > 0){ sdlManager.getScreenManager().setSoftButtonObjects(softButtonObjects); @@ -1315,7 +1281,7 @@ private void showForecast(boolean includeSpeak, int numberOfForecasts) { forecast_items[forecastCounter].numberOfForecasts = numberOfForecasts; forecast_items[forecastCounter].highTemperature = high; forecast_items[forecastCounter].lowTemperature = low; - forecast_items[forecastCounter].conditionIcon = currentForecast.conditionIcon; + forecast_items[forecastCounter].conditionId = currentForecast.conditionId; String[] titleWords = currentForecast.conditionTitle.split("[\\s]"); String title; @@ -1428,30 +1394,16 @@ private void showForecast(boolean includeSpeak, int numberOfForecasts) { } /** - * @param icon_url + * @param conditionId * @return sdlArtwork for Forecast choiceset */ - private SdlArtwork getArtWork(String title, URL icon_url) { - /* String mappedName = null; - int conditionID = 0; - if (mWeatherConditions.conditionIcon != null) { - String imageName = ImageProcessor.getFileFromURL(icon_url); - //mappedName = ImageProcessor.getMappedConditionsImageName(imageName, false); - - /*if (mappedName != null) { - mConditionIconFileName = mappedName + ".png"; - conditionID = getResources().getIdentifier(mappedName, "drawable", getPackageName()); - }* / - } - */ - - //SdlArtwork tempArtworkName = new SdlArtwork(mConditionIconFileName, FileType.GRAPHIC_PNG, conditionID, true); - //return tempArtworkName; - + private SdlArtwork getArtWork(String title, String conditionId) { SdlArtwork artwork; - - Uri graphicUri = ImageProcessor.getImageUriFromURL(title, icon_url, getApplicationContext()); - artwork = new SdlArtwork(title, FileType.GRAPHIC_PNG, graphicUri,true); + DebugTool.logInfo(TAG, "Title: " + title + " Id: " + conditionId); + Exception testException = new Exception(); + testException.printStackTrace(); + Uri graphicUri = ImageProcessor.getWeatherIconUri(conditionId); + artwork = new SdlArtwork(null, FileType.GRAPHIC_PNG, graphicUri,true); return artwork; } @@ -1459,51 +1411,41 @@ private SdlArtwork getArtWork(String title, URL icon_url) { * pre loads choiceset for Hourly and Daily forecast */ private void createForecastChoiceSet() { - executorService.execute(new Runnable() { - @Override - public void run() { - /* Choices for Hourly Forecast to be created */ - if (choiceCellList != null) { - sdlManager.getScreenManager().deleteChoices(choiceCellList); - } - choiceCellList = null; + /* Choices for Hourly Forecast to be created */ + if (choiceCellList != null) { + sdlManager.getScreenManager().deleteChoices(choiceCellList); + } + choiceCellList = null; - if (mActiveInfoType == InfoType.HOURLY_FORECAST) { - ChoiceCell cell1 = new ChoiceCell(forecast_items[0].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[0].timeString})), getArtWork(forecast_items[0].title, forecast_items[0].conditionIcon)); - ChoiceCell cell2 = new ChoiceCell(forecast_items[1].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[1].timeString})), getArtWork(forecast_items[1].title, forecast_items[1].conditionIcon)); - ChoiceCell cell3 = new ChoiceCell(forecast_items[2].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[2].timeString})), getArtWork(forecast_items[2].title, forecast_items[2].conditionIcon)); - ChoiceCell cell4 = new ChoiceCell(forecast_items[3].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[3].timeString})), getArtWork(forecast_items[3].title, forecast_items[3].conditionIcon)); - ChoiceCell cell5 = new ChoiceCell(forecast_items[4].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[4].timeString})), getArtWork(forecast_items[4].title, forecast_items[4].conditionIcon)); - ChoiceCell cell6 = new ChoiceCell(forecast_items[5].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[5].timeString})), getArtWork(forecast_items[5].title, forecast_items[5].conditionIcon)); - ChoiceCell cell7 = new ChoiceCell(forecast_items[6].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[6].timeString})), getArtWork(forecast_items[6].title, forecast_items[6].conditionIcon)); - ChoiceCell cell8 = new ChoiceCell(forecast_items[7].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[7].timeString})), getArtWork(forecast_items[7].title, forecast_items[7].conditionIcon)); - ChoiceCell cell9 = new ChoiceCell(forecast_items[8].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[8].timeString})), getArtWork(forecast_items[8].title, forecast_items[8].conditionIcon)); - ChoiceCell cell10 = new ChoiceCell(forecast_items[9].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[9].timeString})), getArtWork(forecast_items[9].title, forecast_items[9].conditionIcon)); - ChoiceCell cell11 = new ChoiceCell(forecast_items[10].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[10].timeString})), getArtWork(forecast_items[10].title, forecast_items[10].conditionIcon)); - ChoiceCell cell12 = new ChoiceCell(forecast_items[11].timeString, new Vector<>(Arrays.asList(new String[]{forecast_items[11].timeString})), getArtWork(forecast_items[11].title, forecast_items[11].conditionIcon)); - forecast_item_counter = 0; - choiceCellList = Arrays.asList(cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8, cell9, cell10, cell11, cell12); - sdlManager.getScreenManager().preloadChoices(choiceCellList, null); - } + if (mActiveInfoType == InfoType.HOURLY_FORECAST) { + choiceCellList = new ArrayList<>(); - /* Choices for Daily Forecast to be created */ - else if (mActiveInfoType == InfoType.DAILY_FORECAST) { - ChoiceCell cell1 = new ChoiceCell(getResources().getString(R.string.cmd_today), new Vector<>(Arrays.asList(new String[]{getResources().getString(R.string.cmd_today)})), getArtWork(forecast_items[0].title, forecast_items[0].conditionIcon)); - ChoiceCell cell2 = new ChoiceCell(getResources().getString(R.string.cmd_tomorrow), new Vector<>(Arrays.asList(new String[]{getResources().getString(R.string.cmd_tomorrow)})), getArtWork(forecast_items[0].title, forecast_items[1].conditionIcon)); - ChoiceCell cell3 = new ChoiceCell(forecast_items[2].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[2].fullDateString})), getArtWork(forecast_items[2].title, forecast_items[2].conditionIcon)); - ChoiceCell cell4 = new ChoiceCell(forecast_items[3].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[3].fullDateString})), getArtWork(forecast_items[3].title, forecast_items[3].conditionIcon)); - ChoiceCell cell5 = new ChoiceCell(forecast_items[4].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[4].fullDateString})), getArtWork(forecast_items[4].title, forecast_items[4].conditionIcon)); - ChoiceCell cell6 = new ChoiceCell(forecast_items[5].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[5].fullDateString})), getArtWork(forecast_items[5].title, forecast_items[5].conditionIcon)); - ChoiceCell cell7 = new ChoiceCell(forecast_items[6].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[6].fullDateString})), getArtWork(forecast_items[6].title, forecast_items[6].conditionIcon)); - ChoiceCell cell8 = new ChoiceCell(forecast_items[7].fullDateString, new Vector<>(Arrays.asList(new String[]{forecast_items[7].fullDateString})), getArtWork(forecast_items[7].title, forecast_items[7].conditionIcon)); - forecast_item_counter = 0; - choiceCellList = Arrays.asList(cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8); - sdlManager.getScreenManager().preloadChoices(choiceCellList, null); - } else { - Log.d(SdlApplication.TAG, "CreateInteractioinChoiceSet requested for something else than hourly or daily forecast"); - } + for(int i = 0; i < 12; i++) { + ChoiceCell cell = new ChoiceCell(forecast_items[i].timeString, new Vector<>(Collections.singletonList(forecast_items[i].timeString)), getArtWork(forecast_items[i].title, forecast_items[i].conditionId)); + choiceCellList.add(cell); } - }); + + forecast_item_counter = 0; + sdlManager.getScreenManager().preloadChoices(choiceCellList, null); + } + + /* Choices for Daily Forecast to be created */ + else if (mActiveInfoType == InfoType.DAILY_FORECAST) { + choiceCellList = new ArrayList<>(); + ChoiceCell cell1 = new ChoiceCell(getResources().getString(R.string.cmd_today), new Vector<>(Collections.singletonList(getResources().getString(R.string.cmd_today))), getArtWork(forecast_items[0].title, forecast_items[0].conditionId)); + ChoiceCell cell2 = new ChoiceCell(getResources().getString(R.string.cmd_tomorrow), new Vector<>(Collections.singletonList(getResources().getString(R.string.cmd_tomorrow))), getArtWork(forecast_items[1].title, forecast_items[1].conditionId)); + choiceCellList.add(cell1); + choiceCellList.add(cell2); + + for (int i = 2; i < 8; i++) { + ChoiceCell cell = new ChoiceCell(forecast_items[i].fullDateString, new Vector<>(Collections.singletonList(forecast_items[i].fullDateString)), getArtWork(forecast_items[i].title, forecast_items[i].conditionId)); + choiceCellList.add(cell); + } + forecast_item_counter = 0; + sdlManager.getScreenManager().preloadChoices(choiceCellList, null); + } else { + Log.d(SdlApplication.TAG, "CreateInteractioinChoiceSet requested for something else than hourly or daily forecast"); + } } private void showStandardForecast(boolean includeSpeak) { @@ -1711,26 +1653,9 @@ public void setGlobalProperties(String helpPrompt, String timeoutPrompt, Integer sdlManager.sendRPC(req); } - private void setWeatherGraphic(final String title, final URL iconURL) { - - Callable callable = new Callable() { - @Override - public SdlArtwork call() { - final SdlArtwork artwork = getArtWork(title, iconURL); - sdlManager.getScreenManager().setPrimaryGraphic(artwork); - - return artwork; - } - }; - - ExecutorCompletionService completionService = new ExecutorCompletionService<>(executorService); - completionService.submit(callable); - - try { - completionService.take(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - + private void setWeatherGraphic(String title, String conditionId) { + SdlArtwork artwork = getArtWork(title, conditionId); + sdlManager.getScreenManager().setPrimaryGraphic(artwork); + sdlManager.getScreenManager().setSecondaryGraphic(artwork); } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/Forecast.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/Forecast.java index b32cb42..7b31385 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/Forecast.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/Forecast.java @@ -1,12 +1,11 @@ package com.sdl.mobileweather.weather; -import java.net.URL; import java.util.Calendar; public class Forecast { public Calendar date; public String conditionTitle; - public URL conditionIcon; + public String conditionId; public Float temperature = (float)0; public Float highTemperature = (float)0; public Float lowTemperature = (float)0; diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherConditions.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherConditions.java index 9e04be7..6306e8d 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherConditions.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherConditions.java @@ -1,10 +1,8 @@ package com.sdl.mobileweather.weather; -import java.net.URL; - public class WeatherConditions { public String conditionTitle; - public URL conditionIcon; + public String conditionId; public Float temperature; public Float humidity; public Float windSpeed; diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundLocationJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundLocationJsonProcessor.java deleted file mode 100644 index 0a006a3..0000000 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundLocationJsonProcessor.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.sdl.mobileweather.wunderground; - -import org.json.JSONException; -import org.json.JSONObject; - -import com.sdl.mobileweather.artifact.GPSLocation; -import com.sdl.mobileweather.artifact.WeatherLocation; -import com.sdl.mobileweather.location.LocationJsonProcessor; - -public class WUndergroundLocationJsonProcessor implements LocationJsonProcessor { - private static final String LOCATION = "location"; - private static final String STATE = "state"; - private static final String CITY = "city"; - private static final String LATITUDE = "lat"; - private static final String LONGITUDE = "lon"; - private static final String ZIPCODE = "zip"; - - @Override - public WeatherLocation getLocation(JSONObject jsonRoot) { - WeatherLocation location = null; - - if (jsonRoot != null) { - location = new WeatherLocation(); - try { - JSONObject jsonLocation = jsonRoot.getJSONObject(LOCATION); - location.state = jsonLocation.getString(STATE); - location.city = jsonLocation.getString(CITY); - location.zipCode = jsonLocation.getString(ZIPCODE); - location.gpsLocation = new GPSLocation(); - location.gpsLocation.latitude = jsonLocation.getString(LATITUDE); - location.gpsLocation.longitude = jsonLocation.getString(LONGITUDE); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - return location; - } -} diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundService.java deleted file mode 100644 index a763b47..0000000 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundService.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.sdl.mobileweather.wunderground; - -import java.net.MalformedURLException; -import java.net.URL; - -import com.sdl.mobileweather.artifact.WeatherLocation; -import com.sdl.mobileweather.weather.WeatherService; - -public class WUndergroundService extends WeatherService { - - private static final String API_KEY = ""; // API key used for WUnderground API - private static final String BASE_URL = "http://api.wunderground.com/api/"; // Base request URL for WUnderground - - public WUndergroundService() { - super(); - mWeatherProcessor = new WUndergroundWeatherJsonProcessor(); - } - - @Override - protected void updateWeatherData(URL... urls) { - // TODO Auto-generated method stub - - } - - /** - * Creates service specific weather data URLs. - */ - @Override - protected URL[] getURLs(WeatherLocation currentLocation) - { - URL conditionsURL = null; - URL forecastURL = null; - URL hourlyForecastURL = null; - URL alertsURL = null; - try { - conditionsURL = new URL(BASE_URL + API_KEY +"/conditions/q/" + currentLocation.gpsLocation.latitude + "," + currentLocation.gpsLocation.longitude + ".json"); - forecastURL = new URL(BASE_URL + API_KEY +"/forecast10day/q/" + currentLocation.gpsLocation.latitude + "," + currentLocation.gpsLocation.longitude + ".json"); - hourlyForecastURL = null; - alertsURL = null; - } catch (MalformedURLException e) { - e.printStackTrace(); - } - return new URL[] { conditionsURL, forecastURL, hourlyForecastURL, alertsURL }; - } - -} diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundWeatherJsonProcessor.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundWeatherJsonProcessor.java deleted file mode 100644 index 5c8b04d..0000000 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/wunderground/WUndergroundWeatherJsonProcessor.java +++ /dev/null @@ -1,339 +0,0 @@ -package com.sdl.mobileweather.wunderground; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Calendar; -import java.util.Vector; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import com.sdl.mobileweather.weather.WeatherAlert; -import com.sdl.mobileweather.weather.Forecast; -import com.sdl.mobileweather.weather.WeatherConditions; -import com.sdl.mobileweather.weather.WeatherJsonProcessor; - -public class WUndergroundWeatherJsonProcessor implements WeatherJsonProcessor { - private static final String CURRENT_OBSERVATION = "current_observation"; - private static final String ICON_URL = "icon_url"; - private static final String WEATHER = "weather"; - private static final String TEMP_C = "temp_c"; - private static final String RELATIVE_HUMIDITY = "relative_humidity"; - private static final String WIND_KPH = "wind_kph"; - private static final String WIND_GUST_KPH = "wind_gust_kph"; - private static final String VISIBILITY_KM = "visibility_km"; - private static final String HEAT_INDEX_C = "heat_index_c"; - private static final String WINDCHILL_C = "windchill_c"; - private static final String PRECIP_1HR_METRIC = "precip_1hr_metric"; - private static final String FORECAST = "forecast"; - private static final String FORECASTDAY = "forecastday"; - private static final String SIMPLEFORECAST = "simpleforecast"; - private static final String DATE = "date"; - private static final String EPOCH = "epoch"; - private static final String HIGH = "high"; - private static final String CELSIUS = "celsius"; - private static final String LOW = "low"; - private static final String CONDITIONS = "conditions"; - private static final String QPF_ALLDAY = "qpf_allday"; - private static final String MM = "mm"; - private static final String SNOW_ALLDAY = "snow_allday"; - private static final String AVEHUMIDITY = "avehumidity"; - private static final String AVEWIND = "avewind"; - private static final String KPH = "kph"; - private static final String CM = "cm"; - private static final String POP = "pop"; - - @Override - public Forecast[] getForecast(JSONObject forecastJson) { - Vector forecastVector = new Vector(); - JSONArray forecastDays = null; - JSONObject simpleForecast = null; - JSONObject forecastObj = null; - - try { - forecastObj = forecastJson.getJSONObject(FORECAST); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (forecastObj != null) { - try { - simpleForecast = forecastObj.getJSONObject(SIMPLEFORECAST); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (simpleForecast != null) { - try { - forecastDays = simpleForecast.getJSONArray(FORECASTDAY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (forecastDays != null) { - int numberOfDays = forecastDays.length(); - for (int dayCounter = 0; dayCounter < numberOfDays; dayCounter++) { - JSONObject day = null; - try { - day = forecastDays.getJSONObject(dayCounter); - } catch (JSONException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - Forecast currentForecast = new Forecast(); - if (day != null && currentForecast != null) { - JSONObject date = null; - try { - date = day.getJSONObject(DATE); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (date != null) { - String epoch = null; - try { - epoch = date.getString(EPOCH); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (epoch != null) { - long epochLong = Long.parseLong(epoch, 10); - Calendar forecastDate = Calendar.getInstance(); - forecastDate.setTimeInMillis(epochLong); - currentForecast.date = forecastDate; - } - } - - JSONObject high = null; - try { - high = day.getJSONObject(HIGH); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (high != null) { - try { - currentForecast.highTemperature = Float.valueOf(high.getInt(CELSIUS)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - JSONObject low = null; - try { - low = day.getJSONObject(LOW); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (low != null) { - try { - currentForecast.lowTemperature = Float.valueOf(low.getInt(CELSIUS)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - try { - currentForecast.precipitationChance = Integer.valueOf(day.getInt(POP)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - currentForecast.conditionTitle = day.getString(CONDITIONS); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - currentForecast.conditionIcon = new URL(day.getString(ICON_URL)); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - JSONObject qpf = null; - try { - qpf = day.getJSONObject(QPF_ALLDAY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (qpf != null) { - try { - currentForecast.precipitation = Float.valueOf((float) qpf.getDouble(MM)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - JSONObject snow = null; - try { - snow = day.getJSONObject(SNOW_ALLDAY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (snow != null) { - try { - currentForecast.snow = Float.valueOf((float) snow.getDouble(CM)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - try { - currentForecast.humidity = Float.valueOf((float) day.getDouble(AVEHUMIDITY)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - JSONObject wind = null; - try { - wind = day.getJSONObject(AVEWIND); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (wind != null) { - try { - currentForecast.windSpeed = Float.valueOf((float) wind.getDouble(KPH)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - forecastVector.add(currentForecast); - } - } - } - } - if (forecastVector.size() > 0) { - Forecast[] forecastArray = forecastVector.toArray(new Forecast[forecastVector.size()]); - return forecastArray; - } - else { - return null; - } - } - - @Override - public Forecast[] getHourlyForecast(JSONObject forecast) { - // TODO Auto-generated method stub - return null; - } - - @Override - public WeatherConditions getWeatherConditions(JSONObject conditions) { - WeatherConditions weatherConditions = null; - if (conditions != null) { - weatherConditions = new WeatherConditions(); - JSONObject currentObservation = null; - // Parse JSON - // Individual try/catch blocks used such that one failure will not abort the whole thing - try { - currentObservation = conditions.getJSONObject(CURRENT_OBSERVATION); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (currentObservation != null) { - try { - weatherConditions.conditionIcon = new URL(currentObservation.getString(ICON_URL)); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.conditionTitle = currentObservation.getString(WEATHER); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.temperature = Float.valueOf((float) currentObservation.getDouble(TEMP_C)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - String humidity = null; - try { - humidity = currentObservation.getString(RELATIVE_HUMIDITY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (humidity != null) { - StringBuilder humidityBuilder = new StringBuilder(humidity); - int percentLocation = humidityBuilder.lastIndexOf("%"); - if (percentLocation > 0) { - humidityBuilder = humidityBuilder.deleteCharAt(percentLocation); - } - weatherConditions.humidity = Float.valueOf(humidityBuilder.toString()); - } - try { - weatherConditions.windSpeed = Float.valueOf((float) currentObservation.getDouble(WIND_KPH)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.windSpeedGust = Float.valueOf((float) currentObservation.getDouble(WIND_GUST_KPH)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.visibility = Float.valueOf((float) currentObservation.getDouble(VISIBILITY_KM)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.feelsLikeTemperature = Float.valueOf((float) currentObservation.getDouble(HEAT_INDEX_C)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.feelsLikeTemperature = Float.valueOf((float) currentObservation.getDouble(WINDCHILL_C)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - weatherConditions.precipitation = Float.valueOf((float) currentObservation.getDouble(PRECIP_1HR_METRIC)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - - return weatherConditions; - } - - @Override - public WeatherAlert[] getAlerts(JSONObject alerts) { - // TODO Auto-generated method stub - return null; - } -} From 70f328037480dffd58aa40a8334f77b39cc31e35 Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Tue, 19 Apr 2022 17:33:06 -0400 Subject: [PATCH 07/11] Migrate to Android 12 and sdl_android 5.4.0 Change compileSdkVersion and targetSdkVersion to 31 Update AndroidManifest to account for Bluetooth and USB changes in Android 12 Add a direct bluetooth permission request to MainActivity Update SdlReceiver to use a shared router service when possible Update PendingIntent flags to prevent crashes on Android 12 in WeatherAlarmManager --- MobileWeather/app/build.gradle | 7 ++- .../app/src/main/AndroidManifest.xml | 31 +++++++++-- .../mobileweather/activity/MainActivity.java | 52 +++++++++++++++---- .../smartdevicelink/SdlReceiver.java | 27 +++++++--- .../weather/WeatherAlarmManager.java | 14 +++-- 5 files changed, 101 insertions(+), 30 deletions(-) diff --git a/MobileWeather/app/build.gradle b/MobileWeather/app/build.gradle index baa3961..cc23c37 100644 --- a/MobileWeather/app/build.gradle +++ b/MobileWeather/app/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 29 + compileSdkVersion 31 defaultConfig { applicationId "com.sdl.mobileweather" minSdkVersion 16 - targetSdkVersion 29 + targetSdkVersion 31 versionCode 27 versionName "1.7.15" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' @@ -37,8 +37,7 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' - // implementation 'com.smartdevicelink:sdl_android:4.+' - implementation 'com.smartdevicelink:sdl_android:5.3.1' + implementation 'com.smartdevicelink:sdl_android:5.4.0' implementation 'net.hockeyapp.android:HockeySDK:5.1.0' implementation 'com.google.android.gms:play-services-location:16.0.0' } diff --git a/MobileWeather/app/src/main/AndroidManifest.xml b/MobileWeather/app/src/main/AndroidManifest.xml index 9431a81..32b516a 100644 --- a/MobileWeather/app/src/main/AndroidManifest.xml +++ b/MobileWeather/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + @@ -23,6 +25,8 @@ + + + android:screenOrientation="portrait" + android:exported="true"> @@ -48,9 +53,24 @@ - - - + + + + + + + + + + - + diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java index 072d38e..d15aedf 100755 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java @@ -1,5 +1,7 @@ package com.sdl.mobileweather.activity; +import static android.Manifest.permission.BLUETOOTH_CONNECT; + import android.Manifest; import android.app.ActionBar; import android.app.ActionBar.Tab; @@ -11,6 +13,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.os.Build; import android.os.Bundle; import androidx.legacy.app.ActionBarDrawerToggle; import androidx.core.app.ActivityCompat; @@ -32,6 +35,7 @@ import com.sdl.mobileweather.fragments.ForecastFragment; import com.sdl.mobileweather.smartdevicelink.SdlActivity; import com.sdl.mobileweather.smartdevicelink.SdlApplication; +import com.sdl.mobileweather.smartdevicelink.SdlReceiver; public class MainActivity extends SdlActivity implements ActionBar.TabListener { @@ -39,6 +43,7 @@ public class MainActivity extends SdlActivity implements ActionBar.TabListener { private static final String SELECTED_NAVIGATION_ITEM = "selected_navigation_item"; private static final String APP_ID = "bf2c3a7bad6b0c79152f50cc42ba1ace"; private static final int LOCATION_PERMISSION_REQUEST_CODE = 100; + private static final int BLUETOOTH_REQUEST_CODE = 200; private Fragment mCurrentFragment; private DrawerLayout mDrawerLayout; @@ -139,14 +144,28 @@ else if ((getResources().getString(R.string.drawer_item_about)).equals(item)){ private void checkForCrashes() {} private void checkForUpdates() {} - + + private boolean checkPermission() { + return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(getApplicationContext(), BLUETOOTH_CONNECT); + } + + private void requestPermission() { + ActivityCompat.requestPermissions(this, new String[]{BLUETOOTH_CONNECT}, BLUETOOTH_REQUEST_CODE); + } + @Override protected void onCreate(Bundle savedInstanceState) { Log.v(SdlApplication.TAG, "onCreate main"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - - + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !checkPermission()) { + requestPermission(); + return; + } + + SdlReceiver.queryForConnectedService(this); + // Create tabs ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); @@ -368,13 +387,26 @@ public void onSaveInstanceState(Bundle outState) { @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { - if (grantResults.length <= 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED){ - Toast.makeText(this, "The app cannot run without this permission!", Toast.LENGTH_SHORT).show(); - finish(); - } else { - startServices(); - } + switch (requestCode) { + case LOCATION_PERMISSION_REQUEST_CODE: + if (grantResults.length <= 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "The app cannot run without this permission!", + Toast.LENGTH_SHORT).show(); + finish(); + } else { + startServices(); + } + break; + case BLUETOOTH_REQUEST_CODE: + if (grantResults.length > 0) { + boolean btConnectGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + + if (btConnectGranted) { + //Bluetooth permissions have been granted by the user so we can try to start out SdlService. + SdlReceiver.queryForConnectedService(this); + } + } + break; } } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java index 594b0b1..1c94004 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java @@ -1,5 +1,6 @@ package com.sdl.mobileweather.smartdevicelink; +import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; @@ -8,6 +9,7 @@ import com.smartdevicelink.transport.SdlBroadcastReceiver; import com.smartdevicelink.transport.SdlRouterService; +import com.smartdevicelink.transport.TransportConstants; public class SdlReceiver extends SdlBroadcastReceiver { @@ -44,14 +46,25 @@ public void onSdlEnabled(Context context, Intent intent) { //Use the provided intent but set the class to the SdlService intent.setClass(context, SdlService.class); - -// SdlService needs to be foregrounded in Android O and above -// This will prevent apps in the background from crashing when they try to start SdlService -// Because Android O doesn't allow background apps to start background services - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(intent); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA) != null) { + PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA); + try { + //Here we are allowing the RouterService that is in the Foreground to start the SdlService on our behalf + pendingIntent.send(context, 0, intent); + } catch (PendingIntent.CanceledException e) { + e.printStackTrace(); + } + } } else { - context.startService(intent); + // SdlService needs to be foregrounded in Android O and above + // This will prevent apps in the background from crashing when they try to start SdlService + // Because Android O doesn't allow background apps to start background services + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); + } else { + context.startService(intent); + } } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java index 65baa72..5391ff3 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java @@ -1,5 +1,6 @@ package com.sdl.mobileweather.weather; +import android.os.Build; import java.util.Calendar; import android.app.AlarmManager; @@ -148,10 +149,15 @@ private void restartAlarm(Context context) { if (mAlarmManager != null && mAlarmIntent != null) { mAlarmManager.cancel(mAlarmIntent); } - - mAlarmIntent = PendingIntent.getBroadcast(context, PENDING_INTENT_ID, mUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - Calendar cal = Calendar.getInstance(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + mAlarmIntent = PendingIntent.getBroadcast(context, PENDING_INTENT_ID, mUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } + else { + mAlarmIntent = PendingIntent.getBroadcast(context, PENDING_INTENT_ID, mUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); cal.add(Calendar.MINUTE, mUpdateInterval); Log.d(SdlApplication.TAG, "restartAlarm mUpdateInterval = " + mUpdateInterval); From d534458f6f4db4f914ffb88ae9bd7184d0c9adf7 Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Wed, 20 Apr 2022 10:22:00 -0400 Subject: [PATCH 08/11] Undo "Migrate to Android 12 and sdl_android 5.4.0" This reverts commit 70f328037480dffd58aa40a8334f77b39cc31e35. --- MobileWeather/app/build.gradle | 7 +-- .../app/src/main/AndroidManifest.xml | 31 ++--------- .../mobileweather/activity/MainActivity.java | 52 ++++--------------- .../smartdevicelink/SdlReceiver.java | 27 +++------- .../weather/WeatherAlarmManager.java | 14 ++--- 5 files changed, 30 insertions(+), 101 deletions(-) diff --git a/MobileWeather/app/build.gradle b/MobileWeather/app/build.gradle index cc23c37..baa3961 100644 --- a/MobileWeather/app/build.gradle +++ b/MobileWeather/app/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 31 + compileSdkVersion 29 defaultConfig { applicationId "com.sdl.mobileweather" minSdkVersion 16 - targetSdkVersion 31 + targetSdkVersion 29 versionCode 27 versionName "1.7.15" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' @@ -37,7 +37,8 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' - implementation 'com.smartdevicelink:sdl_android:5.4.0' + // implementation 'com.smartdevicelink:sdl_android:4.+' + implementation 'com.smartdevicelink:sdl_android:5.3.1' implementation 'net.hockeyapp.android:HockeySDK:5.1.0' implementation 'com.google.android.gms:play-services-location:16.0.0' } diff --git a/MobileWeather/app/src/main/AndroidManifest.xml b/MobileWeather/app/src/main/AndroidManifest.xml index 32b516a..9431a81 100644 --- a/MobileWeather/app/src/main/AndroidManifest.xml +++ b/MobileWeather/app/src/main/AndroidManifest.xml @@ -6,8 +6,6 @@ - @@ -25,8 +23,6 @@ - - + android:screenOrientation="portrait"> @@ -53,24 +48,9 @@ - - - - - - - - - - + + + - + diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java index d15aedf..072d38e 100755 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/activity/MainActivity.java @@ -1,7 +1,5 @@ package com.sdl.mobileweather.activity; -import static android.Manifest.permission.BLUETOOTH_CONNECT; - import android.Manifest; import android.app.ActionBar; import android.app.ActionBar.Tab; @@ -13,7 +11,6 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.os.Build; import android.os.Bundle; import androidx.legacy.app.ActionBarDrawerToggle; import androidx.core.app.ActivityCompat; @@ -35,7 +32,6 @@ import com.sdl.mobileweather.fragments.ForecastFragment; import com.sdl.mobileweather.smartdevicelink.SdlActivity; import com.sdl.mobileweather.smartdevicelink.SdlApplication; -import com.sdl.mobileweather.smartdevicelink.SdlReceiver; public class MainActivity extends SdlActivity implements ActionBar.TabListener { @@ -43,7 +39,6 @@ public class MainActivity extends SdlActivity implements ActionBar.TabListener { private static final String SELECTED_NAVIGATION_ITEM = "selected_navigation_item"; private static final String APP_ID = "bf2c3a7bad6b0c79152f50cc42ba1ace"; private static final int LOCATION_PERMISSION_REQUEST_CODE = 100; - private static final int BLUETOOTH_REQUEST_CODE = 200; private Fragment mCurrentFragment; private DrawerLayout mDrawerLayout; @@ -144,28 +139,14 @@ else if ((getResources().getString(R.string.drawer_item_about)).equals(item)){ private void checkForCrashes() {} private void checkForUpdates() {} - - private boolean checkPermission() { - return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(getApplicationContext(), BLUETOOTH_CONNECT); - } - - private void requestPermission() { - ActivityCompat.requestPermissions(this, new String[]{BLUETOOTH_CONNECT}, BLUETOOTH_REQUEST_CODE); - } - + @Override protected void onCreate(Bundle savedInstanceState) { Log.v(SdlApplication.TAG, "onCreate main"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !checkPermission()) { - requestPermission(); - return; - } - - SdlReceiver.queryForConnectedService(this); - + + // Create tabs ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); @@ -387,26 +368,13 @@ public void onSaveInstanceState(Bundle outState) { @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - switch (requestCode) { - case LOCATION_PERMISSION_REQUEST_CODE: - if (grantResults.length <= 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "The app cannot run without this permission!", - Toast.LENGTH_SHORT).show(); - finish(); - } else { - startServices(); - } - break; - case BLUETOOTH_REQUEST_CODE: - if (grantResults.length > 0) { - boolean btConnectGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED; - - if (btConnectGranted) { - //Bluetooth permissions have been granted by the user so we can try to start out SdlService. - SdlReceiver.queryForConnectedService(this); - } - } - break; + if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { + if (grantResults.length <= 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED){ + Toast.makeText(this, "The app cannot run without this permission!", Toast.LENGTH_SHORT).show(); + finish(); + } else { + startServices(); + } } } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java index 1c94004..594b0b1 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/smartdevicelink/SdlReceiver.java @@ -1,6 +1,5 @@ package com.sdl.mobileweather.smartdevicelink; -import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; @@ -9,7 +8,6 @@ import com.smartdevicelink.transport.SdlBroadcastReceiver; import com.smartdevicelink.transport.SdlRouterService; -import com.smartdevicelink.transport.TransportConstants; public class SdlReceiver extends SdlBroadcastReceiver { @@ -46,25 +44,14 @@ public void onSdlEnabled(Context context, Intent intent) { //Use the provided intent but set the class to the SdlService intent.setClass(context, SdlService.class); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA) != null) { - PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA); - try { - //Here we are allowing the RouterService that is in the Foreground to start the SdlService on our behalf - pendingIntent.send(context, 0, intent); - } catch (PendingIntent.CanceledException e) { - e.printStackTrace(); - } - } + +// SdlService needs to be foregrounded in Android O and above +// This will prevent apps in the background from crashing when they try to start SdlService +// Because Android O doesn't allow background apps to start background services + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); } else { - // SdlService needs to be foregrounded in Android O and above - // This will prevent apps in the background from crashing when they try to start SdlService - // Because Android O doesn't allow background apps to start background services - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(intent); - } else { - context.startService(intent); - } + context.startService(intent); } } diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java index 5391ff3..65baa72 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/weather/WeatherAlarmManager.java @@ -1,6 +1,5 @@ package com.sdl.mobileweather.weather; -import android.os.Build; import java.util.Calendar; import android.app.AlarmManager; @@ -149,15 +148,10 @@ private void restartAlarm(Context context) { if (mAlarmManager != null && mAlarmIntent != null) { mAlarmManager.cancel(mAlarmIntent); } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - mAlarmIntent = PendingIntent.getBroadcast(context, PENDING_INTENT_ID, mUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } - else { - mAlarmIntent = PendingIntent.getBroadcast(context, PENDING_INTENT_ID, mUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - Calendar cal = Calendar.getInstance(); + + mAlarmIntent = PendingIntent.getBroadcast(context, PENDING_INTENT_ID, mUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); cal.add(Calendar.MINUTE, mUpdateInterval); Log.d(SdlApplication.TAG, "restartAlarm mUpdateInterval = " + mUpdateInterval); From feae2674b758ddf9adf93e8cc13a0e1555a18b8c Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Mon, 13 Jun 2022 16:48:24 -0400 Subject: [PATCH 09/11] Clean up TODO and remove personal API key --- .../openweathermap/OpenWeatherMapService.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java index 0d158ca..a2f5c27 100644 --- a/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java +++ b/MobileWeather/app/src/main/java/com/sdl/mobileweather/openweathermap/OpenWeatherMapService.java @@ -21,17 +21,12 @@ import java.net.MalformedURLException; import java.net.URL; -//TODO - Convert WUnderground and ForecastIo specific code to OpenWeatherMap specific code public class OpenWeatherMapService extends WeatherService { /** * API key used for OpenWeatherMap API - *

- * I acquired this free one for testing. We may want to use a different one for broader use. - *

- * -Noah Stanford */ - private static final String API_KEY = "e81bf4160a279541cdaff8a8fc4cdda1"; + private static final String API_KEY = "INSERT YOUR OWN API KEY"; private static final String URI_SCHEME = "https"; /** * Base request URL for OpenWeatherMap From 8e6d9987e415d20232b5adaacb577b6f1d8aee39 Mon Sep 17 00:00:00 2001 From: Noah Stanford Date: Mon, 13 Jun 2022 17:04:15 -0400 Subject: [PATCH 10/11] Update README.md with API key instructions --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 35bb6e3..ff58995 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,7 @@ This tutorial is designed to show you how to include the SmartDeviceLink SDK in Please visit the [wiki](https://github.com/smartdevicelink/sdl_mobileweather_tutorial_android/wiki) of this repository to get the full tutorial. -### API Key -This app was setup to use Weather Underground's API. Recently, they have removed the ability to obtain a free API Key for testing. If you have a Weather Underground API Key, you can place it in the `WundergroundService` class. \ No newline at end of file +### Weather API Key + +1. Sign up and get your own [OpenWeather API Key](https://home.openweathermap.org/api_keys). +2. Set the value of `API_KEY`in `OpenWeatherMapService.java` to your API key \ No newline at end of file From b208c14f94b95eb60634298b9d0f5c3f37fa47b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJKAST=E2=80=9D?= Date: Fri, 24 Jun 2022 13:23:39 -0400 Subject: [PATCH 11/11] Update gradle lines for 5.5.0 --- MobileWeather/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileWeather/app/build.gradle b/MobileWeather/app/build.gradle index cc23c37..aa634be 100644 --- a/MobileWeather/app/build.gradle +++ b/MobileWeather/app/build.gradle @@ -37,7 +37,7 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' - implementation 'com.smartdevicelink:sdl_android:5.4.0' + implementation 'com.smartdevicelink:sdl_android:5.5.0' implementation 'net.hockeyapp.android:HockeySDK:5.1.0' implementation 'com.google.android.gms:play-services-location:16.0.0' }