Commit e792f217 authored by Juan Mojica's avatar Juan Mojica Committed by Chromium LUCI CQ

Use LensController to start a Lens intent.

Also adds feature flags for lens prewarming.

Bug: 1098431
Change-Id: Iae61ec71d6ab2f7d40f1231823cddc8b00ceabb5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2500525
Commit-Queue: Juan Mojica <juanmojica@google.com>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Reviewed-by: default avatarBen Goldberger <benwgold@google.com>
Cr-Commit-Position: refs/heads/master@{#837969}
parent 6b29bb7d
...@@ -91,6 +91,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { ...@@ -91,6 +91,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
private final LensQueryResult mLensQueryResultWithShoppingItent = private final LensQueryResult mLensQueryResultWithShoppingItent =
(new LensQueryResult.Builder()).withIsShoppyIntent(true).build(); (new LensQueryResult.Builder()).withIsShoppyIntent(true).build();
private boolean mEnableLensWithSearchByImageText; private boolean mEnableLensWithSearchByImageText;
private boolean mIsLensIntentInProgress;
private @Nullable UkmRecorder.Bridge mUkmRecorderBridge; private @Nullable UkmRecorder.Bridge mUkmRecorderBridge;
private ContextMenuNativeDelegate mNativeDelegate; private ContextMenuNativeDelegate mNativeDelegate;
private static final String LENS_SEARCH_MENU_ITEM_KEY = "searchWithGoogleLensMenuItem"; private static final String LENS_SEARCH_MENU_ITEM_KEY = "searchWithGoogleLensMenuItem";
...@@ -774,6 +775,11 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { ...@@ -774,6 +775,11 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile()); TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
if (tracker.isInitialized()) tracker.dismissed(FeatureConstants.EPHEMERAL_TAB_FEATURE); if (tracker.isInitialized()) tracker.dismissed(FeatureConstants.EPHEMERAL_TAB_FEATURE);
} }
if (!mIsLensIntentInProgress) {
// TODO(crbug/1158604): Remove leftover Lens dependencies.
LensUtils.terminateLensConnectionsIfNecessary(mItemDelegate.isIncognito());
}
} }
private WindowAndroid getWindow() { private WindowAndroid getWindow() {
...@@ -834,6 +840,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { ...@@ -834,6 +840,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
*/ */
protected void searchWithGoogleLens( protected void searchWithGoogleLens(
boolean requiresConfirmation, @Nullable LensQueryResult lensQueryResult) { boolean requiresConfirmation, @Nullable LensQueryResult lensQueryResult) {
mIsLensIntentInProgress = true;
mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.PNG, (Uri imageUri) -> { mNativeDelegate.retrieveImageForShare(ContextMenuImageFormat.PNG, (Uri imageUri) -> {
ShareHelper.shareImageWithGoogleLens(getWindow(), imageUri, mItemDelegate.isIncognito(), ShareHelper.shareImageWithGoogleLens(getWindow(), imageUri, mItemDelegate.isIncognito(),
mParams.getSrcUrl(), mParams.getTitleText(), mParams.getPageUrl(), mParams.getSrcUrl(), mParams.getTitleText(), mParams.getPageUrl(),
......
...@@ -224,6 +224,8 @@ public class ContextMenuHelper { ...@@ -224,6 +224,8 @@ public class ContextMenuHelper {
Log.i(TAG, "Created mCurrentContextMenu: " + mCurrentContextMenu); Log.i(TAG, "Created mCurrentContextMenu: " + mCurrentContextMenu);
Log.i(TAG, "Activity was " + mWindow.getActivity().get() + " when the menu was created."); Log.i(TAG, "Activity was " + mWindow.getActivity().get() + " when the menu was created.");
// TODO(crbug/1158604): Remove leftover Lens dependencies.
LensUtils.startLensConnectionIfNecessary(mIsIncognito);
if (mChipDelegate != null) { if (mChipDelegate != null) {
menuCoordinator.displayMenuWithChip(mWindow, mWebContents, mCurrentContextMenuParams, menuCoordinator.displayMenuWithChip(mWindow, mWebContents, mCurrentContextMenuParams,
items, mCallback, mOnMenuShown, mOnMenuClosed, mChipDelegate); items, mCallback, mOnMenuShown, mOnMenuClosed, mChipDelegate);
......
...@@ -16,6 +16,8 @@ import org.chromium.base.ContextUtils; ...@@ -16,6 +16,8 @@ import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.gsa.GSAState; import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.lens.LensController;
import org.chromium.chrome.browser.lens.LensIntentParams;
import org.chromium.chrome.browser.lens.LensQueryResult; import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
...@@ -28,6 +30,7 @@ import org.chromium.url.GURL; ...@@ -28,6 +30,7 @@ import org.chromium.url.GURL;
/** /**
* This class provides utilities for intenting into Google Lens. * This class provides utilities for intenting into Google Lens.
*/ */
// TODO(crbug/1157496): Consolidate param-checks into a single function.
public class LensUtils { public class LensUtils {
private static final String LENS_CONTRACT_URI = "googleapp://lens"; private static final String LENS_CONTRACT_URI = "googleapp://lens";
private static final String LENS_DIRECT_INTENT_CONTRACT_URI = "google://lens"; private static final String LENS_DIRECT_INTENT_CONTRACT_URI = "google://lens";
...@@ -47,6 +50,8 @@ public class LensUtils { ...@@ -47,6 +50,8 @@ public class LensUtils {
"minAgsaVersionNameForShopping"; "minAgsaVersionNameForShopping";
private static final String MIN_AGSA_VERSION_DIRECT_INTENT_FEATURE_PARAM_NAME = private static final String MIN_AGSA_VERSION_DIRECT_INTENT_FEATURE_PARAM_NAME =
"minAgsaVersionForDirectIntent"; "minAgsaVersionForDirectIntent";
private static final String MIN_AGSA_VERSION_DIRECT_INTENT_SDK_FEATURE_PARAM_NAME =
"minAgsaVersionForDirectIntentSdk";
private static final String USE_SEARCH_BY_IMAGE_TEXT_FEATURE_PARAM_NAME = private static final String USE_SEARCH_BY_IMAGE_TEXT_FEATURE_PARAM_NAME =
"useSearchByImageText"; "useSearchByImageText";
private static final String LENS_SHOPPING_ALLOWLIST_ENTRIES_FEATURE_PARAM_NAME = private static final String LENS_SHOPPING_ALLOWLIST_ENTRIES_FEATURE_PARAM_NAME =
...@@ -58,12 +63,15 @@ public class LensUtils { ...@@ -58,12 +63,15 @@ public class LensUtils {
private static final String SEND_ALT_PARAM_NAME = "sendAlt"; private static final String SEND_ALT_PARAM_NAME = "sendAlt";
private static final String SEND_PAGE_PARAM_NAME = "sendPage"; private static final String SEND_PAGE_PARAM_NAME = "sendPage";
private static final String USE_DIRECT_INTENT_FEATURE_PARAM_NAME = "useDirectIntent"; private static final String USE_DIRECT_INTENT_FEATURE_PARAM_NAME = "useDirectIntent";
private static final String USE_DIRECT_INTENT_SDK_INTEGRATION_PARAM_NAME =
"useDirectIntentSdkIntegration";
private static final String DISABLE_ON_INCOGNITO_PARAM_NAME = "disableOnIncognito"; private static final String DISABLE_ON_INCOGNITO_PARAM_NAME = "disableOnIncognito";
private static final String ORDER_SHARE_IMAGE_BEFORE_LENS_PARAM_NAME = private static final String ORDER_SHARE_IMAGE_BEFORE_LENS_PARAM_NAME =
"orderShareImageBeforeLens"; "orderShareImageBeforeLens";
private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_POSTCAPTURE = "10.65"; private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_POSTCAPTURE = "10.65";
private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_CHROME_SHOPPING_INTENT = "11.16"; private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_CHROME_SHOPPING_INTENT = "11.16";
private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_DIRECT_INTENT = "11.34"; private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_DIRECT_INTENT = "11.34";
private static final String MIN_AGSA_VERSION_NAME_FOR_LENS_DIRECT_INTENT_SDK = "11.39.7";
private static final int LENS_INTENT_TYPE_LENS_CHROME_SHOPPING = 18; private static final int LENS_INTENT_TYPE_LENS_CHROME_SHOPPING = 18;
private static final String LENS_SHOPPING_FEATURE_FLAG_VARIANT_NAME = "lensShopVariation"; private static final String LENS_SHOPPING_FEATURE_FLAG_VARIANT_NAME = "lensShopVariation";
private static final String LENS_DEFAULT_SHOPPING_URL_PATTERNS = private static final String LENS_DEFAULT_SHOPPING_URL_PATTERNS =
...@@ -220,6 +228,32 @@ public class LensUtils { ...@@ -220,6 +228,32 @@ public class LensUtils {
return ""; return "";
} }
/**
* Gets the minimum AGSA version required to support the direct intent SDK
* integration on this device. Takes the value from a server provided value if a
* field trial is active but otherwise will take the value from a client side
* default (unless the lens feature is not enabled at all, in which case return
* an empty string).
*
* @return The minimum version name string or an empty string if not available.
*/
public static String getMinimumAgsaVersionForDirectIntentSdkSupport() {
// Shopping feature AGSA version takes priority over Search with Google Lens
if (ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS)) {
final String serverProvidedMinAgsaVersion =
ChromeFeatureList.getFieldTrialParamByFeature(
ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS,
MIN_AGSA_VERSION_DIRECT_INTENT_SDK_FEATURE_PARAM_NAME);
if (TextUtils.isEmpty(serverProvidedMinAgsaVersion)) {
// Falls into this block if the user enabled the feature using chrome://flags
// and the param was not set by the server.
return MIN_AGSA_VERSION_NAME_FOR_LENS_DIRECT_INTENT_SDK;
}
return serverProvidedMinAgsaVersion;
}
return "";
}
/** /**
* Checks whether the device is below Android O. We restrict to these versions * Checks whether the device is below Android O. We restrict to these versions
* to limit to OS"s where image processing vulnerabilities can be retroactively * to limit to OS"s where image processing vulnerabilities can be retroactively
...@@ -342,6 +376,59 @@ public class LensUtils { ...@@ -342,6 +376,59 @@ public class LensUtils {
return intent; return intent;
} }
/**
* Start an early Lens AGSA connection if feature parameter is enabled and client is not
* incognito. Eligibity checks happen in LensController.
*
* @param isIncognito Whether the client is incognito
*/
public static void startLensConnectionIfNecessary(boolean isIncognito) {
// TODO(crbug/1157543): Pass isIncognito through to LensController.
if (!isIncognito) {
LensController.getInstance().startLensConnection();
}
}
/**
* Terminate an early Lens AGSA connection if feature parameter is enabled and client is not
* incognito. Eligibity checks happen in LensController.
*
* @param isIncognito Whether the client is incognito
*/
public static void terminateLensConnectionsIfNecessary(boolean isIncognito) {
// TODO(crbug/1157543): Pass isIncognito through to LensController.
if (!isIncognito) {
LensController.getInstance().terminateLensConnections();
}
}
/**
* Build a LensIntentParams object from the provided parameters in order to intent into Lens.
*
* @param imageUri The content provider URI generated by chrome (or
* empty URI) if only resolving the activity.
* @param isIncognito Whether the current tab is in incognito mode.
* @param srcUrl The 'src' attribute of the image.
* @param titleOrAltText The 'title' or, if empty, the 'alt' attribute of the
* image.
* @param pageUrl The url of the top level frame of the page.
* @param requiresConfirmation Whether the request requires an confirmation dialog.
* @return The intent parameters to intent to Google Lens.
*/
public static LensIntentParams buildLensIntentParams(final Uri imageUri,
final boolean isIncognito, final String srcUrl, final String titleOrAltText,
final String pageUrl, final boolean requiresConfirmation) {
LensIntentParams.Builder intentParamsBuilder = new LensIntentParams.Builder();
return intentParamsBuilder.withImageUri(imageUri)
.withIsIncognito(isIncognito)
.withRequiresConfirmation(requiresConfirmation)
.withIntentType(getLensShoppingIntentType())
.withImageTitleOrAltText(titleOrAltText)
.withSrcUrl(srcUrl)
.withPageUrl(pageUrl)
.build();
}
public static boolean isGoogleLensFeatureEnabled(boolean isIncognito) { public static boolean isGoogleLensFeatureEnabled(boolean isIncognito) {
return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS) return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS)
&& !(isIncognito && !(isIncognito
...@@ -379,6 +466,24 @@ public class LensUtils { ...@@ -379,6 +466,24 @@ public class LensUtils {
agsaVersionName, getMinimumAgsaVersionForDirectIntentSupport()); agsaVersionName, getMinimumAgsaVersionForDirectIntentSupport());
} }
/**
* Enables the starting of LenActivity directly, rather than going through the Lens
* session running in AGSA. Also checks if the required AGSA version for direct intent
* is below or equal to the provided version.
*/
public static boolean useDirectIntentSdkIntegration(final Context context) {
// TODO(https://crbug.com/1146591): Refactor GSA state checks to avoid multiple version
// grabs.
String agsaVersionName = sFakeInstalledAgsaVersion != null
? sFakeInstalledAgsaVersion
: getLensActivityVersionNameIfAvailable(context);
return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS,
USE_DIRECT_INTENT_SDK_INTEGRATION_PARAM_NAME, false)
&& !GSAState.getInstance(context).isAgsaVersionBelowMinimum(
agsaVersionName, getMinimumAgsaVersionForDirectIntentSdkSupport());
}
/** /**
* Whether to display the lens menu item with the search by image text * Whether to display the lens menu item with the search by image text
*/ */
......
...@@ -27,6 +27,8 @@ import org.chromium.base.PackageManagerUtils; ...@@ -27,6 +27,8 @@ import org.chromium.base.PackageManagerUtils;
import org.chromium.base.StrictModeContext; import org.chromium.base.StrictModeContext;
import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.lens.LensController;
import org.chromium.chrome.browser.lens.LensIntentParams;
import org.chromium.chrome.browser.lens.LensQueryResult; import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager; import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
...@@ -134,19 +136,27 @@ public class ShareHelper extends org.chromium.components.browser_ui.share.ShareH ...@@ -134,19 +136,27 @@ public class ShareHelper extends org.chromium.components.browser_ui.share.ShareH
public static void shareImageWithGoogleLens(final WindowAndroid window, Uri imageUri, public static void shareImageWithGoogleLens(final WindowAndroid window, Uri imageUri,
boolean isIncognito, GURL srcUrl, String titleOrAltText, GURL pageUrl, boolean isIncognito, GURL srcUrl, String titleOrAltText, GURL pageUrl,
LensQueryResult lensQueryResult, boolean requiresConfirmation) { LensQueryResult lensQueryResult, boolean requiresConfirmation) {
Intent shareIntent = if (LensUtils.useDirectIntentSdkIntegration(ContextUtils.getApplicationContext())) {
LensUtils.getShareWithGoogleLensIntent(ContextUtils.getApplicationContext(), LensIntentParams intentParams = LensUtils.buildLensIntentParams(imageUri, isIncognito,
imageUri, isIncognito, SystemClock.elapsedRealtimeNanos(), srcUrl, srcUrl.getValidSpecOrEmpty(), titleOrAltText, pageUrl.getValidSpecOrEmpty(),
titleOrAltText, pageUrl, lensQueryResult, requiresConfirmation); requiresConfirmation);
try { LensController.getInstance().startLens(window, intentParams);
// Pass an empty callback to ensure the triggered activity can identify the source } else {
// of the intent (startActivityForResult allows app identification). Intent shareIntent =
fireIntent(window, shareIntent, (w, resultCode, data) -> {}); LensUtils.getShareWithGoogleLensIntent(ContextUtils.getApplicationContext(),
} catch (ActivityNotFoundException e) { imageUri, isIncognito, SystemClock.elapsedRealtimeNanos(), srcUrl,
// The initial version check should guarantee that the activity is available. However, titleOrAltText, pageUrl, lensQueryResult, requiresConfirmation);
// the exception may be thrown in test environments after mocking out the version check. try {
if (Boolean.TRUE.equals(sIgnoreActivityNotFoundException)) return; // Pass an empty callback to ensure the triggered activity can identify the source
throw e; // of the intent (startActivityForResult allows app identification).
fireIntent(window, shareIntent, (w, resultCode, data) -> {});
} catch (ActivityNotFoundException e) {
// The initial version check should guarantee that the activity is available.
// However, the exception may be thrown in test environments after mocking out the
// version check.
if (Boolean.TRUE.equals(sIgnoreActivityNotFoundException)) return;
throw e;
}
} }
} }
......
...@@ -29,6 +29,7 @@ import org.chromium.base.test.util.CommandLineFlags; ...@@ -29,6 +29,7 @@ import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.lens.LensIntentParams;
import org.chromium.chrome.browser.lens.LensQueryResult; import org.chromium.chrome.browser.lens.LensQueryResult;
import org.chromium.chrome.test.ChromeBrowserTestRule; import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
...@@ -914,6 +915,36 @@ public class LensUtilsTest { ...@@ -914,6 +915,36 @@ public class LensUtilsTest {
assertTrue(isInShoppingAllowlistOnUiThread(googleShoppingItemUrl)); assertTrue(isInShoppingAllowlistOnUiThread(googleShoppingItemUrl));
} }
/**
* Test {@link LensUtils#buildLensIntentParams()} method for building intent parameters for
* startng Lens.
*/
@Test
@SmallTest
public void buildLensIntentParamsTest() {
final String contentUrl = "content://image-url";
final boolean isIncognito = false;
final String srcUrl = "https://www.google.com";
final String titleOrAltText = "Image Title";
final String pageUrl = "https://www.google.com";
final boolean requiresConfirmation = false;
LensIntentParams lensIntentParams = LensUtils.buildLensIntentParams(Uri.parse(contentUrl),
isIncognito, srcUrl, titleOrAltText, pageUrl, requiresConfirmation);
Assert.assertEquals("Lens intent parameters has incorrect image URI.", contentUrl,
lensIntentParams.getImageUri().toString());
Assert.assertEquals("Lens intent parameters has incorrect incognito value.", isIncognito,
lensIntentParams.getIsIncognito());
Assert.assertEquals("Lens intent parameters has incorrect src URL.", srcUrl,
lensIntentParams.getSrcUrl());
Assert.assertEquals("Lens intent parameters has incorrect title or alt text.",
titleOrAltText, lensIntentParams.getImageTitleOrAltText());
Assert.assertEquals("Lens intent parameters has incorrect page URL.", pageUrl,
lensIntentParams.getPageUrl());
Assert.assertEquals("Lens intent parameters has incorrect requires confirmation value.",
requiresConfirmation, lensIntentParams.getRequiresConfirmation());
}
private boolean isInShoppingAllowlistOnUiThread(GURL imageUri) { private boolean isInShoppingAllowlistOnUiThread(GURL imageUri) {
return TestThreadUtils.runOnUiThreadBlockingNoException( return TestThreadUtils.runOnUiThreadBlockingNoException(
() -> LensUtils.isInShoppingAllowlist(imageUri)); () -> LensUtils.isInShoppingAllowlist(imageUri));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment