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 private Marker mMarker;
61 private LocationManager mLocationManager;
64 protected void onCreate(final Bundle savedInstanceState) {
65 super.onCreate(savedInstanceState);
66 this.setContentView(R.layout.weather_map);
68 // Acquire a reference to the system Location Manager
69 this.mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
71 // Google Play Services Map
72 final MapFragment mapFragment = (MapFragment) this.getFragmentManager()
73 .findFragmentById(R.id.weather_map_fragment_map);
74 this.mMap = mapFragment.getMap();
75 this.mMap.setMyLocationEnabled(false);
76 this.mMap.getUiSettings().setMyLocationButtonEnabled(false);
77 this.mMap.getUiSettings().setZoomControlsEnabled(true);
78 this.mMap.getUiSettings().setCompassEnabled(true);
79 this.mMap.setOnMapLongClickListener(new MapActivityOnMapLongClickListener(this));
83 protected void onRestoreInstanceState(final Bundle savedInstanceState) {
84 // Instead of restoring the state during onCreate() you may choose to
85 // implement onRestoreInstanceState(), which the system calls after the
86 // onStart() method. The system calls onRestoreInstanceState() only if
87 // there is a saved state to restore, so you do not need to check whether
88 // the Bundle is null:
89 super.onRestoreInstanceState(savedInstanceState);
92 this.mRestoreUI = (WeatherLocation) savedInstanceState.getSerializable("WeatherLocation");
96 public void onResume() {
99 final ActionBar actionBar = this.getActionBar();
100 actionBar.setTitle(this.getString(R.string.weather_map_mark_location));
102 WeatherLocation weatherLocation;
103 if (this.mRestoreUI != null) {
105 weatherLocation = this.mRestoreUI;
107 this.mRestoreUI = null;
108 } else if (this.mMarker != null ) {
109 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
110 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
111 final String cityString = city.getText().toString();
112 final String countryString = country.getText().toString();
114 final LatLng point = this.mMarker.getPosition();
115 double latitude = point.latitude;
116 double longitude = point.longitude;
118 weatherLocation = new WeatherLocation()
120 .setCountry(countryString)
121 .setLatitude(latitude)
122 .setLongitude(longitude);
124 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
125 weatherLocation = query.queryDataBase();
128 if (weatherLocation != null) {
129 this.updateUI(weatherLocation);
134 * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
136 * {@link http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult}
139 public void onPostResume() {
140 super.onPostResume();
142 final FragmentManager fm = getSupportFragmentManager();
143 final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
144 if (progressFragment == null) {
145 this.addButtonsFragment();
147 this.removeProgressFragment();
148 final Bundle bundle = progressFragment.getArguments();
149 double latitude = bundle.getDouble("latitude");
150 double longitude = bundle.getDouble("longitude");
151 this.addProgressFragment(latitude, longitude);
156 public void onSaveInstanceState(final Bundle savedInstanceState) {
158 // Save Google Maps Marker
159 if (this.mMarker != null) {
160 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
161 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
162 final String cityString = city.getText().toString();
163 final String countryString = country.getText().toString();
165 final LatLng point = this.mMarker.getPosition();
166 double latitude = point.latitude;
167 double longitude = point.longitude;
169 final WeatherLocation location = new WeatherLocation()
171 .setCountry(countryString)
172 .setLatitude(latitude)
173 .setLongitude(longitude);
174 savedInstanceState.putSerializable("WeatherLocation", location);
177 super.onSaveInstanceState(savedInstanceState);
181 public void onPause() {
184 this.mLocationManager.removeUpdates(this);
187 public void onClickSaveLocation(final View v) {
188 if (this.mMarker != null) {
189 final LatLng position = this.mMarker.getPosition();
191 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
192 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
193 final String cityString = city.getText().toString();
194 final String countryString = country.getText().toString();
196 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
197 final WeatherLocation weatherLocation = query.queryDataBase();
198 if (weatherLocation != null) {
201 .setCountry(countryString)
202 .setLatitude(position.latitude)
203 .setLongitude(position.longitude)
204 .setLastCurrentUIUpdate(null)
205 .setLastForecastUIUpdate(null)
207 query.updateDataBase(weatherLocation);
209 final WeatherLocation location = new WeatherLocation()
211 .setCountry(countryString)
213 .setLatitude(position.latitude)
214 .setLongitude(position.longitude)
216 query.insertIntoDataBase(location);
221 public void onClickGetLocation(final View v) {
222 // TODO: Somehow I should show a progress dialog.
223 // If Google Play Services is available
224 if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
225 // TODO: Hopefully there will be results even if location did not change...
226 final Criteria criteria = new Criteria();
227 criteria.setAccuracy(Criteria.ACCURACY_FINE);
228 criteria.setAltitudeRequired(false);
229 criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
230 criteria.setBearingRequired(false);
231 criteria.setCostAllowed(false);
232 criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
233 criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
234 criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
235 criteria.setSpeedRequired(false);
236 criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
238 this.mLocationManager.requestSingleUpdate(criteria, this, null);
240 Toast.makeText(this, this.getString(R.string.weather_map_not_enabled_location), Toast.LENGTH_LONG).show();
242 // Trying to use the synchronous calls. Problems: mGoogleApiClient read/store from different threads.
243 // new GetLocationTask(this).execute();
246 private void updateUI(final WeatherLocation weatherLocation) {
248 final TextView city = (TextView) this.findViewById(R.id.weather_map_city);
249 final TextView country = (TextView) this.findViewById(R.id.weather_map_country);
250 city.setText(weatherLocation.getCity());
251 country.setText(weatherLocation.getCountry());
253 final LatLng point = new LatLng(weatherLocation.getLatitude(), weatherLocation.getLongitude());
254 if (this.mMarker != null) {
255 // Just one marker on map
256 this.mMarker.remove();
258 this.mMarker = this.mMap.addMarker(new MarkerOptions().position(point).draggable(true));
259 this.mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(point, 5));
260 this.mMap.animateCamera(CameraUpdateFactory.zoomIn());
261 this.mMap.animateCamera(CameraUpdateFactory.zoomTo(8), 2000, null);
264 private class MapActivityOnMapLongClickListener implements OnMapLongClickListener {
265 // Store the context passed to the AsyncTask when the system instantiates it.
266 private final Context localContext;
268 private MapActivityOnMapLongClickListener(final Context context) {
269 this.localContext = context;
273 public void onMapLongClick(final LatLng point) {
274 final MapActivity activity = (MapActivity) this.localContext;
275 activity.getAddressAndUpdateUI(point.latitude, point.longitude);
281 * Getting the address of the current location, using reverse geocoding only works if
282 * a geocoding service is available.
285 private void getAddressAndUpdateUI(final double latitude, final double longitude) {
286 // In Gingerbread and later, use Geocoder.isPresent() to see if a geocoder is available.
287 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent()) {
288 this.removeButtonsFragment();
289 this.removeProgressFragment();
290 this.addProgressFragment(latitude, longitude);
292 this.removeProgressFragment();
293 this.addButtonsFragment();
294 // No geocoder is present. Issue an error message.
295 Toast.makeText(this, this.getString(R.string.weather_map_no_geocoder_available), Toast.LENGTH_LONG).show();
298 final String city = this.getString(R.string.city_not_found);
299 final String country = this.getString(R.string.country_not_found);
300 final WeatherLocation weatherLocation = new WeatherLocation()
301 .setLatitude(latitude)
302 .setLongitude(longitude)
304 .setCountry(country);
306 updateUI(weatherLocation);
310 /*****************************************************************************************************
312 * MapProgressFragment.TaskCallbacks
314 *****************************************************************************************************/
316 public void onPostExecute(WeatherLocation weatherLocation) {
318 this.updateUI(weatherLocation);
319 this.removeProgressFragment();
321 this.addButtonsFragment();
324 /*****************************************************************************************************
326 * MapProgressFragment
327 * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
330 * "Avoid performing transactions inside asynchronous callback methods." :(
331 * see: http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult
332 * see: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
333 * How do you do what I am doing in a different way without using fragments?
334 *****************************************************************************************************/
336 private void addProgressFragment(final double latitude, final double longitude) {
337 final Fragment progressFragment = new MapProgressFragment();
338 progressFragment.setRetainInstance(true);
339 final Bundle args = new Bundle();
340 args.putDouble("latitude", latitude);
341 args.putDouble("longitude", longitude);
342 progressFragment.setArguments(args);
344 final FragmentManager fm = this.getSupportFragmentManager();
345 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
346 fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
347 fragmentTransaction.add(R.id.weather_map_buttons_container, progressFragment, PROGRESS_FRAGMENT_TAG).commit();
348 fm.executePendingTransactions();
351 private void removeProgressFragment() {
352 final FragmentManager fm = this.getSupportFragmentManager();
353 final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
354 if (progressFragment != null) {
355 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
356 fragmentTransaction.remove(progressFragment).commit();
357 fm.executePendingTransactions();
361 private void addButtonsFragment() {
362 final FragmentManager fm = this.getSupportFragmentManager();
363 Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
364 if (buttonsFragment == null) {
365 buttonsFragment = new MapButtonsFragment();
366 buttonsFragment.setRetainInstance(true);
367 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
368 fragmentTransaction.setCustomAnimations(R.anim.weather_map_enter_progress, R.anim.weather_map_exit_progress);
369 fragmentTransaction.add(R.id.weather_map_buttons_container, buttonsFragment, BUTTONS_FRAGMENT_TAG).commit();
370 fm.executePendingTransactions();
373 if (this.mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
374 final Button getLocationButton = (Button) this.findViewById(R.id.weather_map_button_getlocation);
375 getLocationButton.setEnabled(true);
379 private void removeButtonsFragment() {
380 final FragmentManager fm = this.getSupportFragmentManager();
381 final Fragment buttonsFragment = fm.findFragmentByTag(BUTTONS_FRAGMENT_TAG);
382 if (buttonsFragment != null) {
383 final FragmentTransaction fragmentTransaction = fm.beginTransaction();
384 fragmentTransaction.remove(buttonsFragment).commit();
385 fm.executePendingTransactions();
389 /*****************************************************************************************************
391 * android.location.LocationListener
393 *****************************************************************************************************/
396 public void onLocationChanged(final Location location) {
397 // It was called from onClickGetLocation (UI thread) This method will run in the same thread (the UI thread)
399 // Display the current location in the UI
400 // TODO: May location not be null?
401 this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude());
405 public void onStatusChanged(String provider, int status, Bundle extras) {}
408 public void onProviderEnabled(String provider) {}
411 public void onProviderDisabled(String provider) {}