[Ai Assistant Service] Added experiment params
This CL adds experiment params to adjust the duration of the availability cache and to toggle the intent fallback. Additionally it adds a toast error message whenever the cache is stale and a feature is not available. Bug: 402813682 Change-Id: I60e84e61c4ddcb765a7397c3550cd6c0b10b777f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6393816 Commit-Queue: Salvador Guerrero Ramos <salg@google.com> Reviewed-by: Siddhartha S <ssid@chromium.org> Cr-Commit-Position: refs/heads/main@{#1439820}
This commit is contained in:
parent
d7062ef688
commit
a9ed2724d5
chrome
android
java/src/org/chromium/chrome/browser/ai
junit/src/org/chromium/chrome/browser/ai
browser
about_flags.cc
ui/android/strings
@ -22,6 +22,7 @@ import org.chromium.base.ThreadUtils;
|
||||
import org.chromium.base.shared_preferences.SharedPreferencesManager;
|
||||
import org.chromium.build.annotations.NullMarked;
|
||||
import org.chromium.build.annotations.Nullable;
|
||||
import org.chromium.chrome.R;
|
||||
import org.chromium.chrome.browser.ai.proto.SystemAiProviderService.Account;
|
||||
import org.chromium.chrome.browser.ai.proto.SystemAiProviderService.AnalyzeAttachment;
|
||||
import org.chromium.chrome.browser.ai.proto.SystemAiProviderService.AvailabilityRequest;
|
||||
@ -46,6 +47,7 @@ import org.chromium.components.signin.Tribool;
|
||||
import org.chromium.components.signin.base.CoreAccountInfo;
|
||||
import org.chromium.components.signin.identitymanager.ConsentLevel;
|
||||
import org.chromium.components.signin.identitymanager.IdentityManager;
|
||||
import org.chromium.ui.widget.Toast;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@ -62,8 +64,6 @@ import java.util.Optional;
|
||||
public class AiAssistantService {
|
||||
|
||||
private static final String TAG = "AiAssistantService";
|
||||
// TODO(402813682): Control this with experiment param.
|
||||
private static final long AVAILABILITY_CHECK_CACHE_DURATION_MS = Duration.ofDays(1).toMillis();
|
||||
static final String EXTRA_LAUNCH_REQUEST =
|
||||
"org.chromium.chrome.browser.ai.proto.SystemAiProviderService.LaunchRequest";
|
||||
|
||||
@ -293,8 +293,9 @@ public class AiAssistantService {
|
||||
.getSupportedCapabilitiesList()
|
||||
.contains(Capability.ANALYZE_ATTACHMENT_CAPABILITY);
|
||||
} else {
|
||||
mIsSummarizeAvailable = true;
|
||||
mIsAnalyzeAttachmentAvailable = true;
|
||||
var shouldUseFallback = isIntentFallbackEnabled();
|
||||
mIsSummarizeAvailable = shouldUseFallback;
|
||||
mIsAnalyzeAttachmentAvailable = shouldUseFallback;
|
||||
}
|
||||
|
||||
saveAvailabilityToPrefs();
|
||||
@ -317,7 +318,7 @@ public class AiAssistantService {
|
||||
mSharedPreferencesManager.readLong(
|
||||
ChromePreferenceKeys.AI_ASSISTANT_AVAILABILITY_CHECK_TIMESTAMP_MS);
|
||||
var timeSinceLastCheckMs = System.currentTimeMillis() - lastCheckTimestampMs;
|
||||
if (timeSinceLastCheckMs >= AVAILABILITY_CHECK_CACHE_DURATION_MS) {
|
||||
if (timeSinceLastCheckMs >= getAvailabilityCacheDurationMs()) {
|
||||
// If the cache is expired then delete its values from prefs, but keep them loaded in
|
||||
// memory.
|
||||
deleteAvailabilityFromPrefs();
|
||||
@ -328,6 +329,24 @@ public class AiAssistantService {
|
||||
return PrefLoadingResult.LOADED;
|
||||
}
|
||||
|
||||
private boolean isIntentFallbackEnabled() {
|
||||
boolean intentFallback =
|
||||
ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY,
|
||||
"intent_fallback",
|
||||
false);
|
||||
return intentFallback;
|
||||
}
|
||||
|
||||
private long getAvailabilityCacheDurationMs() {
|
||||
var durationDays =
|
||||
ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY,
|
||||
"availability_cache_duration_days",
|
||||
1);
|
||||
return Duration.ofDays(durationDays).toMillis();
|
||||
}
|
||||
|
||||
private void saveAvailabilityToPrefs() {
|
||||
// Don't cache results if system provider is not available.
|
||||
if (!mIsInitialized || !mIsSystemAiProviderAvailable) {
|
||||
@ -361,6 +380,18 @@ public class AiAssistantService {
|
||||
private void requestLaunch(Context context, Tab tab) {
|
||||
if (!isTabElegible(tab)) return;
|
||||
|
||||
if (!mIsSystemAiProviderAvailable && !isIntentFallbackEnabled()) {
|
||||
ThreadUtils.postOnUiThread(
|
||||
() -> {
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.ai_assistant_service_error_toast,
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTabPdf(tab)
|
||||
&& tab.getNativePage() instanceof PdfPage pdfPage
|
||||
&& mIsAnalyzeAttachmentAvailable) {
|
||||
@ -383,6 +414,15 @@ public class AiAssistantService {
|
||||
context, tab, shouldUseSystemProvider, innerText);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ThreadUtils.postOnUiThread(
|
||||
() -> {
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.ai_assistant_service_error_toast,
|
||||
Toast.LENGTH_LONG)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@ -24,6 +25,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
@ -43,7 +46,9 @@ import org.robolectric.android.util.concurrent.PausedExecutorService;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.annotation.LooperMode.Mode;
|
||||
import org.robolectric.shadows.ShadowApplication;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
import org.robolectric.shadows.ShadowToast;
|
||||
|
||||
import org.chromium.base.Callback;
|
||||
import org.chromium.base.Promise;
|
||||
@ -70,6 +75,8 @@ import org.chromium.components.signin.base.CoreAccountInfo;
|
||||
import org.chromium.components.signin.identitymanager.IdentityManager;
|
||||
import org.chromium.content_public.browser.RenderFrameHost;
|
||||
import org.chromium.content_public.browser.WebContents;
|
||||
import org.chromium.ui.base.TestActivity;
|
||||
import org.chromium.ui.widget.ToastManager;
|
||||
import org.chromium.url.JUnitTestGURLs;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -77,14 +84,19 @@ import java.util.Optional;
|
||||
|
||||
/** Unit tests for {@link AiAssistantService}. */
|
||||
@RunWith(BaseRobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
@Config(
|
||||
manifest = Config.NONE,
|
||||
shadows = {ShadowToast.class})
|
||||
@LooperMode(Mode.PAUSED)
|
||||
@EnableFeatures({ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY})
|
||||
public class AiAssistantServiceUnitTest {
|
||||
|
||||
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Rule
|
||||
public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
|
||||
new ActivityScenarioRule<>(TestActivity.class);
|
||||
|
||||
@Mock private RenderFrameHost mRenderFrameHost;
|
||||
@Mock private WebContents mWebContents;
|
||||
@Mock private Tab mTab;
|
||||
@ -104,6 +116,7 @@ public class AiAssistantServiceUnitTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ToastManager.resetForTesting();
|
||||
AiAssistantService.resetForTesting();
|
||||
when(mSystemAiProviderFactory.createSystemAiProvider()).thenReturn(mSystemAiProvider);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.GOOGLE_URL_CAT);
|
||||
@ -122,26 +135,34 @@ public class AiAssistantServiceUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeatures(
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY + ":intent_fallback/true")
|
||||
public void showAi_fallsBackToIntentWhenNoDownstreamImpl() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(SystemAiProvider.class, null);
|
||||
var pageContents = "Page contents for one.com";
|
||||
setInnerTextExtractionResult(pageContents);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_1);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(service);
|
||||
|
||||
service.canShowAiForTab(mContext, mTab);
|
||||
service.showAi(mContext, mTab);
|
||||
ShadowLooper.idleMainLooper();
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
initializeAiAssistantService(activity, service);
|
||||
service.showAi(activity, mTab);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
var launchRequest = assertVoiceActivityStartedWithLaunchRequest();
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
launchRequest, JUnitTestGURLs.URL_1.getSpec(), pageContents);
|
||||
var launchRequest = assertVoiceActivityStartedWithLaunchRequest();
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
launchRequest, JUnitTestGURLs.URL_1.getSpec(), pageContents);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeatures(
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY + ":intent_fallback/true")
|
||||
public void showAi_fallsBackToIntentWhenDownstreamImplNotAvailable() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
var pageContents = "Page contents for google.com/dog";
|
||||
@ -150,17 +171,24 @@ public class AiAssistantServiceUnitTest {
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.GOOGLE_URL_DOG);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(service);
|
||||
service.showAi(mContext, mTab);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
var launchRequest = assertVoiceActivityStartedWithLaunchRequest();
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
launchRequest, JUnitTestGURLs.GOOGLE_URL_DOG.getSpec(), pageContents);
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
initializeAiAssistantService(activity, service);
|
||||
service.showAi(activity, mTab);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
var launchRequest = assertVoiceActivityStartedWithLaunchRequest();
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
launchRequest, JUnitTestGURLs.GOOGLE_URL_DOG.getSpec(), pageContents);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeatures(
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY + ":intent_fallback/true")
|
||||
public void showAi_fallsBackToIntentWhenDownstreamImplNotAvailable_pdfPage() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
setSystemAiProviderAsUnavailable();
|
||||
@ -171,38 +199,107 @@ public class AiAssistantServiceUnitTest {
|
||||
when(mTab.getNativePage()).thenReturn(pdfPage);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(service);
|
||||
service.showAi(mContext, mTab);
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
initializeAiAssistantService(activity, service);
|
||||
service.showAi(activity, mTab);
|
||||
|
||||
var launchRequest = assertVoiceActivityStartedWithLaunchRequest();
|
||||
assertLaunchRequestIsForAnalyzeAttachment(launchRequest, pdfUri);
|
||||
var launchRequest = assertVoiceActivityStartedWithLaunchRequest();
|
||||
assertLaunchRequestIsForAnalyzeAttachment(launchRequest, pdfUri);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeatures(
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY
|
||||
+ ":intent_fallback/false")
|
||||
public void showAi_showsErrorWhenDownstreamImplNotAvailableAndFallbackDisabled() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
var pageContents = "Page contents for google.com/dog";
|
||||
setInnerTextExtractionResult(pageContents);
|
||||
setSystemAiProviderAsUnavailable();
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.GOOGLE_URL_DOG);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
initializeAiAssistantService(activity, service);
|
||||
service.showAi(activity, mTab);
|
||||
ShadowLooper.idleMainLooper();
|
||||
// Assert no intent was sent.
|
||||
assertNull(ShadowApplication.getInstance().getNextStartedActivity());
|
||||
// Assert system provider wasn't called.
|
||||
verify(mSystemAiProvider, never()).launch(any(), any());
|
||||
// Toast should be shown instead.
|
||||
assertEquals(1, ShadowToast.shownToastCount());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFeatures(
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY
|
||||
+ ":intent_fallback/false")
|
||||
public void showAi_showsErrorWhenFeatureNotAvailableAndFallbackDisabled() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
var pageContents = "Page contents for google.com/dog";
|
||||
setInnerTextExtractionResult(pageContents);
|
||||
// Set system provider as available, but only for analyzing attachments.
|
||||
setSystemAiProviderAsAvailable(
|
||||
/* canUseSummarizeUrl= */ false, /* canUseAnalyzeAttachment= */ true);
|
||||
// Try to use the summarize URL feature.
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.GOOGLE_URL_DOG);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
initializeAiAssistantService(activity, service);
|
||||
service.showAi(activity, mTab);
|
||||
ShadowLooper.idleMainLooper();
|
||||
// Assert no intent was sent.
|
||||
assertNull(ShadowApplication.getInstance().getNextStartedActivity());
|
||||
// Assert system provider wasn't called.
|
||||
verify(mSystemAiProvider, never()).launch(any(), any());
|
||||
// Toast should be shown instead.
|
||||
assertEquals(1, ShadowToast.shownToastCount());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showAi_usesDownstreamImplWhenAvailable() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
setSystemAiProviderAsAvailable();
|
||||
setSystemAiProviderAsAvailableWithAllFeatures();
|
||||
var pageContents = "Page contents for URL_2";
|
||||
setInnerTextExtractionResult(pageContents);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_2);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(service);
|
||||
service.showAi(mContext, mTab);
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
initializeAiAssistantService(activity, service);
|
||||
service.showAi(activity, mTab);
|
||||
|
||||
ShadowLooper.idleMainLooper();
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mSystemAiProvider).launch(eq(mContext), mLaunchRequestCaptor.capture());
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
mLaunchRequestCaptor.getValue(), JUnitTestGURLs.URL_2.getSpec(), pageContents);
|
||||
verify(mSystemAiProvider).launch(eq(activity), mLaunchRequestCaptor.capture());
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
mLaunchRequestCaptor.getValue(),
|
||||
JUnitTestGURLs.URL_2.getSpec(),
|
||||
pageContents);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showAi_usesAnalyzeDocumentForPdfs() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
setSystemAiProviderAsAvailable();
|
||||
setSystemAiProviderAsAvailableWithAllFeatures();
|
||||
var pdfUri = "https://google.com/file.pdf";
|
||||
var pdfPage = mock(PdfPage.class);
|
||||
when(pdfPage.getUri()).thenReturn(Uri.parse(pdfUri));
|
||||
@ -210,91 +307,109 @@ public class AiAssistantServiceUnitTest {
|
||||
when(mTab.getNativePage()).thenReturn(pdfPage);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(service);
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
initializeAiAssistantService(activity, service);
|
||||
|
||||
service.showAi(mContext, mTab);
|
||||
// Simulate availability query response.
|
||||
mPausedExecutorService.runAll();
|
||||
service.showAi(activity, mTab);
|
||||
// Simulate availability query response.
|
||||
mPausedExecutorService.runAll();
|
||||
|
||||
verify(mSystemAiProvider).launch(eq(mContext), mLaunchRequestCaptor.capture());
|
||||
assertLaunchRequestIsForAnalyzeAttachment(mLaunchRequestCaptor.getValue(), pdfUri);
|
||||
verify(mSystemAiProvider).launch(eq(activity), mLaunchRequestCaptor.capture());
|
||||
assertLaunchRequestIsForAnalyzeAttachment(
|
||||
mLaunchRequestCaptor.getValue(), pdfUri);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canShowAiForTab_initialCall() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_1);
|
||||
setSystemAiProviderAsAvailable();
|
||||
setSystemAiProviderAsAvailableWithAllFeatures();
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
var result = service.canShowAiForTab(activity, mTab);
|
||||
|
||||
var result = service.canShowAiForTab(mContext, mTab);
|
||||
|
||||
assertFalse("AI service should not be available, as we just queried the provider", result);
|
||||
// System provider should be queried on the first call.
|
||||
verify(mSystemAiProvider).isAvailable(eq(mContext), any());
|
||||
assertFalse(
|
||||
"AI service should not be available, as we just queried the provider",
|
||||
result);
|
||||
// System provider should be queried on the first call.
|
||||
verify(mSystemAiProvider).isAvailable(eq(activity), any());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canShowAiForTab_inMemoryCache() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_1);
|
||||
setSystemAiProviderAsAvailable();
|
||||
setSystemAiProviderAsAvailableWithAllFeatures();
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
var firstAvailabilityResult = service.canShowAiForTab(activity, mTab);
|
||||
|
||||
var firstAvailabilityResult = service.canShowAiForTab(mContext, mTab);
|
||||
assertFalse(
|
||||
"AI service should not be available, as we just queried the provider",
|
||||
firstAvailabilityResult);
|
||||
// System provider should be queried on the first call.
|
||||
verify(mSystemAiProvider).isAvailable(eq(activity), any());
|
||||
// Simulate query response.
|
||||
mPausedExecutorService.runAll();
|
||||
|
||||
assertFalse(
|
||||
"AI service should not be available, as we just queried the provider",
|
||||
firstAvailabilityResult);
|
||||
// System provider should be queried on the first call.
|
||||
verify(mSystemAiProvider).isAvailable(eq(mContext), any());
|
||||
// Simulate query response.
|
||||
mPausedExecutorService.runAll();
|
||||
var secondAvailabilityResult = service.canShowAiForTab(activity, mTab);
|
||||
|
||||
var secondAvailabilityResult = service.canShowAiForTab(mContext, mTab);
|
||||
assertTrue(
|
||||
"AI service should now be available, as its response was cached in"
|
||||
+ " memory",
|
||||
secondAvailabilityResult);
|
||||
|
||||
assertTrue(
|
||||
"AI service should now be available, as its response was cached in memory",
|
||||
secondAvailabilityResult);
|
||||
|
||||
verifyNoMoreInteractions(mSystemAiProvider);
|
||||
verifyNoMoreInteractions(mSystemAiProvider);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canShowAiForTab_preferenceCache() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_1);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
setSystemAiProviderAsAvailable();
|
||||
setSystemAiProviderAsAvailableWithAllFeatures();
|
||||
|
||||
var firstAvailabilityResult = service.canShowAiForTab(mContext, mTab);
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
var service = AiAssistantService.getInstance();
|
||||
var firstAvailabilityResult = service.canShowAiForTab(activity, mTab);
|
||||
|
||||
assertFalse(
|
||||
"AI service should not be available, as we just queried the provider",
|
||||
firstAvailabilityResult);
|
||||
// System provider should be queried on the first call.
|
||||
verify(mSystemAiProvider).isAvailable(eq(mContext), any());
|
||||
// Simulate query response.
|
||||
mPausedExecutorService.runAll();
|
||||
assertFalse(
|
||||
"AI service should not be available, as we just queried the provider",
|
||||
firstAvailabilityResult);
|
||||
// System provider should be queried on the first call.
|
||||
verify(mSystemAiProvider).isAvailable(eq(activity), any());
|
||||
// Simulate query response.
|
||||
mPausedExecutorService.runAll();
|
||||
|
||||
service = null;
|
||||
AiAssistantService.resetForTesting();
|
||||
service = null;
|
||||
AiAssistantService.resetForTesting();
|
||||
|
||||
var newServiceInstance = AiAssistantService.getInstance();
|
||||
var newServiceInstance = AiAssistantService.getInstance();
|
||||
|
||||
var secondInstanceResult = newServiceInstance.canShowAiForTab(mContext, mTab);
|
||||
var secondInstanceResult = newServiceInstance.canShowAiForTab(activity, mTab);
|
||||
|
||||
assertTrue(
|
||||
"AI service should now be available, as its response was cached in prefs",
|
||||
secondInstanceResult);
|
||||
assertTrue(
|
||||
"AI service should now be available, as its response was cached in"
|
||||
+ " prefs",
|
||||
secondInstanceResult);
|
||||
|
||||
verifyNoMoreInteractions(mSystemAiProvider);
|
||||
verifyNoMoreInteractions(mSystemAiProvider);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -302,27 +417,30 @@ public class AiAssistantServiceUnitTest {
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY
|
||||
+ ":attach_client_info/true")
|
||||
public void showAi_attachesAccountInfoWithFlag() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
setSystemAiProviderAsAvailable();
|
||||
setSystemAiProviderAsAvailableWithAllFeatures();
|
||||
var pageContents = "Page contents for URL_2";
|
||||
var clientEmail = "foo@bar.com";
|
||||
setInnerTextExtractionResult(pageContents);
|
||||
setClientEmail(clientEmail);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_2);
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(activity, service);
|
||||
fulfillAccountCapabilities(false);
|
||||
service.showAi(activity, mTab);
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(service);
|
||||
fulfillAccountCapabilities(false);
|
||||
|
||||
service.showAi(mContext, mTab);
|
||||
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mSystemAiProvider).launch(eq(mContext), mLaunchRequestCaptor.capture());
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
mLaunchRequestCaptor.getValue(), JUnitTestGURLs.URL_2.getSpec(), pageContents);
|
||||
assertLaunchRequestHasClientInfo(mLaunchRequestCaptor.getValue(), clientEmail);
|
||||
verify(mSystemAiProvider).launch(eq(activity), mLaunchRequestCaptor.capture());
|
||||
assertLaunchRequestIsForSummarizeUrl(
|
||||
mLaunchRequestCaptor.getValue(),
|
||||
JUnitTestGURLs.URL_2.getSpec(),
|
||||
pageContents);
|
||||
assertLaunchRequestHasClientInfo(mLaunchRequestCaptor.getValue(), clientEmail);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -330,45 +448,61 @@ public class AiAssistantServiceUnitTest {
|
||||
ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY
|
||||
+ ":attach_client_info/true")
|
||||
public void showAi_parentControlDisables() {
|
||||
var activityScenario = mActivityScenarioRule.getScenario();
|
||||
|
||||
ServiceLoaderUtil.setInstanceForTesting(
|
||||
SystemAiProviderFactory.class, mSystemAiProviderFactory);
|
||||
setSystemAiProviderAsAvailable();
|
||||
setSystemAiProviderAsAvailableWithAllFeatures();
|
||||
var pageContents = "Page contents for URL_2";
|
||||
var clientEmail = "foo@bar.com";
|
||||
setInnerTextExtractionResult(pageContents);
|
||||
setClientEmail(clientEmail);
|
||||
when(mTab.getUrl()).thenReturn(JUnitTestGURLs.URL_2);
|
||||
|
||||
var service = AiAssistantService.getInstance();
|
||||
setSystemAiProviderAsAvailable();
|
||||
initializeAiAssistantService(service);
|
||||
fulfillAccountCapabilities(true);
|
||||
activityScenario.onActivity(
|
||||
activity -> {
|
||||
var service = AiAssistantService.getInstance();
|
||||
initializeAiAssistantService(activity, service);
|
||||
fulfillAccountCapabilities(true);
|
||||
|
||||
service.showAi(mContext, mTab);
|
||||
service.showAi(activity, mTab);
|
||||
|
||||
ShadowLooper.idleMainLooper();
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
verify(mSystemAiProvider, never()).launch(any(), any());
|
||||
verify(mSystemAiProvider, never()).launch(any(), any());
|
||||
});
|
||||
}
|
||||
|
||||
private void setSystemAiProviderAsAvailableWithAllFeatures() {
|
||||
setSystemAiProviderAsAvailable(
|
||||
/* canUseSummarizeUrl= */ true, /* canUseAnalyzeAttachment= */ true);
|
||||
}
|
||||
|
||||
// Sets the result of calling the system provider, the result gets scheduled on
|
||||
// mPausedExecutorService, so we need to call mPausedExecutorService.runAll() afterwards to
|
||||
// simulate the query response.
|
||||
private void setSystemAiProviderAsAvailable() {
|
||||
var serviceAvailableBuilder =
|
||||
ServiceAvailable.newBuilder()
|
||||
.addSupportedCapabilities(Capability.ANALYZE_ATTACHMENT_CAPABILITY)
|
||||
.addSupportedCapabilities(Capability.SUMMARIZE_URL_CAPABILITY);
|
||||
private void setSystemAiProviderAsAvailable(
|
||||
boolean canUseSummarizeUrl, boolean canUseAnalyzeAttachment) {
|
||||
var serviceAvailableBuilder = ServiceAvailable.newBuilder();
|
||||
|
||||
if (canUseAnalyzeAttachment) {
|
||||
serviceAvailableBuilder.addSupportedCapabilities(
|
||||
Capability.ANALYZE_ATTACHMENT_CAPABILITY);
|
||||
}
|
||||
if (canUseSummarizeUrl) {
|
||||
serviceAvailableBuilder.addSupportedCapabilities(Capability.SUMMARIZE_URL_CAPABILITY);
|
||||
}
|
||||
|
||||
var availabilityResponse =
|
||||
AvailabilityResponse.newBuilder().setAvailable(serviceAvailableBuilder).build();
|
||||
when(mSystemAiProvider.isAvailable(eq(mContext), any()))
|
||||
when(mSystemAiProvider.isAvailable(any(), any()))
|
||||
.thenReturn(
|
||||
MoreExecutors.listeningDecorator(mPausedExecutorService)
|
||||
.submit(() -> availabilityResponse));
|
||||
}
|
||||
|
||||
private void initializeAiAssistantService(AiAssistantService service) {
|
||||
service.canShowAiForTab(mContext, mTab);
|
||||
private void initializeAiAssistantService(Context context, AiAssistantService service) {
|
||||
service.canShowAiForTab(context, mTab);
|
||||
mPausedExecutorService.runAll();
|
||||
}
|
||||
|
||||
@ -386,7 +520,7 @@ public class AiAssistantServiceUnitTest {
|
||||
AvailabilityResponse.newBuilder()
|
||||
.setNotAvailable(ServiceNotAvailable.getDefaultInstance())
|
||||
.build();
|
||||
when(mSystemAiProvider.isAvailable(eq(mContext), any()))
|
||||
when(mSystemAiProvider.isAvailable(any(), any()))
|
||||
.thenReturn(Futures.immediateFuture(availabilityResponse));
|
||||
}
|
||||
|
||||
@ -438,9 +572,7 @@ public class AiAssistantServiceUnitTest {
|
||||
}
|
||||
|
||||
private LaunchRequest assertVoiceActivityStartedWithLaunchRequest() {
|
||||
verify(mContext).startActivity(mIntentCaptor.capture());
|
||||
|
||||
var startedIntent = mIntentCaptor.getValue();
|
||||
var startedIntent = ShadowApplication.getInstance().getNextStartedActivity();
|
||||
var launchRequestBytes =
|
||||
startedIntent.getByteArrayExtra(AiAssistantService.EXTRA_LAUNCH_REQUEST);
|
||||
assertEquals(Intent.ACTION_VOICE_COMMAND, startedIntent.getAction());
|
||||
|
@ -608,6 +608,18 @@ const FeatureEntry::FeatureVariation kCCTAdaptiveButtonVariations[] = {
|
||||
std::size(kCCTAdaptiveButtonEnableBoth), nullptr},
|
||||
};
|
||||
|
||||
const FeatureEntry::FeatureParam
|
||||
kAdaptiveButtonInTopToolbarPageSummaryDisableFallback[] = {
|
||||
{"intent_fallback", "false"},
|
||||
};
|
||||
const FeatureEntry::FeatureVariation
|
||||
kAdaptiveButtonInTopToolbarPageSummaryVariations[] = {
|
||||
{"(Disable intent fallback)",
|
||||
kAdaptiveButtonInTopToolbarPageSummaryDisableFallback,
|
||||
std::size(kAdaptiveButtonInTopToolbarPageSummaryDisableFallback),
|
||||
nullptr},
|
||||
};
|
||||
|
||||
const FeatureEntry::FeatureParam kCCTAuthTabHttpsVerificationTimeout10000Ms[] =
|
||||
{{"verification_timeout_ms", "10000"}};
|
||||
const FeatureEntry::FeatureParam kCCTAuthTabHttpsVerificationTimeout1000Ms[] = {
|
||||
@ -5319,8 +5331,10 @@ const FeatureEntry kFeatureEntries[] = {
|
||||
flag_descriptions::kAdaptiveButtonInTopToolbarPageSummaryName,
|
||||
flag_descriptions::kAdaptiveButtonInTopToolbarPageSummaryDescription,
|
||||
kOsAndroid,
|
||||
FEATURE_VALUE_TYPE(
|
||||
chrome::android::kAdaptiveButtonInTopToolbarPageSummary)},
|
||||
FEATURE_WITH_PARAMS_VALUE_TYPE(
|
||||
chrome::android::kAdaptiveButtonInTopToolbarPageSummary,
|
||||
kAdaptiveButtonInTopToolbarPageSummaryVariations,
|
||||
"AdaptiveButtonInTopToolbarPageSummary")},
|
||||
{"contextual-page-actions-share-model",
|
||||
flag_descriptions::kContextualPageActionsShareModelName,
|
||||
flag_descriptions::kContextualPageActionsShareModelDescription, kOsAndroid,
|
||||
|
@ -4278,6 +4278,9 @@ To change this setting, <ph name="BEGIN_LINK">BEGIN_LINK</ph>delete the Chrome d
|
||||
<message name="IDS_TOOLBAR_COPY_LINK" desc="The menu option for copying link in the address bar.">
|
||||
Copy link
|
||||
</message>
|
||||
<message name="IDS_AI_ASSISTANT_SERVICE_ERROR_TOAST" desc="Text to be shown in a toast when unable to invoke an AI assistant">
|
||||
Couldn’t open AI assistant. Try again.
|
||||
</message>
|
||||
|
||||
<!-- Main menu items -->
|
||||
<message name="IDS_MENU_UPDATE" desc="Menu item for updating chrome. [CHAR_LIMIT=24]">
|
||||
|
1
chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_AI_ASSISTANT_SERVICE_ERROR_TOAST.png.sha1
Normal file
1
chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_AI_ASSISTANT_SERVICE_ERROR_TOAST.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
99bed0517640b62306e48b9f2fdfed89cc963a22
|
Loading…
x
Reference in New Issue
Block a user