package net.osmand.access;
import android.content.Context;
import android.content.DialogInterface;
import android.os.SystemClock;
import android.os.Vibrator;
import android.support.v7.app.AlertDialog;
import net.osmand.Location;
import net.osmand.data.LatLon;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmAndLocationProvider.OsmAndCompassListener;
import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.TargetPointsHelper.TargetPoint;
import net.osmand.plus.access.RelativeDirectionStyle;
import net.osmand.plus.base.MapViewTrackingUtilities;
import net.osmand.plus.routing.RouteCalculationResult.NextDirectionInfo;
import net.osmand.plus.routing.RoutingHelper;
import java.util.ArrayList;
import java.util.List;
public class NavigationInfo implements OsmAndCompassListener, OsmAndLocationListener {
private static final float FULL_CIRCLE = 360.0f;
private class RelativeDirection {
private static final int UNKNOWN = -1;
private final int[] direction = {R.string.front,
R.string.front_right,
R.string.right,
R.string.back_right,
R.string.back,
R.string.back_left,
R.string.left,
R.string.front_left};
private RelativeDirectionStyle style;
private int value;
public RelativeDirection() {
style = settings.DIRECTION_STYLE.get();
clear();
}
// The argument must be not null as well as the currentLocation
// and currentLocation must have bearing.
public RelativeDirection(final Location point) {
style = settings.DIRECTION_STYLE.get();
value = directionTo(point, currentLocation.getBearing());
}
// The first argument must be not null as well as the currentLocation.
public RelativeDirection(final Location point, float heading) {
style = settings.DIRECTION_STYLE.get();
value = directionTo(point, heading);
}
public void clear() {
value = UNKNOWN;
}
// The first argument must be not null as well as the currentLocation.
public boolean update(final Location point, float heading) {
boolean result = false;
final RelativeDirectionStyle newStyle = settings.DIRECTION_STYLE.get();
if (style != newStyle) {
style = newStyle;
result = true;
}
final int newValue = directionTo(point, heading);
if (value != newValue) {
value = newValue;
result = true;
}
return result;
}
// The argument must be not null as well as the currentLocation
// and currentLocation must have bearing.
public boolean update(final Location point) {
return update(point, currentLocation.getBearing());
}
public String getString() {
if (value < 0) // unknown direction
return null;
if (style == RelativeDirectionStyle.CLOCKWISE) {
String result = NavigationInfo.this.getString(R.string.towards);
result += " " + ((value != 0) ? value : 12); //$NON-NLS-1$
result += " " + NavigationInfo.this.getString(R.string.oclock); //$NON-NLS-1$
return result;
} else {
return NavigationInfo.this.getString(direction[value]);
}
}
public Integer getInclination() {
if (value < 0) // unknown direction
return null;
final int nSectors = (style == RelativeDirectionStyle.CLOCKWISE) ? 12 : direction.length;
final int halfRound = nSectors / 2;
if (value == halfRound) // opposite direction
return null;
if (value > halfRound)
return value - nSectors;
return value;
}
protected int getValue() {
return value;
}
// The first argument must be not null as well as the currentLocation.
private int directionTo(final Location point, float heading) {
final float bearing = currentLocation.bearingTo(point) - heading;
final int nSectors = (style == RelativeDirectionStyle.CLOCKWISE) ? 12 : direction.length;
int sector = Math.round(Math.abs(bearing) * (float) nSectors / FULL_CIRCLE) % nSectors;
if ((bearing < 0) && (sector != 0))
sector = nSectors - sector;
return sector;
}
}
private final int[] cardinal = {R.string.north,
R.string.north_north_east,
R.string.north_east,
R.string.east_north_east,
R.string.east,
R.string.east_south_east,
R.string.south_east,
R.string.south_south_east,
R.string.south,
R.string.south_south_west,
R.string.south_west,
R.string.west_south_west,
R.string.west,
R.string.west_north_west,
R.string.north_west,
R.string.north_north_west};
private final long HAPTIC_INCLINATION_LEFT[] = { 0, 60 };
private final long HAPTIC_INCLINATION_RIGHT[] = { 0, 20, 80, 20 };
private final OsmandApplication app;
private final OsmandSettings settings;
private Location currentLocation;
private RelativeDirection lastDirection;
private long lastNotificationTime;
private volatile boolean autoAnnounce;
private volatile boolean targetDirectionFlag;
public NavigationInfo(OsmandApplication app) {
this.app = app;
settings = app.getSettings();
currentLocation = null;
lastDirection = new RelativeDirection();
lastNotificationTime = SystemClock.uptimeMillis();
autoAnnounce = false;
targetDirectionFlag = false;
}
private String getString(int id) {
return app.getString(id);
}
// The argument must be not null as well as the currentLocation
private String distanceString(final Location point) {
return OsmAndFormatter.getFormattedDistance(currentLocation.distanceTo(point), app);
}
// The argument must be not null as well as the currentLocation
private String absoluteDirectionString(float bearing) {
int direction = Math.round(Math.abs(bearing) * (float) cardinal.length / FULL_CIRCLE) % cardinal.length;
if ((bearing < 0) && (direction != 0))
direction = cardinal.length - direction;
return getString(cardinal[direction]);
}
// Get distance and direction string for specified point
public synchronized String getDirectionString(final LatLon apoint, Float heading) {
if ((currentLocation != null) && (apoint != null)) {
Location point = new Location("");
point.setLatitude(apoint.getLatitude());
point.setLongitude(apoint.getLongitude());
RelativeDirection direction = null;
String result = distanceString(point);
result += " "; //$NON-NLS-1$
if (currentLocation.hasBearing() && !MapViewTrackingUtilities.isSmallSpeedForCompass(currentLocation))
direction = new RelativeDirection(point);
else if (heading != null)
direction = new RelativeDirection(point, heading);
if (direction != null) {
// relative direction
result += direction.getString();
} else {
// absolute direction
result += getString(R.string.towards) + " "; //$NON-NLS-1$
result += absoluteDirectionString(currentLocation.bearingTo(point));
}
return result;
}
return null;
}
// Get current travelling speed and direction
public synchronized String getSpeedString() {
if ((currentLocation != null) && currentLocation.hasSpeed()) {
String result = OsmAndFormatter.getFormattedSpeed(currentLocation.getSpeed(), app);
if (currentLocation.hasBearing())
result += " " + absoluteDirectionString(currentLocation.getBearing()); //$NON-NLS-1$
return result;
}
return null;
}
// Get positioning accuracy and provider information if available
public synchronized String getAccuracyString() {
String result = null;
if (currentLocation != null) {
String provider = currentLocation.getProvider();
if (currentLocation.hasAccuracy())
result = getString(R.string.accuracy) + " " + OsmAndFormatter.getFormattedDistance(currentLocation.getAccuracy(), app); //$NON-NLS-1$
if (result != null)
result += " (" + provider + ")"; //$NON-NLS-1$ //$NON-NLS-2$
else
result = provider;
}
return result;
}
// Get altitude information string
public synchronized String getAltitudeString() {
if ((currentLocation != null) && currentLocation.hasAltitude())
return getString(R.string.altitude)
+ " " + OsmAndFormatter.getFormattedDistance((float) currentLocation.getAltitude(), app); //$NON-NLS-1$
return null;
}
@Override
public synchronized void updateLocation(Location location) {
currentLocation = location;
if (autoAnnounce && app.accessibilityEnabled()) {
final TargetPoint point = app.getTargetPointsHelper().getPointToNavigate();
if (point != null) {
if ((currentLocation != null) && currentLocation.hasBearing() && !MapViewTrackingUtilities.isSmallSpeedForCompass(currentLocation)) {
final long now = SystemClock.uptimeMillis();
if ((now - lastNotificationTime) >= settings.ACCESSIBILITY_AUTOANNOUNCE_PERIOD.get()) {
Location destination = new Location("map"); //$NON-NLS-1$
destination.setLatitude(point.getLatitude());
destination.setLongitude(point.getLongitude());
if (lastDirection.update(destination) || !settings.ACCESSIBILITY_SMART_AUTOANNOUNCE.get()) {
final String notification = distanceString(destination) + " " + lastDirection.getString(); //$NON-NLS-1$
lastNotificationTime = now;
app.runInUIThread(new Runnable() {
@Override
public void run() {
app.showToastMessage(notification);
}
});
}
}
} else {
lastDirection.clear();
}
}
}
}
public synchronized void updateTargetDirection(final Location point, float heading) {
if ((currentLocation != null) && (point != null)) {
RelativeDirection direction = new RelativeDirection(point, heading);
Integer inclination = direction.getInclination();
if (targetDirectionFlag && ((inclination == null) || (inclination != 0))) {
targetDirectionFlag = false;
if (settings.DIRECTION_AUDIO_FEEDBACK.get()) {
AccessibilityPlugin accessibilityPlugin = OsmandPlugin.getEnabledPlugin(AccessibilityPlugin.class);
if (accessibilityPlugin != null) {
if (inclination == null) {
accessibilityPlugin.playSoundIcon(AccessibilityPlugin.DIRECTION_NOTIFICATION);
} else if (inclination > 0) {
accessibilityPlugin.playSoundIcon(AccessibilityPlugin.INCLINATION_LEFT);
} else {
accessibilityPlugin.playSoundIcon(AccessibilityPlugin.INCLINATION_RIGHT);
}
}
}
if (settings.DIRECTION_HAPTIC_FEEDBACK.get()) {
Vibrator haptic = (Vibrator)app.getSystemService(Context.VIBRATOR_SERVICE);
if ((haptic != null) && haptic.hasVibrator()) {
if (inclination == null) {
haptic.vibrate(200);
} else if (inclination > 0) {
haptic.vibrate(HAPTIC_INCLINATION_LEFT, -1);
} else {
haptic.vibrate(HAPTIC_INCLINATION_RIGHT, -1);
}
}
}
} else if ((!targetDirectionFlag) && (direction.getValue() == 0)) {
targetDirectionFlag = true;
}
}
}
public synchronized void updateTargetDirection(final LatLon point, float heading) {
if (point != null) {
Location destination = new Location("map"); //$NON-NLS-1$
destination.setLatitude(point.getLatitude());
destination.setLongitude(point.getLongitude());
updateTargetDirection(destination, heading);
}
}
@Override
public synchronized void updateCompassValue(float heading) {
RoutingHelper router = app.getRoutingHelper();
if (router.isFollowingMode() && router.isRouteCalculated()) {
synchronized (router) {
NextDirectionInfo nextDirection = router.getNextRouteDirectionInfo(new NextDirectionInfo(), true);
if (nextDirection != null) {
updateTargetDirection(router.getLocationFromRouteDirection(nextDirection.directionInfo), heading);
}
}
} else {
TargetPoint target = app.getTargetPointsHelper().getPointToNavigate();
updateTargetDirection((target != null) ? target.point : null, heading);
}
}
// Show all available info
public void show(final TargetPoint point, Float heading, Context ctx) {
final List<String> attributes = new ArrayList<String>();
String item;
item = getDirectionString(point == null ? null : point.point, heading);
if (item != null)
attributes.add(item);
item = getSpeedString();
if (item != null)
attributes.add(item);
item = getAccuracyString();
if (item != null)
attributes.add(item);
item = getAltitudeString();
if (item != null)
attributes.add(item);
if (attributes.isEmpty())
attributes.add(getString(R.string.no_info));
AlertDialog.Builder info = new AlertDialog.Builder(ctx);
if (point != null)
info.setPositiveButton(autoAnnounce ? R.string.auto_announce_off : R.string.auto_announce_on,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
autoAnnounce = !autoAnnounce;
dialog.cancel();
}
});
info.setNegativeButton(R.string.shared_string_close, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
info.setItems(attributes.toArray(new String[attributes.size()]), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
info.show();
}
}