/* Copyright (C) 2014,2015 Björn Stelter, Hagen Sparka
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package de.hu_berlin.informatik.spws2014.mapever.navigation;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.TriangleImagePositionLocator;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.GpsPoint;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.ILDMIOHandler;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.ImagePositionLocator;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.IPLSettingsContainer;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.LDMIOEmpty;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.LeastSquaresImagePositionLocator;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.LocationDataManager;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.Marker;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.NoGpsDataAvailableException;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.Point2D;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.PointNotInImageBoundsException;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.TrackDB;
import de.hu_berlin.informatik.spws2014.ImagePositionLocator.TrackDBEntry;
import de.hu_berlin.informatik.spws2014.mapever.BaseActivity;
import de.hu_berlin.informatik.spws2014.mapever.MapEverApp;
import de.hu_berlin.informatik.spws2014.mapever.R;
import de.hu_berlin.informatik.spws2014.mapever.Start;
import de.hu_berlin.informatik.spws2014.mapever.Thumbnail;
public class Navigation extends BaseActivity implements LocationListener {
// ////// KEYS F�R ZU SPEICHERNDE DATEN IM SAVEDINSTANCESTATE-BUNDLE
// Key f�r ID der geladenen Karte (long)
private static final String SAVEDCURRENTMAPID = "savedCurrentMapID";
// Key f�r UserPosition (Point2D)
private static final String SAVEDUSERPOS = "savedUserPosition";
// Key f�r den Zustand der Navigation (NavigationState)
private static final String SAVEDSTATE = "savedState";
// Key f�r den Zustand der Hilfe (boolean)
private static final String SAVEDHELPSTATE = "savedHelpState";
// Key f�r den Zustand der Positionszentrierungsfunktion (boolean)
private static final String SAVEDTRACKPOSITION = "savedTrackPosition";
// ////// STATIC VARIABLES AND CONSTANTS
/**
* INTENT_LOADMAPID: ID der zu ladenden Map als long
*/
public static final String INTENT_LOADMAPID = "de.hu_berlin.informatik.spws2014.mapever.navigation.Navigation.LoadMapID";
public static final String INTENT_POS = "de.hu_berlin.informatik.spws2014.mapever.navigation.Navigation.IntentPos";
// ////// VIEWS
// unsere Karte
private MapView mapView;
// der Button zum Setzen des Referenzpunkts
private ImageButton setRefPointButton;
// der Button zum Setzen des Referenzpunkts (Akzeptieren)
private ImageButton acceptRefPointButton;
// der Button zum Setzen des Referenzpunkts (Abbrechen)
private ImageButton cancelRefPointButton;
// der Button zum L�schen eines Referenzpunkts
private ImageButton deleteRefPointButton;
// Button um Position zu verfolgen
private ImageButton trackPositionButton;
// Liste aller ImageButtons
private ArrayList<ImageButton> imageButtonList = new ArrayList<ImageButton>();
// ////// KARTEN- UND NAVIGATIONSINFORMATIONEN
// ID der aktuell geladenen Karte (zugleich Dateiname des Kartenbildes)
private long currentMapID;
// Position des Benutzers auf der Karte in Pixeln
private Point2D userPosition = null;
private double[] intentPos = null;
// Soll die aktuelle Position verfolgt (= zentriert) werden?
private boolean trackPosition = false;
// ////// LOKALISIERUNG
// GPS-Lokalisierung
private LocationManager locationManager;
// Lokalisierungsalgorithmus
private LocationDataManager locationDataManager;
// LocationDataManagerListener, der das Eintreffen neuer Positionen handled
private LocationDataManagerListener locDatManListener;
// Debug-GPS-Mocker
private Toast mockStatusToast = null;
private Location mockBaseLocation = null;
// Umbenennen der Karte
private String newMapName = "";
// TODO bitte das hier drunter alles mal oben einsortieren
// soll der Zustand gespeichert werden?
private boolean saveState = true;
// der aktuelle Zustand der Navigation, der Anfangszustand ist RUNNING
public NavigationStates state = NavigationStates.RUNNING;
// "SuperLayout", in welches die anderen Layouts eingebunden werden, erm�glicht einfache Umsetzung von Overlays
private FrameLayout layoutFrame;
// XXX besser L�sen per Zustands�bergang
private boolean quickTutorial = false;
// unser Men�
private Menu menu;
// die Anbindung an die Datenbank
private ILDMIOHandler iLDMIOHandler;
TrackDBEntry thisMap;
// ////////////////////////////////////////////////////////////////////////
// //////////// ACTIVITY LIFECYCLE UND INITIALISIERUNG
// ////////////////////////////////////////////////////////////////////////
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Wenn die Activity in Folge einer Rotation neu erzeugt wird, gibt es einen gespeicherten Zustand in
// savedInstanceState, sonst normal starten.
Log.d("Navigation", "onCreate..." + (savedInstanceState != null ? " with savedInstanceState" : ""));
// ////// LAYOUT UND VIEWS
// Layout aufbauen
layoutFrame = new FrameLayout(getBaseContext());
setContentView(layoutFrame);
getLayoutInflater().inflate(R.layout.activity_navigation, layoutFrame);
// Initialisieren der ben�tigten Komponenten
mapView = (MapView) findViewById(R.id.map);
mapView.setFocusable(true);
mapView.setFocusableInTouchMode(true);
// Initialisieren der Buttons & Eintragen in die Liste
setRefPointButton = (ImageButton) findViewById(R.id.set_refpoint);
imageButtonList.add(setRefPointButton);
acceptRefPointButton = (ImageButton) findViewById(R.id.accept_refpoint);
imageButtonList.add(acceptRefPointButton);
cancelRefPointButton = (ImageButton) findViewById(R.id.cancel_refpoint);
imageButtonList.add(cancelRefPointButton);
deleteRefPointButton = (ImageButton) findViewById(R.id.delete_refpoint);
imageButtonList.add(deleteRefPointButton);
trackPositionButton = (ImageButton) findViewById(R.id.track_position);
// Nicht den trackPositionButton in der Liste speichern, da dieser unabh�ngig vom Zustand gezeigt werden soll
// imageButtonList.add(trackPositionButton);
// Im Zweifelsfall kennen wir den Kartennamen nicht (sonst wird der aus der Datenbank geladen)
setTitle(getString(R.string.navigation_const_name_of_unnamed_maps));
// ////// PARAMETER ERMITTELN UND ZUSTANDSVARIABLEN INITIALISIEREN (GGF. AUS STATE LADEN)
intentPos = null;
if (savedInstanceState == null) {
// -- Frischer Start --
Intent intent = getIntent();
// ID der Karte (= Dateiname des Bildes)
currentMapID = intent.getExtras().getLong(INTENT_LOADMAPID);
// Initialisiere Startzustand der Navigation
changeState(NavigationStates.RUNNING);
saveState = true;
// aktuelle Position des Nutzers auf der Karte, null: noch nicht bekannt
userPosition = null;
intentPos = intent.getDoubleArrayExtra(INTENT_POS);
}
else {
// -- Gespeicherter Zustand --
// ID der Karte (= Dateiname des Bildes)
currentMapID = savedInstanceState.getLong(SAVEDCURRENTMAPID);
// aktuelle Position des Nutzers auf der Karte
userPosition = (Point2D) savedInstanceState.getSerializable(SAVEDUSERPOS);
// ist das Kurztutorial aktiviert?
quickTutorial = savedInstanceState.getBoolean(SAVEDHELPSTATE);
// ist die Positionszentrierung aktiviert?
trackPosition = savedInstanceState.getBoolean(SAVEDTRACKPOSITION);
// Wiederherstellung des Zustands
NavigationStates restoredState = NavigationStates.values()[savedInstanceState.getInt(SAVEDSTATE)];
if (restoredState.isHelpState()) {
// Sonderbehandlung beim Drehen im Bildschirm der Schnellhilfe
// XXX besser machen
state = restoredState;
getLayoutInflater().inflate(R.layout.navigation_help_running, layoutFrame);
endQuickHelp();
startQuickHelp();
}
else {
changeState(restoredState);
}
}
// ////// KARTE LADEN UND KOMPONENTEN INITIALISIEREN
// Lade Karte und erstelle gegebenenfalls einen neuen Eintrag
Log.d("onCreate", "Loading map: " + currentMapID + (currentMapID == -1 ? " (new map!)" : ""));
initLoadMap();
// Initialisiere GPS-Modul
initGPSModule();
// Initialiales update(), damit alles korrekt dargestellt wird
mapView.update();
if (intentPos != null) {
boolean prev = locationDataManager.setSpeedFiltering(false);
locationDataManager.addPoint(new GpsPoint(intentPos[1], intentPos[0], SystemClock.elapsedRealtime()));
locationDataManager.setSpeedFiltering(prev);
// Change mode to set ref point
changeState(NavigationStates.MARK_REFPOINT);
}
// Aktuelle Position zentrieren, falls tracking aktiviert
if (trackPosition) {
trackPosition(trackPositionButton);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("Navigation", "onDestroy...");
// Stelle NOCH MAL sicher, dass LDM-IO-Handler letzte Daten geschrieben hat.
// Passiert eigentlich schon in onResume(), aber um auf Nummer sicher zu gehen...
// TODO hier vielleicht mit resetIOHandler(null)? @diedricm ?
if (locationDataManager != null) {
iLDMIOHandler.save();
}
}
@Override
protected void onStart() {
super.onStart();
Log.d("Navigation", "onStart...");
//Prompt user to activate GPS
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
DialogInterface.OnClickListener gpsPromptListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
dialog.dismiss();
}
};
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
alertBuilder.setMessage(R.string.navigation_gps_activation_popup_question)
.setPositiveButton(android.R.string.yes, gpsPromptListener)
.setNegativeButton(android.R.string.no, gpsPromptListener)
.show();
}
}
@Override
protected void onStop() {
super.onStop();
Log.d("Navigation", "onStop...");
}
@Override
protected void onResume() {
super.onResume();
Log.d("Navigation", "onResume...");
// Abonniere GPS Updates
// TODO Genauigkeit (Parameter 2, 3)? default aus Tutorial (400, 1)
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 200, (float) 0.2, this);
}
@Override
protected void onPause() {
super.onPause();
Log.d("Navigation", "onPause...");
// Deabonniere GPS Updates
locationManager.removeUpdates(this);
// Stelle sicher, dass LDM-IO-Handler letzte Daten geschrieben hat
if (locationDataManager != null) {
Log.d("onPause", "Schreibe letzte LDM-IO-Daten...");
iLDMIOHandler.save();
}
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
Log.d("Navigation", "onSaveInstanceState...");
if (saveState) {
// ////// SAVE THE CURRENT STATE
// ID der Karte (= Dateiname des Bildes)
savedInstanceState.putLong(SAVEDCURRENTMAPID, currentMapID);
// aktuelle Position des Nutzers auf der Karte als Point2D
savedInstanceState.putSerializable(SAVEDUSERPOS, userPosition);
// Zustand der Navigation speichern
savedInstanceState.putInt(SAVEDSTATE, state.ordinal());
// Zustand der Hilfe speichern
savedInstanceState.putBoolean(SAVEDHELPSTATE, quickTutorial);
// Zustand der Hilfe speichern
savedInstanceState.putBoolean(SAVEDTRACKPOSITION, trackPosition);
// neu erstellt werden:
// LocationManager
// LocationDataManager
// LocationDataManagerListener
// LocationDataManagerListenerExpertenWohnungsvermittlungFachangestellter
}
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
/**
* Beim Bet�tigen der Zur�cktaste gelangen wir wieder zum Startbildschirm.
*/
@Override
public void onBackPressed() {
// Mit der Zur�ck-Taste kann das L�schen von Referenzpunkten abgebrochen werden
if (state == NavigationStates.DELETE_REFPOINT) {
refPointDeleteBack(null);
return;
}
// ... genauso wie das Setzen von Referenzpunkten
if (state == NavigationStates.MARK_REFPOINT || state == NavigationStates.ACCEPT_REFPOINT) {
cancelReferencePoint(null);
return;
}
// ... und die Schnellhilfe
if (state.isHelpState()) {
endQuickHelp();
return;
}
// Wenn wir zur�ckgehen, muss der Zustand nicht gespeichert werden
saveState = false;
// Startbildschirm aufrufen und Activity finishen
Intent intent = new Intent(getApplicationContext(), Start.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (intentPos != null) {
intentPos = null;
intent.putExtra(Start.INTENT_EXIT, true);
}
startActivity(intent);
finish();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.navigation, menu);
this.menu = menu;
// Aktiviere Debug-Optionen, falls Debugmode aktiviert
if (MapEverApp.isDebugModeEnabled(this)) {
menu.findItem(R.id.action_debugmode_mockgps).setVisible(true);
}
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_rename_map:
changeState(NavigationStates.RENAME_MAP);
return true;
case R.id.action_quick_help:
// Schnellhilfe-Button
startQuickHelp();
return true;
case R.id.action_debugmode_mockgps:
// DEBUGMODE: Mock GPS coordinates
debug_mockGPS();
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Gibt eine Referenz auf unser MapView zur�ck.
*/
public MapView getMapView() {
return mapView;
}
// ////////////////////////////////////////////////////////////////////////
// //////////// INITIALISIERUNG VON KARTE UND GPS-MODUL
// ////////////////////////////////////////////////////////////////////////
// //////// KARTE LADEN / NEUE KARTE ERSTELLEN
static final int LOAD_TEST_MAP = 0;
static final int CREATE_NEW_MAP = -1;
private void initLoadMap() {
// ////// LDM-IO-HANDLER INITIALISIEREN
Log.i("Nav" ,"currentMapID:" + currentMapID);
if (currentMapID == LOAD_TEST_MAP) {
// Spezialfall 0: Lade Testkarte
// => Lade nichts aus der Datenbank, sondern benutze nichtpersistenten LDM. (Debugging)
iLDMIOHandler = new LDMIOEmpty();
} else {
if (!TrackDB.loadDB(new File(MapEverApp.getAbsoluteFilePath("")))) {
Log.e("Nav", "Could not load DB");
finish();
}
// Haben wir eine neue Karte erstellt?
if (currentMapID != CREATE_NEW_MAP) {
// Lade Karte mit der gegebenen ID aus der Datenbank
// Falls ID = -1, wird ein neuer Eintrag f�r eine neue Karte erstellt (ID per auto increment)
// pr�fen, ob Karte mit der ID existiert, sonst Fehlermeldung
thisMap = TrackDB.main.getMap(currentMapID);
currentMapID = thisMap.getIdentifier();
} else {
// ID der neuen Karte anfragen und merken
thisMap = TrackDB.main.createMap();
currentMapID = thisMap.getIdentifier();
Log.d("Navigation/initLoadMap", "Neu erstellte Karte mit ID: " + thisMap.getIdentifier());
String targetFilename = MapEverApp.getAbsoluteFilePath(String.valueOf(thisMap.getIdentifier()));
String targetFilenameThumb = targetFilename + MapEverApp.THUMB_EXT;
// Bilddatei umbenennen
File renameFrom = new File(MapEverApp.getAbsoluteFilePath(MapEverApp.TEMP_IMAGE_FILENAME));
File renameTo = new File(targetFilename);
renameFrom.renameTo(renameTo);
// Thumbnail erstellen
try {
int thumbSize = Start.getThumbnailSize();
Thumbnail.generate(targetFilename, targetFilenameThumb, thumbSize, thumbSize);
} catch (IOException e) {
Log.e("Navigation/initLoadMap", "Failed generating thumbnail for image '" + targetFilename + "'!");
e.printStackTrace();
}
}
try {
iLDMIOHandler = TrackDB.main.getLDMIO(thisMap);
} catch (IOException e) {
// ResourceID der Fehlermeldung an den Startbildschirm geben, dieser zeigt Fehlermeldung an
setResult(R.string.navigation_map_not_found);
finish();
return;
}
// wenn ein sinnvoller name vorhanden ist -> diesen anzeigen, sonst default Wert
if (thisMap.getMapname().isEmpty())
setTitle(R.string.navigation_const_name_of_unnamed_maps);
else
setTitle(thisMap.getMapname());
}
// ////// BILD IN DIE MAPVIEW LADEN
// (bei currentMapID == 0 wird die Testkarte geladen)
try {
mapView.loadMap(currentMapID);
} catch (FileNotFoundException e) {
Log.e("Navigation/initLoadMap", "Konnte Karte " + currentMapID + " nicht laden!");
// ResourceID der Fehlermeldung an den Startbildschirm geben, dieser zeigt Fehlermeldung an
setResult(R.string.navigation_image_not_found);
finish();
return;
}
// Bilddimensionen m�ssen erkannt worden sein
if (mapView.getImageWidth() == 0 || mapView.getImageHeight() == 0) {
Log.e("Navigation/initLoadMap", "Bilddimensionen sind " + mapView.getImageWidth() + "x" + mapView.getImageHeight());
// (Sollte eigentlich eh nie vorkommen, also gib "Error" aus...)
setResult(R.string.general_error_title);
finish();
return;
}
// ////// LOCATIONDATAMANAGER INITIALISIEREN
// Listener f�r neue Userkoordinaten erstellen
locDatManListener = new LocationDataManagerListener(this);
// LocationDataManager initialisieren
Point2D imageSize = new Point2D(mapView.getImageWidth(), mapView.getImageHeight());
ImagePositionLocator locator;
if (de.hu_berlin.informatik.spws2014.mapever.Settings.getPreference_leastsquares(this)) {
locator = new LeastSquaresImagePositionLocator();
} else {
locator = new TriangleImagePositionLocator(imageSize, IPLSettingsContainer.DefaultContainer);
}
locationDataManager = new LocationDataManager(locDatManListener, iLDMIOHandler,
imageSize,
locator);
locationDataManager.refreshLastPosition();
// ////// GELADENE REFERENZPUNKTE DARSTELLEN
ArrayList<Marker> loadedMarkers = iLDMIOHandler.getAllMarkers();
// TODO Check for corrupted maps! (LDM returns lots of nulls)
if (loadedMarkers != null) {
Log.d("Navigation/initLoadMap", "Lade " + loadedMarkers.size() + " Referenzpunkte...");
// Erstelle alle geladenen Referenzpunkte
for (Marker marker : loadedMarkers) {
mapView.createLoadedReferencePoint(marker.imgpoint, marker.time);
}
}
}
// //////// GPS-MODUL INITIALISIEREN
private void initGPSModule() {
// Initialisiere GPS-Modul
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 200, (float) 0.2, this);
Log.d("initGPSModule", "Location provider: " + LocationManager.GPS_PROVIDER);
// Wir machen nichts mit der lastKnownLocation, siehe #169
}
// ////////////////////////////////////////////////////////////////////////
// //////////// HANDLING VON USERINPUT
// ////////////////////////////////////////////////////////////////////////
// //////// HILFE
/**
* Startet die Schnellhilfe, wenn nicht bereits aktiv.
*/
public void startQuickHelp() {
// Bis RUNNING wieder erreicht ist, wird der Hilfebildschirm immer angezeigt
quickTutorial = true;
// Ansonsten zeigen wir abh�ngig vom aktuellen Zustand einen passendes Hilfs-Overlay an
// und deaktivieren die entsprechenden Buttons
switch (state) {
case ACCEPT_REFPOINT:
getLayoutInflater().inflate(R.layout.navigation_help_accept_refpoint, layoutFrame);
changeState(NavigationStates.HELP_ACCEPT_REFPOINT);
break;
case DELETE_REFPOINT:
getLayoutInflater().inflate(R.layout.navigation_help_delete_refpoint, layoutFrame);
changeState(NavigationStates.HELP_DELETE_REFPOINT);
break;
case MARK_REFPOINT:
getLayoutInflater().inflate(R.layout.navigation_help_mark_refpoint, layoutFrame);
changeState(NavigationStates.HELP_MARK_REFPOINT);
break;
case RUNNING:
getLayoutInflater().inflate(R.layout.navigation_help_running, layoutFrame);
changeState(NavigationStates.HELP_RUNNING);
break;
// Wenn wir in der Schnellhilfe sind, beenden wir sie jetzt
case HELP_ACCEPT_REFPOINT:
case HELP_DELETE_REFPOINT:
case HELP_MARK_REFPOINT:
case HELP_RUNNING:
endQuickHelp();
break;
default:
Log.e("startQuickHelp", "Schnellhilfe f�r diesen Zustand fehlt noch!");
return;
}
}
/**
* Beendet die Schnellhilfe und stellt den vorherigen Zustand wieder her.
*/
public void endQuickHelp() {
// alle Layer bis auf den untersten aus dem FrameLayout entfernen
if (layoutFrame.getChildCount() > 1) {
layoutFrame.removeViews(layoutFrame.getChildCount() - 1, layoutFrame.getChildCount() - 1);
}
// zur�ck zum vorherigen Zustand
switch (state) {
case HELP_ACCEPT_REFPOINT:
changeState(NavigationStates.ACCEPT_REFPOINT);
break;
case HELP_DELETE_REFPOINT:
changeState(NavigationStates.DELETE_REFPOINT);
break;
case HELP_MARK_REFPOINT:
changeState(NavigationStates.MARK_REFPOINT);
break;
case HELP_RUNNING:
changeState(NavigationStates.RUNNING);
break;
default:
Log.e("endQuickHelp", "Diesen Text sollte man nie angezeigt bekommen! Zustand: " + state);
break;
}
}
// //////// BUTTONS
/**
* Der User hat auf den "Referenzpunkt setzen" Button gedr�ckt.
*
* @param view
*/
public void setRefPoint(View view) {
if (state != NavigationStates.RUNNING) {
Log.w("setRefPoint", "Inkonsistenter Zustand: state != RUNNING");
}
// Pr�fe, ob wir momentan einen Referenzpunkt setzen d�rfen (sind bereits GPS-Koordinaten bekannt?)
if (!locationDataManager.isMarkerPlacingAllowed()) {
// Zeige Nachricht an, dass auf GPS Ortung gewartet werden muss
Toast.makeText(this, getString(R.string.navigation_toast_no_gpsfix_yet), Toast.LENGTH_SHORT).show();
return;
}
// Zustands�nderung zum "Referenzpunkt Setzen" Zustand
changeState(NavigationStates.MARK_REFPOINT);
// der bet�tigte Button wird verborgen
setRefPointButton.setVisibility(View.INVISIBLE);
setRefPointButton.setEnabled(false);
}
/**
* Der Nutzer will den neu gesetzten Referenzpunkt behalten.
*
* @param view
*/
public void acceptReferencePoint(View view) {
mapView.acceptReferencePoint();
// Anschlie�end befinden wir uns wieder im Anfangszustand
changeState(NavigationStates.RUNNING);
}
/**
* Der Nutzer will den neu gesetzten Referenzpunkt verwerfen.
*
* @param view
*/
public void cancelReferencePoint(View view) {
// kann damit auch von onBackPressed aufgerufen werden, wenn wir noch in MARK_REFPOINT sind
if (state == NavigationStates.ACCEPT_REFPOINT) {
mapView.cancelReferencePoint();
}
// Anschlie�end befinden wir uns wieder im Anfangszustand
changeState(NavigationStates.RUNNING);
}
/**
* Der Button zum L�schen des Referenzpunkts wurde bet�tigt.
*
* @param view
*/
public void deleteReferencePoint(View view) {
mapView.deleteReferencePoint();
// Anschlie�end befinden wir uns wieder im Anfangszustand
changeState(NavigationStates.RUNNING);
}
/**
* Der ausgew�hlte Referenzpunkt soll doch nicht gel�scht werden.
*
* @param view
*/
public void refPointDeleteBack(View view) {
mapView.dontDeleteReferencePoint();
// Anschlie�end befinden wir uns wieder im Anfangszustand
changeState(NavigationStates.RUNNING);
}
/**
* Aktiviere Zentrierung des Locationmarkers.
*
* @param view
*/
public void trackPosition(View view) {
trackPosition = true;
view.setVisibility(View.GONE);
mapView.centerCurrentLocation();
}
// ////////////////////////////////////////////////////////////////////////
// //////////// HILFSFUNKTIONEN
// ////////////////////////////////////////////////////////////////////////
// //////// ZUSTANDS�NDERUNGEN
/**
* Simpler Zustands�bergang ohne gro�e Rafinesse.
*
* @param nextState
*/
public void changeState(NavigationStates nextState) {
boolean changeToHelp = false;
boolean changeFromHelp = false;
NavigationStates oldState = state;
state = nextState;
Log.d("changeState", "Zustands�bergang von " + oldState + " zu " + state);
if (oldState.isHelpState()) {
changeFromHelp = true;
}
else if (oldState == NavigationStates.RENAME_MAP) {
// wenn ein neuer Name eingegeben wurde, so ist er in newMapName gespeichert
if (newMapName != "") {
this.getSupportActionBar().setTitle(newMapName);
thisMap.setMapname(newMapName);
newMapName = "";
}
}
// erst mal alle Buttons deaktivieren
for (ImageButton imageButton : imageButtonList) {
imageButton.setEnabled(false);
imageButton.setVisibility(View.INVISIBLE);
}
// alle Layer bis auf den untersten aus dem FrameLayout entfernen
// TODO nee, oder? siehe Bug #231
// if (layoutFrame.getChildCount() > 1) {
// layoutFrame.removeViews(layoutFrame.getChildCount() - 1, layoutFrame.getChildCount() - 1);
// }
// Rename Men�option deaktivieren
if (menu != null) {
menu.findItem(R.id.action_rename_map).setVisible(false);
}
// und dann je nach Zustand die passenden Buttons reaktivieren
switch (state) {
case ACCEPT_REFPOINT:
// Buttons zum Akzeptieren und Verwerfen des gesetzten Referenzpunkts m�ssen angezeigt werden
acceptRefPointButton.setVisibility(View.VISIBLE);
acceptRefPointButton.setEnabled(true);
cancelRefPointButton.setVisibility(View.VISIBLE);
cancelRefPointButton.setEnabled(true);
break;
case DELETE_REFPOINT:
// Button zum L�schen des Referenzpunkts muss angezeigt werden
deleteRefPointButton.setVisibility(View.VISIBLE);
deleteRefPointButton.setEnabled(true);
break;
case HELP_ACCEPT_REFPOINT:
// Buttons von Accept_Refpoint einblenden aber nicht aktivieren
acceptRefPointButton.setVisibility(View.VISIBLE);
cancelRefPointButton.setVisibility(View.VISIBLE);
// Es handelt sich um einen Wechsel zur Schnellhilfe
changeToHelp = true;
break;
case HELP_DELETE_REFPOINT:
// Buttons von Delete_Refpoint einblenden aber nicht aktivieren
deleteRefPointButton.setVisibility(View.VISIBLE);
changeToHelp = true;
break;
case HELP_MARK_REFPOINT:
// hier gibt es aktuell keine Buttons anzuzeigen
changeToHelp = true;
break;
case HELP_RUNNING:
// Buttons von RUNNING einblenden aber nicht aktivieren
setRefPointButton.setVisibility(View.VISIBLE);
changeToHelp = true;
break;
case MARK_REFPOINT:
// hier gibt es aktuell keine Buttons anzuzeigen
break;
case RENAME_MAP:
// TODO das wirkt irgendwie alles so umst�ndlich... xD
// erstelle AlertDialog f�r die schickere Umbenennung
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.navigation_rename_map);
// Dr�cken des Umbenennen-Buttons benennt die Karte um
builder.setPositiveButton(R.string.navigation_rename_map_rename, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
changeState(NavigationStates.RUNNING);
}
});
// Dr�cken des Cancel-Buttons beendet die Umbenennung
builder.setNegativeButton(R.string.navigation_rename_map_cancel, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// der Kartenname soll nicht ge�ndert werden!
newMapName = "";
changeState(NavigationStates.RUNNING);
}
});
// TODO was ist hier die Alternative zu null?
View renameDialogLayout = getLayoutInflater().inflate(R.layout.navigation_rename_map, null);
// bindet das Layout navigation_rename_map ein
builder.setView(renameDialogLayout);
AlertDialog dialog = builder.create();
// Behandelt alle Arten den AlertDialog abzubrechen, ohne die Kn�pfe zu verwenden
dialog.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// der Kartenname soll nicht ge�ndert werden!
newMapName = "";
changeState(NavigationStates.RUNNING);
}
});
// Anzeigen des Dialog
dialog.show();
EditText input = (EditText) renameDialogLayout.findViewById(R.id.editTextToNameMap);
// Wenn der Text ge�ndert wird, wird der String newMapName angepasst
input.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
newMapName = s.toString();
}
});
// der Aktuelle Kartenname wird dem Benutzer angezeigt
input.setText(getTitle());
break;
case RUNNING:
// Button zum Setzen von Referenzpunkten einblenden
setRefPointButton.setVisibility(View.VISIBLE);
setRefPointButton.setEnabled(true);
// Wenn wir keine aktuelle GPS Position haben, Button "ausgegraut" anzeigen und deaktivieren
if (locationDataManager == null || !locationDataManager.isMarkerPlacingAllowed()) {
disableSetRefPointButton(true);
}
else {
// ansonsten Button wieder aktivieren
disableSetRefPointButton(false);
}
if (isUserPositionKnown()) {
// track position button anzeigen, ist default ausgeblendet um crashes zu verhinden
trackPositionButton.setVisibility(View.VISIBLE);
}
// nur in RUNNING kann man die Karte umbenennen
if (menu != null) {
menu.findItem(R.id.action_rename_map).setVisible(true);
}
if (intentPos != null) {
onBackPressed();
return;
}
break;
default:
break;
}
// Das Kurztutorial soll nur angezeigt werden, wenn es
// a) aktiviert ist
// und b) wir nicht zu einem Hilfebildschirm wechseln oder von einem solchen kommen
if (quickTutorial && !changeFromHelp && !changeToHelp) {
// wenn wir wieder bei RUNNING angekommen sind, wird die Hilfe erst wieder auf Nutzerwunsch ausgel�st
if (state == NavigationStates.RUNNING) {
quickTutorial = false;
return;
}
else {
// Ansonsten zeigen wir den Hilfe-Bildschirm an
startQuickHelp();
}
}
}
/**
* Deaktiviert den Referenzpunkt-Setzen-Button (d.h. er wird ausgegraut, bleibt aber enabled) oder aktiviert ihn.
*
* @param disable false zum Reaktivieren des Buttons
*/
private void disableSetRefPointButton(boolean disable) {
if (disable) {
// Button "ausgegraut" anzeigen
setRefPointButton.setColorFilter(Color.GRAY);
setRefPointButton.getBackground().setAlpha(127);
}
else {
// Button aktivieren und ColorFilter entfernen
setRefPointButton.clearColorFilter();
setRefPointButton.getBackground().setAlpha(255);
}
}
// ////////////////////////////////////////////////////////////////////////
// //////////// LOKALISIERUNG
// ////////////////////////////////////////////////////////////////////////
// //////// GPS
@Override
public void onLocationChanged(Location location) {
if (location == null || locationDataManager == null || intentPos != null)
return;
// Wenn wir in RUNNING sind und wir bisher keine aktuellen Koordinaten hatten, m�ssen wir den (ausgegrauten)
// Referenzpunkt-Setzen-Button reaktivieren.
if (state == NavigationStates.RUNNING && !locationDataManager.isMarkerPlacingAllowed()) {
disableSetRefPointButton(false);
}
// DEBUG: GPS-Mocker: zuf�llige Koordinaten sollen um Startposition herum gestreut werden
if (mockBaseLocation == null) {
mockBaseLocation = location;
}
// Ermittle Koordinatenkomponenten
double lng = location.getLongitude();
double lat = location.getLatitude();
Log.d("Navigation", "GPS location changed: " + lat + "� N / " + lng + "� E");
// �bergebe der Lokalisierung den aktuellen GPS-Punkt
GpsPoint gpsPoint = new GpsPoint(lng, lat, SystemClock.elapsedRealtime());
locationDataManager.addPoint(gpsPoint);
// Der LocationDataManagerListener wird spaeter onNewUserPosition()
// aufrufen, was das LocationView entsprechend verschiebt.
}
// TODO bin mir nicht sicher, ob hier was gemacht werden muss...
@Override
public void onProviderEnabled(String provider) {
Log.d("Navigation", "Provider enabled: " + provider);
}
@Override
public void onProviderDisabled(String provider) {
Log.d("Navigation", "Provider disabled: " + provider);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d("Navigation", "Status changed: " + provider + ", status = " + status);
}
// //////// POSITION TRACKING (AUTO CENTER)
public boolean isPositionTracked() {
return trackPosition;
}
public void stopTrackingPosition() {
trackPosition = false;
trackPositionButton.setVisibility(View.VISIBLE);
}
// //////// DEBUG MODE - GPS
// Simuliert zuf�llige GPS-Koordinaten
private void debug_mockGPS() {
// TODO eventuell GPS-Provider deaktivieren? mit der Methode, die in onPause verwendet wird
Random random = new Random();
double mockRadius = 0.1;
// mockBaseLocation ist der Punkt, der als Zentrum f�r die zuf�llige Verteilung gew�hlt wird (Startkoordinaten)
if (mockBaseLocation == null) {
// Verwende folgende Default-Koordinaten, wenn keine Startkoordinaten bekannt (das ist der Fernsehturm :) )
mockBaseLocation = new Location("mock");
mockBaseLocation.setLatitude(52.520818);
mockBaseLocation.setLongitude(13.409403);
}
Location mockLoc = new Location(mockBaseLocation);
double dLat, dLon;
String toastText;
// Falls wir im Referenzpunkt-Setz-Modus sind, simuliere Hilfspunkte statt zuf�llige!
// D.h. je nach Anzahl der Referenzpunkte, biete einen Punkt oben mittig, links unten oder rechts unten an.
if (state == NavigationStates.MARK_REFPOINT || state == NavigationStates.ACCEPT_REFPOINT) {
switch (mapView.countReferencePoints()) {
case 0:
dLat = 0.5 * mockRadius;
dLon = 0;
toastText = "Mitte oben (.5, 0)";
break;
case 1:
dLat = -0.5 * mockRadius;
dLon = -0.5 * mockRadius;
toastText = "Links unten (-.5, -.5)";
break;
case 2:
dLat = -0.5 * mockRadius;
dLon = 0.5 * mockRadius;
toastText = "Rechts unten (-.5, .5)";
break;
default:
dLat = dLon = 0;
toastText = "Mittelpunkt (0, 0)";
}
}
else {
dLat = (random.nextDouble() - 0.5) * 2 * mockRadius;
dLon = (random.nextDouble() - 0.5) * 2 * mockRadius;
toastText = "dLat " + dLat / mockRadius + "\ndLon " + dLon / mockRadius + "\n(relative to mock radius)";
}
mockLoc.setLatitude(mockLoc.getLatitude() + dLat);
mockLoc.setLongitude(mockLoc.getLongitude() + dLon);
// Koordinaten einspei�en
Log.d("debug_mockGPS", "Set GPS coordinates to dLat = " + dLat + ", dLon = " + dLon + " (relative to starting point)");
onLocationChanged(mockLoc);
// Koordinatendifferenz in Toast anzeigen
if (mockStatusToast != null) {
mockStatusToast.cancel();
}
mockStatusToast = Toast.makeText(this, toastText, Toast.LENGTH_SHORT);
mockStatusToast.show();
}
// //////// BILD-LOKALISIERUNG
/**
* Wird vom LocationDataManagerListener beim Erhalten neuer Userposition aufgerufen.
*/
public void onNewUserPosition() {
// Aktuellste Position vom LocationDataManager holen und setzen
Point2D userPosition = locationDataManager.getLastImagePoint();
if (userPosition == null || (userPosition.x == 0 && userPosition.y == 0)) {
// Sollte eigentlich nicht mehr auftreten... wenn doch, return.
Log.w("onNewUserPosition", "getLastImagePoint() returned " + (userPosition == null ? "null" : "(0,0"));
return;
}
Log.d("Navigation", "Image position changed: " + userPosition.x + "px / " + userPosition.y + "px");
// Verschiebe den LocationView-Marker
setUserPosition(userPosition);
}
/**
* Position des Users gemessen in Pixeln relativ zum Bild setzen.
* Aktualisiert die Darstellung der LocationView.
*
* @param newPos Koordinaten als Point2D
*/
public void setUserPosition(Point2D newPos) {
userPosition = newPos;
// Locationmarker aktualisieren
mapView.updateLocationIcon();
// Aktuelle Position zentrieren, falls tracking aktiviert
if (isPositionTracked()) {
mapView.centerCurrentLocation();
}
}
/**
* Position des Users gemessen in Pixeln relativ zum Bild als Point2D.
*/
public Point2D getUserPosition() {
return userPosition;
}
/**
* Gibt true zur�ck, wenn die aktuelle Benutzerposition bekannt ist, sonst false.
*/
public boolean isUserPositionKnown() {
return userPosition != null;
}
/**
* Tr�gt einen neuen Referenzpunkt an der �bergebenen Position und mit dem
* �bergebenen timestamp beim LocationDataManager ein
*
* @param position
* @param timestamp
*/
public boolean registerReferencePoint(Point2D position, long timestamp) {
Log.d("registerReferencePoint", "Position: " + position + ", time: " + timestamp);
try {
locationDataManager.addMarker(position, timestamp);
}
catch (NoGpsDataAvailableException e) {
// Keine (neuen) GPS-Daten sind verf�gbar -> f�r diese GPS-Koordinaten existiert bereits ein Referenzpunkt
Log.w("registerReferencePoint", "addMarker failed because of NoGpsDataAvailableException: " + e.getMessage());
// Fehlermeldung per Toast anzeigen
int errorMsgID = e.getMessage() == "Point already known!"
? R.string.navigation_toast_refpoint_already_set_for_this_position
: R.string.navigation_toast_no_gpsfix_yet;
Toast.makeText(this, getString(errorMsgID), Toast.LENGTH_SHORT).show();
// aufrufende Funktion muss z.B. den unakzeptierten Referenzpunkt aufr�umen
return false;
}
catch (PointNotInImageBoundsException e) {
// Gew�nschter Referenzpunkt befindet sich au�erhalb der Bildgrenzen! (nicht m�glich)
Log.w("registerReferencePoint", "addMarker failed because of PointNotInImageBoundsException: " + e.getMessage());
// Fehlermeldung per Toast anzeigen
Toast.makeText(this, getString(R.string.navigation_toast_refpoint_out_of_boundaries), Toast.LENGTH_SHORT).show();
// aufrufende Funktion muss z.B. den unakzeptierten Referenzpunkt aufr�umen
return false;
}
// Wieviele Referenzpunkte muss der Nutzer noch setzen?
int refPointsLeftToSet = locationDataManager.remainingUserMarkerInputs();
// Wenn der Nutzer noch nicht genug Referenzpunkte gesetzt hat, wird er darauf hingewiesen
if (refPointsLeftToSet > 0) {
Toast.makeText(this,
getString(R.string.navigation_toast_set_refpoint_prompt, refPointsLeftToSet),
Toast.LENGTH_SHORT
).show();
}
return true;
}
/**
* Weist den LocationDataManager an, einen Referenzpunkt zu l�schen.
*
* @param position
*/
public boolean unregisterReferencePoint(Point2D position) {
Log.d("unregisterReferencePoint", "Position: " + position);
boolean result = iLDMIOHandler.removeMarker(position);
locationDataManager.refreshLastPosition();
return result;
}
}