1 package name.gumartinm.weather.information.activity;
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;
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;
35 public class MapActivity extends FragmentActivity implements
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;
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;
47 private LocationManager mLocationManager;
50 protected void onCreate(final Bundle savedInstanceState) {
51 super.onCreate(savedInstanceState);
52 this.setContentView(R.layout.weather_map);
54 // Acquire a reference to the system Location Manager
55 this.mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
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));
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);
78 this.mRestoreUI = (WeatherLocation) savedInstanceState.getSerializable("WeatherLocation");
82 public void onResume() {
85 final ActionBar actionBar = this.getActionBar();
86 actionBar.setTitle(this.getString(R.string.weather_map_mark_location));
88 WeatherLocation weatherLocation;
89 if (this.mRestoreUI != null) {
91 weatherLocation = this.mRestoreUI;
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();
100 final LatLng point = this.mMarker.getPosition();
101 double latitude = point.latitude;
102 double longitude = point.longitude;
104 weatherLocation = new WeatherLocation()
106 .setCountry(countryString)
107 .setLatitude(latitude)
108 .setLongitude(longitude);
110 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
111 weatherLocation = query.queryDataBase();
114 if (weatherLocation != null) {
115 this.updateUI(weatherLocation);
120 * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
122 * {@link http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult}
125 public void onPostResume() {
126 super.onPostResume();
128 final FragmentManager fm = getSupportFragmentManager();
129 final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
130 if (progressFragment == null) {
131 this.addButtonsFragment();
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);
142 public void onSaveInstanceState(final Bundle savedInstanceState) {
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();
151 final LatLng point = this.mMarker.getPosition();
152 double latitude = point.latitude;
153 double longitude = point.longitude;
155 final WeatherLocation location = new WeatherLocation()
157 .setCountry(countryString)
158 .setLatitude(latitude)
159 .setLongitude(longitude);
160 savedInstanceState.putSerializable("WeatherLocation", location);
163 super.onSaveInstanceState(savedInstanceState);
167 public void onPause() {
170 this.mLocationManager.removeUpdates(this);
173 public void onClickSaveLocation(final View v) {
174 if (this.mMarker != null) {
175 final LatLng position = this.mMarker.getPosition();
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();
182 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
183 final WeatherLocation weatherLocation = query.queryDataBase();
184 if (weatherLocation != null) {
187 .setCountry(countryString)
188 .setLatitude(position.latitude)
189 .setLongitude(position.longitude)
190 .setLastCurrentUIUpdate(null)
191 .setLastForecastUIUpdate(null)
193 query.updateDataBase(weatherLocation);
195 final WeatherLocation location = new WeatherLocation()
197 .setCountry(countryString)
199 .setLatitude(position.latitude)
200 .setLongitude(position.longitude)
202 query.insertIntoDataBase(location);
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);
224 this.mLocationManager.requestSingleUpdate(criteria, this, null);
226 Toast.makeText(this, this.getString(R.string.weather_map_not_enabled_location), Toast.LENGTH_LONG).show();
228 // Trying to use the synchronous calls. Problems: mGoogleApiClient read/store from different threads.
229 // new GetLocationTask(this).execute();
232 private void updateUI(final WeatherLocation weatherLocation) {
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());
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();
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);
250 private class MapActivityOnMapLongClickListener implements OnMapLongClickListener {
251 // Store the context passed to the AsyncTask when the system instantiates it.
252 private final Context localContext;
254 private MapActivityOnMapLongClickListener(final Context context) {
255 this.localContext = context;
259 public void onMapLongClick(final LatLng point) {
260 final MapActivity activity = (MapActivity) this.localContext;
261 activity.getAddressAndUpdateUI(point.latitude, point.longitude);
267 * Getting the address of the current location, using reverse geocoding only works if
268 * a geocoding service is available.
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);
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();
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)
290 .setCountry(country);
292 updateUI(weatherLocation);
296 /*****************************************************************************************************
298 * MapProgressFragment.TaskCallbacks
300 *****************************************************************************************************/
302 public void onPostExecute(WeatherLocation weatherLocation) {
304 this.updateUI(weatherLocation);
305 this.removeProgressFragment();
307 this.addButtonsFragment();
310 /*****************************************************************************************************
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.
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 *****************************************************************************************************/
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);
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();
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();
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();
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);
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();
375 /*****************************************************************************************************
377 * android.location.LocationListener
379 *****************************************************************************************************/
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.
386 // Display the current location in the UI
387 // TODO: May location not be null?
388 this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude());
392 public void onStatusChanged(String provider, int status, Bundle extras) {}
395 public void onProviderEnabled(String provider) {}
398 public void onProviderDisabled(String provider) {}