// Copyright 2008 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.android.stardroid.control; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.DialogInterface.OnClickListener; import android.content.SharedPreferences.Editor; import android.location.Address; import android.location.Criteria; import android.location.Geocoder; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.Settings; import android.util.Log; import android.widget.Toast; import com.google.android.stardroid.R; import com.google.android.stardroid.units.LatLong; import com.google.android.stardroid.util.MiscUtil; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Sets the AstronomerModel's (and thus the user's) position using one of the * network, GPS or user-set preferences. * * @author John Taylor */ public class LocationController extends AbstractController implements LocationListener { // Must match the key in the preferences file. private static final String NO_AUTO_LOCATE = "no_auto_locate"; // Must match the key in the preferences file. private static final String FORCE_GPS = "force_gps"; private static final int MINIMUM_DISTANCE_BEFORE_UPDATE_METRES = 2000; private static final int LOCATION_UPDATE_TIME_MILLISECONDS = 600000; private static final String TAG = MiscUtil.getTag(LocationController.class); private static final float MIN_DIST_TO_SHOW_TOAST_DEGS = 0.01f; private Context context; private LocationManager locationManager; public LocationController(Context context) { this.context = context; locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); if (locationManager != null) { Log.d(TAG, "Got location Manager"); } else { Log.d(TAG, "Didn't get location manager"); } } @Override public void start() { Log.d(TAG, "LocationController start"); boolean noAutoLocate = PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NO_AUTO_LOCATE, false); boolean forceGps = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(FORCE_GPS, false); if (noAutoLocate) { Log.d(TAG, "User has elected to set location manually."); setLocationFromPrefs(); Log.d(TAG, "LocationController -start"); return; } if (locationManager == null) { // TODO(johntaylor): find out under what circumstances this can happen. Log.e(TAG, "Location manager was null - using preferences"); setLocationFromPrefs(); return; } Criteria locationCriteria = new Criteria(); locationCriteria.setAccuracy(forceGps ? Criteria.ACCURACY_FINE : Criteria.ACCURACY_COARSE); locationCriteria.setAltitudeRequired(false); locationCriteria.setBearingRequired(false); locationCriteria.setCostAllowed(true); locationCriteria.setSpeedRequired(false); locationCriteria.setPowerRequirement(Criteria.POWER_LOW); String locationProvider = locationManager.getBestProvider(locationCriteria, true); if (locationProvider == null) { Log.w(TAG, "No location provider is enabled"); String possiblelocationProvider = locationManager.getBestProvider(locationCriteria, false); if (possiblelocationProvider == null) { // TODO(johntaylor): should we make this a dialog? Toast.makeText(context, R.string.location_no_auto, Toast.LENGTH_LONG).show(); setLocationFromPrefs(); return; } AlertDialog.Builder alertDialog = getSwitchOnGPSDialog(); alertDialog.show(); return; } else { Log.d(TAG, "Got location provider " + locationProvider); } locationManager.requestLocationUpdates(locationProvider, LOCATION_UPDATE_TIME_MILLISECONDS, MINIMUM_DISTANCE_BEFORE_UPDATE_METRES, this); Location location = locationManager.getLastKnownLocation(locationProvider); if (location != null) { LatLong myLocation = new LatLong(location.getLatitude(), location.getLongitude()); setLocationInModel(myLocation, location.getProvider()); } Log.d(TAG, "LocationController -start"); } private void setLocationInModel(LatLong location, String provider) { LatLong oldLocation = model.getLocation(); if (location.distanceFrom(oldLocation) > MIN_DIST_TO_SHOW_TOAST_DEGS) { Log.d(TAG, "Informing user of change of location"); showLocationToUser(location, provider); } else { Log.d(TAG, "Location not changed sufficiently to tell the user"); } model.setLocation(location); } private Builder getSwitchOnGPSDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(context); dialog.setTitle(R.string.location_offer_to_enable_gps_title); dialog.setMessage(R.string.location_offer_to_enable); dialog.setPositiveButton(android.R.string.yes, new OnClickListener() { public void onClick(DialogInterface dialog, int which) { Log.d(TAG, "Sending to editor location prefs page"); Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); context.startActivity(intent); } }); dialog.setNegativeButton(android.R.string.no, new OnClickListener() { public void onClick(DialogInterface dialog, int which) { Log.d(TAG, "User doesn't want to enable location."); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); Editor editor = prefs.edit(); editor.putBoolean(NO_AUTO_LOCATE, true); editor.commit(); setLocationFromPrefs(); } }); return dialog; } private void setLocationFromPrefs() { String longitude_s = PreferenceManager.getDefaultSharedPreferences(context) .getString("longitude", ""); String latitude_s = PreferenceManager.getDefaultSharedPreferences(context) .getString("latitude", ""); float longitude = -80, latitude = 40; try { longitude = Float.parseFloat(longitude_s); latitude = Float.parseFloat(latitude_s); } catch (NumberFormatException nfe) { Log.e(TAG, "Error parsing latitude or longitude preference"); Toast.makeText(context, R.string.malformed_loc_error, Toast.LENGTH_SHORT).show(); } Location location = new Location(context.getString(R.string.preferences)); location.setLatitude(latitude); location.setLongitude(longitude); Log.d(TAG, "Latitude " + longitude); Log.d(TAG, "Longitude " + latitude); LatLong myPosition = new LatLong(latitude, longitude); setLocationInModel(myPosition, context.getString(R.string.preferences)); } @Override public void stop() { Log.d(TAG, "LocationController stop"); if (locationManager == null) { return; } locationManager.removeUpdates(this); Log.d(TAG, "LocationController -stop"); } @Override public void onLocationChanged(Location location) { Log.d(TAG, "LocationController onLocationChanged"); if (location == null) { Log.e(TAG, "Didn't get location even though onLocationChanged called"); setLocationFromPrefs(); return; } LatLong newLocation = new LatLong(location.getLatitude(), location.getLongitude()); Log.d(TAG, "Latitude " + newLocation.latitude); Log.d(TAG, "Longitude " + newLocation.longitude); setLocationInModel(newLocation, location.getProvider()); // Only need get the location once. locationManager.removeUpdates(this); Log.d(TAG, "LocationController -onLocationChanged"); } private void showLocationToUser(LatLong location, String provider) { // TODO(johntaylor): move this notification to a separate thread) Log.d(TAG, "Reverse geocoding location"); Geocoder geoCoder = new Geocoder(context); List<Address> addresses = new ArrayList<Address>(); String place = "Unknown"; try { addresses = geoCoder.getFromLocation(location.latitude, location.longitude, 1); } catch (IOException e) { Log.e(TAG, "Unable to reverse geocode location " + location); } if (addresses == null || addresses.size() == 0) { Log.d(TAG, "No addresses returned"); place = String.format(context.getString(R.string.location_long_lat), location.longitude, location.latitude); } else { place = getSummaryOfPlace(location, addresses.get(0)); } Log.d(TAG, "Location set to " + place); String messageTemplate = context.getString(R.string.location_set_auto); String message = String.format(messageTemplate, provider, place); Toast.makeText(context, message, Toast.LENGTH_LONG).show(); } private String getSummaryOfPlace(LatLong location, Address address) { String template = context.getString(R.string.location_long_lat); String longLat = String.format(template, location.longitude, location.latitude); if (address == null) { return longLat; } String place = null; place = address.getLocality(); if (place == null) { place = address.getSubAdminArea(); } if (place == null) { place = address.getAdminArea(); } if (place == null) { place = longLat; } return place; } @Override public void onProviderDisabled(String provider) { // No action. } @Override public void onProviderEnabled(String provider) { // No action. } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // No action. } }