2 * Copyright 2014 Gustavo Martin Morcuende
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package name.gumartinm.weather.information.activity;
18 import android.app.ActionBar;
19 import android.content.Context;
20 import android.location.Criteria;
21 import android.location.Geocoder;
22 import android.location.Location;
23 import android.location.LocationListener;
24 import android.location.LocationManager;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.support.v4.app.Fragment;
28 import android.support.v4.app.FragmentActivity;
29 import android.support.v4.app.FragmentManager;
30 import android.support.v4.app.FragmentTransaction;
31 import android.view.View;
32 import android.widget.Button;
33 import android.widget.TextView;
34 import android.widget.Toast;
36 import com.google.android.gms.maps.CameraUpdateFactory;
37 import com.google.android.gms.maps.GoogleMap;
38 import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
39 import com.google.android.gms.maps.MapFragment;
40 import com.google.android.gms.maps.model.LatLng;
41 import com.google.android.gms.maps.model.Marker;
42 import com.google.android.gms.maps.model.MarkerOptions;
43 import name.gumartinm.weather.information.R;
44 import name.gumartinm.weather.information.fragment.map.MapButtonsFragment;
45 import name.gumartinm.weather.information.fragment.map.MapProgressFragment;
46 import name.gumartinm.weather.information.model.DatabaseQueries;
47 import name.gumartinm.weather.information.model.WeatherLocation;
50 public class MapActivity extends FragmentActivity implements
52 MapProgressFragment.TaskCallbacks {
53 private static final String PROGRESS_FRAGMENT_TAG = "PROGRESS_FRAGMENT";
54 private static final String BUTTONS_FRAGMENT_TAG = "BUTTONS_FRAGMENT";
55 private WeatherLocation mRestoreUI;
57 // Google Play Services Map
58 private GoogleMap mMap;
59 // TODO: read and store from different threads? Hopefully always from UI thread.
60 private Marker mMarker;
62 private LocationManager mLocationManager;
65 protected void onCreate(final Bundle savedInstanceState) {
66 super.onCreate(savedInstanceState);
67 this.setContentView(R.layout.weather_map);
69 // Acquire a reference to the system Location Manager
70 this.mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
72 // Google Play Services Map
73 final MapFragment mapFragment = (MapFragment) this.getFragmentManager()
74 .findFragmentById(R.id.weather_map_fragment_map);
75 this.mMap = mapFragment.getMap();
76 this.mMap.setMyLocationEnabled(false);
77 this.mMap.getUiSettings().setMyLocationButtonEnabled(false);
78 this.mMap.getUiSettings().setZoomControlsEnabled(true);
79 this.mMap.getUiSettings().setCompassEnabled(true);
80 this.mMap.setOnMapLongClickListener(new MapActivityOnMapLongClickListener(this));
84 protected void onRestoreInstanceState(final Bundle savedInstanceState) {
85 // Instead of restoring the state during onCreate() you may choose to
86 // implement onRestoreInstanceState(), which the system calls after the
87 // onStart() method. The system calls onRestoreInstanceState() only if
88 // there is a saved state to restore, so you do not need to check whether
89 // the Bundle is null:
90 super.onRestoreInstanceState(savedInstanceState);
93 this.mRestoreUI = (WeatherLocation) savedInstanceState.getSerializable("WeatherLocation");
97 public void onResume() {
100 final ActionBar actionBar = this.getActionBar();
101 actionBar.setTitle(this.getString(R.string.weather_map_mark_location));
103 WeatherLocation weatherLocation;
104 if (this.mRestoreUI != null) {
106 weatherLocation = this.mRestoreUI;
108 this.mRestoreUI = null;
109 } else if (this.mMarker != null ) {
110 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
111 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
112 final String cityString = city.getText().toString();
113 final String countryString = country.getText().toString();
115 final LatLng point = this.mMarker.getPosition();
116 double latitude = point.latitude;
117 double longitude = point.longitude;
119 weatherLocation = new WeatherLocation()
121 .setCountry(countryString)
122 .setLatitude(latitude)
123 .setLongitude(longitude);
125 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
126 weatherLocation = query.queryDataBase();
129 if (weatherLocation != null) {
130 this.updateUI(weatherLocation);
135 * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
137 * {@link http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult}
140 public void onPostResume() {
141 super.onPostResume();
143 final FragmentManager fm = getSupportFragmentManager();
144 final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
145 if (progressFragment == null) {
146 this.addButtonsFragment();
148 this.removeProgressFragment();
149 final Bundle bundle = progressFragment.getArguments();
150 double latitude = bundle.getDouble("latitude");
151 double longitude = bundle.getDouble("longitude");
152 this.addProgressFragment(latitude, longitude);
157 public void onSaveInstanceState(final Bundle savedInstanceState) {
159 // Save Google Maps Marker
160 if (this.mMarker != null) {
161 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
162 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
163 final String cityString = city.getText().toString();
164 final String countryString = country.getText().toString();
166 final LatLng point = this.mMarker.getPosition();
167 double latitude = point.latitude;
168 double longitude = point.longitude;
170 final WeatherLocation location = new WeatherLocation()
172 .setCountry(countryString)
173 .setLatitude(latitude)
174 .setLongitude(longitude);
175 savedInstanceState.putSerializable("WeatherLocation", location);
178 super.onSaveInstanceState(savedInstanceState);
182 public void onPause() {
185 this.mLocationManager.removeUpdates(this);
188 public void onClickSaveLocation(final View v) {
189 if (this.mMarker != null) {
190 final LatLng position = this.mMarker.getPosition();
192 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
193 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
194 final String cityString = city.getText().toString();
195 final String countryString = country.getText().toString();
197 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
198 final WeatherLocation weatherLocation = query.queryDataBase();
199 if (weatherLocation != null) {
202 .setCountry(countryString)
203 .setLatitude(position.latitude)
204 .setLongitude(position.longitude)
205 .setLastCurrentUIUpdate(null)
206 .setLastForecastUIUpdate(null)
208 query.updateDataBase(weatherLocation);
210 final WeatherLocation location = new WeatherLocation()
212 .setCountry(countryString)
214 .setLatitude(position.latitude)
215 .setLongitude(position.longitude)
217 query.insertIntoDataBase(location);
222 public void onClickGetLocation(final View v) {
223 // TODO: Somehow I should show a progress dialog.
224 // If Google Play Services is available
225 if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
226 // TODO: Hopefully there will be results even if location did not change...
227 final Criteria criteria = new Criteria();
228 criteria.setAccuracy(Criteria.ACCURACY_FINE);
229 criteria.setAltitudeRequired(false);
230 criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
231 criteria.setBearingRequired(false);
232 criteria.setCostAllowed(false);
233 criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
234 criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
235 criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
236 criteria.setSpeedRequired(false);
237 criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
239 this.mLocationManager.requestSingleUpdate(criteria, this, null);
241 Toast.makeText(this, this.getString(R.string.weather_map_not_enabled_location), Toast.LENGTH_LONG).show();
243 // Trying to use the synchronous calls. Problems: mGoogleApiClient read/store from different threads.
244 // new GetLocationTask(this).execute();
247 private void updateUI(final WeatherLocation weatherLocation) {
249 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
250 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
251 city.setText(weatherLocation.getCity());
252 country.setText(weatherLocation.getCountry());
254 final LatLng point = new LatLng(weatherLocation.getLatitude(), weatherLocation.getLongitude());
255 if (this.mMarker != null) {
256 // Just one marker on map
257 this.mMarker.remove();
259 this.mMarker = this.mMap.addMarker(new MarkerOptions().position(point).draggable(true));
260 this.mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(point, 5));
261 this.mMap.animateCamera(CameraUpdateFactory.zoomIn());
262 this.mMap.animateCamera(CameraUpdateFactory.zoomTo(8), 2000, null);
265 private class MapActivityOnMapLongClickListener implements OnMapLongClickListener {
266 // Store the context passed to the AsyncTask when the system instantiates it.
267 private final Context localContext;
269 private MapActivityOnMapLongClickListener(final Context context) {
270 this.localContext = context;
274 public void onMapLongClick(final LatLng point) {
275 final MapActivity activity = (MapActivity) this.localContext;
276 activity.getAddressAndUpdateUI(point.latitude, point.longitude);
282 * Getting the address of the current location, using reverse geocoding only works if
283 * a geocoding service is available.
286 private void getAddressAndUpdateUI(final double latitude, final double longitude) {
287 // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available.
288 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent()) {
289 this.removeButtonsFragment();
290 this.removeProgressFragment();
291 this.addProgressFragment(latitude, longitude);
293 this.removeProgressFragment();
294 this.addButtonsFragment();
295 // No geocoder is present. Issue an error message.
296 Toast.makeText(this, this.getString(R.string.weather_map_no_geocoder_available), Toast.LENGTH_LONG).show();
299 final String city = this.getString(R.string.city_not_found);
300 final String country = this.getString(R.string.country_not_found);
301 final WeatherLocation weatherLocation = new WeatherLocation()
302 .setLatitude(latitude)
303 .setLongitude(longitude)
305 .setCountry(country);
307 updateUI(weatherLocation);
311 /*****************************************************************************************************
313 * MapProgressFragment.TaskCallbacks
315 *****************************************************************************************************/
317 public void onPostExecute(WeatherLocation weatherLocation) {
319 this.updateUI(weatherLocation);
320 this.removeProgressFragment();
322 this.addButtonsFragment();
325 /*****************************************************************************************************
327 * MapProgressFragment
328 * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
331 * "Avoid performing transactions inside asynchronous callback methods." :(
332 * see: http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult
333 * see: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
334 * How do you do what I am doing in a different way without using fragments?
335 *****************************************************************************************************/
337 private void addProgressFragment(final double latitude, final double longitude) {
338 final Fragment progressFragment = new MapProgressFragment();
339 progressFragment.setRetainInstance(true);
340 final Bundle args = new Bundle();
341 args.putDouble("latitude", latitude);
342 args.putDouble("longitude", longitude);
343 progressFragment.setArguments(args);
345 final FragmentManager fm = this.getSupportFragmentManager();
346 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
347 fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
348 fragmentTransaction.add(R.id.weather_map_buttons_container, progressFragment, PROGRESS_FRAGMENT_TAG).commit();
349 fm.executePendingTransactions();
352 private void removeProgressFragment() {
353 final FragmentManager fm = this.getSupportFragmentManager();
354 final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
355 if (progressFragment != null) {
356 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
357 fragmentTransaction.remove(progressFragment).commit();
358 fm.executePendingTransactions();
362 private void addButtonsFragment() {
363 final FragmentManager fm = this.getSupportFragmentManager();
364 Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
365 if (buttonsFragment == null) {
366 buttonsFragment = new MapButtonsFragment();
367 buttonsFragment.setRetainInstance(true);
368 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
369 fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
370 fragmentTransaction.add(R.id.weather_map_buttons_container, buttonsFragment, BUTTONS_FRAGMENT_TAG).commit();
371 fm.executePendingTransactions();
374 if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
375 final Button getLocationButton = (Button) this.findViewById(R.id.weather_map_button_getlocation);
376 getLocationButton.setEnabled(true);
380 private void removeButtonsFragment() {
381 final FragmentManager fm = this.getSupportFragmentManager();
382 final Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
383 if (buttonsFragment != null) {
384 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
385 fragmentTransaction.remove(buttonsFragment).commit();
386 fm.executePendingTransactions();
390 /*****************************************************************************************************
392 * android.location.LocationListener
394 *****************************************************************************************************/
397 public void onLocationChanged(final Location location) {
398 // It was called from onClickGetLocation (UI thread) This method will run in the same thread (the UI thread)
399 // so, I do no think there should be any problem.
401 // Display the current location in the UI
402 // TODO: May location not be null?
403 this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude());
407 public void onStatusChanged(String provider, int status, Bundle extras) {}
410 public void onProviderEnabled(String provider) {}
413 public void onProviderDisabled(String provider) {}