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