11efcffd00789ce135d911c91741649c4ff49bdf
[JavaForFun] /
1 package name.gumartinm.weather.information.activity;
2
3 import android.app.ActionBar;
4 import android.content.Context;
5 import android.location.Criteria;
6 import android.location.Geocoder;
7 import android.location.Location;
8 import android.location.LocationListener;
9 import android.location.LocationManager;
10 import android.os.Build;
11 import android.os.Bundle;
12 import android.support.v4.app.Fragment;
13 import android.support.v4.app.FragmentActivity;
14 import android.support.v4.app.FragmentManager;
15 import android.support.v4.app.FragmentTransaction;
16 import android.view.View;
17 import android.widget.Button;
18 import android.widget.TextView;
19 import android.widget.Toast;
20
21 import com.google.android.gms.maps.CameraUpdateFactory;
22 import com.google.android.gms.maps.GoogleMap;
23 import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
24 import com.google.android.gms.maps.MapFragment;
25 import com.google.android.gms.maps.model.LatLng;
26 import com.google.android.gms.maps.model.Marker;
27 import com.google.android.gms.maps.model.MarkerOptions;
28 import name.gumartinm.weather.information.R;
29 import name.gumartinm.weather.information.fragment.map.MapButtonsFragment;
30 import name.gumartinm.weather.information.fragment.map.MapProgressFragment;
31 import name.gumartinm.weather.information.model.DatabaseQueries;
32 import name.gumartinm.weather.information.model.WeatherLocation;
33
34
35 public class MapActivity extends FragmentActivity implements
36                                                                         LocationListener,
37                                                                         MapProgressFragment.TaskCallbacks {
38     private static final String PROGRESS_FRAGMENT_TAG = "PROGRESS_FRAGMENT";
39     private static final String BUTTONS_FRAGMENT_TAG = "BUTTONS_FRAGMENT";
40     private WeatherLocation mRestoreUI;
41        
42     // Google Play Services Map
43     private GoogleMap mMap;
44     // TODO: read and store from different threads? Hopefully always from UI thread.
45     private Marker mMarker;
46     
47     private LocationManager mLocationManager;
48
49     @Override
50     protected void onCreate(final Bundle savedInstanceState) {
51         super.onCreate(savedInstanceState);
52         this.setContentView(R.layout.weather_map);
53         
54         // Acquire a reference to the system Location Manager
55         this.mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
56         
57         // Google Play Services Map
58         final MapFragment mapFragment = (MapFragment) this.getFragmentManager()
59                 .findFragmentById(R.id.weather_map_fragment_map);
60         this.mMap = mapFragment.getMap();
61         this.mMap.setMyLocationEnabled(false);
62         this.mMap.getUiSettings().setMyLocationButtonEnabled(false);
63         this.mMap.getUiSettings().setZoomControlsEnabled(true);
64         this.mMap.getUiSettings().setCompassEnabled(true);
65         this.mMap.setOnMapLongClickListener(new MapActivityOnMapLongClickListener(this));
66     }
67     
68     @Override
69     protected void onRestoreInstanceState(final Bundle savedInstanceState) {
70         // Instead of restoring the state during onCreate() you may choose to
71         // implement onRestoreInstanceState(), which the system calls after the
72         // onStart() method. The system calls onRestoreInstanceState() only if
73         // there is a saved state to restore, so you do not need to check whether
74         // the Bundle is null:
75         super.onRestoreInstanceState(savedInstanceState);
76         
77         // Restore UI state
78         this.mRestoreUI = (WeatherLocation) savedInstanceState.getSerializable("WeatherLocation");
79     }
80
81     @Override
82     public void onResume() {
83         super.onResume();
84
85         final ActionBar actionBar = this.getActionBar();
86         actionBar.setTitle(this.getString(R.string.weather_map_mark_location));
87         
88         WeatherLocation weatherLocation;
89         if (this.mRestoreUI != null) {
90                 // Restore UI state
91                 weatherLocation = this.mRestoreUI;
92                 // just once
93                 this.mRestoreUI = null;
94         } else if (this.mMarker != null ) {
95                 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
96             final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
97             final String cityString = city.getText().toString();
98             final String countryString = country.getText().toString();
99             
100             final LatLng point = this.mMarker.getPosition();
101             double latitude = point.latitude;
102             double longitude = point.longitude;
103
104             weatherLocation = new WeatherLocation()
105                         .setCity(cityString)
106                         .setCountry(countryString)
107                         .setLatitude(latitude)
108                         .setLongitude(longitude);
109         } else {
110                 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
111                 weatherLocation = query.queryDataBase();
112         }
113         
114         if (weatherLocation != null) {
115                 this.updateUI(weatherLocation);
116         }
117     }
118     
119     /**
120      * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
121      * 
122      * {@link http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult}
123      */
124     @Override
125     public void onPostResume() {
126         super.onPostResume();
127         
128         final FragmentManager fm = getSupportFragmentManager();
129         final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
130         if (progressFragment == null) {
131                  this.addButtonsFragment();
132         } else {
133                 this.removeProgressFragment();
134                 final Bundle bundle = progressFragment.getArguments();
135                 double latitude = bundle.getDouble("latitude");
136                 double longitude = bundle.getDouble("longitude");
137                 this.addProgressFragment(latitude, longitude);
138         }
139     }
140     
141     @Override
142     public void onSaveInstanceState(final Bundle savedInstanceState) {
143         // Save UI state
144         // Save Google Maps Marker
145         if (this.mMarker != null) {
146                 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
147             final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
148             final String cityString = city.getText().toString();
149             final String countryString = country.getText().toString();
150             
151             final LatLng point = this.mMarker.getPosition();
152             double latitude = point.latitude;
153             double longitude = point.longitude;
154
155             final WeatherLocation location = new WeatherLocation()
156                         .setCity(cityString)
157                         .setCountry(countryString)
158                         .setLatitude(latitude)
159                         .setLongitude(longitude);
160             savedInstanceState.putSerializable("WeatherLocation", location);
161         }
162                 
163         super.onSaveInstanceState(savedInstanceState);
164     }
165     
166         @Override
167         public void onPause() {
168                 super.onPause();
169                 
170                 this.mLocationManager.removeUpdates(this);
171         }
172         
173     public void onClickSaveLocation(final View v) {
174         if (this.mMarker != null) {
175                 final LatLng position = this.mMarker.getPosition();
176                 
177                 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
178             final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
179             final String cityString = city.getText().toString();
180             final String countryString = country.getText().toString();
181             
182                 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
183                 final WeatherLocation weatherLocation = query.queryDataBase();
184             if (weatherLocation != null) {
185                 weatherLocation
186                 .setCity(cityString)
187                 .setCountry(countryString)
188                 .setLatitude(position.latitude)
189                 .setLongitude(position.longitude)
190                 .setLastCurrentUIUpdate(null)
191                 .setLastForecastUIUpdate(null)
192                 .setIsNew(true);
193                 query.updateDataBase(weatherLocation);
194             } else {
195                 final WeatherLocation location = new WeatherLocation()
196                         .setCity(cityString)
197                         .setCountry(countryString)
198                         .setIsSelected(true)
199                         .setLatitude(position.latitude)
200                         .setLongitude(position.longitude)
201                     .setIsNew(true);
202                 query.insertIntoDataBase(location);
203             }
204         }
205     }
206     
207     public void onClickGetLocation(final View v) {
208         // TODO: Somehow I should show a progress dialog.
209         // If Google Play Services is available
210         if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
211                 // TODO: Hopefully there will be results even if location did not change...   
212             final Criteria criteria = new Criteria();
213             criteria.setAccuracy(Criteria.ACCURACY_FINE);
214             criteria.setAltitudeRequired(false);
215             criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
216             criteria.setBearingRequired(false);
217             criteria.setCostAllowed(false);
218             criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
219             criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
220             criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
221             criteria.setSpeedRequired(false);
222             criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
223             
224             this.mLocationManager.requestSingleUpdate(criteria, this, null);
225         } else {
226                 Toast.makeText(this, this.getString(R.string.weather_map_not_enabled_location), Toast.LENGTH_LONG).show();
227         }
228         // Trying to use the synchronous calls. Problems: mGoogleApiClient read/store from different threads.
229         // new GetLocationTask(this).execute();
230     }
231     
232     private void updateUI(final WeatherLocation weatherLocation) {
233
234         final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
235         final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
236         city.setText(weatherLocation.getCity());
237         country.setText(weatherLocation.getCountry());
238
239         final LatLng point = new LatLng(weatherLocation.getLatitude(), weatherLocation.getLongitude());
240         if (this.mMarker != null) {
241                 // Just one marker on map
242                 this.mMarker.remove();
243         }
244         this.mMarker = this.mMap.addMarker(new MarkerOptions().position(point).draggable(true));
245         this.mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(point, 5));
246         this.mMap.animateCamera(CameraUpdateFactory.zoomIn());
247         this.mMap.animateCamera(CameraUpdateFactory.zoomTo(8), 2000, null);
248     }
249     
250     private class MapActivityOnMapLongClickListener implements OnMapLongClickListener {
251         // Store the context passed to the AsyncTask when the system instantiates it.
252         private final Context localContext;
253         
254         private MapActivityOnMapLongClickListener(final Context context) {
255                 this.localContext = context;
256         }
257         
258                 @Override
259                 public void onMapLongClick(final LatLng point) {
260                         final MapActivity activity = (MapActivity) this.localContext;
261                         activity.getAddressAndUpdateUI(point.latitude, point.longitude);
262                 }
263         
264     }
265
266     /**
267      * Getting the address of the current location, using reverse geocoding only works if
268      * a geocoding service is available.
269      *
270      */
271     private void getAddressAndUpdateUI(final double latitude, final double longitude) {
272         // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available.
273         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent()) {
274                 this.removeButtonsFragment();
275                 this.removeProgressFragment();
276                 this.addProgressFragment(latitude, longitude);
277         } else {
278                 this.removeProgressFragment();
279                 this.addButtonsFragment();
280                 // No geocoder is present. Issue an error message.
281             Toast.makeText(this, this.getString(R.string.weather_map_no_geocoder_available), Toast.LENGTH_LONG).show();
282             
283             // Default values
284             final String city = this.getString(R.string.city_not_found);
285             final String country = this.getString(R.string.country_not_found); 
286             final WeatherLocation weatherLocation = new WeatherLocation()
287                         .setLatitude(latitude)
288                         .setLongitude(longitude)
289                         .setCity(city)
290                         .setCountry(country);
291             
292             updateUI(weatherLocation);
293         }
294     }
295
296         /*****************************************************************************************************
297          *
298          *                                                      MapProgressFragment.TaskCallbacks
299          *
300          *****************************************************************************************************/
301         @Override
302         public void onPostExecute(WeatherLocation weatherLocation) {
303
304         this.updateUI(weatherLocation);
305         this.removeProgressFragment();
306
307         this.addButtonsFragment();
308         }
309
310         /*****************************************************************************************************
311          *
312          *                                                      MapProgressFragment
313          * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
314      * Android sucks.
315      *
316      * "Avoid performing transactions inside asynchronous callback methods." :(
317      * see: http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult
318      * see: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
319      * How do you do what I am doing in a different way without using fragments?
320          *****************************************************************************************************/
321         
322         private void addProgressFragment(final double latitude, final double longitude) {
323         final Fragment progressFragment = new MapProgressFragment();
324         progressFragment.setRetainInstance(true);
325         final Bundle args = new Bundle();
326         args.putDouble("latitude", latitude);
327         args.putDouble("longitude", longitude);
328         progressFragment.setArguments(args);
329         
330         final FragmentManager fm = this.getSupportFragmentManager();
331         final FragmentTransaction fragmentTransaction = fm.beginTransaction();
332         fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
333         fragmentTransaction.add(R.id.weather_map_buttons_container, progressFragment, PROGRESS_FRAGMENT_TAG).commit();
334         fm.executePendingTransactions();
335         }
336         
337         private void removeProgressFragment() {
338         final FragmentManager fm = this.getSupportFragmentManager();
339         final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
340         if (progressFragment != null) {
341                 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
342                 fragmentTransaction.remove(progressFragment).commit();
343                 fm.executePendingTransactions();
344         }
345         }
346         
347         private void addButtonsFragment() {
348                 final FragmentManager fm = this.getSupportFragmentManager();
349         Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
350         if (buttonsFragment == null) {
351                 buttonsFragment = new MapButtonsFragment();
352                 buttonsFragment.setRetainInstance(true);
353                 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
354                 fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
355                 fragmentTransaction.add(R.id.weather_map_buttons_container, buttonsFragment, BUTTONS_FRAGMENT_TAG).commit();
356                 fm.executePendingTransactions();
357         }
358         
359         if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
360                 final Button getLocationButton = (Button) this.findViewById(R.id.weather_map_button_getlocation);
361                 getLocationButton.setEnabled(true);
362         }
363         }
364         
365         private void removeButtonsFragment() {
366         final FragmentManager fm = this.getSupportFragmentManager();
367         final Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
368         if (buttonsFragment != null) {
369                 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
370                 fragmentTransaction.remove(buttonsFragment).commit();
371                 fm.executePendingTransactions();
372         }
373         }
374
375    /*****************************************************************************************************
376     *
377     *                                                   android.location.LocationListener
378     *
379     *****************************************************************************************************/
380         
381         @Override
382         public void onLocationChanged(final Location location) {
383                 // It was called from onClickGetLocation (UI thread) This method will run in the same thread (the UI thread)
384                 // so, I do no think there should be any problem.
385
386                 // Display the current location in the UI
387                 // TODO: May location not be null?
388                 this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude());
389         }
390         
391         @Override
392         public void onStatusChanged(String provider, int status, Bundle extras) {}
393
394         @Override
395         public void onProviderEnabled(String provider) {}
396
397         @Override
398         public void onProviderDisabled(String provider) {}
399 }