6f4ae7f13e90a75b23080b607bc5555d8537c377
[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.activity;
17
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;
35
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;
48
49
50 public class MapActivity extends FragmentActivity implements
51                                                                         LocationListener,
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;
56        
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;
61     
62     private LocationManager mLocationManager;
63
64     @Override
65     protected void onCreate(final Bundle savedInstanceState) {
66         super.onCreate(savedInstanceState);
67         this.setContentView(R.layout.weather_map);
68         
69         // Acquire a reference to the system Location Manager
70         this.mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
71         
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));
81     }
82     
83     @Override
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);
91         
92         // Restore UI state
93         this.mRestoreUI = (WeatherLocation) savedInstanceState.getSerializable("WeatherLocation");
94     }
95
96     @Override
97     public void onResume() {
98         super.onResume();
99
100         final ActionBar actionBar = this.getActionBar();
101         actionBar.setTitle(this.getString(R.string.weather_map_mark_location));
102         
103         WeatherLocation weatherLocation;
104         if (this.mRestoreUI != null) {
105                 // Restore UI state
106                 weatherLocation = this.mRestoreUI;
107                 // just once
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();
114             
115             final LatLng point = this.mMarker.getPosition();
116             double latitude = point.latitude;
117             double longitude = point.longitude;
118
119             weatherLocation = new WeatherLocation()
120                         .setCity(cityString)
121                         .setCountry(countryString)
122                         .setLatitude(latitude)
123                         .setLongitude(longitude);
124         } else {
125                 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
126                 weatherLocation = query.queryDataBase();
127         }
128         
129         if (weatherLocation != null) {
130                 this.updateUI(weatherLocation);
131         }
132     }
133     
134     /**
135      * I am not using fragment transactions in the right way. But I do not know other way for doing what I am doing.
136      * 
137      * {@link http://stackoverflow.com/questions/16265733/failure-delivering-result-onactivityforresult}
138      */
139     @Override
140     public void onPostResume() {
141         super.onPostResume();
142         
143         final FragmentManager fm = getSupportFragmentManager();
144         final Fragment progressFragment = fm.findFragmentByTag(PROGRESS_FRAGMENT_TAG);
145         if (progressFragment == null) {
146                  this.addButtonsFragment();
147         } else {
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);
153         }
154     }
155     
156     @Override
157     public void onSaveInstanceState(final Bundle savedInstanceState) {
158         // Save UI state
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();
165             
166             final LatLng point = this.mMarker.getPosition();
167             double latitude = point.latitude;
168             double longitude = point.longitude;
169
170             final WeatherLocation location = new WeatherLocation()
171                         .setCity(cityString)
172                         .setCountry(countryString)
173                         .setLatitude(latitude)
174                         .setLongitude(longitude);
175             savedInstanceState.putSerializable("WeatherLocation", location);
176         }
177                 
178         super.onSaveInstanceState(savedInstanceState);
179     }
180     
181         @Override
182         public void onPause() {
183                 super.onPause();
184                 
185                 this.mLocationManager.removeUpdates(this);
186         }
187         
188     public void onClickSaveLocation(final View v) {
189         if (this.mMarker != null) {
190                 final LatLng position = this.mMarker.getPosition();
191                 
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();
196             
197                 final DatabaseQueries query = new DatabaseQueries(this.getApplicationContext());
198                 final WeatherLocation weatherLocation = query.queryDataBase();
199             if (weatherLocation != null) {
200                 weatherLocation
201                 .setCity(cityString)
202                 .setCountry(countryString)
203                 .setLatitude(position.latitude)
204                 .setLongitude(position.longitude)
205                 .setLastCurrentUIUpdate(null)
206                 .setLastForecastUIUpdate(null)
207                 .setIsNew(true);
208                 query.updateDataBase(weatherLocation);
209             } else {
210                 final WeatherLocation location = new WeatherLocation()
211                         .setCity(cityString)
212                         .setCountry(countryString)
213                         .setIsSelected(true)
214                         .setLatitude(position.latitude)
215                         .setLongitude(position.longitude)
216                     .setIsNew(true);
217                 query.insertIntoDataBase(location);
218             }
219         }
220     }
221     
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);
238             
239             this.mLocationManager.requestSingleUpdate(criteria, this, null);
240         } else {
241                 Toast.makeText(this, this.getString(R.string.weather_map_not_enabled_location), Toast.LENGTH_LONG).show();
242         }
243         // Trying to use the synchronous calls. Problems: mGoogleApiClient read/store from different threads.
244         // new GetLocationTask(this).execute();
245     }
246     
247     private void updateUI(final WeatherLocation weatherLocation) {
248
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());
253
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();
258         }
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);
263     }
264     
265     private class MapActivityOnMapLongClickListener implements OnMapLongClickListener {
266         // Store the context passed to the AsyncTask when the system instantiates it.
267         private final Context localContext;
268         
269         private MapActivityOnMapLongClickListener(final Context context) {
270                 this.localContext = context;
271         }
272         
273                 @Override
274                 public void onMapLongClick(final LatLng point) {
275                         final MapActivity activity = (MapActivity) this.localContext;
276                         activity.getAddressAndUpdateUI(point.latitude, point.longitude);
277                 }
278         
279     }
280
281     /**
282      * Getting the address of the current location, using reverse geocoding only works if
283      * a geocoding service is available.
284      *
285      */
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);
292         } else {
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();
297             
298             // Default values
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)
304                         .setCity(city)
305                         .setCountry(country);
306             
307             updateUI(weatherLocation);
308         }
309     }
310
311         /*****************************************************************************************************
312          *
313          *                                                      MapProgressFragment.TaskCallbacks
314          *
315          *****************************************************************************************************/
316         @Override
317         public void onPostExecute(WeatherLocation weatherLocation) {
318
319         this.updateUI(weatherLocation);
320         this.removeProgressFragment();
321
322         this.addButtonsFragment();
323         }
324
325         /*****************************************************************************************************
326          *
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.
329      * Android sucks.
330      *
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          *****************************************************************************************************/
336         
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);
344         
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();
350         }
351         
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();
359         }
360         }
361         
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();
372         }
373         
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);
377         }
378         }
379         
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();
387         }
388         }
389
390    /*****************************************************************************************************
391     *
392     *                                                   android.location.LocationListener
393     *
394     *****************************************************************************************************/
395         
396         @Override
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.
400
401                 // Display the current location in the UI
402                 // TODO: May location not be null?
403                 this.getAddressAndUpdateUI(location.getLatitude(), location.getLongitude());
404         }
405         
406         @Override
407         public void onStatusChanged(String provider, int status, Bundle extras) {}
408
409         @Override
410         public void onProviderEnabled(String provider) {}
411
412         @Override
413         public void onProviderDisabled(String provider) {}
414 }