/* * FindNearestLocationActionBarActivity.java * Last modified on 03-25-2014 14:52-0400 by brianhmayo * * Copyright (c) 2014 SEPTA. All rights reserved. */ package org.septa.android.app.activities; import android.content.DialogInterface; import android.content.IntentSender; import android.location.Location; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.location.LocationClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; import org.septa.android.app.R; import org.septa.android.app.dialogs.FindNearestLocationEditRadiusDialog; import org.septa.android.app.fragments.FindNearestLocationsListFragment; import org.septa.android.app.fragments.FindNearestLocationsListFragment.OnRetryLocationSearchListener; import org.septa.android.app.managers.SharedPreferencesManager; import org.septa.android.app.models.LocationModel; import org.septa.android.app.services.apiproxies.LocationServiceProxy; import java.util.ArrayList; import java.util.List; import retrofit.Callback; import retrofit.RetrofitError; import retrofit.client.Response; public class FindNearestLocationActionBarActivity extends BaseAnalyticsActionBarActivity implements LocationListener, OnRetryLocationSearchListener, GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener { public static final String TAG = FindNearestLocationActionBarActivity.class.getName(); //State Keys public static final String STATE_CURRENT_LOCATION = "currentLocation"; public static final int UPDATE_INTERVAL_IN_SECONDS = 120; private static final int MILLISECONDS_PER_SECOND = 1000; private static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS; private static final int FASTEST_INTERVAL_IN_SECONDS = 60; private static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS; private final int RQS_GooglePlayServices = 1; private LocationClient mLocationClient; private boolean inChangeRadiusMode = false; private float defaultZoom; private float maxDistanceFromCityCenter; //if new locations exceed maximum we return to city center instead private float maxDistancePerStep; //max distance to be traveled between list reloads. private GoogleMap mMap; private FindNearestLocationEditRadiusDialog findNearestLocationEditRadiusDialog; private int locationServiceCalls = 0; private float mapSearchRadius; private FindNearestLocationsListFragment mListFragment; private List<LocationModel> mLocationList; private Location defaultLocation; private Location currentLocation; private Location lastListedLocation; //the last location from which we loaded the list. This presents unnecessary UI flicker. private float getMapSearchRadius() { return mapSearchRadius; } private void setMapSearchRadius(float mapSearchRadius) { this.mapSearchRadius = mapSearchRadius; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String titleText = getIntent().getStringExtra(getString(R.string.actionbar_titletext_key)); setContentView(R.layout.findnearestlocation); mListFragment = (FindNearestLocationsListFragment) getSupportFragmentManager(). findFragmentById(R.id.nearestLocationListFragment); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setIcon(R.drawable.ic_actionbar_findnearestlocation); getSupportActionBar().setTitle(titleText); mapSearchRadius = SharedPreferencesManager.getInstance().getNearestLocationMapSearchRadius(); mMap = ((SupportMapFragment) getSupportFragmentManager(). findFragmentById(R.id.nearestLocationMapFragment)). getMap(); mLocationList = new ArrayList<LocationModel>(); // set the initial center point of the map on Center City, Philadelphia with a default zoom double defaultLatitude = Double.parseDouble(getResources().getString(R.string.generalmap_default_location_latitude)); double defaultLongitude = Double.parseDouble(getResources().getString(R.string.generalmap_default_location_longitude)); maxDistanceFromCityCenter = Float.parseFloat(getResources().getString(R.string.generalmap_max_distance_from_center)); maxDistancePerStep = Float.parseFloat(getResources().getString(R.string.generalmap_max_distance_per_step)); defaultZoom = Float.parseFloat(getResources().getString(R.string.findnearestlocation_map_zoom_level_float)); defaultLocation = new Location("default"); defaultLocation.setLatitude(defaultLatitude); defaultLocation.setLongitude(defaultLongitude); mMap.setMyLocationEnabled(true); moveMap(defaultLocation, false); mLocationClient = new LocationClient(this, this, this); if(savedInstanceState != null){ currentLocation = (Location)savedInstanceState.get(STATE_CURRENT_LOCATION); } if(currentLocation != null){ moveMapAndLoadList(currentLocation, false); } } @Override public boolean onCreateOptionsMenu(Menu menu) { Log.d(TAG, "creating the menu in find nearest location actionbar activity"); // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.find_nearest_location_action_bar, menu); menu.findItem(R.id.actionmenu_findnearestlocationactionbar_changeradius).setVisible(!inChangeRadiusMode); menu.findItem(R.id.actionmenu_findnearestlocationactionbar_changeradius_done).setVisible(inChangeRadiusMode); return true; } @Override protected void onStart() { super.onStart(); if(mLocationClient != null) mLocationClient.connect(); } @Override protected void onStop() { super.onStop(); if(mLocationClient != null) mLocationClient.disconnect(); } @Override public boolean onPrepareOptionsMenu(Menu menu) { Log.d(TAG, "onPrepareOptionsMenu"); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.actionmenu_findnearestlocationactionbar_changeradius: inChangeRadiusMode = true; ActivityCompat.invalidateOptionsMenu(this); findNearestLocationEditRadiusDialog = new FindNearestLocationEditRadiusDialog(this, SharedPreferencesManager.getInstance().getNearestLocationMapSearchRadius()); findNearestLocationEditRadiusDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { inChangeRadiusMode = false; ActivityCompat.invalidateOptionsMenu(FindNearestLocationActionBarActivity.this); float radius = findNearestLocationEditRadiusDialog.getMapSearchRadius(); if (getMapSearchRadius() != radius) { setMapSearchRadius(radius); SharedPreferencesManager.getInstance().setNearestLocationMapSearchRadius(radius); loadMapAndListView(currentLocation, radius); } } }); findNearestLocationEditRadiusDialog.show(); return true; case R.id.actionmenu_findnearestlocationactionbar_changeradius_done: findNearestLocationEditRadiusDialog.dismiss(); inChangeRadiusMode = false; ActivityCompat.invalidateOptionsMenu(this); return true; default: return super.onOptionsItemSelected(item); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(STATE_CURRENT_LOCATION, currentLocation); outState.putParcelable(STATE_CURRENT_LOCATION, currentLocation); } private void loadMapAndListView(Location newLocation, float mapSearchRadius) { double longitude = newLocation.getLongitude(); double latitude = newLocation.getLatitude(); if (mMap != null) { mMap.clear(); } lastListedLocation = newLocation; mLocationList.clear(); mListFragment.clearLocationLists(); locationServiceCalls += 3; LocationServiceProxy busStopsLocationServiceProxy = new LocationServiceProxy(); busStopsLocationServiceProxy.getLocation(longitude, latitude, mapSearchRadius, "bus_stops", new RouteFetchCallback()); LocationServiceProxy railStopsLocationServiceProxy = new LocationServiceProxy(); railStopsLocationServiceProxy.getLocation(longitude, latitude, mapSearchRadius, "rail_stations", new RouteFetchCallback()); LocationServiceProxy trolleyStopsLocationServiceProxy = new LocationServiceProxy(); trolleyStopsLocationServiceProxy.getLocation(longitude, latitude, mapSearchRadius, "trolley_stops", new RouteFetchCallback()); } private void moveMapAndLoadList(Location location, boolean animated){ moveMap(location, animated); loadMapAndListView(location, SharedPreferencesManager.getInstance().getNearestLocationMapSearchRadius()); } private void moveMap(Location location, boolean animate){ CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(new LatLng(location.getLatitude(), location.getLongitude()), defaultZoom); if(animate){ mMap.animateCamera(cameraUpdate); } else{ mMap.moveCamera(cameraUpdate); } } @Override public void onLocationChanged(Location newLocation) { float distanceFromCenter = defaultLocation.distanceTo(newLocation); if (newLocation.getAccuracy()< getResources().getInteger(R.integer.findnearestlocation_map_accuracy_limit_in_meters) && distanceFromCenter < maxDistanceFromCityCenter) { this.currentLocation = newLocation; } else { //too far away. no need for more updates. mLocationClient.disconnect(); this.currentLocation = defaultLocation; } /* * We do not want to refresh the map on every location change but we do * want to keep track of our newest location. Once the user travels past the * max step we reload the list. Otherwise we just move the map. */ if(lastListedLocation == null || lastListedLocation.distanceTo(currentLocation) > maxDistancePerStep ){ moveMapAndLoadList(currentLocation, true); } else { moveMap(currentLocation, true); } } /* * Called by LocationModel Services when the request to connect the * client finishes successfully. At this point, you can * request the current location or start periodic updates */ @Override public void onConnected(Bundle dataBundle) { // Display the connection status LocationRequest mLocationRequest = LocationRequest.create(); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); mLocationRequest.setInterval(UPDATE_INTERVAL); mLocationRequest.setFastestInterval(FASTEST_INTERVAL); mLocationClient.requestLocationUpdates(mLocationRequest, this); } /* * Called by LocationModel Services if the connection to the * location client drops because of an error. */ @Override public void onDisconnected() { // Display the connection status Log.d(TAG, "location services disconnected."); } /* * Called by LocationModel Services if the attempt to * LocationModel Services fails. */ @Override public void onConnectionFailed(ConnectionResult connectionResult) { /* * Google Play services can resolve some errors it detects. * If the error has a resolution, try sending an Intent to * start a Google Play services activity that can resolve * error. */ if (connectionResult.hasResolution()) { try { // Start an Activity that tries to resolve the error connectionResult.startResolutionForResult( this, 9000); /* * Thrown if Google Play services canceled the original * PendingIntent */ } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } else { mLocationClient.disconnect(); currentLocation = defaultLocation; moveMapAndLoadList(currentLocation, false); } } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext()); if (resultCode != ConnectionResult.SUCCESS){ GooglePlayServicesUtil.getErrorDialog(resultCode, this, RQS_GooglePlayServices); } } private void updateDisplay(){ mListFragment.setLocationList(mLocationList); for (LocationModel location : mLocationList) { // check to make sure that mMap is not null if (mMap != null) { mMap.addMarker(new MarkerOptions() .position(new LatLng(location.getLocationLatitude(), location.getLocationLongitude())) .title(location.getLocationName()) .snippet("Route: ")); } } } public void onRetryLocationSearch() { updateDisplay(); } private class RouteFetchCallback implements Callback{ @Override public void success(Object o, Response response) { mLocationList.addAll((ArrayList<LocationModel>) o); if (--locationServiceCalls < 1) { setProgressBarIndeterminateVisibility(Boolean.FALSE); updateDisplay(); } } @Override public void failure(RetrofitError retrofitError) { if (--locationServiceCalls < 1) { setProgressBarIndeterminateVisibility(Boolean.FALSE); updateDisplay(); } } } }