blob: 14dec4749e50296f8c304cbe5d8f5a6b454d355c [file] [log] [blame]
// Copyright 2018 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.voice;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.view.ViewGroup;
import androidx.annotation.ColorRes;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.SysUtils;
import org.chromium.base.jank_tracker.DummyJankTracker;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.omnibox.LocationBarDataProvider;
import org.chromium.chrome.browser.omnibox.NewTabPageDelegate;
import org.chromium.chrome.browser.omnibox.UrlBarData;
import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteControllerJni;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdownEmbedder;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.AssistantActionPerformed;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.AudioPermissionState;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.TranslateBridgeWrapper;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceIntentTarget;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceInteractionSource;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceResult;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
import org.chromium.components.omnibox.AutocompleteMatch;
import org.chromium.components.omnibox.AutocompleteResult;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.base.ActivityWindowAndroid;
import org.chromium.ui.base.AndroidPermissionDelegate;
import org.chromium.ui.base.IntentRequestTracker;
import org.chromium.ui.base.PermissionCallback;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.base.WindowAndroid.IntentCallback;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.url.GURL;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* Tests for {@link VoiceRecognitionHandler}.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class VoiceRecognitionHandlerTest {
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@Rule
public JniMocker mJniMocker = new JniMocker();
@Mock
Intent mIntent;
@Mock
AssistantVoiceSearchService mAssistantVoiceSearchService;
@Mock
ModalDialogManager mModalDialogManager;
@Mock
TranslateBridgeWrapper mTranslateBridgeWrapper;
@Mock
Tab mTab;
@Mock
VoiceRecognitionHandler.Observer mObserver;
@Mock
AutocompleteController mController;
@Mock
AutocompleteController.Natives mControllerJniMock;
@Mock
AutocompleteMatch mMatch;
private TestDataProvider mDataProvider;
private TestDelegate mDelegate;
private TestVoiceRecognitionHandler mHandler;
private TestAndroidPermissionDelegate mPermissionDelegate;
private TestWindowAndroid mWindowAndroid;
private List<VoiceResult> mAutocompleteVoiceResults;
private ObservableSupplierImpl<Profile> mProfileSupplier;
private static final OnSuggestionsReceivedListener sEmptySuggestionListener =
new OnSuggestionsReceivedListener() {
@Override
public void onSuggestionsReceived(
AutocompleteResult autocompleteResult, String inlineAutocompleteText) {}
};
// The default Tab URL.
private static final String DEFAULT_URL = "https://example.com/";
private static final String DEFAULT_USER_EMAIL = "test@test.com";
/**
* An implementation of the real {@link VoiceRecognitionHandler} except instead of
* recording histograms we just flag whether we would have or not.
*/
private class TestVoiceRecognitionHandler extends VoiceRecognitionHandler {
@VoiceInteractionSource
public int mStartSource = -1;
@VoiceIntentTarget
public int mStartTarget = -1;
@VoiceInteractionSource
public int mFinishSource = -1;
@VoiceIntentTarget
public int mFinishTarget = -1;
@VoiceInteractionSource
public int mDismissedSource = -1;
@VoiceIntentTarget
public int mDismissedTarget = -1;
@VoiceInteractionSource
public int mFailureSource = -1;
@VoiceIntentTarget
public int mFailureTarget = -1;
@VoiceInteractionSource
public int mUnexpectedResultSource = -1;
@VoiceIntentTarget
public int mUnexpectedResultTarget = -1;
@AssistantActionPerformed
private int mActionPerformed = -1;
@VoiceInteractionSource
private int mActionPerformedSource = -1;
private Boolean mResult;
@VoiceIntentTarget
private int mResultTarget;
private Float mVoiceConfidenceValue;
@VoiceIntentTarget
private int mVoiceConfidenceValueTarget;
public TestVoiceRecognitionHandler(
Delegate delegate, ObservableSupplierImpl<Profile> profileSupplier) {
super(delegate, () -> mAssistantVoiceSearchService, () -> {}, profileSupplier);
}
@Override
protected void recordVoiceSearchStartEvent(
@VoiceInteractionSource int source, @VoiceIntentTarget int target) {
mStartSource = source;
mStartTarget = target;
}
@Override
protected void recordVoiceSearchFinishEvent(
@VoiceInteractionSource int source, @VoiceIntentTarget int target) {
mFinishSource = source;
mFinishTarget = target;
}
@Override
protected void recordAssistantActionPerformed(
@VoiceInteractionSource int source, @AssistantActionPerformed int action) {
mActionPerformedSource = source;
mActionPerformed = action;
}
@Override
protected void recordVoiceSearchFailureEvent(
@VoiceInteractionSource int source, @VoiceIntentTarget int target) {
mFailureSource = source;
mFailureTarget = target;
}
@Override
protected void recordVoiceSearchDismissedEvent(
@VoiceInteractionSource int source, @VoiceIntentTarget int target) {
mDismissedSource = source;
mDismissedTarget = target;
}
@Override
protected void recordVoiceSearchUnexpectedResult(
@VoiceInteractionSource int source, @VoiceIntentTarget int target) {
mUnexpectedResultSource = source;
mUnexpectedResultTarget = target;
}
@Override
protected void recordVoiceSearchResult(@VoiceIntentTarget int target, boolean result) {
mResultTarget = target;
mResult = result;
}
@Override
protected void recordVoiceSearchConfidenceValue(
@VoiceIntentTarget int target, float value) {
mVoiceConfidenceValueTarget = target;
mVoiceConfidenceValue = value;
}
@Override
protected boolean isRecognitionIntentPresent(boolean useCachedValue) {
return true;
}
@VoiceInteractionSource
public int getVoiceSearchStartEventSource() {
return mStartSource;
}
@VoiceIntentTarget
public int getVoiceSearchStartEventTarget() {
return mStartTarget;
}
@VoiceInteractionSource
public int getVoiceSearchFinishEventSource() {
return mFinishSource;
}
@VoiceIntentTarget
public int getVoiceSearchFinishEventTarget() {
return mFinishTarget;
}
@VoiceInteractionSource
public int getVoiceSearchDismissedEventSource() {
return mDismissedSource;
}
@VoiceIntentTarget
public int getVoiceSearchDismissedEventTarget() {
return mDismissedTarget;
}
@VoiceInteractionSource
public int getVoiceSearchFailureEventSource() {
return mFailureSource;
}
@VoiceIntentTarget
public int getVoiceSearchFailureEventTarget() {
return mFailureTarget;
}
@VoiceInteractionSource
public int getVoiceSearchUnexpectedResultSource() {
return mUnexpectedResultSource;
}
@VoiceIntentTarget
public int getVoiceSearchUnexpectedResultTarget() {
return mUnexpectedResultTarget;
}
@AssistantActionPerformed
public int getAssistantActionPerformed() {
return mActionPerformed;
}
@VoiceInteractionSource
public int getAssistantActionPerformedSource() {
return mActionPerformedSource;
}
public Boolean getVoiceSearchResult() {
return mResult;
}
@VoiceIntentTarget
public int getVoiceSearchResultTarget() {
return mResultTarget;
}
public Float getVoiceConfidenceValue() {
return mVoiceConfidenceValue;
}
@VoiceIntentTarget
public int getVoiceConfidenceValueTarget() {
return mVoiceConfidenceValueTarget;
}
}
/**
* Test implementation of {@link ToolbarDataProvider}.
*/
private class TestDataProvider implements LocationBarDataProvider {
private boolean mIncognito;
public void setIncognito(boolean incognito) {
mIncognito = incognito;
}
@Override
public Tab getTab() {
return mTab;
}
@Override
public boolean hasTab() {
return false;
}
@Override
public void addObserver(Observer observer) {}
@Override
public void removeObserver(Observer observer) {}
@Override
public String getCurrentUrl() {
return null;
}
@Override
public NewTabPageDelegate getNewTabPageDelegate() {
return NewTabPageDelegate.EMPTY;
}
@Override
public boolean isIncognito() {
return mIncognito;
}
@Override
public boolean isInOverviewAndShowingOmnibox() {
return false;
}
@Override
public UrlBarData getUrlBarData() {
return UrlBarData.EMPTY;
}
@Override
public String getTitle() {
return null;
}
@Override
public int getPrimaryColor() {
return 0;
}
@Override
public boolean isUsingBrandColor() {
return false;
}
@Override
public boolean isOfflinePage() {
return false;
}
@Override
public int getSecurityLevel() {
return 0;
}
@Override
public int getPageClassification(boolean isFocusedFromFakebox) {
return PageClassification.NTP_VALUE;
}
@Override
public int getSecurityIconResource(boolean isTablet) {
return 0;
}
@Override
public @ColorRes int getSecurityIconColorStateList() {
return 0;
}
@Override
public int getSecurityIconContentDescriptionResourceId() {
return 0;
}
}
/**
* TODO(crbug.com/962527): Remove this dependency on {@link AutocompleteCoordinator}.
*/
private class TestAutocompleteCoordinator extends AutocompleteCoordinator {
public TestAutocompleteCoordinator(ViewGroup parent, AutocompleteDelegate delegate,
OmniboxSuggestionsDropdownEmbedder dropdownEmbedder,
UrlBarEditingTextStateProvider urlBarEditingTextProvider) {
// clang-format off
super(parent, delegate, dropdownEmbedder, urlBarEditingTextProvider,
() -> mModalDialogManager, null, null, mDataProvider,
mProfileSupplier, (tab) -> {}, null, (url) -> false, new DummyJankTracker(),
(pixelSize, callback) -> {});
// clang-format on
}
@Override
public void onVoiceResults(List<VoiceResult> results) {
mAutocompleteVoiceResults = results;
}
}
/**
* Test implementation of {@link VoiceRecognitionHandler.Delegate}.
*/
private class TestDelegate implements VoiceRecognitionHandler.Delegate {
private String mUrl;
private AutocompleteCoordinator mAutocompleteCoordinator;
TestDelegate() {
ViewGroup parent =
(ViewGroup) mActivityTestRule.getActivity().findViewById(android.R.id.content);
Assert.assertNotNull(parent);
mAutocompleteCoordinator = new TestAutocompleteCoordinator(parent, null, null, null);
}
@Override
public void loadUrlFromVoice(String url) {
mUrl = url;
}
@Override
public void setSearchQuery(final String query) {}
@Override
public LocationBarDataProvider getLocationBarDataProvider() {
return mDataProvider;
}
@Override
public AutocompleteCoordinator getAutocompleteCoordinator() {
return mAutocompleteCoordinator;
}
@Override
public WindowAndroid getWindowAndroid() {
return mWindowAndroid;
}
@Override
public void clearOmniboxFocus() {}
public String getUrl() {
return mUrl;
}
}
/**
* Test implementation of {@link ActivityWindowAndroid}.
*/
private class TestWindowAndroid extends ActivityWindowAndroid {
private boolean mCancelableIntentSuccess = true;
private int mResultCode = Activity.RESULT_OK;
private Intent mResults = new Intent();
private Activity mActivity;
private boolean mWasCancelableIntentShown;
private Intent mCancelableIntent;
private IntentCallback mCallback;
public TestWindowAndroid(Activity activity) {
super(activity, /* listenToActivityState= */ true,
IntentRequestTracker.createFromActivity(activity));
}
public void setCancelableIntentSuccess(boolean success) {
mCancelableIntentSuccess = success;
}
public void setResultCode(int resultCode) {
mResultCode = resultCode;
}
public void setVoiceResults(Bundle results) {
mResults.putExtras(results);
}
public void setActivity(Activity activity) {
mActivity = activity;
}
public boolean wasCancelableIntentShown() {
return mWasCancelableIntentShown;
}
public Intent getCancelableIntent() {
return mCancelableIntent;
}
public IntentCallback getIntentCallback() {
return mCallback;
}
@Override
public int showCancelableIntent(Intent intent, IntentCallback callback, Integer errorId) {
mWasCancelableIntentShown = true;
mCancelableIntent = intent;
mCallback = callback;
if (mCancelableIntentSuccess) {
callback.onIntentCompleted(mWindowAndroid, mResultCode, mResults);
return 0;
}
return WindowAndroid.START_INTENT_FAILURE;
}
@Override
public WeakReference<Activity> getActivity() {
if (mActivity == null) return super.getActivity();
return new WeakReference<>(mActivity);
}
}
/**
* Test implementation of {@link AndroidPermissionDelegate}.
*/
private class TestAndroidPermissionDelegate implements AndroidPermissionDelegate {
private boolean mHasPermission;
private boolean mCanRequestPermission;
private boolean mCalledHasPermission;
private boolean mCalledCanRequestPermission;
private int mPermissionResult = PackageManager.PERMISSION_GRANTED;
public void setPermissionResults(int result) {
mPermissionResult = result;
}
public void setHasPermission(boolean hasPermission) {
mHasPermission = hasPermission;
}
public void setCanRequestPermission(boolean canRequestPermission) {
mCanRequestPermission = canRequestPermission;
}
public boolean calledHasPermission() {
return mCalledHasPermission;
}
public boolean calledCanRequestPermission() {
return mCalledCanRequestPermission;
}
@Override
public boolean hasPermission(String permission) {
mCalledHasPermission = true;
return mHasPermission;
}
@Override
public boolean canRequestPermission(String permission) {
mCalledCanRequestPermission = true;
return mCanRequestPermission;
}
@Override
public boolean isPermissionRevokedByPolicy(String permission) {
return false;
}
@Override
public void requestPermissions(String[] permissions, PermissionCallback callback) {
int[] results = new int[permissions.length];
for (int i = 0; i < permissions.length; i++) {
results[i] = mPermissionResult;
}
callback.onRequestPermissionsResult(permissions, results);
}
@Override
public boolean handlePermissionResult(
int requestCode, String[] permissions, int[] grantResults) {
return false;
}
}
@Before
public void setUp() throws InterruptedException, ExecutionException {
MockitoAnnotations.initMocks(this);
mJniMocker.mock(AutocompleteControllerJni.TEST_HOOKS, mControllerJniMock);
doReturn(mController).when(mControllerJniMock).getForProfile(any());
doReturn(mMatch).when(mController).classify(any(), anyBoolean());
doReturn(new GURL("https://www.google.com/search?q=abc")).when(mMatch).getUrl();
doReturn(true).when(mMatch).isSearchSuggestion();
mActivityTestRule.startMainActivityOnBlankPage();
TestThreadUtils.runOnUiThreadBlocking(() -> {
mWindowAndroid = new TestWindowAndroid(mActivityTestRule.getActivity());
mProfileSupplier = new ObservableSupplierImpl<>();
});
mDataProvider = new TestDataProvider();
mDelegate = TestThreadUtils.runOnUiThreadBlocking(() -> new TestDelegate());
mHandler = new TestVoiceRecognitionHandler(mDelegate, mProfileSupplier);
mHandler.addObserver(mObserver);
mPermissionDelegate = new TestAndroidPermissionDelegate();
TestThreadUtils.runOnUiThreadBlocking(() -> {
mWindowAndroid.setAndroidPermissionDelegate(mPermissionDelegate);
});
doReturn(new GURL(DEFAULT_URL)).when(mTab).getUrl();
doReturn(false).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
doReturn(false).when(mAssistantVoiceSearchService).needsEnabledCheck();
doReturn(mIntent).when(mAssistantVoiceSearchService).getAssistantVoiceSearchIntent();
doReturn(DEFAULT_USER_EMAIL).when(mAssistantVoiceSearchService).getUserEmail();
doReturn(true).when(mTranslateBridgeWrapper).canManuallyTranslate(notNull());
doReturn("fr").when(mTranslateBridgeWrapper).getSourceLanguage(notNull());
doReturn("de").when(mTranslateBridgeWrapper).getCurrentLanguage(notNull());
doReturn("ja").when(mTranslateBridgeWrapper).getTargetLanguage();
mHandler.setTranslateBridgeWrapper(mTranslateBridgeWrapper);
}
@After
public void tearDown() {
mHandler.removeObserver(mObserver);
SysUtils.resetForTesting();
TestThreadUtils.runOnUiThreadBlocking(() -> { mWindowAndroid.destroy(); });
}
/**
* Tests for {@link VoiceRecognitionHandler#isVoiceSearchEnabled}.
*/
@Test
@SmallTest
public void testIsVoiceSearchEnabled_FalseOnNullTab() {
Assert.assertFalse(isVoiceSearchEnabled());
}
@Test
@SmallTest
public void testIsVoiceSearchEnabled_FalseOnNullDataProvider() {
mDataProvider = null;
Assert.assertFalse(isVoiceSearchEnabled());
}
@Test
@SmallTest
public void testIsVoiceSearchEnabled_FalseWhenIncognito() {
mDataProvider.setIncognito(true);
Assert.assertFalse(isVoiceSearchEnabled());
}
@Test
@SmallTest
public void testIsVoiceSearchEnabled_FalseWhenNoPermissionAndCantRequestPermission() {
Assert.assertFalse(isVoiceSearchEnabled());
Assert.assertTrue(mPermissionDelegate.calledHasPermission());
Assert.assertTrue(mPermissionDelegate.calledCanRequestPermission());
}
@Test
@SmallTest
public void testIsVoiceSearchEnabled_Success() {
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setHasPermission(true);
Assert.assertTrue(isVoiceSearchEnabled());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY})
public void testIsVoiceSearchEnabled_AllowedByPolicy() {
setAudioCapturePref(true);
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setHasPermission(true);
Assert.assertTrue(isVoiceSearchEnabled());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY})
public void testIsVoiceSearchEnabled_DisabledByPolicy() {
setAudioCapturePref(false);
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setHasPermission(true);
Assert.assertFalse(isVoiceSearchEnabled());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY})
public void testIsVoiceSearchEnabled_AudioCapturePolicyAllowsByDefault() {
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setHasPermission(true);
Assert.assertTrue(isVoiceSearchEnabled());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@DisableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY})
public void testIsVoiceSearchEnabled_SkipPolicyCheckWhenDisabled() {
setAudioCapturePref(false);
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setHasPermission(true);
Assert.assertTrue(isVoiceSearchEnabled());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY})
public void testIsVoiceSearchEnabled_UpdateAfterProfileSet() {
setAudioCapturePref(true);
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setHasPermission(true);
verify(mObserver, never()).onVoiceAvailabilityImpacted();
Assert.assertTrue(isVoiceSearchEnabled());
setAudioCapturePref(false);
TestThreadUtils.runOnUiThreadBlocking(
() -> { mProfileSupplier.set(Profile.getLastUsedRegularProfile()); });
Assert.assertFalse(isVoiceSearchEnabled());
verify(mObserver).onVoiceAvailabilityImpacted();
}
/** Calls isVoiceSearchEnabled(), ensuring it is run on the UI thread. */
private boolean isVoiceSearchEnabled() {
return TestThreadUtils.runOnUiThreadBlockingNoException(
() -> mHandler.isVoiceSearchEnabled());
}
/**
* Tests for {@link VoiceRecognitionHandler#startVoiceRecognition}.
*/
@Test
@SmallTest
public void testStartVoiceRecognition_OnlyUpdateMicButtonStateIfCantRequestPermission() {
verify(mObserver, never()).onVoiceAvailabilityImpacted();
TestThreadUtils.runOnUiThreadBlocking(
() -> { mHandler.startVoiceRecognition(VoiceInteractionSource.OMNIBOX); });
Assert.assertEquals(-1, mHandler.getVoiceSearchStartEventSource());
verify(mObserver).onVoiceAvailabilityImpacted();
verify(mAssistantVoiceSearchService).reportMicPressUserEligibility();
}
@Test
@SmallTest
public void
testStartVoiceRecognition_DontUpdateMicIfPermissionsNotGrantedButCanRequestPermissions() {
verify(mObserver, never()).onVoiceAvailabilityImpacted();
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setPermissionResults(PackageManager.PERMISSION_DENIED);
TestThreadUtils.runOnUiThreadBlocking(
() -> { mHandler.startVoiceRecognition(VoiceInteractionSource.OMNIBOX); });
Assert.assertEquals(-1, mHandler.getVoiceSearchStartEventSource());
verify(mObserver, never()).onVoiceAvailabilityImpacted();
verify(mAssistantVoiceSearchService).reportMicPressUserEligibility();
}
@Test
@SmallTest
public void
testStartVoiceRecognition_UpdateMicIfPermissionsNotGrantedAndCantRequestPermissions() {
verify(mObserver, never()).onVoiceAvailabilityImpacted();
mPermissionDelegate.setCanRequestPermission(false);
mPermissionDelegate.setPermissionResults(PackageManager.PERMISSION_DENIED);
TestThreadUtils.runOnUiThreadBlocking(
() -> { mHandler.startVoiceRecognition(VoiceInteractionSource.OMNIBOX); });
Assert.assertEquals(-1, mHandler.getVoiceSearchStartEventSource());
verify(mObserver).onVoiceAvailabilityImpacted();
verify(mAssistantVoiceSearchService).reportMicPressUserEligibility();
}
@Test
@SmallTest
@Feature({"OmniboxAssistantVoiceSearch"})
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH})
public void testStartVoiceRecognition_StartsAssistantVoiceSearch() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
Assert.assertEquals(mIntent, mWindowAndroid.getCancelableIntent());
Assert.assertEquals(VoiceIntentTarget.ASSISTANT, mHandler.getVoiceSearchStartEventTarget());
verify(mAssistantVoiceSearchService).reportMicPressUserEligibility();
verify(mIntent).putExtra(
eq(VoiceRecognitionHandler.EXTRA_INTENT_SENT_TIMESTAMP), anyLong());
verify(mIntent).putExtra(
VoiceRecognitionHandler.EXTRA_VOICE_ENTRYPOINT, VoiceInteractionSource.OMNIBOX);
verify(mIntent).putExtra(
VoiceRecognitionHandler.EXTRA_INTENT_USER_EMAIL, DEFAULT_USER_EMAIL);
}
@Test
@SmallTest
@Feature({"OmniboxAssistantVoiceSearch"})
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH})
public void testStartVoiceRecognition_ShouldRequestConditionsFail() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(false).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
verify(mAssistantVoiceSearchService).reportMicPressUserEligibility();
// We check for the consent dialog when canRequestAssistantVoiceSearch() is true.
verify(mAssistantVoiceSearchService).needsEnabledCheck();
verify(mAssistantVoiceSearchService, times(0)).getAssistantVoiceSearchIntent();
}
@Test
@SmallTest
@Feature("AssistantIntentExperimentId")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH + "<Study"})
@DisableFeatures({ChromeFeatureList.ASSISTANT_INTENT_EXPERIMENT_ID + "<Study"})
@CommandLineFlags.Add({"force-fieldtrials=Study/Group",
"force-fieldtrial-params=Study.Group:"
+ VoiceRecognitionHandler.ASSISTANT_EXPERIMENT_ID_PARAM_NAME + "/test"})
public void
testStartVoiceRecognition_AssistantExperimentIdDisabled() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent, never())
.putExtra(eq(VoiceRecognitionHandler.EXTRA_EXPERIMENT_ID), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentExperimentId")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH + "<Study",
ChromeFeatureList.ASSISTANT_INTENT_EXPERIMENT_ID + "<Study"})
@CommandLineFlags.Add({"force-fieldtrials=Study/Group",
"force-fieldtrial-params=Study.Group:"
+ VoiceRecognitionHandler.ASSISTANT_EXPERIMENT_ID_PARAM_NAME + "/test"})
public void
testStartVoiceRecognition_IncludeExperimentIdInAssistantIntentFromToolbar() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_EXPERIMENT_ID, "test");
}
@Test
@SmallTest
@Feature("AssistantIntentExperimentId")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH + "<Study",
ChromeFeatureList.ASSISTANT_INTENT_EXPERIMENT_ID + "<Study"})
@CommandLineFlags.Add({"force-fieldtrials=Study/Group",
"force-fieldtrial-params=Study.Group:"
+ VoiceRecognitionHandler.ASSISTANT_EXPERIMENT_ID_PARAM_NAME + "/test"})
public void
testStartVoiceRecognition_IncludeExperimentIdInAssistantIntentFromNonToolbar() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_EXPERIMENT_ID, "test");
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testStartVoiceRecognition_ToolbarButtonIncludesPageUrl() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
Assert.assertEquals(mIntent, mWindowAndroid.getCancelableIntent());
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_PAGE_URL, DEFAULT_URL);
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testStartVoiceRecognition_OmitPageUrlWhenAssistantVoiceSearchDisabled() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(false).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testStartVoiceRecognition_OmitPageUrlForNonToolbar() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.NTP);
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testStartVoiceRecognition_OmitPageUrlForIncognito() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
doReturn(true).when(mTab).isIncognito();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testStartVoiceRecognition_OmitPageUrlForInternalPages() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
GURL url = new GURL("chrome://version");
doReturn(url).when(mTab).getUrl();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testStartVoiceRecognition_OmitPageUrlForNonHttp() {
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
GURL url = new GURL("ftp://example.org/");
doReturn(url).when(mTab).getUrl();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentTranslateInfo")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH,
ChromeFeatureList.ASSISTANT_INTENT_TRANSLATE_INFO})
public void
testStartVoiceRecognition_ToolbarButtonIncludesTranslateInfo() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
Assert.assertEquals(mIntent, mWindowAndroid.getCancelableIntent());
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_TRANSLATE_ORIGINAL_LANGUAGE, "fr");
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_TRANSLATE_CURRENT_LANGUAGE, "de");
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_TRANSLATE_TARGET_LANGUAGE, "ja");
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_PAGE_URL, DEFAULT_URL);
}
@Test
@SmallTest
@Feature("AssistantIntentTranslateInfo")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH})
@DisableFeatures({ChromeFeatureList.ASSISTANT_INTENT_TRANSLATE_INFO})
public void testStartVoiceRecognition_TranslateExtrasDisabled() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_ORIGINAL_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_CURRENT_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_TARGET_LANGUAGE), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentTranslateInfo")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH,
ChromeFeatureList.ASSISTANT_INTENT_TRANSLATE_INFO})
public void
testStartVoiceRecognition_NoTranslateExtrasForNonToolbar() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_ORIGINAL_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_CURRENT_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_TARGET_LANGUAGE), anyString());
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentTranslateInfo")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH,
ChromeFeatureList.ASSISTANT_INTENT_TRANSLATE_INFO})
public void
testStartVoiceRecognition_NoTranslateExtrasForNonTranslatePage() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
doReturn(false).when(mTranslateBridgeWrapper).canManuallyTranslate(notNull());
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_ORIGINAL_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_CURRENT_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_TARGET_LANGUAGE), anyString());
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentTranslateInfo")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH,
ChromeFeatureList.ASSISTANT_INTENT_TRANSLATE_INFO})
public void
testStartVoiceRecognition_NoTranslateExtrasWhenLanguagesUndetected() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
doReturn(null).when(mTranslateBridgeWrapper).getSourceLanguage(notNull());
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_ORIGINAL_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(
eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_CURRENT_LANGUAGE), anyString());
verify(mIntent, never())
.putExtra(eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_TARGET_LANGUAGE), anyString());
verify(mIntent, never()).putExtra(eq(VoiceRecognitionHandler.EXTRA_PAGE_URL), anyString());
}
@Test
@SmallTest
@Feature("AssistantIntentTranslateInfo")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH,
ChromeFeatureList.ASSISTANT_INTENT_TRANSLATE_INFO})
public void
testStartVoiceRecognition_TranslateInfoTargetLanguageOptional() {
doReturn(true).when(mAssistantVoiceSearchService).canRequestAssistantVoiceSearch();
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
doReturn(null).when(mTranslateBridgeWrapper).getTargetLanguage();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_TRANSLATE_ORIGINAL_LANGUAGE, "fr");
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_TRANSLATE_CURRENT_LANGUAGE, "de");
verify(mIntent, never())
.putExtra(eq(VoiceRecognitionHandler.EXTRA_TRANSLATE_TARGET_LANGUAGE), anyString());
verify(mIntent).putExtra(VoiceRecognitionHandler.EXTRA_PAGE_URL, DEFAULT_URL);
}
@Test
@SmallTest
public void testStartVoiceRecognition_StartsVoiceSearchWithFailedIntent() {
verify(mObserver, never()).onVoiceAvailabilityImpacted();
mWindowAndroid.setCancelableIntentSuccess(false);
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceSearchStartEventTarget());
verify(mObserver).onVoiceAvailabilityImpacted();
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchFailureEventSource());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceSearchFailureEventTarget());
}
@Test
@SmallTest
public void testStartVoiceRecognition_StartsVoiceSearchWithSuccessfulIntent() {
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceSearchStartEventTarget());
verify(mObserver, never()).onVoiceAvailabilityImpacted();
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY,
ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH})
public void
testStartVoiceRecognition_AudioCaptureAllowedByPolicy() {
setAudioCapturePref(true);
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY,
ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH})
public void
testStartVoiceRecognition_AudioCaptureDisabledByPolicy() {
setAudioCapturePref(false);
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertFalse(mWindowAndroid.wasCancelableIntentShown());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY,
ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH})
public void
testStartVoiceRecognition_AudioCapturePolicyAllowsByDefault() {
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
}
@Test
@SmallTest
@Feature("VoiceSearchAudioCapturePolicy")
@EnableFeatures({ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH})
@DisableFeatures({ChromeFeatureList.VOICE_SEARCH_AUDIO_CAPTURE_POLICY})
public void testStartVoiceRecognition_SkipPolicyWhenFeatureDisabled() {
setAudioCapturePref(false);
doReturn(true).when(mAssistantVoiceSearchService).shouldRequestAssistantVoiceSearch();
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mWindowAndroid.wasCancelableIntentShown());
}
/**
* Tests for the {@link VoiceRecognitionHandler.VoiceRecognitionCompleteCallback}.
*
* These tests are kicked off by
* {@link VoiceRecognitionHandler#startVoiceRecognition} to test the flow as it would
* be in reality.
*/
@Test
@SmallTest
public void testCallback_noVoiceSearchResultWithBadResultCode() {
mWindowAndroid.setResultCode(Activity.RESULT_FIRST_USER);
startVoiceRecognition(VoiceInteractionSource.NTP);
Assert.assertEquals(VoiceInteractionSource.NTP, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(null, mHandler.getVoiceSearchResult());
Assert.assertEquals(
VoiceInteractionSource.NTP, mHandler.getVoiceSearchFailureEventSource());
Assert.assertEquals(0,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
}
@Test
@SmallTest
public void testCallback_noVoiceSearchResultCanceled() {
mWindowAndroid.setResultCode(Activity.RESULT_CANCELED);
startVoiceRecognition(VoiceInteractionSource.NTP);
Assert.assertEquals(VoiceInteractionSource.NTP, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(null, mHandler.getVoiceSearchResult());
Assert.assertEquals(
VoiceInteractionSource.NTP, mHandler.getVoiceSearchDismissedEventSource());
Assert.assertEquals(0,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
}
@Test
@SmallTest
public void testCallback_noVoiceSearchResultWithNullAutocompleteResult() {
mWindowAndroid.setVoiceResults(new Bundle());
startVoiceRecognition(VoiceInteractionSource.SEARCH_WIDGET);
Assert.assertEquals(
VoiceInteractionSource.SEARCH_WIDGET, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(false, mHandler.getVoiceSearchResult());
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
}
@Test
@SmallTest
public void testCallback_noVoiceSearchResultWithNoMatch() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mWindowAndroid.setVoiceResults(createDummyBundle("", 1f));
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(false, mHandler.getVoiceSearchResult());
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
});
}
@Test
@SmallTest
public void testCallback_successWithLowConfidence() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
float confidence =
VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD - 0.01f;
mWindowAndroid.setVoiceResults(createDummyBundle("testing", confidence));
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchFinishEventSource());
Assert.assertTrue(mHandler.getVoiceSearchResult());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceSearchResultTarget());
Assert.assertTrue(confidence == mHandler.getVoiceConfidenceValue());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceConfidenceValueTarget());
assertVoiceResultsAreEqual(
mAutocompleteVoiceResults, new String[] {"testing"}, new float[] {confidence});
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
});
}
@Test
@SmallTest
public void testCallback_successWithHighConfidence() {
// Needs to run on the UI thread because we use the TemplateUrlService on success.
TestThreadUtils.runOnUiThreadBlocking(() -> {
mWindowAndroid.setVoiceResults(createDummyBundle(
"testing", VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD));
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchFinishEventSource());
Assert.assertTrue(mHandler.getVoiceSearchResult());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceSearchResultTarget());
Assert.assertTrue(VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD
== mHandler.getVoiceConfidenceValue());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceConfidenceValueTarget());
assertVoiceResultsAreEqual(mAutocompleteVoiceResults, new String[] {"testing"},
new float[] {
VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD});
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
});
}
@Test
@SmallTest
public void testCallback_successWithLanguages() {
// Needs to run on the UI thread because we use the TemplateUrlService on success.
TestThreadUtils.runOnUiThreadBlocking(() -> {
mWindowAndroid.setVoiceResults(createDummyBundle("testing",
VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD, "en-us"));
startVoiceRecognition(VoiceInteractionSource.OMNIBOX);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchFinishEventSource());
Assert.assertTrue(mHandler.getVoiceSearchResult());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceSearchResultTarget());
Assert.assertTrue(VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD
== mHandler.getVoiceConfidenceValue());
Assert.assertEquals(VoiceIntentTarget.SYSTEM, mHandler.getVoiceConfidenceValueTarget());
assertVoiceResultsAreEqual(mAutocompleteVoiceResults, new String[] {"testing"},
new float[] {
VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD},
new String[] {"en-us"});
Assert.assertTrue(mDelegate.getUrl().contains("&hl=en-us"));
});
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testCallback_nonTranscriptionAction() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
Bundle bundle = new Bundle();
bundle.putString(VoiceRecognitionHandler.EXTRA_ACTION_PERFORMED, "TRANSLATE");
mWindowAndroid.setVoiceResults(bundle);
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertEquals(
VoiceInteractionSource.TOOLBAR, mHandler.getVoiceSearchStartEventSource());
Assert.assertEquals(
VoiceInteractionSource.TOOLBAR, mHandler.getVoiceSearchFinishEventSource());
Assert.assertEquals(
AssistantActionPerformed.TRANSLATE, mHandler.getAssistantActionPerformed());
Assert.assertEquals(
VoiceInteractionSource.TOOLBAR, mHandler.getAssistantActionPerformedSource());
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
});
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testCallback_defaultToTranscription() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mWindowAndroid.setVoiceResults(createDummyBundle(
"testing", VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD));
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertEquals(
AssistantActionPerformed.TRANSCRIPTION, mHandler.getAssistantActionPerformed());
Assert.assertEquals(
VoiceInteractionSource.TOOLBAR, mHandler.getAssistantActionPerformedSource());
Assert.assertTrue(mHandler.getVoiceSearchResult());
assertVoiceResultsAreEqual(mAutocompleteVoiceResults, new String[] {"testing"},
new float[] {
VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD});
});
}
@Test
@SmallTest
@Feature("AssistantIntentPageUrl")
@DisableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testCallback_pageUrlExtraDisabled() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mWindowAndroid.setVoiceResults(createDummyBundle(
"testing", VoiceRecognitionHandler.VOICE_SEARCH_CONFIDENCE_NAVIGATE_THRESHOLD));
startVoiceRecognition(VoiceInteractionSource.TOOLBAR);
Assert.assertTrue(mHandler.getVoiceSearchResult());
// Ensure that we don't record UMA when the feature is disabled.
Assert.assertEquals(-1, mHandler.getAssistantActionPerformed());
Assert.assertEquals(-1, mHandler.getAssistantActionPerformedSource());
});
}
@Test
@SmallTest
public void testParseResults_EmptyBundle() {
Assert.assertNull(mHandler.convertBundleToVoiceResults(new Bundle()));
}
@Test
@SmallTest
public void testParseResults_MismatchedTextAndConfidenceScores() {
Assert.assertNull(mHandler.convertBundleToVoiceResults(
createDummyBundle(new String[] {"blah"}, new float[] {0f, 1f})));
Assert.assertNull(mHandler.convertBundleToVoiceResults(
createDummyBundle(new String[] {"blah", "foo"}, new float[] {7f})));
Assert.assertNull(mHandler.convertBundleToVoiceResults(createDummyBundle(
new String[] {"blah", "foo"}, new float[] {7f, 1f}, new String[] {"foo"})));
}
@Test
@SmallTest
public void testParseResults_ValidBundle() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
String[] texts = new String[] {"a", "b", "c"};
float[] confidences = new float[] {0.8f, 1.0f, 1.0f};
List<VoiceResult> results =
mHandler.convertBundleToVoiceResults(createDummyBundle(texts, confidences));
assertVoiceResultsAreEqual(results, texts, confidences);
});
}
@Test
@SmallTest
public void testParseResults_VoiceResponseURLConversion() {
doReturn(false).when(mMatch).isSearchSuggestion();
TestThreadUtils.runOnUiThreadBlocking(() -> {
String[] texts =
new String[] {"a", "www. b .co .uk", "engadget .com", "www.google.com"};
float[] confidences = new float[] {1.0f, 1.0f, 1.0f, 1.0f};
List<VoiceResult> results =
mHandler.convertBundleToVoiceResults(createDummyBundle(texts, confidences));
assertVoiceResultsAreEqual(results,
new String[] {"a", "www.b.co.uk", "engadget.com", "www.google.com"},
new float[] {1.0f, 1.0f, 1.0f, 1.0f});
});
}
@Test
@SmallTest
@DisableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testRecordSuccessMetrics_noActionMetrics() {
mHandler.setQueryStartTimeForTesting(100L);
mHandler.recordSuccessMetrics(VoiceInteractionSource.OMNIBOX, VoiceIntentTarget.ASSISTANT,
AssistantActionPerformed.TRANSCRIPTION);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchFinishEventSource());
Assert.assertEquals(
VoiceIntentTarget.ASSISTANT, mHandler.getVoiceSearchFinishEventTarget());
Assert.assertEquals(-1, mHandler.getAssistantActionPerformed());
Assert.assertEquals(-1, mHandler.getAssistantActionPerformedSource());
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
// Split action metrics should not be recorded.
Assert.assertEquals(0,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android.Transcription"));
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.ASSISTANT_INTENT_PAGE_URL)
public void testRecordSuccessMetrics_splitActionMetrics() {
mHandler.setQueryStartTimeForTesting(100L);
mHandler.recordSuccessMetrics(VoiceInteractionSource.OMNIBOX, VoiceIntentTarget.ASSISTANT,
AssistantActionPerformed.TRANSLATE);
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getVoiceSearchFinishEventSource());
Assert.assertEquals(
VoiceIntentTarget.ASSISTANT, mHandler.getVoiceSearchFinishEventTarget());
Assert.assertEquals(
AssistantActionPerformed.TRANSLATE, mHandler.getAssistantActionPerformed());
Assert.assertEquals(
VoiceInteractionSource.OMNIBOX, mHandler.getAssistantActionPerformedSource());
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
Assert.assertEquals(0,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android.Transcription"));
Assert.assertEquals(1,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android.Translate"));
}
@Test
@SmallTest
public void testRecordSuccessMetrics_calledWithNullStartTime() {
mHandler.setQueryStartTimeForTesting(null);
mHandler.recordSuccessMetrics(VoiceInteractionSource.OMNIBOX, VoiceIntentTarget.SYSTEM,
AssistantActionPerformed.TRANSCRIPTION);
Assert.assertEquals(0,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android"));
Assert.assertEquals(0,
RecordHistogram.getHistogramTotalCountForTesting(
"VoiceInteraction.QueryDuration.Android.Transcription"));
}
@Test
@SmallTest
public void testRecordAudioState_deniedCannotAsk() {
mPermissionDelegate.setHasPermission(false);
mPermissionDelegate.setCanRequestPermission(false);
TestThreadUtils.runOnUiThreadBlocking(
() -> { mHandler.startVoiceRecognition(VoiceInteractionSource.OMNIBOX); });
Assert.assertEquals(1,
RecordHistogram.getHistogramValueCountForTesting(
"VoiceInteraction.AudioPermissionEvent",
AudioPermissionState.DENIED_CANNOT_ASK_AGAIN));
}
@Test
@SmallTest
public void testRecordAudioState_deniedCanAsk() {
mPermissionDelegate.setCanRequestPermission(true);
mPermissionDelegate.setPermissionResults(PackageManager.PERMISSION_DENIED);
TestThreadUtils.runOnUiThreadBlocking(
() -> { mHandler.startVoiceRecognition(VoiceInteractionSource.OMNIBOX); });
Assert.assertEquals(1,
RecordHistogram.getHistogramValueCountForTesting(
"VoiceInteraction.AudioPermissionEvent",
AudioPermissionState.DENIED_CAN_ASK_AGAIN));
}
@Test
@SmallTest
public void testRecordAudioState_granted() {
mPermissionDelegate.setHasPermission(true);
TestThreadUtils.runOnUiThreadBlocking(
() -> { mHandler.startVoiceRecognition(VoiceInteractionSource.OMNIBOX); });
Assert.assertEquals(1,
RecordHistogram.getHistogramValueCountForTesting(
"VoiceInteraction.AudioPermissionEvent", AudioPermissionState.GRANTED));
}
@Test
@SmallTest
public void testCallback_CalledTwice() {
startVoiceRecognition(VoiceInteractionSource.NTP);
Assert.assertEquals(-1, mHandler.getVoiceSearchUnexpectedResultSource());
IntentCallback callback = mWindowAndroid.getIntentCallback();
callback.onIntentCompleted(mWindowAndroid, Activity.RESULT_CANCELED, null);
Assert.assertEquals(
VoiceInteractionSource.NTP, mHandler.getVoiceSearchUnexpectedResultSource());
}
/**
* Kicks off voice recognition with the given source, for testing
* {@linkVoiceRecognitionHandler.VoiceRecognitionCompleteCallback}.
*
* @param source The source of the voice recognition initiation.
*/
private void startVoiceRecognition(@VoiceInteractionSource int source) {
mPermissionDelegate.setHasPermission(true);
TestThreadUtils.runOnUiThreadBlocking(() -> { mHandler.startVoiceRecognition(source); });
}
private static Bundle createDummyBundle(String text, float confidence) {
return createDummyBundle(new String[] {text}, new float[] {confidence}, null);
}
private static Bundle createDummyBundle(
String text, float confidence, @Nullable String language) {
return createDummyBundle(new String[] {text}, new float[] {confidence},
language == null ? null : new String[] {language});
}
private static Bundle createDummyBundle(String[] texts, float[] confidences) {
return createDummyBundle(texts, confidences, null);
}
private static Bundle createDummyBundle(
String[] texts, float[] confidences, @Nullable String[] languages) {
Bundle b = new Bundle();
b.putStringArrayList(
RecognizerIntent.EXTRA_RESULTS, new ArrayList<String>(Arrays.asList(texts)));
b.putFloatArray(RecognizerIntent.EXTRA_CONFIDENCE_SCORES, confidences);
if (languages != null) {
b.putStringArrayList(VoiceRecognitionHandler.VOICE_QUERY_RESULT_LANGUAGES,
new ArrayList<String>(Arrays.asList(languages)));
}
return b;
}
private static void assertVoiceResultsAreEqual(
List<VoiceResult> results, String[] texts, float[] confidences) {
assertVoiceResultsAreEqual(results, texts, confidences, null);
}
private static void assertVoiceResultsAreEqual(
List<VoiceResult> results, String[] texts, float[] confidences, String[] languages) {
Assert.assertTrue("Invalid array sizes",
results.size() == texts.length && texts.length == confidences.length);
if (languages != null) {
Assert.assertTrue("Invalid array sizes", confidences.length == languages.length);
}
for (int i = 0; i < texts.length; ++i) {
VoiceResult result = results.get(i);
Assert.assertEquals("Match text is not equal", texts[i], result.getMatch());
Assert.assertEquals(
"Confidence is not equal", confidences[i], result.getConfidence(), 0);
if (languages != null) {
Assert.assertEquals("Languages not equal", result.getLanguage(), languages[i]);
}
}
}
private static void setAudioCapturePref(boolean value) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
UserPrefs.get(Profile.getLastUsedRegularProfile())
.setBoolean(Pref.AUDIO_CAPTURE_ALLOWED, value);
});
}
}