diff --git a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java index 57fe274509..d426a0f157 100644 --- a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java +++ b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java @@ -2,6 +2,7 @@ package net.osmand.plus.routing; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,940 +22,935 @@ import net.osmand.plus.voice.CommandBuilder; import net.osmand.plus.voice.CommandPlayer; import net.osmand.router.RouteSegmentResult; import net.osmand.router.TurnType; - -import java.lang.ref.WeakReference; - import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; - import alice.tuprolog.Struct; import alice.tuprolog.Term; - import android.media.AudioManager; import android.media.SoundPool; + public class VoiceRouter { - private static final int STATUS_UTWP_TOLD = -1; - private static final int STATUS_UNKNOWN = 0; - private static final int STATUS_LONG_PREPARE = 1; - private static final int STATUS_PREPARE = 2; - private static final int STATUS_TURN_IN = 3; - private static final int STATUS_TURN = 4; - private static final int STATUS_TOLD = 5; - public static final String TO_REF = "toRef"; - public static final String TO_STREET_NAME = "toStreetName"; - public static final String TO_DEST = "toDest"; - public static final String FROM_REF = "fromRef"; - public static final String FROM_STREET_NAME = "fromStreetName"; - public static final String FROM_DEST = "fromDest"; - - protected final RoutingHelper router; - protected static CommandPlayer player; - protected final OsmandSettings settings; - - private static boolean mute = false; - private static int currentStatus = STATUS_UNKNOWN; - private static boolean playedAndArriveAtTarget = false; - private static float playGoAheadDist = 0; - private static long lastAnnouncedSpeedLimit = 0; - private static long waitAnnouncedSpeedLimit = 0; - private static long lastAnnouncedOffRoute = 0; - private static long waitAnnouncedOffRoute = 0; - private static boolean suppressDest = false; - private static boolean announceBackOnRoute = false; - // private static long lastTimeRouteRecalcAnnounced = 0; - // Remember when last announcement was made - private static long lastAnnouncement = 0; - - // Default speed to have comfortable announcements (Speed in m/s) - private float DEFAULT_SPEED = 12; - private float TURN_DEFAULT_SPEED = 5; - - private int PREPARE_LONG_DISTANCE = 0; - private int PREPARE_LONG_DISTANCE_END = 0; - protected int PREPARE_DISTANCE = 0; - private int PREPARE_DISTANCE_END = 0; - private int TURN_IN_DISTANCE = 0; - private int TURN_IN_DISTANCE_END = 0; - private int TURN_DISTANCE = 0; - - private static VoiceCommandPending pendingCommand = null; - private static RouteDirectionInfo nextRouteDirection; - - public interface VoiceMessageListener { - void onVoiceMessage(); - } - - private ConcurrentHashMap, Integer> voiceMessageListeners; - - VoiceRouter(RoutingHelper router, final OsmandSettings settings) { - this.router = router; - this.settings = settings; - mute = settings.VOICE_MUTE.get(); - voiceMessageListeners = new ConcurrentHashMap<>(); - } - - public void setPlayer(CommandPlayer player) { - VoiceRouter.player = player; - if (pendingCommand != null && player != null) { - CommandBuilder newCommand = getNewCommandPlayerToPlay(); - if (newCommand != null) { - pendingCommand.play(newCommand); - } - pendingCommand = null; - } - } - - public CommandPlayer getPlayer() { - return player; - } - - public void setMute(boolean mute) { - this.mute = mute; - } - - public boolean isMute() { - return mute; - } - - private CommandBuilder getNewCommandPlayerToPlay() { - if (player == null) { - return null; - } - lastAnnouncement = System.currentTimeMillis(); - return player.newCommandBuilder(); - } - - public void updateAppMode() { - // Turn prompt starts either at distance, or additionally (TURN_IN and TURN only) if actual-lead-time(currentSpeed) < maximum-lead-time(defined by default speed) - if (router.getAppMode().isDerivedRoutingFrom(ApplicationMode.CAR)) { - PREPARE_LONG_DISTANCE = 3500; // [105 sec @ 120 km/h] - // Issue 1411: Do not play prompts for PREPARE_LONG_DISTANCE, not needed. - PREPARE_LONG_DISTANCE_END = 3000 + 1000; // [ 90 sec @ 120 km/h] - PREPARE_DISTANCE = 1500; // [125 sec] - PREPARE_DISTANCE_END = 1200; // [100 sec] - TURN_IN_DISTANCE = 300; // 23 sec - TURN_IN_DISTANCE_END = 210; // 16 sec - TURN_DISTANCE = 50; // 7 sec - TURN_DEFAULT_SPEED = 7f; // 25 km/h - DEFAULT_SPEED = 13; // 48 km/h - } else if (router.getAppMode().isDerivedRoutingFrom(ApplicationMode.BICYCLE)) { - PREPARE_LONG_DISTANCE = 500; // [100 sec] - // Do not play: - PREPARE_LONG_DISTANCE_END = 300 + 1000; // [ 60 sec] - PREPARE_DISTANCE = 200; // [ 40 sec] - PREPARE_DISTANCE_END = 120; // [ 24 sec] - TURN_IN_DISTANCE = 80; // 16 sec - TURN_IN_DISTANCE_END = 60; // 12 sec - TURN_DISTANCE = 30; // 6 sec. Check if this works with GPS accuracy! - TURN_DEFAULT_SPEED = DEFAULT_SPEED = 5; // 18 km/h - } else if (router.getAppMode().isDerivedRoutingFrom(ApplicationMode.PEDESTRIAN)) { - // prepare_long_distance warning not needed for pedestrian, but for goAhead prompt - PREPARE_LONG_DISTANCE = 500; - // Do not play: - PREPARE_LONG_DISTANCE_END = 300 + 300; - // Prepare distance is not needed for pedestrian - PREPARE_DISTANCE = 200; // [100 sec] - // Do not play: - PREPARE_DISTANCE_END = 150 + 100; // [ 75 sec] - TURN_IN_DISTANCE = 50; // 25 sec - TURN_IN_DISTANCE_END = 30; // 15 sec - TURN_DISTANCE = 15; // 7,5sec. Check if this works with GPS accuracy! - TURN_DEFAULT_SPEED = DEFAULT_SPEED = 2f; // 7,2 km/h - } else { - DEFAULT_SPEED = router.getAppMode().getDefaultSpeed(); - TURN_DEFAULT_SPEED = DEFAULT_SPEED / 2; - PREPARE_LONG_DISTANCE = (int) (DEFAULT_SPEED * 270); - // Do not play: - PREPARE_LONG_DISTANCE_END = (int) (DEFAULT_SPEED * 230) * 2; - PREPARE_DISTANCE = (int) (DEFAULT_SPEED * 115); - PREPARE_DISTANCE_END = (int) (DEFAULT_SPEED * 92); - TURN_IN_DISTANCE = (int) (DEFAULT_SPEED * 23); - TURN_IN_DISTANCE_END = (int) (DEFAULT_SPEED * 16); - TURN_DISTANCE = (int) (DEFAULT_SPEED * 7); - } - } - - private double btScoDelayDistance = 0; - - public boolean isDistanceLess(float currentSpeed, double dist, double etalon, float defSpeed) { - if (defSpeed <= 0) { - defSpeed = DEFAULT_SPEED; - } - if (currentSpeed <= 0) { - currentSpeed = DEFAULT_SPEED; - } - - // Trigger close prompts earlier if delayed for BT SCO connection establishment - if ((settings.AUDIO_STREAM_GUIDANCE.getModeValue(router.getAppMode()) == 0) && !AbstractPrologCommandPlayer.btScoStatus) { - btScoDelayDistance = currentSpeed * (double) settings.BT_SCO_DELAY.get() / 1000; - } - - if ((dist < etalon + btScoDelayDistance) || ((dist - btScoDelayDistance) / currentSpeed) < (etalon / defSpeed)) { - return true; - } - return false; - } - - public int calculateImminent(float dist, Location loc) { - float speed = DEFAULT_SPEED; - if (loc != null && loc.hasSpeed()) { - speed = loc.getSpeed(); - } - if (isDistanceLess(speed, dist, TURN_DISTANCE, 0f)) { - return 0; - } else if (dist <= PREPARE_DISTANCE) { - return 1; - } else if (dist <= PREPARE_LONG_DISTANCE) { - return 2; - } else { - return -1; - } - } - - private void nextStatusAfter(int previousStatus) { - //STATUS_UNKNOWN=0 -> STATUS_LONG_PREPARE=1 -> STATUS_PREPARE=2 -> STATUS_TURN_IN=3 -> STATUS_TURN=4 -> STATUS_TOLD=5 - if (previousStatus != STATUS_TOLD) { - this.currentStatus = previousStatus + 1; - } else { - this.currentStatus = previousStatus; - } - } - - private boolean statusNotPassed(int statusToCheck) { - return currentStatus <= statusToCheck; - } - - public void announceOffRoute(double dist) { - long ms = System.currentTimeMillis(); - if (waitAnnouncedOffRoute == 0 || ms - lastAnnouncedOffRoute > waitAnnouncedOffRoute) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p != null) { - notifyOnVoiceMessage(); - p.offRoute(dist).play(); - announceBackOnRoute = true; - } - if (waitAnnouncedOffRoute == 0) { - waitAnnouncedOffRoute = 60000; - } else { - waitAnnouncedOffRoute *= 2.5; - } - lastAnnouncedOffRoute = ms; - } - } - - public void announceBackOnRoute() { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (announceBackOnRoute) { - if (p != null) { - notifyOnVoiceMessage(); - p.backOnRoute().play(); - } - announceBackOnRoute = false; - } - } - - public void approachWaypoint(Location location, List points) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p == null) { - return; - } - notifyOnVoiceMessage(); - double[] dist = new double[1]; - makeSound(); - String text = getText(location, points, dist); - p.goAhead(dist[0], new StreetName()).andArriveAtWayPoint(text).play(); - } - - public void approachFavorite(Location location, List points) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p == null) { - return; - } - notifyOnVoiceMessage(); - double[] dist = new double[1]; - makeSound(); - String text = getText(location, points, dist); - p.goAhead(dist[0], new StreetName()).andArriveAtFavorite(text).play(); - } - - public void approachPoi(Location location, List points) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p == null) { - return; - } - - notifyOnVoiceMessage(); - double[] dist = new double[1]; - String text = getText(location, points, dist); - p.goAhead(dist[0], new StreetName()).andArriveAtPoi(text).play(); - } - - public void announceWaypoint(List points) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p == null) { - return; - } - notifyOnVoiceMessage(); - makeSound(); - String text = getText(null, points, null); - p.arrivedAtWayPoint(text).play(); - } - - public void announceFavorite(List points) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p == null) { - return; - } - notifyOnVoiceMessage(); - makeSound(); - String text = getText(null, points, null); - p.arrivedAtFavorite(text).play(); - } - - public void announcePoi(List points) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p == null) { - return; - } - notifyOnVoiceMessage(); - String text = getText(null, points, null); - p.arrivedAtPoi(text).play(); - } - - protected String getText(Location location, List points, double[] dist) { - String text = ""; - for (LocationPointWrapper point : points) { - // Need to calculate distance to nearest point - if (text.length() == 0) { - if (location != null && dist != null) { - dist[0] = point.getDeviationDistance() + - MapUtils.getDistance(location.getLatitude(), location.getLongitude(), - point.getPoint().getLatitude(), point.getPoint().getLongitude()); - } - } else { - text += ", "; - } - text += PointDescription.getSimpleName(point.getPoint(), router.getApplication()); - } - return text; - } - - public void announceAlarm(AlarmInfo info, float speed) { - AlarmInfoType type = info.getType(); - if (type == AlarmInfoType.SPEED_LIMIT) { - announceSpeedAlarm(info.getIntValue(), speed); - } else if (type == AlarmInfoType.SPEED_CAMERA) { - if (router.getSettings().SPEAK_SPEED_CAMERA.get()) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p != null) { - notifyOnVoiceMessage(); - p.attention(type + "").play(); - } - } - } else if (type == AlarmInfoType.PEDESTRIAN) { - if (router.getSettings().SPEAK_PEDESTRIAN.get()) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p != null) { - notifyOnVoiceMessage(); - p.attention(type + "").play(); - } - } - } else if (type == AlarmInfoType.TUNNEL) { - if (router.getSettings().SPEAK_TUNNELS.get()) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p != null) { - notifyOnVoiceMessage(); - p.attention(type + "").play(); - } - } - } else { - if (router.getSettings().SPEAK_TRAFFIC_WARNINGS.get()) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p != null) { - notifyOnVoiceMessage(); - p.attention(type + "").play(); - } - // See Issue 2377: Announce destination again - after some motorway tolls roads split shortly after the toll - if (type == AlarmInfoType.TOLL_BOOTH) { - suppressDest = false; - } - } - } - } - - public void announceSpeedAlarm(int maxSpeed, float speed) { - long ms = System.currentTimeMillis(); - if (waitAnnouncedSpeedLimit == 0) { - // Wait 10 seconds before announcement - if (ms - lastAnnouncedSpeedLimit > 120 * 1000) { - waitAnnouncedSpeedLimit = ms; - } - } else { - // If we wait before more than 20 sec (reset counter) - if (ms - waitAnnouncedSpeedLimit > 20 * 1000) { - waitAnnouncedSpeedLimit = 0; - } else if (router.getSettings().SPEAK_SPEED_LIMIT.get() && ms - waitAnnouncedSpeedLimit > 10 * 1000) { - CommandBuilder p = getNewCommandPlayerToPlay(); - if (p != null) { - notifyOnVoiceMessage(); - lastAnnouncedSpeedLimit = ms; - waitAnnouncedSpeedLimit = 0; - p.speedAlarm(maxSpeed, speed).play(); - } - } - } - } - - private boolean isTargetPoint(NextDirectionInfo info) { - boolean in = info != null && info.intermediatePoint; - boolean target = info == null || info.directionInfo == null - || info.directionInfo.distance == 0; - return in || target; - } - - private boolean needsInforming() { - final Integer repeat = settings.KEEP_INFORMING.get(); - if (repeat == null || repeat == 0) return false; - - final long notBefore = lastAnnouncement + repeat * 60 * 1000L; - - return System.currentTimeMillis() > notBefore; - } - - /** - * Updates status of voice guidance - * - * @param currentLocation - */ - protected void updateStatus(Location currentLocation, boolean repeat) { - // Directly after turn: goAhead (dist), unless: - // < PREPARE_LONG_DISTANCE (e.g. 3500m): playPrepareTurn (-not played any more-) - // < PREPARE_DISTANCE (e.g. 1500m): playPrepareTurn ("Turn after ...") - // < TURN_IN_DISTANCE (e.g. 390m or 30sec): playMakeTurnIn ("Turn in ...") - // < TURN_DISTANCE (e.g. 50m or 7sec): playMakeTurn ("Turn ...") - float speed = DEFAULT_SPEED; - if (currentLocation != null && currentLocation.hasSpeed()) { - speed = Math.max(currentLocation.getSpeed(), speed); - } - - NextDirectionInfo nextInfo = router.getNextRouteDirectionInfo(new NextDirectionInfo(), true); - RouteSegmentResult currentSegment = router.getCurrentSegmentResult(); - if (nextInfo == null || nextInfo.directionInfo == null) { - return; - } - int dist = nextInfo.distanceTo; - RouteDirectionInfo next = nextInfo.directionInfo; - - // If routing is changed update status to unknown - if (next != nextRouteDirection) { - nextRouteDirection = next; - currentStatus = STATUS_UNKNOWN; - suppressDest = false; - playedAndArriveAtTarget = false; - announceBackOnRoute = false; - if (playGoAheadDist != -1) { - playGoAheadDist = 0; - } - } - - if (!repeat) { - if (dist <= 0) { - return; - } else if (needsInforming()) { - playGoAhead(dist, getSpeakableStreetName(currentSegment, next, false)); - return; - } else if (currentStatus == STATUS_TOLD) { - // nothing said possibly that's wrong case we should say before that - // however it should be checked manually ? - return; - } - } - - if (currentStatus == STATUS_UNKNOWN) { - // Play "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away - if ((playGoAheadDist == -1) || (dist > PREPARE_LONG_DISTANCE)) { - playGoAheadDist = dist - 3 * TURN_DISTANCE; - } - } - - NextDirectionInfo nextNextInfo = router.getNextRouteDirectionInfoAfter(nextInfo, new NextDirectionInfo(), true); //I think "true" is correct here, not "!repeat" - // Note: getNextRouteDirectionInfoAfter(nextInfo, x, y).distanceTo is distance from nextInfo, not from current position! - - // STATUS_TURN = "Turn (now)" - if ((repeat || statusNotPassed(STATUS_TURN)) && isDistanceLess(speed, dist, TURN_DISTANCE, TURN_DEFAULT_SPEED)) { - if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END && nextNextInfo != null) { - playMakeTurn(currentSegment, next, nextNextInfo); - } else { - playMakeTurn(currentSegment, next, null); - } - if (!next.getTurnType().goAhead() && isTargetPoint(nextNextInfo)) { // !goAhead() avoids isolated "and arrive.." prompt, as goAhead() is not pronounced - if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) { - // Issue #2865: Ensure a distance associated with the destination arrival is always announced, either here, or in subsequent "Turn in" prompt - // Distance fon non-straights already announced in "Turn (now)"'s nextnext code above - if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null) && nextNextInfo.directionInfo.getTurnType().goAhead()) { - playThen(); - playGoAhead(nextNextInfo.distanceTo, new StreetName()); - } - playAndArriveAtDestination(nextNextInfo); - } else if (nextNextInfo.distanceTo < 1.2f * TURN_IN_DISTANCE_END) { - // 1.2 is safety margin should the subsequent "Turn in" prompt not fit in amy more - playThen(); - playGoAhead(nextNextInfo.distanceTo, new StreetName()); - playAndArriveAtDestination(nextNextInfo); - } - } - nextStatusAfter(STATUS_TURN); - - // STATUS_TURN_IN = "Turn in ..." - } else if ((repeat || statusNotPassed(STATUS_TURN_IN)) && isDistanceLess(speed, dist, TURN_IN_DISTANCE, 0f)) { - if (repeat || dist >= TURN_IN_DISTANCE_END) { - if ((isDistanceLess(speed, nextNextInfo.distanceTo, TURN_DISTANCE, 0f) || nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) && - nextNextInfo != null) { - playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, nextNextInfo.directionInfo); - } else { - playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, null); - } - playGoAndArriveAtDestination(repeat, nextInfo, currentSegment); - } - nextStatusAfter(STATUS_TURN_IN); - - // STATUS_PREPARE = "Turn after ..." - } else if ((repeat || statusNotPassed(STATUS_PREPARE)) && (dist <= PREPARE_DISTANCE)) { - if (repeat || dist >= PREPARE_DISTANCE_END) { - if (!repeat && (next.getTurnType().keepLeft() || next.getTurnType().keepRight())) { - // Do not play prepare for keep left/right - } else { - playPrepareTurn(currentSegment, next, dist); - playGoAndArriveAtDestination(repeat, nextInfo, currentSegment); - } - } - nextStatusAfter(STATUS_PREPARE); - - // STATUS_LONG_PREPARE = also "Turn after ...", we skip this now, users said this is obsolete - } else if ((repeat || statusNotPassed(STATUS_LONG_PREPARE)) && (dist <= PREPARE_LONG_DISTANCE)) { - if (repeat || dist >= PREPARE_LONG_DISTANCE_END) { - playPrepareTurn(currentSegment, next, dist); - playGoAndArriveAtDestination(repeat, nextInfo, currentSegment); - } - nextStatusAfter(STATUS_LONG_PREPARE); - - // STATUS_UNKNOWN = "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away - } else if (statusNotPassed(STATUS_UNKNOWN)) { - // Strange how we get here but - nextStatusAfter(STATUS_UNKNOWN); - } else if (repeat || (statusNotPassed(STATUS_PREPARE) && dist < playGoAheadDist)) { - playGoAheadDist = 0; - playGoAhead(dist, getSpeakableStreetName(currentSegment, next, false)); - } - } - - public void announceCurrentDirection(Location currentLocation) { - synchronized (router) { - if (currentStatus != STATUS_UTWP_TOLD) { - updateStatus(currentLocation, true); - } else if (playMakeUTwp()) { - playGoAheadDist = 0; - } - } - } - - private boolean playMakeUTwp() { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - play.makeUTwp().play(); - return true; - } - return false; - } - - void playThen() { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - play.then().play(); - } - } - - private void playGoAhead(int dist, StreetName streetName) { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - play.goAhead(dist, streetName).play(); - } - } - - private StreetName getSpeakableStreetName(RouteSegmentResult currentSegment, RouteDirectionInfo i, boolean includeDest) { - Map result = new HashMap<>(); - if (i == null || !router.getSettings().SPEAK_STREET_NAMES.get()) { - return new StreetName(result); - } - if (player != null && player.supportsStructuredStreetNames()) { - - // Issue 2377: Play Dest here only if not already previously announced, to avoid repetition - if (includeDest == true) { - result.put(TO_REF, getNonNullString(getSpeakablePointName(i.getRef()))); - result.put(TO_STREET_NAME, getNonNullString(getSpeakablePointName(i.getStreetName()))); - result.put(TO_DEST, getNonNullString(getSpeakablePointName(i.getDestinationName()))); - } else { - result.put(TO_REF, getNonNullString(getSpeakablePointName(i.getRef()))); - result.put(TO_STREET_NAME, getNonNullString(getSpeakablePointName(i.getStreetName()))); - result.put(TO_DEST, ""); - } - if (currentSegment != null) { - // Issue 2377: Play Dest here only if not already previously announced, to avoid repetition - if (includeDest == true) { - RouteDataObject obj = currentSegment.getObject(); - result.put(FROM_REF, getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(), - settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())))); - result.put(FROM_STREET_NAME, getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(), - settings.MAP_TRANSLITERATE_NAMES.get())))); - result.put(FROM_DEST, getNonNullString(getSpeakablePointName(obj.getDestinationName(settings.MAP_PREFERRED_LOCALE.get(), - settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())))); - } else { - RouteDataObject obj = currentSegment.getObject(); - result.put(FROM_REF, getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(), - settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())))); - result.put(FROM_STREET_NAME, getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(), - settings.MAP_TRANSLITERATE_NAMES.get())))); - result.put(FROM_DEST, ""); - } - } - - } else { - result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef()))); - result.put(TO_STREET_NAME, getNonNullString(getSpeakablePointName(i.getStreetName()))); - result.put(TO_DEST, ""); - } - return new StreetName(result); - } - - - private String getNonNullString(String speakablePointName) { - return speakablePointName == null ? "" : speakablePointName; - } - - private static String getSpeakablePointName(String pn) { - // Replace characters which may produce unwanted TTS sounds: - String pl = ""; - if (player != null) { - pl = player.getLanguage(); - } - if (pn != null) { - pn = pn.replace('-', ' '); - pn = pn.replace(':', ' '); - pn = pn.replace(";", ", "); // Trailing blank prevents punctuation being pronounced. Replace by comma for better intonation. - pn = pn.replace("/", ", "); // Slash is actually pronounced by many TTS engines, creating an awkward voice prompt, better replace by comma. - if (!pl.startsWith("de")) { - pn = pn.replace("\u00df", "ss"); // Helps non-German TTS voices to pronounce German Straße (=street) - } - if (pl.startsWith("en")) { - pn = pn.replace("SR", "S R"); // Avoid SR (as for State Route or Strada Regionale) be pronounced as "Senior" in English TTS voice - pn = pn.replace("Dr.", "Dr "); // Avoid pause many English TTS voices introduce after period - } - if (pl.startsWith("de")) { - if (pn.startsWith("St ")) { - pn = pn.replace("St ", "S T "); // German Staatsstrasse, abbreviated St, often mispronounced - } - } - } - return pn; - } - - private void playPrepareTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist) { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - String tParam = getTurnType(next.getTurnType()); - if (tParam != null) { - notifyOnVoiceMessage(); - play.prepareTurn(tParam, dist, getSpeakableStreetName(currentSegment, next, true)).play(); - } else if (next.getTurnType().isRoundAbout()) { - notifyOnVoiceMessage(); - play.prepareRoundAbout(dist, next.getTurnType().getExitOut(), getSpeakableStreetName(currentSegment, next, true)).play(); - } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) { - notifyOnVoiceMessage(); - play.prepareMakeUT(dist, getSpeakableStreetName(currentSegment, next, true)).play(); - } - } - } - - private void playMakeTurnIn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist, RouteDirectionInfo pronounceNextNext) { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - String tParam = getTurnType(next.getTurnType()); - boolean isPlay = true; - if (tParam != null) { - play.turn(tParam, dist, getSpeakableStreetName(currentSegment, next, true)); - suppressDest = true; - } else if (next.getTurnType().isRoundAbout()) { - play.roundAbout(dist, next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableStreetName(currentSegment, next, true)); - // Other than in prepareTurn, in prepareRoundabout we do not announce destination, so we can repeat it one more time - suppressDest = false; - } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) { - play.makeUT(dist, getSpeakableStreetName(currentSegment, next, true)); - suppressDest = true; - } else { - isPlay = false; - } - // 'then keep' preparation for next after next. (Also announces an interim straight segment, which is not pronounced above.) - if (pronounceNextNext != null) { - TurnType t = pronounceNextNext.getTurnType(); - isPlay = true; - if (t.getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) { - play.goAhead(dist, getSpeakableStreetName(currentSegment, next, true)); - } - if (t.getValue() == TurnType.TL || t.getValue() == TurnType.TSHL || t.getValue() == TurnType.TSLL - || t.getValue() == TurnType.TU || t.getValue() == TurnType.KL) { - play.then().bearLeft(getSpeakableStreetName(currentSegment, next, false)); - } else if (t.getValue() == TurnType.TR || t.getValue() == TurnType.TSHR || t.getValue() == TurnType.TSLR - || t.getValue() == TurnType.TRU || t.getValue() == TurnType.KR) { - play.then().bearRight(getSpeakableStreetName(currentSegment, next, false)); - } - } - if (isPlay) { - notifyOnVoiceMessage(); - play.play(); - } - } - } - - private void playGoAndArriveAtDestination(boolean repeat, NextDirectionInfo nextInfo, RouteSegmentResult currentSegment) { - RouteDirectionInfo next = nextInfo.directionInfo; - if (isTargetPoint(nextInfo) && (!playedAndArriveAtTarget || repeat)) { - if (next.getTurnType().goAhead()) { - playGoAhead(nextInfo.distanceTo, getSpeakableStreetName(currentSegment, next, false)); - playAndArriveAtDestination(nextInfo); - playedAndArriveAtTarget = true; - } else if (nextInfo.distanceTo <= 2 * TURN_IN_DISTANCE) { - playAndArriveAtDestination(nextInfo); - playedAndArriveAtTarget = true; - } - } - } - - private void playAndArriveAtDestination(NextDirectionInfo info) { - if (isTargetPoint(info)) { - String pointName = (info == null || info.pointName == null) ? "" : info.pointName; - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - if (info != null && info.intermediatePoint) { - play.andArriveAtIntermediatePoint(getSpeakablePointName(pointName)).play(); - } else { - play.andArriveAtDestination(getSpeakablePointName(pointName)).play(); - } - } - } - } - - private void playMakeTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, NextDirectionInfo nextNextInfo) { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - String tParam = getTurnType(next.getTurnType()); - boolean isplay = true; - if (tParam != null) { - play.turn(tParam, getSpeakableStreetName(currentSegment, next, !suppressDest)); - } else if (next.getTurnType().isRoundAbout()) { - play.roundAbout(next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableStreetName(currentSegment, next, !suppressDest)); - } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) { - play.makeUT(getSpeakableStreetName(currentSegment, next, !suppressDest)); - // Do not announce goAheads - //} else if (next.getTurnType().getValue() == TurnType.C)) { - // play.goAhead(); - } else { - isplay = false; - } - // Add turn after next - if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null)) { - - // This case only needed should we want a prompt at the end of straight segments (equivalent of makeTurn) when nextNextInfo should be announced again there. - if (nextNextInfo.directionInfo.getTurnType().getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) { - play.goAhead(); - isplay = true; - } - - String t2Param = getTurnType(nextNextInfo.directionInfo.getTurnType()); - if (t2Param != null) { - if (isplay) { - play.then(); - play.turn(t2Param, nextNextInfo.distanceTo, new StreetName()); - } - } else if (nextNextInfo.directionInfo.getTurnType().isRoundAbout()) { - if (isplay) { - play.then(); - play.roundAbout(nextNextInfo.distanceTo, nextNextInfo.directionInfo.getTurnType().getTurnAngle(), nextNextInfo.directionInfo.getTurnType().getExitOut(), new StreetName()); - } - } else if (nextNextInfo.directionInfo.getTurnType().getValue() == TurnType.TU) { - if (isplay) { - play.then(); - play.makeUT(nextNextInfo.distanceTo, new StreetName()); - } - } - } - if (isplay) { - notifyOnVoiceMessage(); - play.play(); - } - } - } - - private String getTurnType(TurnType t) { - if (TurnType.TL == t.getValue()) { - return AbstractPrologCommandPlayer.A_LEFT; - } else if (TurnType.TSHL == t.getValue()) { - return AbstractPrologCommandPlayer.A_LEFT_SH; - } else if (TurnType.TSLL == t.getValue()) { - return AbstractPrologCommandPlayer.A_LEFT_SL; - } else if (TurnType.TR == t.getValue()) { - return AbstractPrologCommandPlayer.A_RIGHT; - } else if (TurnType.TSHR == t.getValue()) { - return AbstractPrologCommandPlayer.A_RIGHT_SH; - } else if (TurnType.TSLR == t.getValue()) { - return AbstractPrologCommandPlayer.A_RIGHT_SL; - } else if (TurnType.KL == t.getValue()) { - return AbstractPrologCommandPlayer.A_LEFT_KEEP; - } else if (TurnType.KR == t.getValue()) { - return AbstractPrologCommandPlayer.A_RIGHT_KEEP; - } - return null; - } - - public void gpsLocationLost() { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - play.gpsLocationLost().play(); - } - } - - public void gpsLocationRecover() { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - play.gpsLocationRecover().play(); - } - } - - public void newRouteIsCalculated(boolean newRoute) { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - if (!newRoute) { - play.routeRecalculated(router.getLeftDistance(), router.getLeftTime()).play(); - } else { - play.newRouteCalculated(router.getLeftDistance(), router.getLeftTime()).play(); - } - } else if (player == null) { - pendingCommand = new VoiceCommandPending(!newRoute ? VoiceCommandPending.ROUTE_RECALCULATED : VoiceCommandPending.ROUTE_CALCULATED, this); - } - if (newRoute) { - playGoAheadDist = -1; - } - currentStatus = STATUS_UNKNOWN; - suppressDest = false; - nextRouteDirection = null; - } - - public void arrivedDestinationPoint(String name) { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - play.arrivedAtDestination(getSpeakablePointName(name)).play(); - } - } - - public void arrivedIntermediatePoint(String name) { - CommandBuilder play = getNewCommandPlayerToPlay(); - if (play != null) { - notifyOnVoiceMessage(); - play.arrivedAtIntermediatePoint(getSpeakablePointName(name)).play(); - } - } - - // This is not needed, used are only arrivedIntermediatePoint (for points on the route) or announceWaypoint (for points near the route=) - //public void arrivedWayPoint(String name) { - // CommandBuilder play = getNewCommandPlayerToPlay(); - // if (play != null) { - // notifyOnVoiceMessage(); - // play.arrivedAtWayPoint(getSpeakablePointName(name)).play(); - // } - //} - - public void onApplicationTerminate() { - if (player != null) { - player.clear(); - } - } - - public void interruptRouteCommands() { - if (player != null) { - player.stop(); - } - } - - /** - * Command to wait until voice player is initialized - */ - private class VoiceCommandPending { - public static final int ROUTE_CALCULATED = 1; - public static final int ROUTE_RECALCULATED = 2; - protected final int type; - private final VoiceRouter voiceRouter; - - public VoiceCommandPending(int type, VoiceRouter voiceRouter) { - this.type = type; - this.voiceRouter = voiceRouter; - } - - public void play(CommandBuilder newCommand) { - int left = voiceRouter.router.getLeftDistance(); - int time = voiceRouter.router.getLeftTime(); - if (left > 0) { - if (type == ROUTE_CALCULATED) { - notifyOnVoiceMessage(); - newCommand.newRouteCalculated(left, time).play(); - } else if (type == ROUTE_RECALCULATED) { - notifyOnVoiceMessage(); - newCommand.routeRecalculated(left, time).play(); - } - } - } - } - - private void makeSound() { - if (isMute()) { - return; - } - SoundPool sp = new SoundPool(5, AudioManager.STREAM_MUSIC, 0); - int soundClick = -1; - boolean success = true; - try { - // Taken unaltered from https://freesound.org/people/Corsica_S/sounds/91926/ under license http://creativecommons.org/licenses/by/3.0/ : - soundClick = sp.load(settings.getContext().getAssets().openFd("sounds/ding.ogg"), 1); - } catch (IOException e) { - e.printStackTrace(); - success = false; - } - if (success) { - sp.play(soundClick, 1, 1, 0, 0, 1); - } - } - - public void addVoiceMessageListener(VoiceMessageListener voiceMessageListener) { - voiceMessageListeners.put(new WeakReference<>(voiceMessageListener), 0); - } - - public void removeVoiceMessageListener(VoiceMessageListener voiceMessageListener) { - voiceMessageListeners.remove(new WeakReference<>(voiceMessageListener)); - } - - public void notifyOnVoiceMessage() { - for (WeakReference weakReferenceWrapper : voiceMessageListeners.keySet()) { - VoiceMessageListener lnt = weakReferenceWrapper.get(); - if (lnt != null) { - lnt.onVoiceMessage(); - } - } - } -} + private static final int STATUS_UTWP_TOLD = -1; + private static final int STATUS_UNKNOWN = 0; + private static final int STATUS_LONG_PREPARE = 1; + private static final int STATUS_PREPARE = 2; + private static final int STATUS_TURN_IN = 3; + private static final int STATUS_TURN = 4; + private static final int STATUS_TOLD = 5; + public static final String TO_REF = "toRef"; + public static final String TO_STREET_NAME = "toStreetName"; + public static final String TO_DEST = "toDest"; + public static final String FROM_REF = "fromRef"; + public static final String FROM_STREET_NAME = "fromStreetName"; + public static final String FROM_DEST = "fromDest"; + + protected final RoutingHelper router; + protected static CommandPlayer player; + protected final OsmandSettings settings; + + private static boolean mute = false; + private static int currentStatus = STATUS_UNKNOWN; + private static boolean playedAndArriveAtTarget = false; + private static float playGoAheadDist = 0; + private static long lastAnnouncedSpeedLimit = 0; + private static long waitAnnouncedSpeedLimit = 0; + private static long lastAnnouncedOffRoute = 0; + private static long waitAnnouncedOffRoute = 0; + private static boolean suppressDest = false; + private static boolean announceBackOnRoute = false; + // private static long lastTimeRouteRecalcAnnounced = 0; + // Remember when last announcement was made + private static long lastAnnouncement = 0; + + // Default speed to have comfortable announcements (Speed in m/s) + private float DEFAULT_SPEED = 12; + private float TURN_DEFAULT_SPEED = 5; + + private int PREPARE_LONG_DISTANCE = 0; + private int PREPARE_LONG_DISTANCE_END = 0; + protected int PREPARE_DISTANCE = 0; + private int PREPARE_DISTANCE_END = 0; + private int TURN_IN_DISTANCE = 0; + private int TURN_IN_DISTANCE_END = 0; + private int TURN_DISTANCE = 0; + + private static VoiceCommandPending pendingCommand = null; + private static RouteDirectionInfo nextRouteDirection; + + public interface VoiceMessageListener { + void onVoiceMessage(); + } + + private ConcurrentHashMap, Integer> voiceMessageListeners; + + VoiceRouter(RoutingHelper router, final OsmandSettings settings) { + this.router = router; + this.settings = settings; + mute = settings.VOICE_MUTE.get(); + voiceMessageListeners = new ConcurrentHashMap<>(); + } + + public void setPlayer(CommandPlayer player) { + VoiceRouter.player = player; + if (pendingCommand != null && player != null) { + CommandBuilder newCommand = getNewCommandPlayerToPlay(); + if (newCommand != null) { + pendingCommand.play(newCommand); + } + pendingCommand = null; + } + } + + public CommandPlayer getPlayer() { + return player; + } + + public void setMute(boolean mute) { + this.mute = mute; + } + + public boolean isMute() { + return mute; + } + + private CommandBuilder getNewCommandPlayerToPlay() { + if (player == null) { + return null; + } + lastAnnouncement = System.currentTimeMillis(); + return player.newCommandBuilder(); + } + + public void updateAppMode() { + // Turn prompt starts either at distance, or additionally (TURN_IN and TURN only) if actual-lead-time(currentSpeed) < maximum-lead-time(defined by default speed) + if (router.getAppMode().isDerivedRoutingFrom(ApplicationMode.CAR)) { + PREPARE_LONG_DISTANCE = 3500; // [105 sec @ 120 km/h] + // Issue 1411: Do not play prompts for PREPARE_LONG_DISTANCE, not needed. + PREPARE_LONG_DISTANCE_END = 3000 + 1000; // [ 90 sec @ 120 km/h] + PREPARE_DISTANCE = 1500; // [125 sec] + PREPARE_DISTANCE_END = 1200; // [100 sec] + TURN_IN_DISTANCE = 300; // 23 sec + TURN_IN_DISTANCE_END = 210; // 16 sec + TURN_DISTANCE = 50; // 7 sec + TURN_DEFAULT_SPEED = 7f; // 25 km/h + DEFAULT_SPEED = 13; // 48 km/h + } else if (router.getAppMode().isDerivedRoutingFrom(ApplicationMode.BICYCLE)) { + PREPARE_LONG_DISTANCE = 500; // [100 sec] + // Do not play: + PREPARE_LONG_DISTANCE_END = 300 + 1000; // [ 60 sec] + PREPARE_DISTANCE = 200; // [ 40 sec] + PREPARE_DISTANCE_END = 120; // [ 24 sec] + TURN_IN_DISTANCE = 80; // 16 sec + TURN_IN_DISTANCE_END = 60; // 12 sec + TURN_DISTANCE = 30; // 6 sec. Check if this works with GPS accuracy! + TURN_DEFAULT_SPEED = DEFAULT_SPEED = 5; // 18 km/h + } else if (router.getAppMode().isDerivedRoutingFrom(ApplicationMode.PEDESTRIAN)) { + // prepare_long_distance warning not needed for pedestrian, but for goAhead prompt + PREPARE_LONG_DISTANCE = 500; + // Do not play: + PREPARE_LONG_DISTANCE_END = 300 + 300; + // Prepare distance is not needed for pedestrian + PREPARE_DISTANCE = 200; // [100 sec] + // Do not play: + PREPARE_DISTANCE_END = 150 + 100; // [ 75 sec] + TURN_IN_DISTANCE = 50; // 25 sec + TURN_IN_DISTANCE_END = 30; // 15 sec + TURN_DISTANCE = 15; // 7,5sec. Check if this works with GPS accuracy! + TURN_DEFAULT_SPEED = DEFAULT_SPEED = 2f; // 7,2 km/h + } else { + DEFAULT_SPEED = router.getAppMode().getDefaultSpeed(); + TURN_DEFAULT_SPEED = DEFAULT_SPEED / 2; + PREPARE_LONG_DISTANCE = (int) (DEFAULT_SPEED * 270); + // Do not play: + PREPARE_LONG_DISTANCE_END = (int) (DEFAULT_SPEED * 230) * 2; + PREPARE_DISTANCE = (int) (DEFAULT_SPEED * 115); + PREPARE_DISTANCE_END = (int) (DEFAULT_SPEED * 92); + TURN_IN_DISTANCE = (int) (DEFAULT_SPEED * 23); + TURN_IN_DISTANCE_END = (int) (DEFAULT_SPEED * 16); + TURN_DISTANCE = (int) (DEFAULT_SPEED * 7); + } + } + + private double btScoDelayDistance = 0; + + public boolean isDistanceLess(float currentSpeed, double dist, double etalon, float defSpeed) { + if (defSpeed <= 0) { + defSpeed = DEFAULT_SPEED; + } + if (currentSpeed <= 0) { + currentSpeed = DEFAULT_SPEED; + } + + // Trigger close prompts earlier if delayed for BT SCO connection establishment + if ((settings.AUDIO_STREAM_GUIDANCE.getModeValue(router.getAppMode()) == 0) && !AbstractPrologCommandPlayer.btScoStatus) { + btScoDelayDistance = currentSpeed * (double) settings.BT_SCO_DELAY.get() / 1000; + } + + if ((dist < etalon + btScoDelayDistance) || ((dist - btScoDelayDistance) / currentSpeed) < (etalon / defSpeed)) { + return true; + } + return false; + } + + public int calculateImminent(float dist, Location loc) { + float speed = DEFAULT_SPEED; + if (loc != null && loc.hasSpeed()) { + speed = loc.getSpeed(); + } + if (isDistanceLess(speed, dist, TURN_DISTANCE, 0f)) { + return 0; + } else if (dist <= PREPARE_DISTANCE) { + return 1; + } else if (dist <= PREPARE_LONG_DISTANCE) { + return 2; + } else { + return -1; + } + } + + private void nextStatusAfter(int previousStatus) { + //STATUS_UNKNOWN=0 -> STATUS_LONG_PREPARE=1 -> STATUS_PREPARE=2 -> STATUS_TURN_IN=3 -> STATUS_TURN=4 -> STATUS_TOLD=5 + if (previousStatus != STATUS_TOLD) { + this.currentStatus = previousStatus + 1; + } else { + this.currentStatus = previousStatus; + } + } + + private boolean statusNotPassed(int statusToCheck) { + return currentStatus <= statusToCheck; + } + + public void announceOffRoute(double dist) { + long ms = System.currentTimeMillis(); + if (waitAnnouncedOffRoute == 0 || ms - lastAnnouncedOffRoute > waitAnnouncedOffRoute) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p != null) { + notifyOnVoiceMessage(); + p.offRoute(dist).play(); + announceBackOnRoute = true; + } + if (waitAnnouncedOffRoute == 0) { + waitAnnouncedOffRoute = 60000; + } else { + waitAnnouncedOffRoute *= 2.5; + } + lastAnnouncedOffRoute = ms; + } + } + + public void announceBackOnRoute() { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (announceBackOnRoute) { + if (p != null) { + notifyOnVoiceMessage(); + p.backOnRoute().play(); + } + announceBackOnRoute = false; + } + } + + public void approachWaypoint(Location location, List points) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p == null) { + return; + } + notifyOnVoiceMessage(); + double[] dist = new double[1]; + makeSound(); + String text = getText(location, points, dist); + p.goAhead(dist[0], new StreetName()).andArriveAtWayPoint(text).play(); + } + + public void approachFavorite(Location location, List points) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p == null) { + return; + } + notifyOnVoiceMessage(); + double[] dist = new double[1]; + makeSound(); + String text = getText(location, points, dist); + p.goAhead(dist[0], new StreetName()).andArriveAtFavorite(text).play(); + } + + public void approachPoi(Location location, List points) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p == null) { + return; + } + + notifyOnVoiceMessage(); + double[] dist = new double[1]; + String text = getText(location, points, dist); + p.goAhead(dist[0], new StreetName()).andArriveAtPoi(text).play(); + } + + public void announceWaypoint(List points) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p == null) { + return; + } + notifyOnVoiceMessage(); + makeSound(); + String text = getText(null, points,null); + p.arrivedAtWayPoint(text).play(); + } + + public void announceFavorite(List points) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p == null) { + return; + } + notifyOnVoiceMessage(); + makeSound(); + String text = getText(null, points,null); + p.arrivedAtFavorite(text).play(); + } + + public void announcePoi(List points) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p == null) { + return; + } + notifyOnVoiceMessage(); + String text = getText(null, points,null); + p.arrivedAtPoi(text).play(); + } + + protected String getText(Location location, List points, double[] dist) { + String text = ""; + for (LocationPointWrapper point : points) { + // Need to calculate distance to nearest point + if (text.length() == 0) { + if (location != null && dist != null) { + dist[0] = point.getDeviationDistance() + + MapUtils.getDistance(location.getLatitude(), location.getLongitude(), + point.getPoint().getLatitude(), point.getPoint().getLongitude()); + } + } else { + text += ", "; + } + text += PointDescription.getSimpleName(point.getPoint(), router.getApplication()); + } + return text; + } + + public void announceAlarm(AlarmInfo info, float speed) { + AlarmInfoType type = info.getType(); + if (type == AlarmInfoType.SPEED_LIMIT) { + announceSpeedAlarm(info.getIntValue(), speed); + } else if (type == AlarmInfoType.SPEED_CAMERA) { + if (router.getSettings().SPEAK_SPEED_CAMERA.get()) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p != null) { + notifyOnVoiceMessage(); + p.attention(type+"").play(); + } + } + } else if (type == AlarmInfoType.PEDESTRIAN) { + if (router.getSettings().SPEAK_PEDESTRIAN.get()) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p != null) { + notifyOnVoiceMessage(); + p.attention(type+"").play(); + } + } + } else if (type == AlarmInfoType.TUNNEL) { + if (router.getSettings().SPEAK_TUNNELS.get()) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p != null) { + notifyOnVoiceMessage(); + p.attention(type+"").play(); + } + } + } else { + if (router.getSettings().SPEAK_TRAFFIC_WARNINGS.get()) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p != null) { + notifyOnVoiceMessage(); + p.attention(type+"").play(); + } + // See Issue 2377: Announce destination again - after some motorway tolls roads split shortly after the toll + if (type == AlarmInfoType.TOLL_BOOTH) { + suppressDest = false; + } + } + } + } + + public void announceSpeedAlarm(int maxSpeed, float speed) { + long ms = System.currentTimeMillis(); + if (waitAnnouncedSpeedLimit == 0) { + // Wait 10 seconds before announcement + if (ms - lastAnnouncedSpeedLimit > 120 * 1000) { + waitAnnouncedSpeedLimit = ms; + } + } else { + // If we wait before more than 20 sec (reset counter) + if (ms - waitAnnouncedSpeedLimit > 20 * 1000) { + waitAnnouncedSpeedLimit = 0; + } else if (router.getSettings().SPEAK_SPEED_LIMIT.get() && ms - waitAnnouncedSpeedLimit > 10 * 1000 ) { + CommandBuilder p = getNewCommandPlayerToPlay(); + if (p != null) { + notifyOnVoiceMessage(); + lastAnnouncedSpeedLimit = ms; + waitAnnouncedSpeedLimit = 0; + p.speedAlarm(maxSpeed, speed).play(); + } + } + } + } + + private boolean isTargetPoint(NextDirectionInfo info) { + boolean in = info != null && info.intermediatePoint; + boolean target = info == null || info.directionInfo == null + || info.directionInfo.distance == 0; + return in || target; + } + + private boolean needsInforming() { + final Integer repeat = settings.KEEP_INFORMING.get(); + if (repeat == null || repeat == 0) return false; + + final long notBefore = lastAnnouncement + repeat * 60 * 1000L; + + return System.currentTimeMillis() > notBefore; + } + + /** + * Updates status of voice guidance + * @param currentLocation + */ + protected void updateStatus(Location currentLocation, boolean repeat) { + // Directly after turn: goAhead (dist), unless: + // < PREPARE_LONG_DISTANCE (e.g. 3500m): playPrepareTurn (-not played any more-) + // < PREPARE_DISTANCE (e.g. 1500m): playPrepareTurn ("Turn after ...") + // < TURN_IN_DISTANCE (e.g. 390m or 30sec): playMakeTurnIn ("Turn in ...") + // < TURN_DISTANCE (e.g. 50m or 7sec): playMakeTurn ("Turn ...") + float speed = DEFAULT_SPEED; + if (currentLocation != null && currentLocation.hasSpeed()) { + speed = Math.max(currentLocation.getSpeed(), speed); + } + + NextDirectionInfo nextInfo = router.getNextRouteDirectionInfo(new NextDirectionInfo(), true); + RouteSegmentResult currentSegment = router.getCurrentSegmentResult(); + if (nextInfo == null || nextInfo.directionInfo == null) { + return; + } + int dist = nextInfo.distanceTo; + RouteDirectionInfo next = nextInfo.directionInfo; + + // If routing is changed update status to unknown + if (next != nextRouteDirection) { + nextRouteDirection = next; + currentStatus = STATUS_UNKNOWN; + suppressDest = false; + playedAndArriveAtTarget = false; + announceBackOnRoute = false; + if (playGoAheadDist != -1) { + playGoAheadDist = 0; + } + } + + if (!repeat) { + if (dist <= 0) { + return; + } else if (needsInforming()) { + playGoAhead(dist, getSpeakableStreetName(currentSegment, next, false)); + return; + } else if (currentStatus == STATUS_TOLD) { + // nothing said possibly that's wrong case we should say before that + // however it should be checked manually ? + return; + } + } + + if (currentStatus == STATUS_UNKNOWN) { + // Play "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away + if ((playGoAheadDist == -1) || (dist > PREPARE_LONG_DISTANCE)) { + playGoAheadDist = dist - 3 * TURN_DISTANCE; + } + } + + NextDirectionInfo nextNextInfo = router.getNextRouteDirectionInfoAfter(nextInfo, new NextDirectionInfo(), true); //I think "true" is correct here, not "!repeat" + // Note: getNextRouteDirectionInfoAfter(nextInfo, x, y).distanceTo is distance from nextInfo, not from current position! + + // STATUS_TURN = "Turn (now)" + if ((repeat || statusNotPassed(STATUS_TURN)) && isDistanceLess(speed, dist, TURN_DISTANCE, TURN_DEFAULT_SPEED)) { + if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END && nextNextInfo != null) { + playMakeTurn(currentSegment, next, nextNextInfo); + } else { + playMakeTurn(currentSegment, next, null); + } + if (!next.getTurnType().goAhead() && isTargetPoint(nextNextInfo)) { // !goAhead() avoids isolated "and arrive.." prompt, as goAhead() is not pronounced + if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) { + // Issue #2865: Ensure a distance associated with the destination arrival is always announced, either here, or in subsequent "Turn in" prompt + // Distance fon non-straights already announced in "Turn (now)"'s nextnext code above + if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null) && nextNextInfo.directionInfo.getTurnType().goAhead()) { + playThen(); + playGoAhead(nextNextInfo.distanceTo, new StreetName()); + } + playAndArriveAtDestination(nextNextInfo); + } else if (nextNextInfo.distanceTo < 1.2f * TURN_IN_DISTANCE_END) { + // 1.2 is safety margin should the subsequent "Turn in" prompt not fit in amy more + playThen(); + playGoAhead(nextNextInfo.distanceTo, new StreetName()); + playAndArriveAtDestination(nextNextInfo); + } + } + nextStatusAfter(STATUS_TURN); + + // STATUS_TURN_IN = "Turn in ..." + } else if ((repeat || statusNotPassed(STATUS_TURN_IN)) && isDistanceLess(speed, dist, TURN_IN_DISTANCE, 0f)) { + if (repeat || dist >= TURN_IN_DISTANCE_END) { + if ((isDistanceLess(speed, nextNextInfo.distanceTo, TURN_DISTANCE, 0f) || nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) && + nextNextInfo != null) { + playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, nextNextInfo.directionInfo); + } else { + playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, null); + } + playGoAndArriveAtDestination(repeat, nextInfo, currentSegment); + } + nextStatusAfter(STATUS_TURN_IN); + + // STATUS_PREPARE = "Turn after ..." + } else if ((repeat || statusNotPassed(STATUS_PREPARE)) && (dist <= PREPARE_DISTANCE)) { + if (repeat || dist >= PREPARE_DISTANCE_END) { + if (!repeat && (next.getTurnType().keepLeft() || next.getTurnType().keepRight())) { + // Do not play prepare for keep left/right + } else { + playPrepareTurn(currentSegment, next, dist); + playGoAndArriveAtDestination(repeat, nextInfo, currentSegment); + } + } + nextStatusAfter(STATUS_PREPARE); + + // STATUS_LONG_PREPARE = also "Turn after ...", we skip this now, users said this is obsolete + } else if ((repeat || statusNotPassed(STATUS_LONG_PREPARE)) && (dist <= PREPARE_LONG_DISTANCE)) { + if (repeat || dist >= PREPARE_LONG_DISTANCE_END) { + playPrepareTurn(currentSegment, next, dist); + playGoAndArriveAtDestination(repeat, nextInfo, currentSegment); + } + nextStatusAfter(STATUS_LONG_PREPARE); + + // STATUS_UNKNOWN = "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away + } else if (statusNotPassed(STATUS_UNKNOWN)) { + // Strange how we get here but + nextStatusAfter(STATUS_UNKNOWN); + } else if (repeat || (statusNotPassed(STATUS_PREPARE) && dist < playGoAheadDist)) { + playGoAheadDist = 0; + playGoAhead(dist, getSpeakableStreetName(currentSegment, next, false)); + } + } + + public void announceCurrentDirection(Location currentLocation) { + synchronized (router) { + if (currentStatus != STATUS_UTWP_TOLD) { + updateStatus(currentLocation, true); + } else if (playMakeUTwp()) { + playGoAheadDist = 0; + } + } + } + + private boolean playMakeUTwp() { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + play.makeUTwp().play(); + return true; + } + return false; + } + + void playThen() { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + play.then().play(); + } + } + + private void playGoAhead(int dist, StreetName streetName) { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + play.goAhead(dist, streetName).play(); + } + } + + private StreetName getSpeakableStreetName(RouteSegmentResult currentSegment, RouteDirectionInfo i, boolean includeDest) { + Map result = new HashMap<>(); + if (i == null || !router.getSettings().SPEAK_STREET_NAMES.get()) { + return new StreetName(result); + } + if (player != null && player.supportsStructuredStreetNames()) { + + // Issue 2377: Play Dest here only if not already previously announced, to avoid repetition + if (includeDest == true) { + result.put(TO_REF, getNonNullString(getSpeakablePointName(i.getRef()))); + result.put(TO_STREET_NAME, getNonNullString(getSpeakablePointName(i.getStreetName()))); + result.put(TO_DEST, getNonNullString(getSpeakablePointName(i.getDestinationName()))); + } else { + result.put(TO_REF, getNonNullString(getSpeakablePointName(i.getRef()))); + result.put(TO_STREET_NAME, getNonNullString(getSpeakablePointName(i.getStreetName()))); + result.put(TO_DEST, ""); + } + if (currentSegment != null) { + // Issue 2377: Play Dest here only if not already previously announced, to avoid repetition + if (includeDest == true) { + RouteDataObject obj = currentSegment.getObject(); + result.put(FROM_REF, getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(), + settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())))); + result.put(FROM_STREET_NAME, getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(), + settings.MAP_TRANSLITERATE_NAMES.get())))); + result.put(FROM_DEST, getNonNullString(getSpeakablePointName(obj.getDestinationName(settings.MAP_PREFERRED_LOCALE.get(), + settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())))); + } else { + RouteDataObject obj = currentSegment.getObject(); + result.put(FROM_REF, getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(), + settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection())))); + result.put(FROM_STREET_NAME, getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(), + settings.MAP_TRANSLITERATE_NAMES.get())))); + result.put(FROM_DEST, ""); + } + } + + } else { + result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef()))); + result.put(TO_STREET_NAME, getNonNullString(getSpeakablePointName(i.getStreetName()))); + result.put(TO_DEST, ""); + } + return new StreetName(result); + } + + + private String getNonNullString(String speakablePointName) { + return speakablePointName == null ? "" : speakablePointName; + } + + private static String getSpeakablePointName(String pn) { + // Replace characters which may produce unwanted TTS sounds: + String pl = ""; + if (player != null) { + pl = player.getLanguage(); + } + if (pn != null) { + pn = pn.replace('-', ' '); + pn = pn.replace(':', ' '); + pn = pn.replace(";", ", "); // Trailing blank prevents punctuation being pronounced. Replace by comma for better intonation. + pn = pn.replace("/", ", "); // Slash is actually pronounced by many TTS engines, creating an awkward voice prompt, better replace by comma. + if (!pl.startsWith("de")) { + pn = pn.replace("\u00df", "ss"); // Helps non-German TTS voices to pronounce German Straße (=street) + } + if (pl.startsWith("en")) { + pn = pn.replace("SR", "S R"); // Avoid SR (as for State Route or Strada Regionale) be pronounced as "Senior" in English TTS voice + pn = pn.replace("Dr.", "Dr "); // Avoid pause many English TTS voices introduce after period + } + if (pl.startsWith("de")) { + if (pn.startsWith("St ")) { + pn = pn.replace("St ", "S T "); // German Staatsstrasse, abbreviated St, often mispronounced + } + } + } + return pn; + } + + private void playPrepareTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist) { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + String tParam = getTurnType(next.getTurnType()); + if (tParam != null) { + notifyOnVoiceMessage(); + play.prepareTurn(tParam, dist, getSpeakableStreetName(currentSegment, next, true)).play(); + } else if (next.getTurnType().isRoundAbout()) { + notifyOnVoiceMessage(); + play.prepareRoundAbout(dist, next.getTurnType().getExitOut(), getSpeakableStreetName(currentSegment, next, true)).play(); + } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) { + notifyOnVoiceMessage(); + play.prepareMakeUT(dist, getSpeakableStreetName(currentSegment, next, true)).play(); + } + } + } + + private void playMakeTurnIn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist, RouteDirectionInfo pronounceNextNext) { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + String tParam = getTurnType(next.getTurnType()); + boolean isPlay = true; + if (tParam != null) { + play.turn(tParam, dist, getSpeakableStreetName(currentSegment, next, true)); + suppressDest = true; + } else if (next.getTurnType().isRoundAbout()) { + play.roundAbout(dist, next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableStreetName(currentSegment, next, true)); + // Other than in prepareTurn, in prepareRoundabout we do not announce destination, so we can repeat it one more time + suppressDest = false; + } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) { + play.makeUT(dist, getSpeakableStreetName(currentSegment, next, true)); + suppressDest = true; + } else { + isPlay = false; + } + // 'then keep' preparation for next after next. (Also announces an interim straight segment, which is not pronounced above.) + if (pronounceNextNext != null) { + TurnType t = pronounceNextNext.getTurnType(); + isPlay = true; + if (t.getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) { + play.goAhead(dist, getSpeakableStreetName(currentSegment, next, true)); + } + if (t.getValue() == TurnType.TL || t.getValue() == TurnType.TSHL || t.getValue() == TurnType.TSLL + || t.getValue() == TurnType.TU || t.getValue() == TurnType.KL ) { + play.then().bearLeft( getSpeakableStreetName(currentSegment, next, false)); + } else if (t.getValue() == TurnType.TR || t.getValue() == TurnType.TSHR || t.getValue() == TurnType.TSLR + || t.getValue() == TurnType.TRU || t.getValue() == TurnType.KR) { + play.then().bearRight( getSpeakableStreetName(currentSegment, next, false)); + } + } + if (isPlay) { + notifyOnVoiceMessage(); + play.play(); + } + } + } + + private void playGoAndArriveAtDestination(boolean repeat, NextDirectionInfo nextInfo, RouteSegmentResult currentSegment) { + RouteDirectionInfo next = nextInfo.directionInfo; + if (isTargetPoint(nextInfo) && (!playedAndArriveAtTarget || repeat)) { + if (next.getTurnType().goAhead()) { + playGoAhead(nextInfo.distanceTo, getSpeakableStreetName(currentSegment, next, false)); + playAndArriveAtDestination(nextInfo); + playedAndArriveAtTarget = true; + } else if (nextInfo.distanceTo <= 2 * TURN_IN_DISTANCE) { + playAndArriveAtDestination(nextInfo); + playedAndArriveAtTarget = true; + } + } + } + + private void playAndArriveAtDestination(NextDirectionInfo info) { + if (isTargetPoint(info)) { + String pointName = (info == null || info.pointName == null) ? "" : info.pointName; + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + if (info != null && info.intermediatePoint) { + play.andArriveAtIntermediatePoint(getSpeakablePointName(pointName)).play(); + } else { + play.andArriveAtDestination(getSpeakablePointName(pointName)).play(); + } + } + } + } + + private void playMakeTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, NextDirectionInfo nextNextInfo) { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + String tParam = getTurnType(next.getTurnType()); + boolean isplay = true; + if (tParam != null) { + play.turn(tParam, getSpeakableStreetName(currentSegment, next, !suppressDest)); + } else if (next.getTurnType().isRoundAbout()) { + play.roundAbout(next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableStreetName(currentSegment, next, !suppressDest)); + } else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) { + play.makeUT(getSpeakableStreetName(currentSegment, next, !suppressDest)); + // Do not announce goAheads + //} else if (next.getTurnType().getValue() == TurnType.C)) { + // play.goAhead(); + } else { + isplay = false; + } + // Add turn after next + if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null)) { + + // This case only needed should we want a prompt at the end of straight segments (equivalent of makeTurn) when nextNextInfo should be announced again there. + if (nextNextInfo.directionInfo.getTurnType().getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) { + play.goAhead(); + isplay = true; + } + + String t2Param = getTurnType(nextNextInfo.directionInfo.getTurnType()); + if (t2Param != null) { + if (isplay) { + play.then(); + play.turn(t2Param, nextNextInfo.distanceTo, new StreetName()); + } + } else if (nextNextInfo.directionInfo.getTurnType().isRoundAbout()) { + if (isplay) { + play.then(); + play.roundAbout(nextNextInfo.distanceTo, nextNextInfo.directionInfo.getTurnType().getTurnAngle(), nextNextInfo.directionInfo.getTurnType().getExitOut(), new StreetName()); + } + } else if (nextNextInfo.directionInfo.getTurnType().getValue() == TurnType.TU) { + if (isplay) { + play.then(); + play.makeUT(nextNextInfo.distanceTo, new StreetName()); + } + } + } + if (isplay) { + notifyOnVoiceMessage(); + play.play(); + } + } + } + + private String getTurnType(TurnType t) { + if (TurnType.TL == t.getValue()) { + return AbstractPrologCommandPlayer.A_LEFT; + } else if (TurnType.TSHL == t.getValue()) { + return AbstractPrologCommandPlayer.A_LEFT_SH; + } else if (TurnType.TSLL == t.getValue()) { + return AbstractPrologCommandPlayer.A_LEFT_SL; + } else if (TurnType.TR == t.getValue()) { + return AbstractPrologCommandPlayer.A_RIGHT; + } else if (TurnType.TSHR == t.getValue()) { + return AbstractPrologCommandPlayer.A_RIGHT_SH; + } else if (TurnType.TSLR == t.getValue()) { + return AbstractPrologCommandPlayer.A_RIGHT_SL; + } else if (TurnType.KL == t.getValue()) { + return AbstractPrologCommandPlayer.A_LEFT_KEEP; + } else if (TurnType.KR == t.getValue()) { + return AbstractPrologCommandPlayer.A_RIGHT_KEEP; + } + return null; + } + + public void gpsLocationLost() { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + play.gpsLocationLost().play(); + } + } + + public void gpsLocationRecover() { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + play.gpsLocationRecover().play(); + } + } + + public void newRouteIsCalculated(boolean newRoute) { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + if (!newRoute) { + play.routeRecalculated(router.getLeftDistance(), router.getLeftTime()).play(); + } else { + play.newRouteCalculated(router.getLeftDistance(), router.getLeftTime()).play(); + } + } else if (player == null) { + pendingCommand = new VoiceCommandPending(!newRoute ? VoiceCommandPending.ROUTE_RECALCULATED : VoiceCommandPending.ROUTE_CALCULATED, this); + } + if (newRoute) { + playGoAheadDist = -1; + } + currentStatus = STATUS_UNKNOWN; + suppressDest = false; + nextRouteDirection = null; + } + + public void arrivedDestinationPoint(String name) { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + play.arrivedAtDestination(getSpeakablePointName(name)).play(); + } + } + + public void arrivedIntermediatePoint(String name) { + CommandBuilder play = getNewCommandPlayerToPlay(); + if (play != null) { + notifyOnVoiceMessage(); + play.arrivedAtIntermediatePoint(getSpeakablePointName(name)).play(); + } + } + + // This is not needed, used are only arrivedIntermediatePoint (for points on the route) or announceWaypoint (for points near the route=) + //public void arrivedWayPoint(String name) { + // CommandBuilder play = getNewCommandPlayerToPlay(); + // if (play != null) { + // notifyOnVoiceMessage(); + // play.arrivedAtWayPoint(getSpeakablePointName(name)).play(); + // } + //} + + public void onApplicationTerminate() { + if (player != null) { + player.clear(); + } + } + + public void interruptRouteCommands() { + if (player != null) { + player.stop(); + } + } + + /** + * Command to wait until voice player is initialized + */ + private class VoiceCommandPending { + public static final int ROUTE_CALCULATED = 1; + public static final int ROUTE_RECALCULATED = 2; + protected final int type; + private final VoiceRouter voiceRouter; + + public VoiceCommandPending(int type, VoiceRouter voiceRouter) { + this.type = type; + this.voiceRouter = voiceRouter; + } + + public void play(CommandBuilder newCommand) { + int left = voiceRouter.router.getLeftDistance(); + int time = voiceRouter.router.getLeftTime(); + if (left > 0) { + if (type == ROUTE_CALCULATED) { + notifyOnVoiceMessage(); + newCommand.newRouteCalculated(left, time).play(); + } else if (type == ROUTE_RECALCULATED) { + notifyOnVoiceMessage(); + newCommand.routeRecalculated(left, time).play(); + } + } + } + } + + private void makeSound() { + if (isMute()) { + return; + } + SoundPool sp = new SoundPool(5, AudioManager.STREAM_MUSIC, 0); + int soundClick = -1; + boolean success = true; + try { + // Taken unaltered from https://freesound.org/people/Corsica_S/sounds/91926/ under license http://creativecommons.org/licenses/by/3.0/ : + soundClick = sp.load(settings.getContext().getAssets().openFd("sounds/ding.ogg"), 1); + } catch (IOException e) { + e.printStackTrace(); + success = false; + } + if (success) { + sp.play(soundClick, 1 ,1, 0, 0, 1); + } + } + + public void addVoiceMessageListener(VoiceMessageListener voiceMessageListener) { + voiceMessageListeners.put(new WeakReference<>(voiceMessageListener), 0); + } + + public void removeVoiceMessageListener(VoiceMessageListener voiceMessageListener) { + voiceMessageListeners.remove(new WeakReference<>(voiceMessageListener)); + } + + public void notifyOnVoiceMessage() { + for (WeakReference weakReferenceWrapper : voiceMessageListeners.keySet()) { + VoiceMessageListener lnt = weakReferenceWrapper.get(); + if (lnt != null) { + lnt.onVoiceMessage(); + } + } + } +} \ No newline at end of file diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/MainActivity.java b/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/MainActivity.java index 306187f345..f6cb5619a0 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/MainActivity.java +++ b/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/MainActivity.java @@ -57,6 +57,9 @@ public class MainActivity extends AppCompatActivity { private LinearLayout llPluginPreferencesLayout; private RadioGroupWrapper osmandVersionRadioGroup; private FrameLayout btnOpenOsmand; + private ImageView ivTimeSetUpIcon; + private ImageView ivSensorIcon; + private View bottomSensorCardShadow; private View osmandVersionsPanel; private Paint pGreyScale; @@ -99,6 +102,8 @@ public class MainActivity extends AppCompatActivity { tvTime = findViewById(R.id.tvTime); flPanelSensor = findViewById(R.id.flPanelSensor); swSensorEnableSwitcher = findViewById(R.id.swSensorEnableSwitcher); + ivTimeSetUpIcon = findViewById(R.id.ivTimeSetUpIcon); + ivSensorIcon = findViewById(R.id.ivSensorIcon); btnOpenOsmand.setOnClickListener(new View.OnClickListener() { @Override @@ -271,6 +276,8 @@ public class MainActivity extends AppCompatActivity { tvPluginStateDescription.setText(getString(R.string.enabled)); tvPluginStateDescription.setTextColor(getResources().getColor(R.color.black)); tbPluginToolbar.setBackgroundColor(getResources().getColor(R.color.orange)); + ivTimeSetUpIcon.setColorFilter(ContextCompat.getColor(this, R.color.blue), + android.graphics.PorterDuff.Mode.MULTIPLY); llPluginPreferencesLayout.setLayerType(LAYER_TYPE_HARDWARE, null); @@ -289,6 +296,8 @@ public class MainActivity extends AppCompatActivity { tvPluginStateDescription.setText(getString(R.string.disabled)); tvPluginStateDescription.setTextColor(getResources().getColor(R.color.darkGrey)); tbPluginToolbar.setBackgroundColor(getResources().getColor(R.color.darkGrey)); + ivTimeSetUpIcon.setColorFilter(ContextCompat.getColor(this, R.color.darkGrey), + android.graphics.PorterDuff.Mode.MULTIPLY); llPluginPreferencesLayout.setLayerType(LAYER_TYPE_HARDWARE, pGreyScale); @@ -302,8 +311,12 @@ public class MainActivity extends AppCompatActivity { if (isSensorEnabled) { sensorHelper.switchOnSensor(); + ivSensorIcon.setColorFilter(ContextCompat.getColor(this, R.color.blue), + android.graphics.PorterDuff.Mode.MULTIPLY); } else { sensorHelper.switchOffSensor(); + ivSensorIcon.setColorFilter(ContextCompat.getColor(this, R.color.darkGrey), + android.graphics.PorterDuff.Mode.MULTIPLY); } PluginSettings.UnlockTime currentTime = settings.getTime(); @@ -316,7 +329,14 @@ public class MainActivity extends AppCompatActivity { availableOsmandVersions = PluginSettings.OsmandVersion.getOnlyInstalledVersions(); llElementsScreen.removeView(osmandVersionsPanel); + if (bottomSensorCardShadow==null) { + bottomSensorCardShadow = getLayoutInflater().inflate(R.layout.card_top_divider, null, false); + llElementsScreen.addView(bottomSensorCardShadow); + } + if (availableOsmandVersions.size() > 1) { + llElementsScreen.removeView(bottomSensorCardShadow); + bottomSensorCardShadow = null; osmandVersionsPanel = createOsmandVersionsPanel(); llElementsScreen.addView(osmandVersionsPanel); @@ -335,9 +355,9 @@ public class MainActivity extends AppCompatActivity { private View createOsmandVersionsPanel() { LayoutInflater inflater = getLayoutInflater(); - LinearLayout llWraper = new LinearLayout(MainActivity.this); - llWraper.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - llWraper.setOrientation(LinearLayout.VERTICAL); + LinearLayout llWrapper = new LinearLayout(MainActivity.this); + llWrapper.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + llWrapper.setOrientation(LinearLayout.VERTICAL); View panelOsmandVersions = inflater.inflate(R.layout.main_el_osmand_versions, null, false); LinearLayout llOsmandVersions = panelOsmandVersions.findViewById(R.id.llOsmandVersions); @@ -379,11 +399,11 @@ public class MainActivity extends AppCompatActivity { View cardDevider = inflater.inflate(R.layout.card_devider, null, false); View cardTop = inflater.inflate(R.layout.card_top_divider, null, false); - llWraper.addView(cardDevider); - llWraper.addView(panelOsmandVersions); - llWraper.addView(cardTop); + llWrapper.addView(cardDevider); + llWrapper.addView(panelOsmandVersions); + llWrapper.addView(cardTop); - return llWraper; + return llWrapper; } private void setEnableForElements(boolean enable) { diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/PluginDescriptionActivity.java b/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/PluginDescriptionActivity.java index 25360794fb..700de8a3ba 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/PluginDescriptionActivity.java +++ b/plugins/OsmAnd-TurnScreenOn/src/main/java/net/osmand/turnScreenOn/PluginDescriptionActivity.java @@ -7,6 +7,8 @@ import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.widget.FrameLayout; import net.osmand.turnScreenOn.app.TurnScreenApp; @@ -28,6 +30,8 @@ public class PluginDescriptionActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_plugin_description); + setStatusBarColor(R.color.white); + currentMode = getIntent().getIntExtra(MODE_KEY, MODE_HELP); app = (TurnScreenApp) getApplicationContext(); @@ -72,4 +76,10 @@ public class PluginDescriptionActivity extends AppCompatActivity { public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) { super.onSaveInstanceState(outState, outPersistentState); } + + private void setStatusBarColor(int colorResId) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setStatusBarColor(ContextCompat.getColor(this, colorResId)); + } + } } diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/res/drawable/btn_preference.xml b/plugins/OsmAnd-TurnScreenOn/src/main/res/drawable/btn_preference.xml new file mode 100644 index 0000000000..2213dd5841 --- /dev/null +++ b/plugins/OsmAnd-TurnScreenOn/src/main/res/drawable/btn_preference.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/activity_admin_device_permission_set_up.xml b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/activity_admin_device_permission_set_up.xml index a0f8e5208c..ca6772df15 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/activity_admin_device_permission_set_up.xml +++ b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/activity_admin_device_permission_set_up.xml @@ -23,7 +23,7 @@ + tools:context=".PluginDescriptionActivity" + android:background="@color/white"> - diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/dialog_layout.xml b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/dialog_layout.xml index 7708ee9032..53e166f529 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/dialog_layout.xml +++ b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/dialog_layout.xml @@ -10,7 +10,6 @@ @@ -35,6 +35,7 @@ android:layout_gravity="end|bottom" android:gravity="center_vertical" android:text="@string/cancel" + android:layout_marginLeft="@dimen/horizontal_padding" android:layout_marginRight="@dimen/horizontal_padding" android:textColor="@color/blue" android:textSize="16sp" /> diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/item_time.xml b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/item_time.xml index 5b37bf7e8e..6494699f00 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/item_time.xml +++ b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/item_time.xml @@ -5,8 +5,8 @@ + android:background="@drawable/btn_preference" + android:layout_height="@dimen/basic_element_height"> diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/main_el_sensor.xml b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/main_el_sensor.xml index bb7407def7..c90506752a 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/main_el_sensor.xml +++ b/plugins/OsmAnd-TurnScreenOn/src/main/res/layout/main_el_sensor.xml @@ -21,6 +21,7 @@ android:layout_height="@dimen/basic_element_height"> #000000 #6617D3 #A99CBA + #eeeeee diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/res/values/strings.xml b/plugins/OsmAnd-TurnScreenOn/src/main/res/values/strings.xml index c9850525af..f1b266f505 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/res/values/strings.xml +++ b/plugins/OsmAnd-TurnScreenOn/src/main/res/values/strings.xml @@ -9,7 +9,7 @@ OsmAnd Version OsmAnd Plus OsmAnd - continue + Continue Использовать датчик приближения Help You need to install OsmAnd or OsmAnd+, to use this plugin. diff --git a/plugins/OsmAnd-TurnScreenOn/src/main/res/values/styles.xml b/plugins/OsmAnd-TurnScreenOn/src/main/res/values/styles.xml index eeabe7f3e4..1dce2d4a37 100644 --- a/plugins/OsmAnd-TurnScreenOn/src/main/res/values/styles.xml +++ b/plugins/OsmAnd-TurnScreenOn/src/main/res/values/styles.xml @@ -22,7 +22,7 @@