139afe989883f86c53edeb71330ac8857d1ef783
[JavaForFun] /
1 package de.example.exampletdd.fragment.overview;
2
3 import java.io.IOException;
4 import java.net.MalformedURLException;
5 import java.net.URISyntaxException;
6 import java.net.URL;
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;
15
16 import org.apache.http.client.ClientProtocolException;
17
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;
35
36 import com.fasterxml.jackson.core.JsonParseException;
37
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;
48
49 public class OverviewFragment extends ListFragment {
50     private static final String TAG = "OverviewFragment";
51     private BroadcastReceiver mReceiver;
52
53     @Override
54     public void onCreate(final Bundle savedInstanceState) {
55         super.onCreate(savedInstanceState);
56     }
57
58     @Override
59     public void onActivityCreated(final Bundle savedInstanceState) {
60         super.onActivityCreated(savedInstanceState);
61
62         final ListView listWeatherView = this.getListView();
63         listWeatherView.setChoiceMode(ListView.CHOICE_MODE_NONE);
64
65         if (savedInstanceState != null) {
66             // Restore UI state
67             final Forecast forecast = (Forecast) savedInstanceState.getSerializable("Forecast");
68
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);
74             }
75         }
76
77         this.setHasOptionsMenu(false);
78
79         this.setEmptyText(this.getString(R.string.text_field_remote_error));
80         this.setListShownNoAnimation(false);
81     }
82
83     @Override
84     public void onResume() {
85         super.onResume();
86
87         this.mReceiver = new BroadcastReceiver() {
88
89                         @Override
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");
94
95                                         if (forecastRemote != null) {
96
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();
102
103                                         if (forecast == null || !OverviewFragment.this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) {
104                                                 // 2. Update UI.
105                                                 OverviewFragment.this.updateUI(forecastRemote);
106
107                                                 // 3. Update Data.
108                                                 store.saveForecast(forecastRemote);
109                                             weatherLocation.setLastForecastUIUpdate(new Date());
110                                             query.updateDataBase(weatherLocation);
111
112                                             // 4. Show list.
113                                             OverviewFragment.this.setListShownNoAnimation(true);
114                                         }
115
116                                         } else {
117                                                 // Empty list and show error message (see setEmptyText in onCreate)
118                                                 OverviewFragment.this.setListAdapter(null);
119                                                 OverviewFragment.this.setListShownNoAnimation(true);
120                                         }
121                                 }
122                         }
123         };
124
125         // Register receiver
126         final IntentFilter filter = new IntentFilter();
127         filter.addAction("de.example.exampletdd.UPDATEFORECAST");
128         LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext())
129                                                         .registerReceiver(this.mReceiver, filter);
130
131         final DatabaseQueries query = new DatabaseQueries(this.getActivity().getApplicationContext());
132         final WeatherLocation weatherLocation = query.queryDataBase();
133         if (weatherLocation == null) {
134             // Nothing to do.
135                 // Empty list and show error message (see setEmptyText in onCreate)
136                         this.setListAdapter(null);
137                         this.setListShownNoAnimation(true);
138             return;
139         }
140
141         final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
142         final Forecast forecast = store.getForecast();
143
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);
147         } else {
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()));
155
156             task.execute(weatherLocation.getLatitude(), weatherLocation.getLongitude());
157             // TODO: make sure thread UI keeps running in parallel after that. I guess.
158         }
159     }
160
161     @Override
162     public void onSaveInstanceState(final Bundle savedInstanceState) {
163
164         // Save UI state
165         final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
166         final Forecast forecast = store.getForecast();
167
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);
172         }
173
174         super.onSaveInstanceState(savedInstanceState);
175     }
176
177     @Override
178     public void onPause() {
179         LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()).unregisterReceiver(this.mReceiver);
180
181         super.onPause();
182     }
183
184     @Override
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) {
189             // handset layout
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);
195         } else {
196             // tablet layout
197             fragment.updateUIByChosenDay((int) id);
198         }
199     }
200
201     private interface UnitsConversor {
202         
203         public double doConversion(final double value);
204     }
205     
206     private void updateUI(final Forecast forecastWeatherData) {
207
208         final SharedPreferences sharedPreferences = PreferenceManager
209                 .getDefaultSharedPreferences(this.getActivity().getApplicationContext());
210
211         // TODO: repeating the same code in Overview, Specific and Current!!!
212         // 1. Update units of measurement.
213         String symbol;
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])) {
221                 symbol = values[0];
222                 unitsConversor = new UnitsConversor(){
223
224                                 @Override
225                                 public double doConversion(final double value) {
226                                         return value - 273.15;
227                                 }
228                         
229                 };
230         } else if (unitsPreferenceValue.equals(values[1])) {
231                 symbol = values[1];
232                 unitsConversor = new UnitsConversor(){
233
234                                 @Override
235                                 public double doConversion(final double value) {
236                                         return (value * 1.8) - 459.67;
237                                 }
238                         
239                 };
240         } else {
241                 symbol = values[2];
242                 unitsConversor = new UnitsConversor(){
243
244                                 @Override
245                                 public double doConversion(final double value) {
246                                         return value;
247                                 }
248                         
249                 };
250         }
251
252
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);
257
258
259         // 3. Formatters
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);
264
265
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
273                 .getList()) {
274
275             Bitmap picture;
276
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());
283             } else {
284                 picture = BitmapFactory.decodeResource(this.getResources(),
285                         R.drawable.weather_severe_alert);
286             }
287
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);
293
294             Double maxTemp = null;
295             if (forecast.getTemp().getMax() != null) {
296                 maxTemp = (Double) forecast.getTemp().getMax();
297                 maxTemp = unitsConversor.doConversion(maxTemp);
298             }
299
300             Double minTemp = null;
301             if (forecast.getTemp().getMin() != null) {
302                 minTemp = (Double) forecast.getTemp().getMin();
303                 minTemp = unitsConversor.doConversion(minTemp);
304             }
305
306             if ((maxTemp != null) && (minTemp != null)) {
307                 entries.add(new OverviewEntry(dayTextName, monthAndDayNumberText,
308                         tempFormatter.format(maxTemp) + symbol, tempFormatter.format(minTemp) + symbol,
309                         picture));
310             }
311
312             count = count - 1;
313             if (count == 0) {
314                 break;
315             }
316         }
317
318
319         // 5. Update UI.
320         adapter.addAll(entries);
321         this.setListAdapter(adapter);
322     }
323
324     private boolean isDataFresh(final Date lastUpdate) {
325         if (lastUpdate == null) {
326                 return false;
327         }
328         
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(
333                         keyPreference,
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)) {
337                 return true;
338         }
339         
340         return false;
341     }
342
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;
352
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;
358         }
359         
360         @Override
361         protected Forecast doInBackground(final Object... params) {
362             final double latitude = (Double) params[0];
363             final double longitude = (Double) params[1];
364
365             Forecast forecast = null;
366
367             try {
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);
380             } finally {
381                 HTTPClient.close();
382             }
383
384             return forecast;
385         }
386
387         private Forecast doInBackgroundThrowable(final double latitude, final double longitude)
388                         throws URISyntaxException, ClientProtocolException, JsonParseException, IOException {
389
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));
396
397             return weatherService.retrieveForecastFromJPOS(jsonData);
398         }
399
400         @Override
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 :(
404                 
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);
409         }
410     }
411 }