package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.location.Geopoint; import cgeo.geocaching.location.Units; import cgeo.geocaching.log.LoggingUI; import cgeo.geocaching.maps.DefaultMap; import cgeo.geocaching.models.Geocache; import cgeo.geocaching.models.Waypoint; import cgeo.geocaching.sensors.GeoData; import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.GpsStatusProvider.Status; import cgeo.geocaching.sensors.Sensors; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.speech.SpeechService; import cgeo.geocaching.storage.DataStore; import cgeo.geocaching.ui.CompassView; import cgeo.geocaching.ui.WaypointSelectionActionProvider; import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.media.AudioManager; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; import android.widget.ToggleButton; import butterknife.BindView; import butterknife.ButterKnife; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.functions.Consumer; import org.apache.commons.lang3.StringUtils; public class CompassActivity extends AbstractActionBarActivity { @BindView(R.id.nav_type) protected TextView navType; @BindView(R.id.nav_accuracy) protected TextView navAccuracy; @BindView(R.id.nav_satellites) protected TextView navSatellites; @BindView(R.id.nav_location) protected TextView navLocation; @BindView(R.id.distance) protected TextView distanceView; @BindView(R.id.heading) protected TextView headingView; @BindView(R.id.rose) protected CompassView compassView; @BindView(R.id.destination) protected TextView destinationTextView; @BindView(R.id.cacheinfo) protected TextView cacheInfoView; @BindView(R.id.use_compass) protected ToggleButton useCompassSwitch; /** * Destination cache, may be null */ private Geocache cache = null; /** * Destination waypoint, may be null */ private Waypoint waypoint = null; private Geopoint dstCoords = null; private float cacheHeading = 0; private String description; @Override public void onCreate(final Bundle savedInstanceState) { onCreate(savedInstanceState, R.layout.compass_activity); ButterKnife.bind(this); // get parameters final Bundle extras = getIntent().getExtras(); if (extras == null) { finish(); return; } // cache must exist, except for "any point navigation" final String geocode = extras.getString(Intents.EXTRA_GEOCODE); if (geocode != null) { cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); } // find the wanted navigation target if (extras.containsKey(Intents.EXTRA_WAYPOINT_ID)) { final int waypointId = extras.getInt(Intents.EXTRA_WAYPOINT_ID); final Waypoint waypoint = DataStore.loadWaypoint(waypointId); if (waypoint != null) { setTarget(waypoint); } } else if (extras.containsKey(Intents.EXTRA_COORDS)) { setTarget(extras.<Geopoint> getParcelable(Intents.EXTRA_COORDS), extras.getString(Intents.EXTRA_COORD_DESCRIPTION)); } else if (cache != null) { setTarget(cache); } else { Log.w("CompassActivity.onCreate: no cache was found for geocode " + geocode); finish(); } // set activity title just once, independent of what target is switched to if (cache != null) { setCacheTitleBar(cache); } else { setTitle(StringUtils.defaultIfBlank(extras.getString(Intents.EXTRA_NAME), res.getString(R.string.navigation))); } if (Sensors.getInstance().hasCompassCapabilities()) { useCompassSwitch.setOnClickListener(new OnClickListener() { @Override public void onClick(final View view) { Settings.setUseCompass(((ToggleButton) view).isChecked()); } }); useCompassSwitch.setVisibility(View.VISIBLE); } else { useCompassSwitch.setVisibility(View.GONE); } // make sure we can control the TTS volume setVolumeControlStream(AudioManager.STREAM_MUSIC); } @Override public void onResume() { onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODIR), Sensors.getInstance().gpsStatusObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(gpsStatusHandler)); forceRefresh(); } @Override public void onDestroy() { compassView.destroyDrawingCache(); SpeechService.stopService(this); super.onDestroy(); } @Override public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); setContentView(R.layout.compass_activity); ButterKnife.bind(this); setTarget(dstCoords, description); forceRefresh(); } private void forceRefresh() { // Force a refresh of location and direction when data is available. final Sensors sensors = Sensors.getInstance(); // reset the visibility of the compass toggle button if the device does not support it. if (sensors.hasCompassCapabilities()) { useCompassSwitch.setChecked(Settings.isUseCompass()); } else { useCompassSwitch.setVisibility(View.GONE); } geoDirHandler.updateGeoDir(sensors.currentGeo(), sensors.currentDirection()); } @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.compass_activity_options, menu); if (cache != null) { LoggingUI.addMenuItems(this, menu, cache); initializeTargetActionProvider(menu); } return true; } private void initializeTargetActionProvider(final Menu menu) { final MenuItem destinationMenu = menu.findItem(R.id.menu_select_destination); WaypointSelectionActionProvider.initialize(destinationMenu, cache, new WaypointSelectionActionProvider.Callback() { @Override public void onWaypointSelected(final Waypoint waypoint) { setTarget(waypoint); } @Override public void onGeocacheSelected(final Geocache geocache) { setTarget(geocache); } }); } @Override public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); menu.findItem(R.id.menu_tts_start).setVisible(!SpeechService.isRunning()); menu.findItem(R.id.menu_tts_stop).setVisible(SpeechService.isRunning()); menu.findItem(R.id.menu_hint).setVisible(cache != null); return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); switch (id) { case R.id.menu_map: if (waypoint != null) { DefaultMap.startActivityCoords(this, waypoint.getCoords(), waypoint.getWaypointType(), waypoint.getName()); } else if (cache != null) { DefaultMap.startActivityGeoCode(this, cache.getGeocode()); } else { DefaultMap.startActivityCoords(this, dstCoords, null, null); } return true; case R.id.menu_tts_start: SpeechService.startService(this, dstCoords); invalidateOptionsMenuCompatible(); return true; case R.id.menu_tts_stop: SpeechService.stopService(this); invalidateOptionsMenuCompatible(); return true; case R.id.menu_hint: cache.showHintToast(this); return true; default: if (LoggingUI.onMenuItemSelected(item, this, cache, null)) { return true; } } return super.onOptionsItemSelected(item); } private void setTarget(@NonNull final Geopoint coords, final String newDescription) { setDestCoords(coords); setTargetDescription(newDescription); updateDistanceInfo(Sensors.getInstance().currentGeo()); Log.d("destination set: " + newDescription + " (" + dstCoords + ")"); } private void setTarget(@NonNull final Waypoint waypointIn) { waypoint = waypointIn; final Geopoint coordinates = waypointIn.getCoords(); if (coordinates != null) { // handled by WaypointSelectionActionProvider, but the compiler doesn't know setTarget(coordinates, waypointIn.getName()); } } private void setTarget(@NonNull final Geocache cache) { setTarget(cache.getCoords(), Formatter.formatCacheInfoShort(cache)); } private void setDestCoords(final Geopoint coords) { dstCoords = coords; if (dstCoords == null) { return; } destinationTextView.setText(dstCoords.toString()); } private void setTargetDescription(@Nullable final String newDescription) { description = newDescription; if (description == null) { cacheInfoView.setVisibility(View.GONE); return; } cacheInfoView.setVisibility(View.VISIBLE); cacheInfoView.setText(description); } private void updateDistanceInfo(final GeoData geo) { if (dstCoords == null) { return; } cacheHeading = geo.getCoords().bearingTo(dstCoords); distanceView.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(dstCoords))); headingView.setText(Math.round(cacheHeading) + "°"); } private final Consumer<Status> gpsStatusHandler = new Consumer<Status>() { @Override public void accept(final Status gpsStatus) { if (gpsStatus.satellitesVisible >= 0) { navSatellites.setText(res.getString(R.string.loc_sat) + ": " + gpsStatus.satellitesFixed + "/" + gpsStatus.satellitesVisible); } else { navSatellites.setText(""); } } }; private final GeoDirHandler geoDirHandler = new GeoDirHandler() { @Override public void updateGeoDir(@NonNull final GeoData geo, final float dir) { try { navType.setText(res.getString(geo.getLocationProvider().resourceId)); if (geo.getAccuracy() >= 0) { navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy())); } else { navAccuracy.setText(null); } navLocation.setText(geo.getCoords().toString()); updateDistanceInfo(geo); updateNorthHeading(AngleUtils.getDirectionNow(dir)); } catch (final RuntimeException e) { Log.w("Failed to update location", e); } } }; private void updateNorthHeading(final float northHeading) { if (compassView != null) { compassView.updateNorth(northHeading, cacheHeading); } } public static void startActivityWaypoint(final Context context, final Waypoint waypoint) { final Intent navigateIntent = new Intent(context, CompassActivity.class); navigateIntent.putExtra(Intents.EXTRA_GEOCODE, waypoint.getGeocode()); navigateIntent.putExtra(Intents.EXTRA_WAYPOINT_ID, waypoint.getId()); context.startActivity(navigateIntent); } public static void startActivityPoint(final Context context, final Geopoint coords, final String displayedName) { final Intent navigateIntent = new Intent(context, CompassActivity.class); navigateIntent.putExtra(Intents.EXTRA_COORDS, coords); navigateIntent.putExtra(Intents.EXTRA_NAME, displayedName); context.startActivity(navigateIntent); } public static void startActivityCache(final Context context, final Geocache cache) { final Intent navigateIntent = new Intent(context, CompassActivity.class); navigateIntent.putExtra(Intents.EXTRA_GEOCODE, cache.getGeocode()); context.startActivity(navigateIntent); } }