5e43de2389961fe749e5413bb01fc9af816aa049
[JavaForFun] /
1 /**
2  * Copyright 2014 Gustavo Martin Morcuende
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package name.gumartinm.weather.information.fragment.overview;
17
18 import java.io.IOException;
19 import java.net.MalformedURLException;
20 import java.net.URISyntaxException;
21 import java.net.URL;
22 import java.text.DecimalFormat;
23 import java.text.NumberFormat;
24 import java.text.SimpleDateFormat;
25 import java.util.ArrayList;
26 import java.util.Calendar;
27 import java.util.Date;
28 import java.util.List;
29 import java.util.Locale;
30
31 import org.apache.http.client.ClientProtocolException;
32
33 import android.content.BroadcastReceiver;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.content.SharedPreferences;
39 import android.graphics.Bitmap;
40 import android.graphics.BitmapFactory;
41 import android.net.http.AndroidHttpClient;
42 import android.os.AsyncTask;
43 import android.os.Bundle;
44 import android.preference.PreferenceManager;
45 import android.support.v4.app.ListFragment;
46 import android.support.v4.content.LocalBroadcastManager;
47 import android.util.Log;
48 import android.view.View;
49 import android.widget.ListView;
50
51 import com.fasterxml.jackson.core.JsonParseException;
52 import name.gumartinm.weather.information.R;
53 import name.gumartinm.weather.information.fragment.specific.SpecificFragment;
54 import name.gumartinm.weather.information.httpclient.CustomHTTPClient;
55 import name.gumartinm.weather.information.model.DatabaseQueries;
56 import name.gumartinm.weather.information.model.WeatherLocation;
57 import name.gumartinm.weather.information.model.forecastweather.Forecast;
58 import name.gumartinm.weather.information.parser.JPOSForecastParser;
59 import name.gumartinm.weather.information.service.IconsList;
60 import name.gumartinm.weather.information.service.PermanentStorage;
61 import name.gumartinm.weather.information.service.ServiceForecastParser;
62
63 public class OverviewFragment extends ListFragment {
64     private static final String TAG = "OverviewFragment";
65     private BroadcastReceiver mReceiver;
66
67     @Override
68     public void onCreate(final Bundle savedInstanceState) {
69         super.onCreate(savedInstanceState);
70     }
71
72     @Override
73     public void onActivityCreated(final Bundle savedInstanceState) {
74         super.onActivityCreated(savedInstanceState);
75
76         final ListView listWeatherView = this.getListView();
77         listWeatherView.setChoiceMode(ListView.CHOICE_MODE_NONE);
78
79         if (savedInstanceState != null) {
80             // Restore UI state
81             final Forecast forecast = (Forecast) savedInstanceState.getSerializable("Forecast");
82
83             if (forecast != null) {
84                 final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
85                 store.saveForecast(forecast);
86             }
87         }
88
89         this.setHasOptionsMenu(false);
90
91         this.setEmptyText(this.getString(R.string.text_field_remote_error));
92         this.setListShownNoAnimation(false);
93     }
94
95     @Override
96     public void onResume() {
97         super.onResume();
98
99         this.mReceiver = new BroadcastReceiver() {
100
101                         @Override
102                         public void onReceive(final Context context, final Intent intent) {
103                                 final String action = intent.getAction();
104                                 if (action.equals("name.gumartinm.weather.information.UPDATEFORECAST")) {
105                                         final Forecast forecastRemote = (Forecast) intent.getSerializableExtra("forecast");
106
107                                         if (forecastRemote != null) {
108
109                                                 // 1. Check conditions. They must be the same as the ones that triggered the AsyncTask.
110                                                 final DatabaseQueries query = new DatabaseQueries(context.getApplicationContext());
111                                     final WeatherLocation weatherLocation = query.queryDataBase();
112                                     final PermanentStorage store = new PermanentStorage(context.getApplicationContext());
113                                     final Forecast forecast = store.getForecast();
114
115                                         if (forecast == null || !OverviewFragment.this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) {
116                                                 // 2. Update UI.
117                                                 OverviewFragment.this.updateUI(forecastRemote);
118
119                                                 // 3. Update Data.
120                                                 store.saveForecast(forecastRemote);
121                                             weatherLocation.setLastForecastUIUpdate(new Date());
122                                             query.updateDataBase(weatherLocation);
123
124                                             // 4. Show list.
125                                             OverviewFragment.this.setListShownNoAnimation(true);
126                                         }
127
128                                         } else {
129                                                 // Empty list and show error message (see setEmptyText in onCreate)
130                                                 OverviewFragment.this.setListAdapter(null);
131                                                 OverviewFragment.this.setListShownNoAnimation(true);
132                                         }
133                                 }
134                         }
135         };
136
137         // Register receiver
138         final IntentFilter filter = new IntentFilter();
139         filter.addAction("name.gumartinm.weather.information.UPDATEFORECAST");
140         LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext())
141                                                         .registerReceiver(this.mReceiver, filter);
142
143         final DatabaseQueries query = new DatabaseQueries(this.getActivity().getApplicationContext());
144         final WeatherLocation weatherLocation = query.queryDataBase();
145         if (weatherLocation == null) {
146             // Nothing to do.
147                 // Empty list and show error message (see setEmptyText in onCreate)
148                         this.setListAdapter(null);
149                         this.setListShownNoAnimation(true);
150             return;
151         }
152
153         final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
154         final Forecast forecast = store.getForecast();
155
156         if (forecast != null && this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) {
157             this.updateUI(forecast);
158         } else {
159             // Load remote data (aynchronous)
160             // Gets the data from the web.
161             this.setListShownNoAnimation(false);
162             final OverviewTask task = new OverviewTask(
163                         this.getActivity().getApplicationContext(),
164                     new CustomHTTPClient(AndroidHttpClient.newInstance("Android 4.3 WeatherInformation Agent")),
165                     new ServiceForecastParser(new JPOSForecastParser()));
166
167             task.execute(weatherLocation.getLatitude(), weatherLocation.getLongitude());
168         }
169     }
170
171     @Override
172     public void onSaveInstanceState(final Bundle savedInstanceState) {
173
174         // Save UI state
175         final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext());
176         final Forecast forecast = store.getForecast();
177
178         if (forecast != null) {
179             savedInstanceState.putSerializable("Forecast", forecast);
180         }
181
182         super.onSaveInstanceState(savedInstanceState);
183     }
184
185     @Override
186     public void onPause() {
187         LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()).unregisterReceiver(this.mReceiver);
188
189         super.onPause();
190     }
191
192     @Override
193     public void onListItemClick(final ListView l, final View v, final int position, final long id) {
194         final SpecificFragment fragment = (SpecificFragment) this
195                 .getFragmentManager().findFragmentById(R.id.weather_specific_fragment);
196         if (fragment == null) {
197             // handset layout
198             final Intent intent = new Intent("name.gumartinm.weather.information.WEATHERINFO")
199             .setComponent(new ComponentName("name.gumartinm.weather.information",
200                     "name.gumartinm.weather.information.activity.SpecificActivity"));
201             intent.putExtra("CHOSEN_DAY", (int) id);
202             OverviewFragment.this.getActivity().startActivity(intent);
203         } else {
204             // tablet layout
205             fragment.updateUIByChosenDay((int) id);
206         }
207     }
208
209     private interface UnitsConversor {
210         
211         public double doConversion(final double value);
212     }
213     
214     private void updateUI(final Forecast forecastWeatherData) {
215
216         final SharedPreferences sharedPreferences = PreferenceManager
217                 .getDefaultSharedPreferences(this.getActivity().getApplicationContext());
218
219         // TODO: repeating the same code in Overview, Specific and Current!!!
220         // 1. Update units of measurement.
221         String symbol;
222         UnitsConversor unitsConversor;
223         String keyPreference = this.getResources().getString(
224                 R.string.weather_preferences_temperature_key);
225         final String[] values = this.getResources().getStringArray(R.array.weather_preferences_temperature);
226         final String unitsPreferenceValue = sharedPreferences.getString(
227                 keyPreference, this.getString(R.string.weather_preferences_temperature_celsius));
228         if (unitsPreferenceValue.equals(values[0])) {
229                 symbol = values[0];
230                 unitsConversor = new UnitsConversor(){
231
232                                 @Override
233                                 public double doConversion(final double value) {
234                                         return value - 273.15;
235                                 }
236                         
237                 };
238         } else if (unitsPreferenceValue.equals(values[1])) {
239                 symbol = values[1];
240                 unitsConversor = new UnitsConversor(){
241
242                                 @Override
243                                 public double doConversion(final double value) {
244                                         return (value * 1.8) - 459.67;
245                                 }
246                         
247                 };
248         } else {
249                 symbol = values[2];
250                 unitsConversor = new UnitsConversor(){
251
252                                 @Override
253                                 public double doConversion(final double value) {
254                                         return value;
255                                 }
256                         
257                 };
258         }
259
260
261         // 2. Update number day forecast.
262         keyPreference = this.getResources().getString(R.string.weather_preferences_day_forecast_key);
263         final String dayForecast = sharedPreferences.getString(keyPreference, "5");
264         final int mDayForecast = Integer.valueOf(dayForecast);
265
266
267         // 3. Formatters
268         final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
269         tempFormatter.applyPattern("#####.##");
270         final SimpleDateFormat dayNameFormatter = new SimpleDateFormat("EEE", Locale.US);
271         final SimpleDateFormat monthAndDayNumberormatter = new SimpleDateFormat("MMM d", Locale.US);
272
273
274         // 4. Prepare data for UI.
275         final List<OverviewEntry> entries = new ArrayList<OverviewEntry>();
276         final OverviewAdapter adapter = new OverviewAdapter(this.getActivity(),
277                 R.layout.weather_main_entry_list);
278         final Calendar calendar = Calendar.getInstance();
279         int count = mDayForecast;
280         for (final name.gumartinm.weather.information.model.forecastweather.List forecast : forecastWeatherData
281                 .getList()) {
282
283             Bitmap picture;
284
285             if ((forecast.getWeather().size() > 0) &&
286                     (forecast.getWeather().get(0).getIcon() != null) &&
287                     (IconsList.getIcon(forecast.getWeather().get(0).getIcon()) != null)) {
288                 final String icon = forecast.getWeather().get(0).getIcon();
289                 picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon)
290                         .getResourceDrawable());
291             } else {
292                 picture = BitmapFactory.decodeResource(this.getResources(),
293                         R.drawable.weather_severe_alert);
294             }
295
296             final Long forecastUNIXDate = (Long) forecast.getDt();
297             calendar.setTimeInMillis(forecastUNIXDate * 1000L);
298             final Date dayTime = calendar.getTime();
299             final String dayTextName = dayNameFormatter.format(dayTime);
300             final String monthAndDayNumberText = monthAndDayNumberormatter.format(dayTime);
301
302             Double maxTemp = null;
303             if (forecast.getTemp().getMax() != null) {
304                 maxTemp = (Double) forecast.getTemp().getMax();
305                 maxTemp = unitsConversor.doConversion(maxTemp);
306             }
307
308             Double minTemp = null;
309             if (forecast.getTemp().getMin() != null) {
310                 minTemp = (Double) forecast.getTemp().getMin();
311                 minTemp = unitsConversor.doConversion(minTemp);
312             }
313
314             if ((maxTemp != null) && (minTemp != null)) {
315                 entries.add(new OverviewEntry(dayTextName, monthAndDayNumberText,
316                         tempFormatter.format(maxTemp) + symbol, tempFormatter.format(minTemp) + symbol,
317                         picture));
318             }
319
320             count = count - 1;
321             if (count == 0) {
322                 break;
323             }
324         }
325
326
327         // 5. Update UI.
328         adapter.addAll(entries);
329         this.setListAdapter(adapter);
330     }
331
332     private boolean isDataFresh(final Date lastUpdate) {
333         if (lastUpdate == null) {
334                 return false;
335         }
336         
337         final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(
338                         this.getActivity().getApplicationContext());
339         final String keyPreference = this.getString(R.string.weather_preferences_refresh_interval_key);
340         final String refresh = sharedPreferences.getString(
341                         keyPreference,
342                         this.getResources().getStringArray(R.array.weather_preferences_refresh_interval)[0]);
343         final Date currentTime = new Date();
344         if (((currentTime.getTime() - lastUpdate.getTime())) < Long.valueOf(refresh)) {
345                 return true;
346         }
347         
348         return false;
349     }
350
351     private class OverviewTask extends AsyncTask<Object, Void, Forecast> {
352         // Store the context passed to the AsyncTask when the system instantiates it.
353         private final Context localContext;
354         private final CustomHTTPClient HTTPClient;
355         private final ServiceForecastParser weatherService;
356
357         public OverviewTask(final Context context, final CustomHTTPClient HTTPClient,
358                         final ServiceForecastParser weatherService) {
359                 this.localContext = context;
360             this.HTTPClient = HTTPClient;
361             this.weatherService = weatherService;
362         }
363         
364         @Override
365         protected Forecast doInBackground(final Object... params) {
366             final double latitude = (Double) params[0];
367             final double longitude = (Double) params[1];
368
369             Forecast forecast = null;
370
371             try {
372                 forecast = this.doInBackgroundThrowable(latitude, longitude);
373             } catch (final JsonParseException e) {
374                 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
375             } catch (final ClientProtocolException e) {
376                 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
377             } catch (final MalformedURLException e) {
378                 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
379             } catch (final URISyntaxException e) {
380                 Log.e(TAG, "OverviewTask doInBackground exception: ", e);
381             } catch (final IOException e) {
382                 // logger infrastructure swallows UnknownHostException :/
383                 Log.e(TAG, "OverviewTask doInBackground exception: " + e.getMessage(), e);
384             } finally {
385                 HTTPClient.close();
386             }
387
388             return forecast;
389         }
390
391         private Forecast doInBackgroundThrowable(final double latitude, final double longitude)
392                         throws URISyntaxException, ClientProtocolException, JsonParseException, IOException {
393
394             final String APIVersion = localContext.getResources().getString(R.string.api_version);
395             final String urlAPI = localContext.getResources().getString(R.string.uri_api_weather_forecast);
396             // TODO: number as resource
397             final String url = weatherService.createURIAPIForecast(urlAPI, APIVersion, latitude, longitude, "14");
398             final String urlWithoutCache = url.concat("&time=" + System.currentTimeMillis());
399             final String jsonData = HTTPClient.retrieveDataAsString(new URL(urlWithoutCache));
400
401             return weatherService.retrieveForecastFromJPOS(jsonData);
402         }
403
404         @Override
405         protected void onPostExecute(final Forecast forecast) {
406                 
407             // Call updateUI on the UI thread.
408                 final Intent forecastData = new Intent("name.gumartinm.weather.information.UPDATEFORECAST");
409                 forecastData.putExtra("forecast", forecast);
410             LocalBroadcastManager.getInstance(this.localContext).sendBroadcastSync(forecastData);
411         }
412     }
413 }