// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.omnibox; import android.os.Bundle; import android.speech.RecognizerIntent; import org.chromium.base.VisibleForTesting; import org.chromium.chrome.browser.omnibox.OmniboxSuggestion.MatchClassification; import org.chromium.chrome.browser.search_engines.TemplateUrlService; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A search provider that processes and stores voice recognition results and makes them available * in the omnibox results. The voice results are added to the end of the omnibox results. */ public class VoiceSuggestionProvider { private static final float CONFIDENCE_THRESHOLD_SHOW = 0.3f; private static final float CONFIDENCE_THRESHOLD_HIDE_ALTS = 0.8f; private final List<VoiceResult> mResults = new ArrayList<VoiceResult>(); private final float mConfidenceThresholdShow; private final float mConfidenceThresholdHideAlts; /** * Creates an instance of a {@link VoiceSuggestionProvider} class. */ public VoiceSuggestionProvider() { mConfidenceThresholdShow = CONFIDENCE_THRESHOLD_SHOW; mConfidenceThresholdHideAlts = CONFIDENCE_THRESHOLD_HIDE_ALTS; } /** * Creates an instance of a {@link VoiceSuggestionProvider} class. * @param confidenceThresholdShow The minimum confidence a result can have to be shown (does not * include the first result). Confidence values are between * 0.0 and 1.0. * @param confidenceThresholdHideAlts The maximum confidence the first result can have before it * will no longer show alternates. Confidence values are * between 0.0 and 1.0. */ @VisibleForTesting protected VoiceSuggestionProvider(float confidenceThresholdShow, float confidenceThresholdHideAlts) { mConfidenceThresholdShow = confidenceThresholdShow; mConfidenceThresholdHideAlts = confidenceThresholdHideAlts; } /** * Clears the current voice search results. */ public void clearVoiceSearchResults() { mResults.clear(); } /** * @return The current voice search results. This could be {@code null} if no results are * currently present. */ public List<VoiceResult> getResults() { return Collections.unmodifiableList(mResults); } /** * Takes and processes the results from a recognition action. It parses the confidence and * string values and stores the processed results here so they are made available to the * {@link AutocompleteController} and show up in the omnibox results. This method does not * reorder the voice results that come back from the recognizer. * @param extras The {@link Bundle} that contains the recognition results from a * {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH} action. */ public void setVoiceResultsFromIntentBundle(Bundle extras) { clearVoiceSearchResults(); if (extras == null) return; ArrayList<String> strings = extras.getStringArrayList( RecognizerIntent.EXTRA_RESULTS); float[] confidences = extras.getFloatArray( RecognizerIntent.EXTRA_CONFIDENCE_SCORES); if (strings == null || confidences == null) return; assert (strings.size() == confidences.length); if (strings.size() != confidences.length) return; for (int i = 0; i < strings.size(); ++i) { // Remove any spaces in the voice search match when determining whether it // appears to be a URL. This is to prevent cases like ( // "tech crunch.com" and "www. engadget .com" from not appearing like URLs) // from not navigating to the URL. // If the string appears to be a URL, then use it instead of the string returned from // the voice engine. String culledString = strings.get(i).replaceAll(" ", ""); String url = AutocompleteController.nativeQualifyPartialURLQuery(culledString); mResults.add(new VoiceResult( url == null ? strings.get(i) : culledString, confidences[i])); } } /** * Adds the currently stored voice recognition results to the current list of * {@link OmniboxSuggestion}s passed in. Returns the new list to the caller. * @param suggestions The current list of {@link OmniboxSuggestion}s. * @param maxVoiceResults The maximum number of voice results that should be added. * @return A new list of {@link OmniboxSuggestion}s, which can include voice results. */ public List<OmniboxSuggestion> addVoiceSuggestions( List<OmniboxSuggestion> suggestions, int maxVoiceResults) { if (mResults.size() == 0 || maxVoiceResults == 0) return suggestions; List<OmniboxSuggestion> newSuggestions = new ArrayList<OmniboxSuggestion>(); if (suggestions != null && suggestions.size() > 0) { newSuggestions.addAll(suggestions); } VoiceResult firstResult = mResults.get(0); addVoiceResultToOmniboxSuggestions(newSuggestions, firstResult, 0); final int suggestionLength = suggestions != null ? suggestions.size() : 0; if (firstResult.getConfidence() < mConfidenceThresholdHideAlts) { for (int i = 1; i < mResults.size() && newSuggestions.size() < suggestionLength + maxVoiceResults; ++i) { addVoiceResultToOmniboxSuggestions(newSuggestions, mResults.get(i), mConfidenceThresholdShow); } } return newSuggestions; } private void addVoiceResultToOmniboxSuggestions(List<OmniboxSuggestion> suggestions, VoiceResult result, float confidenceThreshold) { if (doesVoiceResultHaveMatch(suggestions, result)) return; if (result.getConfidence() < confidenceThreshold && result.getConfidence() > 0) return; String voiceUrl = TemplateUrlService.getInstance().getUrlForVoiceSearchQuery( result.getMatch()); List<MatchClassification> classifications = new ArrayList<>(); classifications.add(new MatchClassification(0, MatchClassificationStyle.NONE)); suggestions.add(new OmniboxSuggestion( OmniboxSuggestionType.VOICE_SUGGEST, true, 0, 1, result.getMatch(), classifications, null, classifications, null, null, null, voiceUrl, false, false)); } private boolean doesVoiceResultHaveMatch(List<OmniboxSuggestion> suggestions, VoiceResult result) { for (OmniboxSuggestion suggestion : suggestions) { if (suggestion.getDisplayText().equals(result.getMatch())) return true; } return false; } /** * A storage class that holds voice recognition string matches and confidence scores. */ public static class VoiceResult { private final String mMatch; private final float mConfidence; /** * Creates an instance of a VoiceResult. * @param match The text match from the voice recognition. * @param confidence The confidence value of the recognition that should go from 0.0 to 1.0. */ public VoiceResult(String match, float confidence) { mMatch = match; mConfidence = confidence; } /** * @return The text match from the voice recognition. */ public String getMatch() { return mMatch; } /** * @return The confidence value of the recognition that should go from 0.0 to 1.0. */ public float getConfidence() { return mConfidence; } } }