package de.blau.android.voice; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import android.content.Intent; import android.location.Location; import android.location.LocationManager; import android.speech.RecognizerIntent; import android.util.Log; import de.blau.android.App; import de.blau.android.Logic; import de.blau.android.Main; import de.blau.android.R; import de.blau.android.exception.OsmIllegalOperationException; import de.blau.android.names.Names.NameAndTags; import de.blau.android.osm.Node; import de.blau.android.osm.OsmElement.ElementType; import de.blau.android.osm.Tags; import de.blau.android.presets.Preset; import de.blau.android.presets.Preset.PresetItem; import de.blau.android.propertyeditor.Address; import de.blau.android.tasks.Note; import de.blau.android.util.ElementSearch; import de.blau.android.util.GeoMath; import de.blau.android.util.OptimalStringAlignment; import de.blau.android.util.SearchIndexUtils; import de.blau.android.util.Snack; import de.blau.android.util.StringWithDescription; import de.blau.android.util.Util; /** * Support for simple voice commands, format * <location> <number> * for an address * <location> <object> [<name>] * for a POI of some kind- * <location> can be one of left, here and right * @author simon * */ public class Commands { private static final String DEBUG_TAG = Commands.class.getSimpleName(); private Main main; private Map<String,NameAndTags> namesSearchIndex; public Commands(Main main) { this.main = main; namesSearchIndex = App.getNameSearchIndex(main); } public void processIntentResult(Intent data, Location location) { // Fill the list view with the strings the recognizer thought it // could have heard ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); final Logic logic = App.getLogic(); // try to find a command it simply stops at the first string that is valid for (String v:matches) { Snack.toastTopInfo(main,">"+v+"<"); String[] words = v.split("\\s+", 3); if (words.length > 1) { String loc = words[0].toLowerCase(Locale.getDefault()); if (match(R.string.voice_left,loc) || match(R.string.voice_here,loc) || match(R.string.voice_right,loc) || match(R.string.voice_note,loc) ) { if (match(R.string.voice_note,loc)) { Note n = createNote(words, location); Snack.toastTopInfo(main,"Note: " + n.getDescription()); return; } if (!match(R.string.voice_here,loc)) { Snack.toastTopWarning(main,"Sorry currently only the command \"" + main.getString(R.string.voice_here) + "\" is supported"); } // String first = words[1].toLowerCase(Locale.getDefault()); try { int number = Integer.parseInt(first); // worked if there is a further word(s) simply add it/them Snack.toastTopInfo(main,loc + " "+ number + (words.length == 3?words[2]:"")); Node node = createNode(loc,location); if (node != null) { TreeMap<String, String> tags = new TreeMap<String, String>(node.getTags()); tags.put(Tags.KEY_ADDR_HOUSENUMBER, "" + number + (words.length == 3?words[2]:"")); tags.put("source:original_text", v); LinkedHashMap<String, ArrayList<String>> map = Address.predictAddressTags(main, Node.NAME, node.getOsmId(), new ElementSearch(new int[]{node.getLon(),node.getLat()}, true), Util.getArrayListMap(tags), Address.NO_HYSTERESIS); tags = new TreeMap<String, String>(); for (String key:map.keySet()) { tags.put(key, map.get(key).get(0)); } logic.setTags(main, node, tags); } return; } catch (NumberFormatException ex) { // ok wasn't a number } catch (OsmIllegalOperationException e) { Log.e(DEBUG_TAG,e.getMessage()); Snack.toastTopError(main,e.getLocalizedMessage()); e.printStackTrace(); } List<PresetItem> presetItems = SearchIndexUtils.searchInPresets(main, first,ElementType.NODE,2,1); if (presetItems != null && presetItems.size()==1) { addNode(createNode(loc,location), words.length == 3? words[2]:null, presetItems.get(0), logic, v); return; } if (namesSearchIndex == null) { return; } // search in names String input = ""; for (int i=1;i<words.length;i++) { input = input + words[i] + (i<words.length?" ":""); } NameAndTags nt = SearchIndexUtils.searchInNames(main, input, 2); if (nt != null) { HashMap<String, String> map = new HashMap<String, String>(); map.putAll(nt.getTags()); PresetItem pi = Preset.findBestMatch(App.getCurrentPresets(main), map); if (pi != null) { addNode(createNode(loc,location), nt.getName(), pi, logic, v); return; } } } } else if (words.length == 1) { if (match(R.string.voice_follow,words[0])) { main.setFollowGPS(true); return; } else { Snack.toastTopWarning(main,main.getResources().getString(R.string.toast_unknown_voice_command,words[0])); } } } } private boolean addNode(Node node, String name, PresetItem pi, Logic logic, String original) { if (node != null) { Snack.toastTopInfo(main, pi.getName() + (name != null? " name: " + name:"")); if (node != null) { try { TreeMap<String, String> tags = new TreeMap<String, String>(node.getTags()); for (Entry<String, StringWithDescription> tag : pi.getFixedTags().entrySet()) { tags.put(tag.getKey(), tag.getValue().getValue()); } if (name != null) { tags.put(Tags.KEY_NAME, name); } tags.put("source:original_text", original); logic.setTags(main, node, tags); return true; } catch (OsmIllegalOperationException e) { Log.e(DEBUG_TAG,e.getMessage()); Snack.toastTopError(main,e.getLocalizedMessage()); } } } return false; } /** * Create a new node at the current or at a provided GPS pos * @return */ private Node createNode(String loc, Location location) { if (location == null) { location = getLocation(); } if (location != null) { if (main.getString(R.string.voice_here).equals(loc)) { double lon = location.getLongitude(); double lat = location.getLatitude(); if (lon >= -180 && lon <= 180 && lat >= -GeoMath.MAX_LAT && lat <= GeoMath.MAX_LAT) { final Logic logic = App.getLogic(); logic.setSelectedNode(null); Node node = logic.performAddNode(main, lon, lat); logic.setSelectedNode(null); return node; } } } return null; } private Note createNote(String[] words, Location location) { if (location == null) { location = getLocation(); } if (location != null) { double lon = location.getLongitude(); double lat = location.getLatitude(); if (lon >= -180 && lon <= 180 && lat >= -GeoMath.MAX_LAT && lat <= GeoMath.MAX_LAT) { Note n = new Note((int)(lat*1E7D),(int)(lon*1E7D)); StringBuilder input = new StringBuilder(); for (int i=1;i<words.length;i++) { input.append(words[i]); input.append(" "); } n.addComment(input.toString().trim()); n.open(); n.setChanged(); App.getTaskStorage().add(n); return n; } } return null; } private Location getLocation() { LocationManager locationManager = (LocationManager)main.getSystemService(android.content.Context.LOCATION_SERVICE); if (locationManager != null) { try { return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); } catch (SecurityException sex) { // can be safely ignored return null; } } return null; } private boolean match(int resId, String input) { final int maxDistance = 1; int distance = OptimalStringAlignment.editDistance(main.getString(resId), input, maxDistance); return distance >= 0 && distance <= maxDistance; } }