/** * This is a tutorial source code * provided "as is" and without warranties. * * For any question please visit the web site * http://www.survivingwithandroid.com * * or write an email to * survivingwithandroid@gmail.com * */ package com.survivingwithandroid.weather.lib; 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 android.util.Log; 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.exception.WeatherProviderInstantiationException; 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.IProviderType; import com.survivingwithandroid.weather.lib.provider.IWeatherCodeProvider; import com.survivingwithandroid.weather.lib.provider.IWeatherProvider; import com.survivingwithandroid.weather.lib.provider.WeatherProviderFactory; 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 abstract class WeatherClient { private static WeatherClient me; protected IWeatherProvider provider; protected Context ctx; protected CityEventListener cityListener; /* * This parameter represents the amount of time before considering the location out of date * It must be expressed in seconds * * */ public static int LOCATION_TIMEOUT = 5; /** * This method has to be called before any HTTP request * * @param ctx Reference to the {@link android.content.Context} */ public void init(Context ctx) { this.ctx = ctx; } /** * This method update the current provider configuration * * @param config WeatherConfig info * @see com.survivingwithandroid.weather.lib.WeatherConfig */ 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> * * @deprecated 1.5.0 Use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getCurrentCondition(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener)} * * @param location a String representing the location id * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getCurrentCondition(String location, final WeatherEventListener listener) throws ApiKeyRequiredException; /** * 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 */ public abstract void searchCity(String pattern, final CityEventListener listener) throws ApiKeyRequiredException; /** * 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 */ public abstract void searchCity(double lat, double lon, final CityEventListener listener) throws ApiKeyRequiredException; /** * 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> * * @deprecated 1.5.0 Use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getForecastWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener)} * @param location a String representing the location id * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getForecastWeather(String location, final ForecastWeatherEventListener listener) throws ApiKeyRequiredException; /** * 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> * * @deprecated 1.5.0 use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getHourForecastWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener)} * @param location a String representing the location id * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getHourForecastWeather(String location, final HourForecastWeatherEventListener listener) throws ApiKeyRequiredException; /** * 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> * * @deprecated 1.5.0 {@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)} * @param location a String representing the location id * @param d1 is the starting date * @param d2 is the end date * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getHistoricalWeather(String location , Date d1, Date d2, final HistoricalWeatherEventListener 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} * */ public abstract void getDefaultProviderImage(String icon, final WeatherImageListener listener); /** * Search the city using geographic coordinates. It returns a class structure that is independent 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 criteria {@link android.location.Criteria} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void searchCityByLocation(Criteria criteria, final CityEventListener listener) throws LocationProviderNotFoundException; /** * 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 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 */ public abstract void getWeatherImage(String cityId, Params params, final WeatherImageListener 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} */ public void setProvider(IWeatherProvider provider) { this.provider = provider; } public static Criteria createDefaultCriteria() { Criteria criteria = new Criteria(); criteria.setPowerRequirement(Criteria.POWER_LOW); criteria.setAccuracy(Criteria.ACCURACY_COARSE); criteria.setCostAllowed(false); return criteria; } /** * This is the base interface. * * @see com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener * @see com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener * @see com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener */ public static interface WeatherClientListener { /** * This method is called when an error occured during the data parsing * * @param wle {@link com.survivingwithandroid.weather.lib.exception.WeatherLibException} */ public void onWeatherError(WeatherLibException wle); /** * This method is called when an error occured during the HTTP connection * * @param t {@link Throwable} */ public void onConnectionError(Throwable t); } /** * This interface must be implemented by the client that wants to get informed when * the city list is ready. */ public static interface CityEventListener extends WeatherClientListener { /** * This method is called to notify to the listener that the {@link com.survivingwithandroid.weather.lib.model.City} list is ready * * @param cityList {@link java.util.List} */ public void onCityListRetrieved(List<City> cityList); } /** * This interface must be implemented by the client that wants to get informed when * the weather data is ready. */ public static interface WeatherEventListener extends WeatherClientListener { /** * This method is called to notify to the listener that the Weather information is ready * * @param weather {@link com.survivingwithandroid.weather.lib.model.CurrentWeather} */ public void onWeatherRetrieved(CurrentWeather weather); } /** * This interface must be implemented by the client that wants to get informed when * the forecast weather data is ready. */ public static interface ForecastWeatherEventListener extends WeatherClientListener { /** * This method is called to notify to the listener that the Weather information is ready * * @param forecast {@link com.survivingwithandroid.weather.lib.model.WeatherForecast} */ public void onWeatherRetrieved(WeatherForecast forecast); } /** * This interface must be implemented by the client that wants to get informed when * the forecast weather image is ready. */ public static interface WeatherImageListener extends WeatherClientListener { /** * This method is called to notify to the listener that the Weather information is ready * * @param image {@link android.graphics.Bitmap} */ public void onImageReady(Bitmap image); } /** * This interface must be implemented by the client that wants to get informed when * the hour forecast weather data is ready. */ public static interface HourForecastWeatherEventListener extends WeatherClientListener { /** * This method is called to notify to the listener that the Weather information is ready * * @param forecast {@link com.survivingwithandroid.weather.lib.model.WeatherHourForecast} */ public void onWeatherRetrieved(WeatherHourForecast forecast); } /** * This interface must be implemented by the client that wants to get informed when * the historical weather data is ready. */ public static interface HistoricalWeatherEventListener extends WeatherClientListener { /** * This method is called to notify to the listener that the historical Weather information is ready * * @param histWeather {@link com.survivingwithandroid.weather.lib.model.HistoricalWeather} */ public void onWeatherRetrieved(HistoricalWeather histWeather); } /** * This interface must be implemented by the client that wants to get informed when * the generic request is available * * @since 1.5.3 */ public static interface GenericRequestWeatherEventListener<T> extends WeatherClientListener { public void onResponseRetrieved(T data); } /** * This method creates the Weather provider. It is the same: * * <code> * IWeatherProvider provider = null; * try { * provider = WeatherProviderFactory.createProvider(new OpenweathermapProviderType(), config); * client.setProvider(provider); * } * catch (Throwable t) { * t.printStackTrace(); * // There's a problem * } * </code> * * @deprecated Version 1.4 use instead {@link com.survivingwithandroid.weather.lib.WeatherClient.ClientBuilder} **/ public void createProvider(IProviderType providerType, WeatherConfig config) throws WeatherProviderInstantiationException { provider = WeatherProviderFactory.createProvider(providerType, config); } /** * This class must be used to obtain a valid instance of WeatherClient. It accepts several config params * and at the end you should call build() to create the client. * * Ex: * client = builder.attach(this) * .provider(new OpenweathermapProviderType()) * .httpClient(com.survivingwithandroid.weather.lib.client.volley.WeatherClientDefault.class) * .config(new WeatherConfig()) * .build(); */ public static final class ClientBuilder { private Context ctx; private IProviderType providerType; private WeatherConfig config; private Class httpWeatherClient; /** * Attach a {@link android.content.Context} to the WeatherClient * */ public ClientBuilder attach(Context ctx) { this.ctx = ctx; return this; } /** * Set the provider ({@link com.survivingwithandroid.weather.lib.provider.IProviderType}) * */ public ClientBuilder provider(IProviderType providerType) { this.providerType = providerType; return this; } /** * Set the configuration ({@link com.survivingwithandroid.weather.lib.WeatherConfig}) * */ public ClientBuilder config(WeatherConfig config) { this.config = config; return this; } /** * Attach the http Client that has to be used to make requests. * * @see com.survivingwithandroid.weather.lib.StandardHttpClient **/ public ClientBuilder httpClient(Class httpClient) { this.httpWeatherClient = httpClient; return this; } /** * Build the weather client setting the right parameters * */ public WeatherClient build() throws WeatherProviderInstantiationException { // Create provider IWeatherProvider provider = _createProvider(providerType, config); WeatherClient client = null; try { client = (WeatherClient) httpWeatherClient.newInstance(); client.init(ctx); Log.d("SwA", "Client ["+client+"]"); client.setProvider(provider); client.updateWeatherConfig(config); return client; } catch (InstantiationException e) { e.printStackTrace(); throw new WeatherProviderInstantiationException(); } catch (IllegalAccessException e) { e.printStackTrace(); throw new WeatherProviderInstantiationException(); } } private static IWeatherProvider _createProvider(IProviderType providerType, WeatherConfig config) throws WeatherProviderInstantiationException { try { Class<?> clazz = Class.forName(providerType.getProviderClass()); IWeatherProvider provider = (IWeatherProvider) clazz.newInstance(); if (config != null) provider.setConfig(config); if (providerType.getCodeProviderClass() != null) { Class<?> clazzCodeProvider = Class.forName(providerType.getCodeProviderClass()); IWeatherCodeProvider codeProvider = (IWeatherCodeProvider) clazzCodeProvider.newInstance(); provider.setWeatherCodeProvider(codeProvider); } return provider; } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); throw new WeatherProviderInstantiationException(); } catch (InstantiationException e) { e.printStackTrace(); throw new WeatherProviderInstantiationException(); } catch (IllegalAccessException e) { e.printStackTrace(); throw new WeatherProviderInstantiationException(); } } } protected void handleLocation(Criteria criteria, final CityEventListener listener) throws LocationProviderNotFoundException { this.cityListener = listener; LocationManager locManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); String locationProvider = locManager.getBestProvider(criteria, true); LogUtils.LOGD("Provider [" + locationProvider + "]"); if (locationProvider == null || "".equals(locationProvider)) throw new LocationProviderNotFoundException(); Location loc = locManager.getLastKnownLocation(locationProvider); if (loc == null || (SystemClock.elapsedRealtime() - loc.getTime()) > LOCATION_TIMEOUT * 1000) { locManager.requestSingleUpdate(locationProvider, locListener, null); } else searchCityByLocation(loc, listener); } protected LocationListener locListener = new LocationListener() { @Override public void onLocationChanged(Location location) { searchCityByLocation(location, cityListener); } @Override public void onStatusChanged(String s, int i, Bundle bundle) { } @Override public void onProviderEnabled(String s) { } @Override public void onProviderDisabled(String s) { } }; protected abstract void searchCityByLocation(Location location, final CityEventListener listener) throws ApiKeyRequiredException; // New abstract 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 request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getCurrentCondition(WeatherRequest request, final WeatherEventListener listener) throws ApiKeyRequiredException; /** * 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 request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getForecastWeather(WeatherRequest request, final ForecastWeatherEventListener listener) throws ApiKeyRequiredException; /** * 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 request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getHourForecastWeather(WeatherRequest request, final HourForecastWeatherEventListener listener) throws ApiKeyRequiredException; /** * 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 request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest} * @param d1 is the starting date * @param d2 is the end date * @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} * @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException */ public abstract void getHistoricalWeather(WeatherRequest request, Date d1, Date d2, final HistoricalWeatherEventListener listener); /** * 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 */ public abstract <T extends WeatherProviderFeature, S extends Object> void getProviderWeatherFeature(WeatherRequest request, T extRequest, GenericResponseParser<S> parser, GenericRequestWeatherEventListener<S> listener); /** * 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 * */ public abstract void getImage(String url, WeatherImageListener listener); }