1 package de.example.exampletdd.fragment.overview;
3 import java.io.IOException;
4 import java.net.MalformedURLException;
5 import java.net.URISyntaxException;
7 import java.text.DecimalFormat;
8 import java.text.NumberFormat;
9 import java.text.SimpleDateFormat;
10 import java.util.ArrayList;
11 import java.util.Calendar;
12 import java.util.Date;
13 import java.util.List;
14 import java.util.Locale;
16 import org.apache.http.client.ClientProtocolException;
18 import android.content.BroadcastReceiver;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.SharedPreferences;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.net.http.AndroidHttpClient;
27 import android.os.AsyncTask;
28 import android.os.Bundle;
29 import android.preference.PreferenceManager;
30 import android.support.v4.app.ListFragment;
31 import android.support.v4.content.LocalBroadcastManager;
32 import android.util.Log;
33 import android.view.View;
34 import android.widget.ListView;
36 import com.fasterxml.jackson.core.JsonParseException;
38 import de.example.exampletdd.R;
39 import de.example.exampletdd.fragment.specific.SpecificFragment;
40 import de.example.exampletdd.httpclient.CustomHTTPClient;
41 import de.example.exampletdd.model.DatabaseQueries;
42 import de.example.exampletdd.model.WeatherLocation;
43 import de.example.exampletdd.model.forecastweather.Forecast;
44 import de.example.exampletdd.parser.JPOSWeatherParser;
45 import de.example.exampletdd.service.IconsList;
46 import de.example.exampletdd.service.PermanentStorage;
47 import de.example.exampletdd.service.ServiceParser;
49 public class OverviewFragment extends ListFragment {
50 private static final String TAG = "OverviewFragment";
51 private BroadcastReceiver mReceiver;
54 public void onCreate(final Bundle savedInstanceState) {
55 super.onCreate(savedInstanceState);
59 public void onActivityCreated(final Bundle savedInstanceState) {
60 super.onActivityCreated(savedInstanceState);
62 final ListView listWeatherView = this.getListView();
63 listWeatherView.setChoiceMode(ListView.CHOICE_MODE_NONE);
65 if (savedInstanceState != null) {
67 final Forecast forecast = (Forecast) savedInstanceState.getSerializable("Forecast");
69 // TODO: Could it be better to store in global forecast data even if it is null value?
70 // So, perhaps do not check for null value and always store in global variable.
71 if (forecast != null) {
72 final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
73 store.saveForecast(forecast);
77 this.setHasOptionsMenu(false);
79 this.setEmptyText(this.getString(R.string.text_field_remote_error));
80 this.setListShownNoAnimation(false);
84 public void onResume() {
87 this.mReceiver = new BroadcastReceiver() {
90 public void onReceive(final Context context, final Intent intent) {
91 final String action = intent.getAction();
92 if (action.equals("de.example.exampletdd.UPDATEFORECAST")) {
93 final Forecast forecastRemote = (Forecast) intent.getSerializableExtra("forecast");
95 if (forecastRemote != null) {
97 // 1. Check conditions. They must be the same as the ones that triggered the AsyncTask.
98 final DatabaseQueries query = new DatabaseQueries(context.getApplicationContext());
99 final WeatherLocation weatherLocation = query.queryDataBase();
100 final PermanentStorage store = new PermanentStorage(context.getApplicationContext());
101 final Forecast forecast = store.getForecast();
103 if (forecast == null || !OverviewFragment.this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) {
105 OverviewFragment.this.updateUI(forecastRemote);
108 store.saveForecast(forecastRemote);
109 weatherLocation.setLastForecastUIUpdate(new Date());
110 query.updateDataBase(weatherLocation);
113 OverviewFragment.this.setListShownNoAnimation(true);
117 // Empty list and show error message (see setEmptyText in onCreate)
118 OverviewFragment.this.setListAdapter(null);
119 OverviewFragment.this.setListShownNoAnimation(true);
126 final IntentFilter filter = new IntentFilter();
127 filter.addAction("de.example.exampletdd.UPDATEFORECAST");
128 LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext())
129 .registerReceiver(this.mReceiver, filter);
131 final DatabaseQueries query = new DatabaseQueries(this.getActivity().getApplicationContext());
132 final WeatherLocation weatherLocation = query.queryDataBase();
133 if (weatherLocation == null) {
135 // Empty list and show error message (see setEmptyText in onCreate)
136 this.setListAdapter(null);
137 this.setListShownNoAnimation(true);
141 final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
142 final Forecast forecast = store.getForecast();
144 // TODO: store forecast data in permanent storage and check here if there is data in permanent storage
145 if (forecast != null && this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) {
146 this.updateUI(forecast);
148 // Load remote data (aynchronous)
149 // Gets the data from the web.
150 this.setListShownNoAnimation(false);
151 final OverviewTask task = new OverviewTask(
152 this.getActivity().getApplicationContext(),
153 new CustomHTTPClient(AndroidHttpClient.newInstance("Android 4.3 WeatherInformation Agent")),
154 new ServiceParser(new JPOSWeatherParser()));
156 task.execute(weatherLocation.getLatitude(), weatherLocation.getLongitude());
157 // TODO: make sure thread UI keeps running in parallel after that. I guess.
162 public void onSaveInstanceState(final Bundle savedInstanceState) {
165 final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
166 final Forecast forecast = store.getForecast();
168 // TODO: Could it be better to save forecast data even if it is null value?
169 // So, perhaps do not check for null value.
170 if (forecast != null) {
171 savedInstanceState.putSerializable("Forecast", forecast);
174 super.onSaveInstanceState(savedInstanceState);
178 public void onPause() {
179 LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()).unregisterReceiver(this.mReceiver);
185 public void onListItemClick(final ListView l, final View v, final int position, final long id) {
186 final SpecificFragment fragment = (SpecificFragment) this
187 .getFragmentManager().findFragmentById(R.id.weather_specific_fragment);
188 if (fragment == null) {
190 final Intent intent = new Intent("de.example.exampletdd.WEATHERINFO")
191 .setComponent(new ComponentName("de.example.exampletdd",
192 "de.example.exampletdd.SpecificActivity"));
193 intent.putExtra("CHOSEN_DAY", (int) id);
194 OverviewFragment.this.getActivity().startActivity(intent);
197 fragment.updateUIByChosenDay((int) id);
201 private interface UnitsConversor {
203 public double doConversion(final double value);
206 private void updateUI(final Forecast forecastWeatherData) {
208 final SharedPreferences sharedPreferences = PreferenceManager
209 .getDefaultSharedPreferences(this.getActivity().getApplicationContext());
211 // TODO: repeating the same code in Overview, Specific and Current!!!
212 // 1. Update units of measurement.
214 UnitsConversor unitsConversor;
215 String keyPreference = this.getResources().getString(
216 R.string.weather_preferences_temperature_key);
217 final String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature);
218 final String unitsPreferenceValue = sharedPreferences.getString(
219 keyPreference, this.getString(R.string.weather_preferences_temperature_celsius));
220 if (unitsPreferenceValue.equals(values[0])) {
222 unitsConversor = new UnitsConversor(){
225 public double doConversion(final double value) {
226 return value - 273.15;
230 } else if (unitsPreferenceValue.equals(values[1])) {
232 unitsConversor = new UnitsConversor(){
235 public double doConversion(final double value) {
236 return (value * 1.8) - 459.67;
242 unitsConversor = new UnitsConversor(){
245 public double doConversion(final double value) {
253 // 2. Update number day forecast.
254 keyPreference = this.getResources().getString(R.string.weather_preferences_day_forecast_key);
255 final String dayForecast = sharedPreferences.getString(keyPreference, "5");
256 final int mDayForecast = Integer.valueOf(dayForecast);
260 final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
261 tempFormatter.applyPattern("#####.##");
262 final SimpleDateFormat dayNameFormatter = new SimpleDateFormat("EEE", Locale.US);
263 final SimpleDateFormat monthAndDayNumberormatter = new SimpleDateFormat("MMM d", Locale.US);
266 // 4. Prepare data for UI.
267 final List<OverviewEntry> entries = new ArrayList<OverviewEntry>();
268 final OverviewAdapter adapter = new OverviewAdapter(this.getActivity(),
269 R.layout.weather_main_entry_list);
270 final Calendar calendar = Calendar.getInstance();
271 int count = mDayForecast;
272 for (final de.example.exampletdd.model.forecastweather.List forecast : forecastWeatherData
277 if ((forecast.getWeather().size() > 0) &&
278 (forecast.getWeather().get(0).getIcon() != null) &&
279 (IconsList.getIcon(forecast.getWeather().get(0).getIcon()) != null)) {
280 final String icon = forecast.getWeather().get(0).getIcon();
281 picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon)
282 .getResourceDrawable());
284 picture = BitmapFactory.decodeResource(this.getResources(),
285 R.drawable.weather_severe_alert);
288 final Long forecastUNIXDate = (Long) forecast.getDt();
289 calendar.setTimeInMillis(forecastUNIXDate * 1000L);
290 final Date dayTime = calendar.getTime();
291 final String dayTextName = dayNameFormatter.format(dayTime);
292 final String monthAndDayNumberText = monthAndDayNumberormatter.format(dayTime);
294 Double maxTemp = null;
295 if (forecast.getTemp().getMax() != null) {
296 maxTemp = (Double) forecast.getTemp().getMax();
297 maxTemp = unitsConversor.doConversion(maxTemp);
300 Double minTemp = null;
301 if (forecast.getTemp().getMin() != null) {
302 minTemp = (Double) forecast.getTemp().getMin();
303 minTemp = unitsConversor.doConversion(minTemp);
306 if ((maxTemp != null) && (minTemp != null)) {
307 entries.add(new OverviewEntry(dayTextName, monthAndDayNumberText,
308 tempFormatter.format(maxTemp) + symbol, tempFormatter.format(minTemp) + symbol,
320 adapter.addAll(entries);
321 this.setListAdapter(adapter);
324 private boolean isDataFresh(final Date lastUpdate) {
325 if (lastUpdate == null) {
329 final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(
330 this.getActivity().getApplicationContext());
331 final String keyPreference = this.getString(R.string.weather_preferences_refresh_interval_key);
332 final String refresh = sharedPreferences.getString(
334 this.getResources().getStringArray(R.array.weather_preferences_refresh_interval)[0]);
335 final Date currentTime = new Date();
336 if (((currentTime.getTime() - lastUpdate.getTime())) < Long.valueOf(refresh)) {
343 // TODO: How could I show just one progress dialog when I have two fragments in tabs
344 // activity doing the same in background?
345 // I mean, if OverviewTask shows one progress dialog and CurrentTask does the same I will have
346 // have two progress dialogs... How may I solve this problem? I HATE ANDROID.
347 private class OverviewTask extends AsyncTask<Object, Void, Forecast> {
348 // Store the context passed to the AsyncTask when the system instantiates it.
349 private final Context localContext;
350 private final CustomHTTPClient HTTPClient;
351 private final ServiceParser weatherService;
353 public OverviewTask(final Context context, final CustomHTTPClient HTTPClient,
354 final ServiceParser weatherService) {
355 this.localContext = context;
356 this.HTTPClient = HTTPClient;
357 this.weatherService = weatherService;
361 protected Forecast doInBackground(final Object... params) {
362 final double latitude = (Double) params[0];
363 final double longitude = (Double) params[1];
365 Forecast forecast = null;
368 forecast = this.doInBackgroundThrowable(latitude, longitude);
369 } catch (final JsonParseException e) {
370 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
371 } catch (final ClientProtocolException e) {
372 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
373 } catch (final MalformedURLException e) {
374 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
375 } catch (final URISyntaxException e) {
376 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
377 } catch (final IOException e) {
378 // logger infrastructure swallows UnknownHostException :/
379 Log.e(TAG, "OverviewTask doInBackground exception: " + e.getMessage(), e);
387 private Forecast doInBackgroundThrowable(final double latitude, final double longitude)
388 throws URISyntaxException, ClientProtocolException, JsonParseException, IOException {
390 final String APIVersion = localContext.getResources().getString(R.string.api_version);
391 final String urlAPI = localContext.getResources().getString(R.string.uri_api_weather_forecast);
392 // TODO: number as resource
393 final String url = weatherService.createURIAPIForecast(urlAPI, APIVersion, latitude, longitude, "14");
394 final String urlWithoutCache = url.concat("&time=" + System.currentTimeMillis());
395 final String jsonData = HTTPClient.retrieveDataAsString(new URL(urlWithoutCache));
397 return weatherService.retrieveForecastFromJPOS(jsonData);
401 protected void onPostExecute(final Forecast forecast) {
402 // TODO: Is AsyncTask calling this method even when RunTimeException in doInBackground method?
403 // I hope so, otherwise I must catch(Throwable) in doInBackground method :(
405 // Call updateUI on the UI thread.
406 final Intent forecastData = new Intent("de.example.exampletdd.UPDATEFORECAST");
407 forecastData.putExtra("forecast", forecast);
408 LocalBroadcastManager.getInstance(this.localContext).sendBroadcastSync(forecastData);