/* * Copyright (C) 2014 Francesco Azzola * Surviving with Android (http://www.survivingwithandroid.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.survivingwithandroid.weather.lib.client.volley; import android.content.Context; import android.graphics.Bitmap; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.SystemClock; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.ImageRequest; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.Volley; import com.survivingwithandroid.weather.lib.WeatherClient; import com.survivingwithandroid.weather.lib.WeatherConfig; import com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException; import com.survivingwithandroid.weather.lib.exception.LocationProviderNotFoundException; import com.survivingwithandroid.weather.lib.exception.WeatherLibException; import com.survivingwithandroid.weather.lib.model.City; import com.survivingwithandroid.weather.lib.model.CurrentWeather; import com.survivingwithandroid.weather.lib.model.HistoricalWeather; import com.survivingwithandroid.weather.lib.model.WeatherForecast; import com.survivingwithandroid.weather.lib.model.WeatherHourForecast; import com.survivingwithandroid.weather.lib.provider.IWeatherProvider; import com.survivingwithandroid.weather.lib.request.Params; import com.survivingwithandroid.weather.lib.request.WeatherProviderFeature; import com.survivingwithandroid.weather.lib.request.WeatherRequest; import com.survivingwithandroid.weather.lib.response.GenericResponseParser; import com.survivingwithandroid.weather.lib.util.LogUtils; import java.util.Date; import java.util.List; /** * This class represents the entry point to get the Weather information. * This class uses a IWeatherProvider to parse the response. Internally this class is based on Android Volley lib * so that you don't have to worry about Thread hanlding because the class makes the remote HTTP request * on a separate thread. * <p> * Usually you need only one instance of this class in your app so that the class implements the Singleton pattern * To get the reference to this class you have to use getInstance method: * </p> * {@code WeatherClient client = WeatherClient.getInstance();} * <p> * soon after you the the reference and before you make any requests you have to pass the {@link android.content.Context} * to this class. * </p> * * * @author Francesco Azzola */ public class WeatherClientDefault extends WeatherClient { private CityEventListener cityListener; private static WeatherClientDefault me; private RequestQueue queue; /** * @deprecated Release 1.4 repleaced by {@link com.survivingwithandroid.weather.lib.WeatherClient.ClientBuilder} **/ public static WeatherClientDefault getInstance() { if (me != null) return me; me = new WeatherClientDefault(); return me; } @Override public void init(Context ctx) { super.init(ctx); queue = Volley.newRequestQueue(ctx); } /** * This method update the current provider configuration * * @param config WeatherConfig info * @see WeatherConfig */ @Override public void updateWeatherConfig(WeatherConfig config) { provider.setConfig(config); } /** * Get the current weather condition. It returns a class structure that is indipendent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the current weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param location a String representing the location id * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException * @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getCurrentCondition(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener)} */ @Override public void getCurrentCondition(String location, final WeatherEventListener listener) throws ApiKeyRequiredException { getCurrentCondition(new WeatherRequest(location), listener); } /** * Search the city using a name pattern. It returns a class structure that is indipendent from the * provider used that holds the city list matching the pattern. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onCityListRetrieved passing a {@link java.util.List} of cities. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param pattern a String representing the pattern * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ @Override public void searchCity(String pattern, final CityEventListener listener) throws ApiKeyRequiredException { String url = provider.getQueryCityURL(pattern); _doSearch(url, listener); /* LogUtils.LOGD("Search city URL [" + url + "]"); StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { List<City> cityResult = provider.getCityResultList(data); listener.onCityListRetrieved(cityResult); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); */ } /** * Search the city using latitude and longitude. It returns a class structure that is indipendent from the * provider used that holds the city list matching the pattern. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onCityListRetrieved passing a {@link java.util.List} of cities. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param lat a double representing the latitude * @param lon a double representing the longitude * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException * @since 1.5.3 */ @Override public void searchCity(double lat, double lon, CityEventListener listener) throws ApiKeyRequiredException { String url = provider.getQueryCityURLByCoord(lon, lat); _doSearch(url, listener); } private void _doSearch(String url, final CityEventListener listener) throws ApiKeyRequiredException { StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { List<City> cityResult = provider.getCityResultList(data); listener.onCityListRetrieved(cityResult); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); } /** * Get the forecast weather condition. It returns a class structure that is indipendent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherForecast} weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param location a String representing the location id * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException * @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getForecastWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener)} */ @Override public void getForecastWeather(String location, final ForecastWeatherEventListener listener) throws ApiKeyRequiredException { getForecastWeather(new WeatherRequest(location), listener); } /** * Get the forecast weather condition. It returns a class structure that is independent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherHourForecast} weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param location a String representing the location id * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException * @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getHourForecastWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener)} */ @Override public void getHourForecastWeather(String location, final HourForecastWeatherEventListener listener) throws ApiKeyRequiredException { getHourForecastWeather(new WeatherRequest(location), listener); } /** * Get the Historical weather condition. It returns a class structure that is independent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.HistoricalWeather} weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param location a String representing the location id * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException * @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getHistoricalWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, java.util.Date, java.util.Date, com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener)} */ @Override public void getHistoricalWeather(String location, Date startDate, Date endDate, final HistoricalWeatherEventListener listener) throws ApiKeyRequiredException { getHistoricalWeather(new WeatherRequest(location), startDate, endDate, listener); } /** * This is the default image Provider that can be used to get the image provided by the Weather provider * @param icon String The icon containing the weather code to retrieve the image * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherImageListener} */ @Override public void getDefaultProviderImage(String icon, final WeatherImageListener listener) { String imageURL = provider.getQueryImageURL(icon); ImageRequest ir = new ImageRequest(imageURL, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { if (listener != null) listener.onImageReady(response); } }, 0, 0, null, null); queue.add(ir); } @Override public void searchCityByLocation(Criteria criteria, final CityEventListener listener) throws LocationProviderNotFoundException { super.handleLocation(criteria, listener); } /** * This method is used to set the Weather provider. The Weather provider must implement ({@link com.survivingwithandroid.weather.lib.provider.IWeatherProvider} * interface. This method has to be called before making any HTTP request otherwise the client doesn't know how * to parse the request * * @param provider {@link com.survivingwithandroid.weather.lib.provider.IWeatherProvider} * @see {@link com.survivingwithandroid.weather.lib.provider.openweathermap.OpenweathermapProvider} * @see {@link com.survivingwithandroid.weather.lib.provider.yahooweather.YahooWeatherProvider} */ @Override public void setProvider(IWeatherProvider provider) { super.setProvider(provider); // Set a new provider } /** * This method retrieves the image using the url generated by the weahter provider {@link com.survivingwithandroid.weather.lib.provider.IWeatherProvider} * * @param cityId String representing the city id * @param params {@link com.survivingwithandroid.weather.lib.request.Params}: list of parameters used to create the image * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherImageListener} listener that gets notified when the image is ready to use */ @Override public void getWeatherImage(String cityId, Params params, final WeatherImageListener listener) { String imageURL = provider.getQueryLayerURL(cityId, params); ImageRequest ir = new ImageRequest(imageURL, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { if (listener != null) listener.onImageReady(response); } }, 0, 0, null, null); queue.add(ir); } @Override protected void searchCityByLocation(Location location, final CityEventListener listener) throws ApiKeyRequiredException { String url = provider.getQueryCityURLByLocation(location); LogUtils.LOGD("Search city by Loc Url [" + url + "]"); StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { List<City> cityResult = provider.getCityResultList(data); listener.onCityListRetrieved(cityResult); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); } // New methods /** * Get the current weather condition. It returns a class structure that is indipendent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the current weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param wRequest {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ @Override public void getCurrentCondition(WeatherRequest wRequest, final WeatherEventListener listener) throws ApiKeyRequiredException { String url = provider.getQueryCurrentWeatherURL(wRequest); LogUtils.LOGD("Current Condition URL [" + url + "]"); StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { CurrentWeather weather = provider.getCurrentCondition(data); listener.onWeatherRetrieved(weather); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); } /** * Get the forecast weather condition. It returns a class structure that is independent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherForecast} weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param wRequest {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ @Override public void getForecastWeather(WeatherRequest wRequest, final ForecastWeatherEventListener listener) throws ApiKeyRequiredException { String url = provider.getQueryForecastWeatherURL(wRequest); LogUtils.LOGD("Forecast URL [" + url + "]"); StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { WeatherForecast forecast = provider.getForecastWeather(data); listener.onWeatherRetrieved(forecast); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); } /** * Get the forecast weather condition. It returns a class structure that is independent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherHourForecast} weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param wRequest {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ @Override public void getHourForecastWeather(WeatherRequest wRequest, final HourForecastWeatherEventListener listener) throws ApiKeyRequiredException { String url = provider.getQueryHourForecastWeatherURL(wRequest); LogUtils.LOGD("Forecast Hourly URL [" + url + "]"); StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { WeatherHourForecast forecast = provider.getHourForecastWeather(data); listener.onWeatherRetrieved(forecast); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); } /** * Get the historical weather condition. It returns a class structure that is independent from the * provider used to ge the weather data. * This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} to * get notified when the weather data is ready. * <p> * When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.HistoricalWeather} weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param wRequest {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param d1 is the starting date * @param d2 * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} @param2 d2 is the end date * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ @Override public void getHistoricalWeather(WeatherRequest wRequest, Date d1, Date d2, final HistoricalWeatherEventListener listener) { String url = provider.getQueryHistoricalWeatherURL(wRequest, d1, d2); LogUtils.LOGD("Historical weather URL [" + url + "]"); StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { HistoricalWeather hWeather = provider.getHistoricalWeather(data); listener.onWeatherRetrieved(hWeather); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); } /** * Get a specific weather provider feature not implemented in all weather provider * <p> * When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.HistoricalWeather} weather information. * If there are some errors during the request parsing, it calls onWeatherError passing the exception or * onConnectionError if the errors happened during the HTTP connection * </p> * * @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param extRequest is the extended request as required by the weather provider * @param parser is the parser used to parsed the response {@link com.survivingwithandroid.weather.lib.response.GenericResponseParser} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.GenericRequestWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ @Override public <T extends WeatherProviderFeature, S extends Object> void getProviderWeatherFeature(WeatherRequest request, T extRequest, final GenericResponseParser<S> parser, final GenericRequestWeatherEventListener<S> listener) { String url = extRequest.getURL(); LogUtils.LOGD("Generic Weather feature URL [" + url + "]"); StringRequest req = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String data) { // We handle the response try { S result = parser.parseData(data); listener.onResponseRetrieved(result); } catch (WeatherLibException t) { listener.onWeatherError(t); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { listener.onConnectionError(volleyError.getCause()); } } ); queue.add(req); } /** * Get an image at the specified URL and inform the listener when the image is ready * * @param url String representing the url * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherImageListener} * @since 1.5.3 */ @Override public void getImage(String url, final WeatherImageListener listener) { ImageRequest ir = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { if (listener != null) listener.onImageReady(response); } }, 0, 0, null, null); queue.add(ir); } }